package com.dbxml.db.server.labrador;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: CollectionProxy.java,v 1.8 2004/07/30 14:04:32 bradford Exp $
 */

import com.dbxml.db.core.Collection;
import com.dbxml.db.core.DBException;
import com.dbxml.db.core.FaultCodes;
import com.dbxml.db.core.data.Key;
import com.dbxml.db.core.data.Record;
import com.dbxml.db.core.data.Value;
import com.dbxml.db.core.query.helpers.ResultSetWrapper;
import com.dbxml.db.core.security.Access;
import com.dbxml.db.core.security.AccessUtils;
import com.dbxml.db.core.security.Role;
import com.dbxml.db.core.transaction.Transaction;
import com.dbxml.labrador.Handler;
import com.dbxml.labrador.Headers;
import com.dbxml.labrador.broker.Broker;
import com.dbxml.labrador.broker.BrokerContext;
import com.dbxml.labrador.rest.RESTHandler;
import com.dbxml.util.Configuration;
import com.dbxml.xml.NamespaceMap;
import com.dbxml.xml.dom.DOMHelper;
import com.dbxml.xml.dtsm.DTSMException;
import com.dbxml.xml.dtsm.DTSMHelper;
import com.dbxml.xml.dtsm.DocumentTable;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ProcessingInstruction;

/**
 * CollectionProxy is a proxy facade that is used to expose Collection
 * functionality via Labrador.
 */

public final class CollectionProxy {
   private static final int[] INTS = {Access.READ, Access.WRITE, Access.EXECUTE, Access.CREATE};
   private static final String[] STRS = {"r", "w", "e", "c"};

   private static final String PI_TARGET = "xml-stylesheet";

   private static final String COLLECTION = "collection";
   private static final String PATH = "path";
   private static final String TYPE = "type";
   private static final String TYPE_DOCUMENTS = "documents";
   private static final String TYPE_RECORDS = "records";
   private static final String CHILDREN = "children";
   private static final String CHILD = "child";
   private static final String INDEXES = "indexes";
   private static final String INDEX = "index";
   private static final String TRIGGERS = "triggers";
   private static final String TRIGGER = "trigger";
   private static final String EXTENSIONS = "extensions";
   private static final String EXTENSION = "extension";
   private static final String ROLES = "roles";
   private static final String ROLE = "role";
   private static final String PERMISSIONS = "permissions";
   private static final String DOCUMENTS = "documents";
   private static final String DOCUMENT = "document";

   public static final String REST_DEFAULT_METHOD = "get";

   public static final String NAME = "name";
   public static final String PARENT_COLLECTION = "parentCollection";
   public static final String CANONICAL_NAME = "canonicalName";
   public static final String COLLECTION_TYPE = "collectionType";

   private Collection col;

   public CollectionProxy(Collection col) {
      this.col = col;
   }

   public Map getInformation() throws DBException {
      Map result = new HashMap(4);
      result.put(NAME, col.getName());
      result.put(CANONICAL_NAME, col.getCanonicalName());
      result.put(COLLECTION_TYPE, new Integer(col.getCollectionType()));
      try {
         Collection parent = col.getParentCollection();
         result.put(PARENT_COLLECTION, parent.getCanonicalName());
      }
      catch ( Exception e ) {
         // NOOP
      }
      return result;
   }

