package org.dbxml.client.xmldb;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE DBXML GROUP OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: CollectionImpl.java,v 1.32 2001/09/09 18:07:49 kstaken Exp $
 */

import org.dbxml.client.xmldb.resources.*;
import org.dbxml.client.xmldb.services.*;
import org.dbxml.xml.*;
import org.dbxml.xml.dom.*;
import org.dbxml.core.FaultCodes;
import org.dbxml.core.corba.*;
import org.dbxml.core.corba.db.*;
import org.dbxml.client.xmldb.services.CollectionManager;

import org.w3c.dom.*;

import java.util.*;

import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;

/**
 * Implements the XML:DB API Collection interface for encapsulating access to an
 * XML database collection. This allows you to store/retrieve/delete resources
 * within the database and provides access to other services for the database.
 * <p />
 *
 * This API is an implementation of the XML:DB API. More information on this API
 * can be found by looking at
 * <a href="http://www.xmldb.org/xapi/index.html">http://www.xmldb.org/xapi/index.html</a>
 * <p />
 * The dbXML implemenation of Collection makes the following services available
 * to applications.
 * <ul>
 *    <li>XPathQueryService 1.0 for performing XPath queries against the server</li>
 *    <li>DatabaseInstanceManager 1.0 for managing the dbXML database instance</li>
 *    <li>CollectionManager 1.0 for managing dbXML collections</li>
 *    <li>XMLObjectService for invoking XMLObjects within dbXML</li>
 * </ul>
 *
 * @see org.dbxml.client.xmldb.services.XMLObjectService
 * @see org.dbxml.client.xmldb.services.DatabaseInstanceManager
 * @see org.dbxml.client.xmldb.services.CollectionManager
 */
