package org.dbxml.core.corba;

/*
 * 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: CollectionServant.java,v 1.70 2001/09/04 11:13:38 kstaken Exp $
 */

import org.dbxml.core.corba.db.*;
import org.dbxml.core.FaultCodes;
import org.dbxml.core.request.*;
import org.dbxml.core.objects.*;
import org.dbxml.core.data.*;
import org.dbxml.xml.*;
import org.dbxml.xml.dom.*;

import org.w3c.dom.*;

import java.util.*;

/**
 * Provides access to Collection instances through CORBA.
 *
 * @see org.dbxml.core.Collection
 */

public class CollectionServant extends CollectionPOA {
   private static final byte[] EmptyBytes = new byte[0];
   private static final SymbolTableSymbols hcSyms = SymbolTableSymbols.getInstance();

   private SymbolSerializer syms = null;

   org.dbxml.core.Collection collection;

   /**
    * Constructor for the CollectionServant object
    *
    * @param collection The collection that is being wrapped.
    */
   public CollectionServant(org.dbxml.core.Collection collection) {
      this.collection = collection;
      try {
         syms = new SymbolSerializer(collection.getSymbols());
      }
      catch ( Exception e ) {
         // It's ok (in theory) for a Collection to have no symbol table
      }
   }

   /**
    * Sets a document in the collection. setDocument should be called when the
    * document already exists in the collection.
    *
    * @param id The key for the Document to set
    * @param doc The Document value to set
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public void setDocument(String id, EncodedBuffer doc)
          throws APIException {
      Document d = EncodedBufferConverter.convertToDocument(doc);
      if ( d == null )
         throw FaultCodes.createAPIException(FaultCodes.COL_DOCUMENT_MALFORMED, "Unable to parse Document");

      try {
         collection.setDocument(id, d);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Gets the Name of the Collection
    *
    * @return The Name of the collection
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String getName() throws APIException {
      try {
         return collection.getName();
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Returns the Canonical Name of the Collection
    *
    * @return The Canonical Name of the collection
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String getCanonicalName() throws APIException {
      try {
         return collection.getCanonicalName();
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Returns a CollectionManager instance for this collection.
    *
    * @return The CollectionManager instance
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public CollectionManager getCollectionManager() throws APIException {
      try {
         return new CollectionManagerServant(collection)._this(_orb());
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Gets the parent Collection for this Collection
    *
    * @return The parent Collection of this Collection
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public org.dbxml.core.corba.db.Collection getParentCollection()
          throws APIException {
      try {
         if (collection.getParentCollection() != null) {
            return new CollectionServant(collection.getParentCollection())._this(_orb());
         }
         else {
            throw FaultCodes.createAPIException(FaultCodes.DBE_NO_PARENT, "Root Collection has no parent");
         }
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Retrieves a new Collection instance for the named collection.
    *
    * @param path The path of the collection to retrieve
    * @return The Collection instance
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public org.dbxml.core.corba.db.Collection getCollection(String path)
          throws APIException {
      org.dbxml.core.Collection col = null;
      try {
         col = collection.getCollection(path);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }

      if (col != null) {
         return new CollectionServant(col)._this(_orb());
      }
      else {
         throw FaultCodes.createAPIException(FaultCodes.COL_COLLECTION_NOT_FOUND);
      }
   }

   /**
    * Retrieves a document from the collection
    *
    * @param id The id of the document to retrieve.
    * @return The Document
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public EncodedBuffer getDocument(String id, long stamp)
          throws APIException {
      try {
         Document doc = collection.getDocument(id);
         if (doc == null) {
            throw FaultCodes.createAPIException(FaultCodes.COL_DOCUMENT_NOT_FOUND);
         }

         if ( stamp != -1 && syms != null )
            return syms.convertFromDocument(doc, stamp);
         else
            return EncodedBufferConverter.convertFromDocument(doc);
      }
      catch (APIException e) {
         throw e;
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Returns a set containing all documents in the collection.
    *
    * @return A DocumentSet containing all documents
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public org.dbxml.core.corba.db.DocumentSet getDocuments()
          throws APIException {
      try {
         // TODO: make sure we don't get a null here
         return new DocumentSetServant(collection.getDocumentSet(), syms)._this(_orb());
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Returns the number of documents stored in this collection.
    *
    * @return the number of documents
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public int getDocumentCount() throws APIException {
      try {
         // This is bad. IDL maps long to int.
         return (int) collection.getDocumentCount();
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Lists all child collections under this collection
    *
    * @return The list of child collections
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String[] listCollections() throws APIException {
      try {
         String[] result = collection.listCollections();
         if (result == null) {
            result = new String[0];
         }

         return result;
      }
      catch (Exception e) {
         org.dbxml.Debug.printStackTrace(e);
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Inserts a new document into the collection
    *
    * @param nam The key to store the document under. If nam is empty a new
    *    key value will be generated.
    * @param doc The Document instance to store
    * @return The key used to store the document
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String insertDocument(String nam, EncodedBuffer doc)
          throws APIException {
      Document d = EncodedBufferConverter.convertToDocument(doc);
      if ( d == null )
         throw FaultCodes.createAPIException(FaultCodes.COL_DOCUMENT_MALFORMED, "Unable to parse Document");

      try {
         if (nam.equals("")) {
            return collection.insertDocument(d).toString();
         }
         else {
            collection.insertDocument(nam, d);
            return nam;
         }
      }
      catch (Exception e) {
         org.dbxml.Debug.printStackTrace(e);
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Deletes a document from the collection.
    *
    * @param id The key for the document to delete
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public void removeDocument(String id)
          throws APIException {
      try {
         collection.remove(id);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   /**
    * Returns a set containing all documents in the collection.
    *
    * @return an array of strings containing the ids for all documents stored in
    * the collection.
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String[] listDocuments() throws APIException {
      try {
         return collection.listDocuments();
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }

   }

   /**
    * Creates a new unique OID for this collection.
    *
    * @return the oid as a string.
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public String createNewOID() throws APIException {
      try {
         Key key = collection.createNewOID();
         return key.toString();
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }

   private org.dbxml.core.corba.db.EncodedBuffer queryWrapper(NodeSet ns, long stamp) throws Exception {
      // Turn the NodeSet into a document.
      DocumentImpl doc = new DocumentImpl();
      if ( syms != null )
         doc.setSymbols(syms.getSymbols());
      Element root = doc.createElement("result");
      doc.appendChild(root);
      int count = 0;
      while (ns != null && ns.hasMoreNodes()) {
         Node n = ns.getNextNode();
         if ( n.getNodeType() == Node.DOCUMENT_NODE )
            n = ((Document)n).getDocumentElement();
         if ( n instanceof DBNode )
            ((DBNode)n).expandSource();
         root.appendChild(doc.importNode(n, true));
         count++;
      }
      root.setAttribute("count", Integer.toString(count));

      if ( stamp != -1 && syms != null ) {
         SymbolTable s = syms.getSymbols();
         byte[] b = DOMCompressor.Compress(doc, s);
         doc = new DocumentImpl(b, s, null);
         return syms.convertFromDocument(doc, stamp);
      }
      else
         return EncodedBufferConverter.convertFromDocument(doc);
   }
   
   /**
    * Executes an query against a this collection
    *
    * @param style The query style to use
    * @param query The xpath query string to use.
    * @return A document containing the serialized NodeList of resuls. The
    *    results are bracketed with a tag named "result"
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public org.dbxml.core.corba.db.EncodedBuffer queryCollection(String style, 
         String query, NamedVal[] namespaces, long stamp)
         throws APIException {
      try {
         NamespaceMap nsMap = null;
         if ( namespaces.length > 0 ) {
            nsMap = new NamespaceMap();
            for ( int i = 0; i < namespaces.length; i++ )
               nsMap.setNamespace(namespaces[i].nam, namespaces[i].val);
         }
         NodeSet ns = collection.queryCollection(style, query, nsMap);
         return queryWrapper(ns, stamp);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }
   
   /**
    * Executes a query against a Document in this collection
    *
    * @param style The query style to use
    * @param query The xpath query string to use.
    * @return A document containing the serialized NodeList of resuls. The
    *    results are bracketed with a tag named "result"
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public org.dbxml.core.corba.db.EncodedBuffer queryDocument(String style, 
         String query, NamedVal[] namespaces, String id, long stamp)
         throws APIException {
      try {
         NamespaceMap nsMap = null;
         if ( namespaces.length > 0 ) {
            nsMap = new NamespaceMap();
            for ( int i = 0; i < namespaces.length; i++ )
               nsMap.setNamespace(namespaces[i].nam, namespaces[i].val);
         }
         NodeSet ns = collection.queryDocument(style, query, nsMap, id);
         return queryWrapper(ns, stamp);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }
   }
   
   /**
    * Invokes an XMLObject using the uri provided. The URI must include the full
    * path to the XMLObject including method name and all parameters.
    *
    * @param uri The URI to use to locate and invoke the XMLObject
    * @return An encoded buffer containing the result of the invocation.
    * @exception APIException Thrown for all errors check faultCode for details.
    */
   public EncodedBuffer invokeXMLObject(String uri) throws APIException {
      URIMapper map = null;
      try {
         map = collection.getDatabase().getURIMapper(uri);
         map.setURI(uri);
      }
      catch (Exception e) {
         throw FaultCodes.createAPIException(e);
      }

      Object result = null;

      if (map.getObjectType() == URIMapper.XMLOBJECT) {
         try {
            result = map.invokeRequest();
         }
         catch (XMLObjectException e) {
            throw FaultCodes.createAPIException(e);
         }

         if (result == null) {
            throw FaultCodes.createAPIException(FaultCodes.OBJ_NULL_RESULT);
         }
         else if (result instanceof Node) {
            return EncodedBufferConverter.convertFromDocument((Node) result);
         }
         else {
            String res = result.toString();
            EncodedBuffer buf = new EncodedBuffer(-1, EmptyBytes, res.getBytes());
            return buf;
         }
      }
      else {
         throw FaultCodes.createAPIException(FaultCodes.OBJ_OBJECT_NOT_FOUND);
      }

   }

   /**
    * Releases all resources associated with this object. This method must be
    * called when usage of the object is complete. If it is not called the
    * server side servant will not be properly released.
    */
   public void remove() {
      try {
         _poa().deactivate_object(_object_id());
      }
      catch (Exception e) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }
}