   public Document get() throws DBException {
      Document doc = DOMHelper.newDocument();

      // Let's see if we can interact with Labrador's HTTP Server for REST calls
      BrokerContext context = Broker.getInstance().getBrokerContext();
      Handler handler = context.getHandler();
      boolean restHandler = handler instanceof RESTHandler;

      if ( restHandler ) {
         StringBuffer sb = new StringBuffer();
         sb.append("type=\"text/xsl\" href=\"");
         sb.append(RESTHandler.PREFIX);
         sb.append("stylesheets/collection.xsl");
         sb.append('\"');
         ProcessingInstruction pi = doc.createProcessingInstruction(PI_TARGET, sb.toString());
         doc.appendChild(pi);
      }

      Element rootElem = doc.createElement(COLLECTION);
      rootElem.setAttribute(PATH, col.getCanonicalName());
      switch ( col.getCollectionType() ) {
         case Collection.TYPE_DOCUMENTS:
            rootElem.setAttribute(TYPE, TYPE_DOCUMENTS);
            break;

         case Collection.TYPE_RECORDS:
            rootElem.setAttribute(TYPE, TYPE_RECORDS);
            break;
      }
      doc.appendChild(rootElem);

      try {
         Element childrenElem = doc.createElement(CHILDREN);
         rootElem.appendChild(childrenElem);
         String[] cols = col.listCollections();
         for ( int i = 0; i < cols.length; i++ ) {
            Element childElem = doc.createElement(CHILD);
            childElem.appendChild(doc.createTextNode(cols[i]));
            childrenElem.appendChild(childElem);
         }
      }
      catch ( DBException e ) {
      }

      try {
         Element indexesElem = doc.createElement(INDEXES);
         rootElem.appendChild(indexesElem);
         String[] indexes = col.getIndexManager().list();
         for ( int i = 0; i < indexes.length; i++ ) {
            Element indexElem = doc.createElement(INDEX);
            indexElem.appendChild(doc.createTextNode(indexes[i]));
            indexesElem.appendChild(indexElem);
         }
      }
      catch ( DBException e ) {
      }

      try {
         Element triggersElem = doc.createElement(TRIGGERS);
         rootElem.appendChild(triggersElem);
         String[] triggers = col.getTriggerManager().list();
         for ( int i = 0; i < triggers.length; i++ ) {
            Element triggerElem = doc.createElement(TRIGGER);
            triggerElem.appendChild(doc.createTextNode(triggers[i]));
            triggersElem.appendChild(triggerElem);
         }
      }
      catch ( DBException e ) {
      }

      try {
         Element extensionsElem = doc.createElement(EXTENSIONS);
         rootElem.appendChild(extensionsElem);
         String[] extensions = col.getExtensionManager().list();
         for ( int i = 0; i < extensions.length; i++ ) {
            Element extensionElem = doc.createElement(EXTENSION);
            extensionElem.appendChild(doc.createTextNode(extensions[i]));
            extensionsElem.appendChild(extensionElem);
         }
      }
      catch ( DBException e ) {
      }

      try {
         Element rolesElem = doc.createElement(ROLES);
         rootElem.appendChild(rolesElem);
         AccessUtils utils = new AccessUtils(col.getDatabase());
         Access access = utils.readAccess(col.getCanonicalName());
         String[] roles = access.listRoles();
         for ( int i = 0; i < roles.length; i++ ) {
            Element roleElem = doc.createElement(ROLE);
            roleElem.appendChild(doc.createTextNode(roles[i]));
            rolesElem.appendChild(roleElem);

            Role role = utils.readRole(roles[i]);
            int permissions = access.getPermissions(role);
            if ( permissions != 0 ) {
               StringBuffer sb = new StringBuffer();
               for ( int j = 0; j < STRS.length; j++ )
                  if ( (permissions & INTS[j]) != 0 )
                     sb.append(STRS[j]);
               roleElem.setAttribute(PERMISSIONS, sb.toString());
            }
         }
      }
      catch ( DBException e ) {
      }

      Transaction tx = new Transaction();
      try {
         Element docsElem = doc.createElement(DOCUMENTS);
         rootElem.appendChild(docsElem);
         Key[] keys = col.listKeys(tx);
         for ( int i = 0; i < keys.length; i++ ) {
            Element docElem = doc.createElement(DOCUMENT);
            docElem.appendChild(doc.createTextNode(keys[i].toString()));
            docsElem.appendChild(docElem);
         }
      }
      catch ( DBException e ) {
         try {
            tx.cancel();
         }
         catch ( DBException ex ) {
         }
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }

      return doc;
   }

   public String getDatabase() {
      return col.getDatabase().getCanonicalName();
   }

   public String getSystemCollection() throws DBException {
      return col.getDatabase().getSystemCollection().getCanonicalName();
   }

   public static final String[] PARAMS_getCollection = {"name"};

   public String getCollection(String name) throws DBException {
      return col.getCollection(name).getCanonicalName();
   }

   public static final String[] PARAMS_createCollection = {"path", "configuration"};

   public String createCollection(String path, Document configuration) throws DBException {
      Configuration cfg = new Configuration(configuration);
      return col.createCollection(path, cfg).getCanonicalName();
   }

   public String[] listCollections() throws DBException {
      return col.listCollections();
   }

   public static final String[] PARAMS_dropCollection = {"name"};

   public boolean dropCollection(String name) throws DBException {
      Collection c = col.getCollection(name);
      if ( c != null )
         return col.dropCollection(c);
      else
         return false;
   }

   public static final String[] PARAMS_createTrigger = {"configuration"};

   public String createTrigger(Document configuration) throws DBException {
      Configuration cfg = new Configuration(configuration);
      return col.getTriggerManager().create(cfg).getName();
   }

   public static final String[] PARAMS_dropTrigger = {"name"};

   public boolean dropTrigger(String name) throws DBException {
      return col.getTriggerManager().drop(name);
   }

   public String[] listTriggers() throws DBException {
      return col.getTriggerManager().list();
   }

   public static final String[] PARAMS_createIndexer = {"configuration"};

   public String createIndexer(Document configuration) throws DBException {
      Configuration cfg = new Configuration(configuration);
      return col.getIndexManager().create(cfg).getName();
   }

   public static final String[] PARAMS_dropIndexer = {"name"};

   public boolean dropIndexer(String name) throws DBException {
      return col.getIndexManager().drop(name);
   }