public class CollectionImpl extends CommonConfigurable
       implements org.xmldb.api.base.Collection, ManagedObject {
   private static final byte[] EmptyBytes = new byte[0];

   private SymbolDeserializer syms = new SymbolDeserializer();

   protected org.dbxml.core.corba.db.Collection collection = null;
   protected org.dbxml.core.corba.db.Database db = null;
   protected DatabaseImpl database = null;

   protected Hashtable services = null;

   // CORBA Servant management variables
   protected boolean isOpen = false;
   protected ArrayList objects = null;

   /**
    * Creates a new CollectionImpl for the XML:DB API Collection interface.
    *
    * @param collection the dbXML collection to wrap
    * @param db the dbXML Database instance that the collection is
    *        associated with.
    * @param database XML:DB Database instance for dbXML
    * @exception XMLDBException
    */
   public CollectionImpl(org.dbxml.core.corba.db.Collection collection,
         org.dbxml.core.corba.db.Database db, DatabaseImpl database)
         throws XMLDBException {
      this.collection = collection;
      this.db = db;
      this.database = database;
      isOpen = true;

      services = new Hashtable();

      // Register all services supported by this collection implementation.
      XPathQueryServiceImpl xpath = new XPathQueryServiceImpl();
      xpath.setCollection(this);
      xpath.setSymbolDeserializer(syms);
      registerService(xpath);

      XUpdateQueryServiceImpl xupdate = new XUpdateQueryServiceImpl();
      xupdate.setCollection(this);
      registerService(xupdate);

      XMLObjectService xobj = new XMLObjectService();
      xobj.setCollection(this);
      registerService(xobj);

      try {
         CollectionManager manager = 
            new CollectionManager(collection.getCollectionManager(),
               db, database);
         manager.setCollection(this);
         registerService(manager);

         // CollectionManager provides both standard access as a
         // CollectionManagementService and dbXML specific access as a
         // CollectionManager. We need to register it explicitly a second time 
         // to make it available as both.
         services.put("CollectionManagementService" + manager.getVersion(), manager);         
         
         registerService(
               new DatabaseInstanceManager(db.getDatabaseManager(),
               db, database));
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }      
   }

   /**
    * Returns the name of the Collection
    *
    * @return The name of the collection.
    * @exception XMLDBException
    */
   public String getName() throws XMLDBException {
      try {
         return collection.getName();
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns the fully qualified name of the Collection
    *
    * @return The name of the collection.
    * @exception XMLDBException
    */
   public String getCanonicalName() throws XMLDBException {
      try {
         return collection.getCanonicalName();
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }
   
   /**
    * Returns the list of Services supported by this Collection.
    *
    * @return A list of supported Services
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Service[] getServices() throws XMLDBException {
      checkOpen();
      
      Enumeration e = services.elements();
      Service[] result = new Service[services.size()];

      int i = 0;
      while (e.hasMoreElements()) {
         result[i] = (Service) e.nextElement();
         i++;
      }

      return result;
   }

   /**
    * Get a Service instance based on the name and version.
    *
    * @param name The Service instance to retrieve
    * @param version The version of the service to retrieve.
    * @return The Service instance or null if no service was found.
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Service getService(String name, String version)
          throws XMLDBException {
      checkOpen();
      
      Service result = (Service) services.get(name + version);

      return result;
   }

   /**
    * Gets the ParentCollection for this Collection if one exists.
    *
    * @return The ParentCollection instance or null if none exists
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Collection getParentCollection()
          throws XMLDBException {
      checkOpen();
      
      try {
         org.dbxml.core.corba.db.Collection col = collection.getParentCollection();
         if (col != null) {
            return (org.xmldb.api.base.Collection)
                  new CollectionImpl(col, db, database);
         }
         else {
            return null;
         }
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns the number of child Collections
    *
    * @return the number of child collections.
    * @exception XMLDBException
    */
   public int getChildCollectionCount() throws XMLDBException {
      checkOpen();
      
      try {
         String[] collections = collection.listCollections();
         return collections.length;
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns a list of Collection names naming all child collections
    * of the current collection. If no child collections exist an empty list is
    * returned.
    *
    * @return an array containing Collection names for all child
    *      collections.
    * @exception XMLDBException
    */
   public String[] listChildCollections() throws XMLDBException {
      checkOpen();
      
      try {
          return(collection.listCollections());
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns a Collection instance for the requested child collection
    * if it exists.
    *
    * @param name the name of the child collection to retrieve.
    * @return the requested child collection or null if it couldn't be found.
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Collection getChildCollection(String name)
         throws XMLDBException {
      checkOpen();
      
      try {
          org.dbxml.core.corba.db.Collection col = collection.getCollection(name);
          if (col == null) {
             return null;
          }

          return(new CollectionImpl(col, db, database));
      }
      catch (APIException e) {
         if (e.faultCode == FaultCodes.COL_COLLECTION_NOT_FOUND) {
            return null;
         }
         
         throw FaultCodes.createXMLDBException(e);
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns the number of resources stored by this Collection
    *
    * @return The ResourceCount value
    * @exception XMLDBException
    */
   public int getResourceCount() throws XMLDBException {
      checkOpen();
      
      try {
         return collection.getDocumentCount();
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Returns a list of the ids for all resources stored in the collection.
    *
    * @return a string array containing the names for all resources in the
    *         collection.
    * @exception XMLDBException
    */
   public String[] listResources() throws XMLDBException {
      checkOpen();
      
      try {
         return(collection.listDocuments());
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Retrieves the Resource identified by id from the repository.
    *
    * @param id The unique id for the resource
    * @return The requested resource.
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Resource getResource(String id)
          throws XMLDBException {
      checkOpen();
      
      Resource resource = null;
      try {
         // TODO: Return null if no document is found.
         EncodedBuffer buffer = collection.getDocument(id, syms.getLastModified());
         if ( buffer.stamp != -1 ) {
            SymbolTable s = syms.getSymbols(buffer);
            resource = new XMLResourceImpl(id, this, s, buffer.buf);
         }
         else
            resource = new XMLResourceImpl(id, this, new String(buffer.buf));
      }
      catch (APIException e) {
         if (e.faultCode == FaultCodes.COL_DOCUMENT_NOT_FOUND) {
            return null;
         }
         else {
            throw FaultCodes.createXMLDBException(e);
         }
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }

      return resource;
   }

   /**
    * Registers a new Service with this Collection.
    *
    * @param service Description of Parameter
    * @exception XMLDBException
    */
   public void registerService(org.xmldb.api.base.Service service)
          throws XMLDBException {
      checkOpen();
      
      /*if ((service == null) ||
            (service.getVersion() == null) || (service.getVersion().length() == 0) ||
            (service.getName() == null) || (service.getName().length() == 0)) {

         throw new XMLDBException(ErrorCodes.INVALID_SERVICE);
      }*/

      service.setCollection(this);
      services.put(service.getName() + service.getVersion(), service);
   }

   /**
    * Creates a new Resource instance for storing data in the repository.
    * <p />
    * The dbXML implementation only knows how to handle XMLResources.
    *
    * @param id The unique id to use for the created resource.
    * @return The newly created Resource.
    * @exception XMLDBException
    */
   public org.xmldb.api.base.Resource createResource(String id, String type)
          throws XMLDBException {
      checkOpen();
      
      if (id == null) {
         id = createId();
      }

      return new XMLResourceImpl(id, this);      
   }

   /**
    * Removes the provided resource from the repository.
    *
    * @param resource The resource to remove.
    * @exception XMLDBException
    */
   public void removeResource(org.xmldb.api.base.Resource resource)
          throws XMLDBException {
      checkOpen();
      
      try {
         collection.removeDocument(resource.getId());
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * Stores the Resource in the repository
    *
    * @param resource The resource to store.
    * @exception XMLDBException
    */
   public void storeResource(org.xmldb.api.base.Resource resource)
          throws XMLDBException {
      checkOpen();
      
      try {
         EncodedBuffer buffer = new EncodedBuffer();
         buffer.sym = EmptyBytes;
         
         String content = (String) resource.getContent();
         if ( content == null ) {
            throw new XMLDBException(ErrorCodes.INVALID_RESOURCE);
         }
         
         buffer.buf = content.getBytes();
         buffer.stamp = -1;

         String id = resource.getId();
         if ( ( id == null ) || ( id.length() == 0 ) ) {
            id = createId();
         }

         collection.insertDocument(id, buffer);
      }
      catch (XMLDBException e) {
         throw e;
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * @return Description of the Returned Value
    * @exception XMLDBException
    */
   public String createId() throws XMLDBException {
      checkOpen();
      
      try {
         return collection.createNewOID();
      }
      catch (Exception e) {
         throw FaultCodes.createXMLDBException(e);
      }
   }

   /**
    * The close method must be called when access to a collection is complete.
    * It will release any resources allocated for use by the collection.
    *
    * @exception XMLDBException
    */
   public void close() throws XMLDBException {
      if ( isOpen ) {
         remove();
      }
   }

   /**
    * Returns true if the  <code>Collection</code> is open false otherwise.
    *
    * @return true if the <code>Collection</code> is open, false otherwise.
    * @exception XMLDBException
    */
   public boolean isOpen() throws XMLDBException {
      return isOpen;
   }
   
   /**
    * Frees any server side resources consumed by this collection and releases
    * the server side Servant.
    */
   public void remove() {
      try {
         ((DatabaseInstanceManager) getService("DatabaseInstanceManager", "1.0")).remove();
         ((CollectionManager) getService("CollectionManager", "1.0")).remove();
      }
      catch (Exception e) {
         e.printStackTrace();         
      }

      collection.remove();
      isOpen = false;
   }

   /**
    * Returns the dbXML underlying collection implementation.
    *
    * @return The ServerObject value
    */
   public org.dbxml.core.corba.db.Collection getServerObject() {
      return collection;
   }

   /**
    * Adds a reference to a managable object to the managed objects list.
    *
    * @param ref The feature to be added to the ManagedObject attribute
    */
   void addManagedObject(ManagedObject ref) {
      objects.add(ref);
   }
   
   /**
    * Throws an exception if the collection is not open.
    */
   protected void checkOpen() throws XMLDBException {
      if ( isOpen == false ) {
         throw new XMLDBException(ErrorCodes.COLLECTION_CLOSED);
      }
   }
}

