package com.dbxml.db.core.extension;

/*
 * 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: ExtensionManager.java,v 1.5 2004/02/12 00:17:57 bradford Exp $
 */

import com.dbxml.util.*;

import com.dbxml.db.core.ClassResolver;
import com.dbxml.db.core.Collection;
import com.dbxml.db.core.DBException;
import com.dbxml.db.core.FaultCodes;
import com.dbxml.db.core.security.Access;
import com.dbxml.db.core.security.SecurityException;
import com.dbxml.labrador.ID;
import com.dbxml.labrador.objects.ObjectResolver;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;


/**
 * ExtensionManager manages Extension instances.
 */

public final class ExtensionManager extends SimpleConfigurable {
   private static final String[] EmptyStrings = new String[0];

   private static final String EXTENSION = "extension";
   private static final String NAME = "name";
   private static final String CLASS = "class";

   private Map objects = Collections.synchronizedMap(new TreeMap()); // String to ExtensionInfo
   private Collection collection;

   public ExtensionManager(Collection collection) {
      this.collection = collection;
   }

   public void setConfig(Configuration config) throws dbXMLException {
      super.setConfig(config);
      config.processChildren(EXTENSION,
         new ConfigurationCallback() {
            public void process(Configuration cfg) {
               String className = cfg.getAttribute(CLASS);
               try {
                  register(ClassResolver.get(className), cfg);
               }
               catch ( Exception e ) {
                  e.printStackTrace(System.err);
               }
            }
         });
   }

   private void checkAccess(int permissions) throws SecurityException {
      collection.getDatabase().getSecurityManager().access(collection.getCanonicalName(), permissions);
   }

   /**
    * list returns a list of the Extensions that this ExtensionManager has
    * registered.
    *
    * @return An array containing the Extension names
    */
   public String[] list() {
      return (String[])objects.keySet().toArray(EmptyStrings);
   }

   /**
    * drop physically removes the specified Extension and any
    * associated system resources that the Extension uses.
    *
    * @param name The Extension to drop
    * @return Whether or not the Extension was dropped
    */
   public boolean drop(final String name) throws DBException {
      checkAccess(Access.CREATE);

      Extension ext = get(name);
      if ( ext == null )
         throw new ExtensionException(FaultCodes.OBJ_OBJECT_NOT_FOUND, "Extension '"+name+"' does not exist");

      unregister(name);
      config.processChildren(EXTENSION,
         new ConfigurationCallback() {
            public void process(Configuration cfg) {
               try {
                  if ( cfg.getAttribute(NAME).equals(name) )
                     cfg.delete();
               }
               catch ( Exception e ) {
                  e.printStackTrace(System.err);
               }
            }
         });
      collection.getDatabase().flushConfig();
      return true;
   }

   /**
    * create creates a new Extension object and any associated
    * system resources that the Extension will need.
    *
    * @param cfg The Extension's configuration
    * @return The Extension that was created
    */
   public Extension create(Configuration cfg) throws DBException {
      checkAccess(Access.CREATE);

      String className = cfg.getAttribute(CLASS);
      try {
         // Check for duplicates
         String name = cfg.getAttribute(NAME);
         Configuration[] cfgs = config.getChildren();
         for ( int i = 0; i < cfgs.length; i++ )
            if ( cfgs[i].getAttribute(NAME).equals(name) )
               throw new DuplicateObjectException("Duplicate Extension '" + name + "'");

         Class c = ClassResolver.get(className);
         Extension obj = register(c, cfg);

         config.add(cfg);

         collection.getDatabase().flushConfig();
         return obj;
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         throw new CannotCreateException("Cannot create Extension");
      }
   }

   private Extension register(Class c, Configuration cfg) throws DBException {
      String name = null;
      try {
         Extension obj = (Extension)c.newInstance();
         initialize(obj, cfg);
         name = obj.getName();

         if ( name == null || name.trim().equals("") )
            throw new CannotCreateException("No name specified");

         objects.put(name, new ExtensionInfo(name, obj, cfg));

         if ( !collection.getDatabase().isDatabaseEmbedded() )
            ObjectResolver.register(new ID(getCanonicalName(name)), obj);

         return obj;
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         throw new InvalidContextException("'" + name + "' is not a valid Extension", e);
      }
   }

   private void unregister(String name) {
      if ( !collection.getDatabase().isDatabaseEmbedded() )
         ObjectResolver.unregister(new ID(getCanonicalName(name)));

      objects.remove(name);
   }

   private void initialize(Extension obj, Configuration cfg) throws dbXMLException {
      obj.setConfig(cfg);
      obj.setCollection(collection);
   }

   /**
    * get retrieves an Extension by name, and will also do the work of
    * instantiation and pool-retrieval.
    *
    * @param name The Extension name
    * @return The Extension
    */
   public Extension get(String name) throws DBException {
      checkAccess(Access.EXECUTE);

      ExtensionInfo info = (ExtensionInfo)objects.get(name);
      return info.instance;
   }

   public String getCanonicalName(String name) {
      return collection.getCanonicalName() + "/" + name;
   }


   /**
    * ExtensionInfo
    */

   private class ExtensionInfo extends ObjectPool {
      public String name;
      public Class c;
      public Extension instance;
      public Configuration cfg;

      public ExtensionInfo(String name, Extension instance, Configuration cfg) {
         this.name = name;
         this.cfg = cfg;
         this.instance = instance;
      }

      public Poolable createObject() {
         try {
            Poolable obj = (Poolable)c.newInstance();
            Extension xobj = (Extension)obj;
            initialize(xobj, cfg);
            return obj;
         }
         catch ( Exception e ) {
            e.printStackTrace(System.err);
         }
         return null;
      }
   }
}