   public String[] listIndexers() throws DBException {
      return col.getIndexManager().list();
   }

   public static final String[] PARAMS_getExtension = {"name"};

   public String getExtension(String name) throws DBException {
      return col.getExtensionManager().getCanonicalName(name);
   }

   public static final String[] PARAMS_createExtension = {"configuration"};

   public String createExtension(Document configuration) throws DBException {
      Configuration cfg = new Configuration(configuration);
      String name = col.getExtensionManager().create(cfg).getName();
      return col.getExtensionManager().getCanonicalName(name);
   }

   public String[] listExtensions() throws DBException {
      return col.getExtensionManager().list();
   }

   public static final String[] PARAMS_dropExtension = {"name"};

   public boolean dropExtension(String name) throws DBException {
      return col.getExtensionManager().drop(name);
   }

   public String createKey() throws DBException {
      return col.createNewOID().toString();
   }

   public static final String[] PARAMS_getDocument = {"docKey"};
   public static final String REST_CONTENT_TYPE_getDocument = Headers.TYPE_TEXT_XML;

   public String getDocument(String docKey) throws DBException {
      Transaction tx = new Transaction();
      try {
         DocumentTable dt = col.getDocument(tx, docKey);
         if ( dt == null )
            throw new DBException(FaultCodes.COL_CANNOT_RETRIEVE, "Document '"+docKey+"' not found");

         return DTSMHelper.tableToText(dt);
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      catch ( DTSMException e ) {
         tx.cancel();
         throw new DBException(FaultCodes.COL_CANNOT_RETRIEVE, "Can't convert Document '" + docKey + "' to text", e);
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_insertDocument = {"document"};

   public String insertDocument(String document) throws DBException {
      Transaction tx = new Transaction();
      try {
         DocumentTable doc = DTSMHelper.textToTable(document, col.getSymbols());
         return col.insertDocument(tx, doc).toString();
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      catch ( DTSMException e ) {
         tx.cancel();
         throw new DBException(FaultCodes.COL_CANNOT_STORE, "Can't parse Document", e);
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_setDocument = {"docKey", "document"};

   public void setDocument(String docKey, String document) throws DBException {
      Transaction tx = new Transaction();
      try {
         DocumentTable dt = DTSMHelper.textToTable(document, col.getSymbols());
         col.setDocument(tx, docKey, dt);
         tx.commit();
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      catch ( DTSMException e ) {
         tx.cancel();
         throw new DBException(FaultCodes.COL_CANNOT_STORE, "Can't parse Document '" + docKey + "'", e);
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_remove = {"docKey"};

   public void remove(String docKey) throws DBException {
      Transaction tx = new Transaction();
      try {
         col.remove(tx, docKey);
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public String[] listKeys() throws DBException {
      Transaction tx = new Transaction();
      try {
         Key[] keys = col.listKeys(tx);
         String[] result = new String[keys.length];
         for ( int i = 0; i < keys.length; i++ )
            result[i] = keys[i].toString();
         return result;
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public long getKeyCount() throws DBException {
      Transaction tx = new Transaction();
      try {
         return col.getKeyCount(tx);
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_insertValue = {"value"};

   public String insertValue(byte[] value) throws DBException {
      Transaction tx = new Transaction();
      try {
         return col.insertRecord(tx, new Value(value)).toString();
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_setValue = {"key", "value"};

   public void setValue(String key, byte[] value) throws DBException {
      Transaction tx = new Transaction();
      try {
         col.setRecord(tx, key, new Value(value));
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_getValue = {"key"};

   public byte[] getValue(String key) throws DBException {
      Transaction tx = new Transaction();
      try {
         Record rec = col.getRecord(tx, key);
         if ( rec == null )
            throw new DBException(FaultCodes.COL_CANNOT_RETRIEVE, "Record '"+key+"' not found");

         return rec.getValue().getData();
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_queryCollection = {"style", "query", "nsMap"};
   public static final String REST_CONTENT_TYPE_queryCollection = Headers.TYPE_TEXT_XML;

   public String queryCollection(String style, String query, Map nsMap) throws DBException {
      Transaction tx = new Transaction();
      try {
         return ResultSetWrapper.toText(col.queryCollection(tx, style, query, new NamespaceMap(nsMap)));
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }

   public static final String[] PARAMS_queryDocument = {"style", "query", "nsMap", "key"};
   public static final String REST_CONTENT_TYPE_queryDocument = Headers.TYPE_TEXT_XML;

   public String queryDocument(String style, String query, Map nsMap, String key) throws DBException {
      Transaction tx = new Transaction();
      try {
         return ResultSetWrapper.toText(col.queryDocument(tx, style, query, new NamespaceMap(nsMap), key));
      }
      catch ( DBException e ) {
         tx.cancel();
         throw e;
      }
      finally {
         if ( tx.getStatus() == Transaction.ACTIVE )
            tx.commit();
      }
   }
}


