/*
 * JBoss, the OpenSource J2EE webOS
 * 
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.media.entity;

import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.emb.ContentAccessException;
import javax.emb.MediaEntityLocal;
import javax.emb.MediaException;
import javax.emb.MediaFormat;
import javax.emb.MetaDataEntityLocal;
import javax.emb.MetaDataSyntaxException;
import javax.emb.UnsupportedQueryLanguageException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.jboss.ejb.plugins.keygenerator.KeyGenerator;
import org.jboss.ejb.plugins.keygenerator.KeyGeneratorFactory;
import org.jboss.logging.Logger;
import org.jboss.media.entity.query.JBossMediaQueryLanguage;
import org.jboss.media.entity.query.MediaQueryLanguage;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

/**
 * JBoss implementation of the MetaDataEntity EJB.
 * 
 * @version <tt>$Revision: 1.4 $</tt>
 * @author <a href="mailto:ricardoarguello@users.sourceforge.net">Ricardo Argüello</a>
 * 
 * @ejb.bean name="MetaDataEntity"
 *           local-jndi-name="ejb/media/MetaDataEntity"
 *           view-type="local"
 *           reentrant="true"
 *           type="CMP"
 *           cmp-version="2.x"
 *           primkey-field="managedIdentity"
 * 
 * @ejb.interface generate="false" local-class="javax.emb.MetaDataEntityLocal"
 * @ejb.home generate="false" local-class="javax.emb.MetaDataEntityLocalHome"
 * @ejb.transaction type="Required"
 * @ejb.util generate="false"
 * 
 * @ejb.persistence table-name="JBOSS_METADATA_ENTITY_BEANS"
 * 
 * @jboss.create-table "true"
 * @jboss.remove-table "false"
 */
public abstract class MetaDataEntityBean implements EntityBean
{
   private EntityContext entityContext;

   /** Logger. */
   private Logger log = Logger.getLogger(MetaDataEntityBean.class);

   /** KeyGenerator Factory JNDI. */
   private static final String KEY_GENERATOR_JNDI = "UUIDKeyGeneratorFactory";

   /** Query languages supported. */
   private static final MediaQueryLanguage[] MEDIA_QUERY_LANGUAGES =
      { new JBossMediaQueryLanguage()};

   //--------------------------------------------------------------------------

   /**
    * @ejb.pk-field
    * @ejb.persistence column-name="IDENTITY"
    */
   public abstract String getManagedIdentity();
   public abstract void setManagedIdentity(String identity);

   /**
    * @ejb.persistence column-name="XML"
    */
   public abstract String getManagedXML();
   public abstract void setManagedXML(String xml);

   /**
    * @ejb.persistence column-name="NAME"
    */
   public abstract String getManagedName();
   public abstract void setManagedName(String name);

   /**
    * @ejb.persistence column-name="LAST_MODIFIED"
    */
   public abstract long getManagedLastModified();
   public abstract void setManagedLastModified(long lastModified);

   /**
    * @ejb.relation name="PreviousMetaDataVersion-NextMetaDataVersion"
    *               role-name="previous-metadata-version-has-next-metadata-version"
    * 
    * @jboss.relation related-pk-field="managedIdentity"
    *                 fk-column="previous_identity_fk"
    */
   public abstract MetaDataEntityLocal getManagedPreviousVersion();
   public abstract void setManagedPreviousVersion(MetaDataEntityLocal previousVersion);

   /**
    * @ejb.relation name="PreviousMetaDataVersion-NextMetaDataVersion"
    *               role-name="next-metadata-version-has-previous-metadata-version"
    * 
    * @jboss.relation related-pk-field="managedIdentity"
    *                 fk-column="child_identity_fk"
    */
   public abstract MetaDataEntityLocal getManagedNextVersion();
   public abstract void setManagedNextVersion(MetaDataEntityLocal nextVersion);

   /**
    * @ejb.relation name="ParentMetaData-ChildMetaData"
    *               role-name="parent-metadata-has-child-metadata"
    * 
    * @jboss.relation related-pk-field="managedIdentity"
    *                 fk-column="parent_identity_fk"
    */
   public abstract Collection getManagedParents();
   public abstract void setManagedParents(Collection parents);

   /**
    * @ejb.relation name="ParentMetaData-ChildMetaData"
    *               role-name="child-metadata-has-parent-metadata"
    * 
    * @jboss.relation related-pk-field="managedIdentity"
    *                 fk-column="child_identity_fk"
    */
   public abstract Collection getManagedChildren();
   public abstract void setManagedChildren(Collection children);

   /**
    * @ejb.relation name="Media-MetaData"
    *               role-name="metadata-has-medias"
    * 
    * @jboss.relation related-pk-field="managedIdentity"
    *                 fk-column="media_identity_fk"
    */
   public abstract Collection getManagedMedias();
   public abstract void setManagedMedias(Collection medias);

   //--------------------------------------------------------------------------

   /**
    * @see javax.emb.MetaDataEntityLocal#addChild(javax.emb.MetaDataEntityLocal)
    */
   public void addChild(MetaDataEntityLocal child) throws MediaException
   {
      if (child == null)
      {
         throw new NullPointerException();
      }

      if (!this.getManagedChildren().contains(child))
      {
         this.getManagedChildren().add(child);
      }
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#addMediaEntity(javax.emb.MediaEntityLocal)
    */
   public void addMediaEntity(MediaEntityLocal mediaEntity)
      throws MediaException
   {
      if (mediaEntity == null)
      {
         throw new NullPointerException();
      }

      if (!this.getManagedMedias().contains(mediaEntity))
      {
         this.getManagedMedias().add(mediaEntity);
         this.updateLastModified();
      }
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getChildren()
    */
   public MetaDataEntityLocal[] getChildren() throws MediaException
   {
      return (MetaDataEntityLocal[]) this.getManagedChildren().toArray(
         new MetaDataEntityLocal[0]);
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getLastModified()
    */
   public long getLastModified() throws MediaException
   {
      return this.getManagedLastModified();
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getMediaEntities()
    */
   public MediaEntityLocal[] getMediaEntities() throws MediaException
   {
      return (MediaEntityLocal[]) this.getManagedMedias().toArray(
         new MediaEntityLocal[0]);
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getMediaEntities(javax.emb.MediaFormat, boolean)
    */
   public MediaEntityLocal[] getMediaEntities(
      MediaFormat mediaFormat,
      boolean searchChildren)
      throws MediaException
   {
      if (mediaFormat == null)
      {
         throw new NullPointerException();
      }

      Collection mediaEntities = new ArrayList();
      Iterator it = this.getManagedMedias().iterator();

      while (it.hasNext())
      {
         MediaEntityLocal mediaEntity = (MediaEntityLocal) it.next();
         MediaFormat format = mediaEntity.getFormat();
         if (mediaFormat == format)
         {
            mediaEntities.add(mediaEntity);
         }
      }

      if (searchChildren)
      {
         MetaDataEntityLocal children[] = this.getChildren();
         for (int i = 0; i < children.length; i++)
         {
            MediaEntityLocal mediaEntity[] =
               children[i].getMediaEntities(mediaFormat, searchChildren);

            for (int j = 0; j < mediaEntity.length; j++)
            {
               mediaEntities.add(mediaEntity[j]);
            }
         }
      }

      return (MediaEntityLocal[]) mediaEntities.toArray(
         new MediaEntityLocal[0]);
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getMediaEntities(java.lang.String, boolean)
    */
   public MediaEntityLocal[] getMediaEntities(
      String mimeType,
      boolean searchChildren)
      throws MediaException
   {
      if (mimeType == null)
      {
         throw new NullPointerException();
      }

      Collection mediaEntities = new ArrayList();
      Iterator it = this.getManagedMedias().iterator();

      while (it.hasNext())
      {
         MediaEntityLocal mediaEntity = (MediaEntityLocal) it.next();
         String type = mediaEntity.getMimeType();
         if (mimeType.equals(type))
         {
            mediaEntities.add(mediaEntity);
         }
      }

      if (searchChildren)
      {
         MetaDataEntityLocal children[] = this.getChildren();
         for (int i = 0; i < children.length; i++)
         {
            MediaEntityLocal mediaEntity[] =
               children[i].getMediaEntities(mimeType, searchChildren);

            for (int j = 0; j < mediaEntity.length; j++)
            {
               mediaEntities.add(mediaEntity[j]);
            }
         }
      }

      return (MediaEntityLocal[]) mediaEntities.toArray(
         new MediaEntityLocal[0]);
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getName()
    */
   public String getName() throws MediaException
   {
      return this.getManagedName();
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getNextVersion()
    */
   public MetaDataEntityLocal getNextVersion() throws MediaException
   {
      return this.getManagedNextVersion();
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getParents()
    */
   public MetaDataEntityLocal[] getParents() throws MediaException
   {
      return (MetaDataEntityLocal[]) this.getManagedParents().toArray(
         new MetaDataEntityLocal[0]);
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getPreviousVersion()
    */
   public MetaDataEntityLocal getPreviousVersion() throws MediaException
   {
      return this.getManagedPreviousVersion();
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#getXML()
    */
   public String getXML() throws MediaException
   {
      String xml = this.getManagedXML();

      if (xml == null)
      {
         throw new ContentAccessException();
      }

      return xml;
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#removeChild(javax.emb.MetaDataEntityLocal)
    */
   public void removeChild(MetaDataEntityLocal child) throws MediaException
   {
      if (child == null)
      {
         throw new NullPointerException();
      }

      if (this.getManagedChildren().contains(child))
      {
         this.getManagedChildren().remove(child);
      }
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#removeMediaEntity(javax.emb.MediaEntityLocal)
    */
   public void removeMediaEntity(MediaEntityLocal mediaEntity)
      throws MediaException
   {
      if (mediaEntity == null)
      {
         throw new NullPointerException();
      }

      if (this.getManagedMedias().contains(mediaEntity))
      {
         this.getManagedMedias().remove(mediaEntity);
      }
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#setName(java.lang.String)
    */
   public void setName(String name) throws MediaException
   {
      if (name == null)
      {
         throw new NullPointerException();
      }

      this.setManagedName(name);
      this.updateLastModified();
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#setPreviousVersion(javax.emb.MetaDataEntityLocal)
    */
   public void setPreviousVersion(MetaDataEntityLocal metadata)
      throws MediaException
   {
      // TODO: Implement
      throw new UnsupportedOperationException("Not yet implemented!");
   }

   /**
    * @see javax.emb.MetaDataEntityLocal#setXML(java.lang.String, boolean)
    */
   public void setXML(String xmlContent, boolean validate)
      throws MediaException
   {
      if (xmlContent == null)
      {
         throw new NullPointerException();
      }

      if (validate)
      {
         Reader xmlReader = new StringReader(xmlContent);

         try
         {
            InputSource xmlSource = new InputSource(xmlReader);
            DocumentBuilderFactory factory =
               DocumentBuilderFactory.newInstance();

            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(xmlSource);

            // Document well formed
         }
         catch (Exception e)
         {
            // Document NOT well formed
            throw new MetaDataSyntaxException(e);
         }
      }

      this.setManagedXML(xmlContent);
      this.updateLastModified();
   }

   // javax.emb.MetaDataEntityLocalHome interface: ----------------------------

   /**
    * @see javax.emb.MetaDataEntityLocalHome#create()
    */
   public String ejbCreate() throws CreateException
   {
      String identity = this.generateIdentity();

      this.setManagedIdentity(identity);
      this.updateLastModified();

      return null;
   }

   /**
    * @see javax.emb.MetaDataEntityLocalHome#create()
    */
   public void ejbPostCreate() throws CreateException
   {
   }

   /**
    * @see javax.emb.MetaDataEntityLocalHome#query(java.lang.String, java.lang.String, java.util.Map)
    */
   public Collection ejbHomeQuery(
      String query,
      String queryLanguage,
      Map options)
      throws FinderException, MediaException
   {
      if ((query == null) || (queryLanguage == null))
      {
         throw new NullPointerException();
      }

      MediaQueryLanguage mediaQueryLanguage = null;

      for (int i = 0; i < MEDIA_QUERY_LANGUAGES.length; i++)
      {
         if (queryLanguage.equals(MEDIA_QUERY_LANGUAGES[i].getName()))
         {
            mediaQueryLanguage = MEDIA_QUERY_LANGUAGES[i];
         }
      }

      // Query language not found
      if (mediaQueryLanguage == null)
      {
         throw new UnsupportedQueryLanguageException();
      }

      mediaQueryLanguage.setOptions(options);

      return mediaQueryLanguage.query(this, query);
   }

   /**
    * @see javax.emb.MetaDataEntityLocalHome#retrieveSupportedOptions(java.lang.String)
    */
   public String[] ejbHomeRetrieveSupportedOptions(String queryLanguage)
      throws MediaException
   {
      if (queryLanguage == null)
      {
         throw new NullPointerException();
      }

      for (int i = 0; i < MEDIA_QUERY_LANGUAGES.length; i++)
      {
         MediaQueryLanguage mediaQueryLanguage = MEDIA_QUERY_LANGUAGES[i];

         if (queryLanguage.equals(mediaQueryLanguage.getName()))
         {
            Map options = mediaQueryLanguage.getOptions();
            String[] supportedOptions = new String[options.size()];

            Iterator it = options.keySet().iterator();
            int j = 0;

            while (it.hasNext())
            {
               String option = (String) it.next();
               supportedOptions[j] = option;
               j++;
            }

            return supportedOptions;
         }
      }

      // Query language not found
      throw new UnsupportedQueryLanguageException();
   }

   /**
    * @see javax.emb.MetaDataEntityLocalHome#retrieveSupportedQueryLanguages()
    */
   public String[] ejbHomeRetrieveSupportedQueryLanguages()
      throws MediaException
   {
      String[] supportedQueryLanguages =
         new String[MEDIA_QUERY_LANGUAGES.length];

      for (int i = 0; i < MEDIA_QUERY_LANGUAGES.length; i++)
      {
         supportedQueryLanguages[i] = MEDIA_QUERY_LANGUAGES[i].getName();
      }

      return supportedQueryLanguages;
   }

   //--------------------------------------------------------------------------

   /**
    * @ejb.select query="SELECT DISTINCT OBJECT(metaDataEntity) FROM MetaDataEntity AS metaDataEntity WHERE LOCATE(?1, metaDataEntity.managedXML) > -1"
    */
   public abstract Collection ejbSelectByPartialXML(String partialXML)
      throws FinderException;

   // javax.ejb.EntityBean interface: -----------------------------------------

   public void ejbActivate()
   
   {
   }

   public void ejbPassivate()
   {
   }

   public void ejbRemove() throws RemoveException
   {
   }

   public void setEntityContext(EntityContext entityContext)
   {
      this.entityContext = entityContext;
   }

   public void unsetEntityContext()
   {
      this.entityContext = null;
   }

   public void ejbLoad()
   {
   }

   public void ejbStore()
   {
   }

   // Private methods: --------------------------------------------------------

   private void updateLastModified()
   {
      this.setManagedLastModified(System.currentTimeMillis());
   }

   /**
    * Create the identity (primary key) of this bean using a UUID key
    * generator.
    * 
    * @return a String containing the primary key.
    * @throws CreateException if an error accours when generating the primary
    *         key.
    */
   private String generateIdentity() throws CreateException
   {
      KeyGenerator keyGenerator = null;

      try
      {
         KeyGeneratorFactory keyGeneratorFactory =
            (KeyGeneratorFactory) new InitialContext().lookup(
               KEY_GENERATOR_JNDI);

         keyGenerator = keyGeneratorFactory.getKeyGenerator();
      }
      catch (NamingException e)
      {
         throw new CreateException(
            "Error: can't find key generator factory: "
               + KEY_GENERATOR_JNDI
               + "; "
               + e.getMessage());
      }
      catch (Exception e)
      {
         throw new CreateException(
            "Error: can't create key generator instance; key generator factory: "
               + KEY_GENERATOR_JNDI
               + "; "
               + e.getMessage());
      }

      return (String) keyGenerator.generateKey();
   }
}