/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/common/Namespace.java,v 1.76 2005/03/29 08:08:28 luetzkendorf Exp $
 * $Revision: 1.76 $
 * $Date: 2005/03/29 08:08:28 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.common;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.content.ContentInterceptor;
import org.apache.slide.extractor.Extractor;
import org.apache.slide.extractor.ExtractorManager;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.DefaultIndexer;
import org.apache.slide.store.IndexStore;
import org.apache.slide.store.LockStore;
import org.apache.slide.store.MacroStore;
import org.apache.slide.store.NodeStore;
import org.apache.slide.store.RevisionDescriptorStore;
import org.apache.slide.store.RevisionDescriptorsStore;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.store.SequenceStore;
import org.apache.slide.store.Store;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.transaction.SlideTransactionManager;
import org.apache.slide.util.conf.Configurable;
import org.apache.slide.util.conf.Configuration;
import org.apache.slide.util.conf.ConfigurationException;
import org.apache.slide.util.logger.Logger;

/**
 * A Namespace contains a hierarchically organized tree of information.
 *
 * <p>
 *   Objects in the namespace are generally referred to as <i>Nodes</i>. Nodes
 *   may have a parent, children, content and meta-data. They can also be
 *   versioned (so that multiple revisions of the object's content and
 *   metadata are stored) and locked (so that only specific principals are
 *   allowed to read or modify the object). In addition, access control
 *   information can be assigned to every node.
 * </p>
 * <p>
 *   Nodes in the hierarchy are identified by their URI (Unique Resource
 *   Identifier). A URI is analogous to a file path in traditional file
 *   systems. For example:
 *   <pre>
 *     /users/john/documents/my_document.txt
 *   </pre>
 *   As you can see, the slash (&quot;/&quot;) is used to separate nodes in the path.
 * </p>
 * <p>
 *   Client applications can not access a Namespace object directly. Instead,
 *   access must be requested from the {@link Domain Domain}, which will hand
 *   out a proxy object ({@link NamespaceAccessToken NamespaceAccessToken})
 *   that enables the client application to access the namespace using the
 *   helpers.
 * </p>
 * <p>
 *   Namespaces are necessarily self-contained. What this means is that a
 *   namespace cannot reference or contain links to another namespace. A
 *   namespace is typically assigned per-application, which effectively
 *   isolates it's data and security context from those of other applications.
 * </p>
 *
 * @version $Revision: 1.76 $
 */
public final class Namespace {
    
    
    // -------------------------------------------------------------- Constants
    
    
    public static final String REFERENCE = "reference";
    public static final String NODE_STORE = "nodestore";
    public static final String SECURITY_STORE = "securitystore";
    public static final String LOCK_STORE = "lockstore";
    public static final String REVISION_DESCRIPTORS_STORE =
        "revisiondescriptorsstore";
    public static final String REVISION_DESCRIPTOR_STORE =
        "revisiondescriptorstore";
    public static final String CONTENT_STORE = "contentstore";
    public static final String PROPERTIES_INDEX_STORE = "propertiesindexer";
    public static final String CONTENT_INDEX_STORE = "contentindexer";
    public static final String SEQUENCE_STORE = "sequencestore";
    public static final String MACRO_STORE = "macrostore";
    
    
    /**
     * Log channel for logger
     */
    private static final String LOG_CHANNEL = Namespace.class.getName();
    
    protected static final String I_CREATESTORELISTENERCLASS         = "createStoreListenerClass";
    protected static final String I_CREATESTORELISTENERCLASS_DEFAULT = "org.apache.slide.webdav.util.UriHandler";
    
    protected static Class createStoreListenerClass;
    
    
    static {
        try {
            String createStoreListenerClassName = Domain.getParameter(I_CREATESTORELISTENERCLASS, I_CREATESTORELISTENERCLASS_DEFAULT);
            createStoreListenerClass = Class.forName( createStoreListenerClassName );
        }
        catch( Exception x ) {
            Domain.warn( "Loading of create_store_listener class failed: "+x.getMessage() );
        }
    }
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Namespace name.
     */
    private String name;
    
    
    /**
     * classname of the search implementation
     */
    private String searchClassName;
    
    
    /**
     * Static Vector which holds a reference, and provides access to all
     * the services instances used by the Slide namespace.
     */
    private transient Vector connectedServices;
    
    
    /**
     * Registered DescriptorStores on this Namespace.
     */
    private transient Hashtable stores;
    
    
    /**
     * Current namespace configuration.
     */
    private NamespaceConfig config;
    
    
    /**
     * Uri cache.
     */
    private Hashtable uriCache;
    
    
    /**
     * Default descriptors store classname.
     */
    private String defaultStoreClassname =
        "org.apache.slide.store.ExtendedStore";
    
    
    /**
     * Transaction manager associated with this namespace.
     */
    private TransactionManager transactionManager =
        new SlideTransactionManager();
    
    
    /**
     * Logger.
     */
    private Logger logger;
    
    
    /**
     * Application logger.
     */
    private Logger applicationLogger;
    
    
    // ------------------------------------------------------------ Constructor
    
    
    /**
     * Constructor.
     */
    Namespace() {
        stores = new Hashtable();
        connectedServices = new Vector();
        name = "";
        uriCache = new Hashtable();
    }
    
    
    // ------------------------------------------------------------- Properties
    
    
    /**
     * Sets the qualified name of the namespace.
     *
     * @param name Name of the namespace
     */
    public void setName(String name) {
        this.name = name;
    }
    
    
    /**
     * Gets the qulified name of the namespace.
     *
     * @return String Namespace name
     */
    public String getName() {
        return name;
    }
    
    
    /**
     * Method setSearchClassName
     *
     * @param    searchClassName     classname of the search implementation
     */
    public void setSearchClassName (String searchClassName) {
        this.searchClassName = searchClassName;
    }
    
    
    /**
     * Method getSearchClassName
     *
     * @return   classname of the search implementation
     */
    public String getSearchClassName()  {
        return searchClassName;
    }
    
    
    /**
     * Returns the namespace configuration.
     *
     * @return NamespaceConfig Namespace configuration
     */
    public NamespaceConfig getConfig() {
        return config;
    }
    
    
    /**
     * Enumerate all scopes managed by this namespace.
     *
     * @return     return an enumeration of all scopes
     */
    public Enumeration enumerateScopes() {
        return stores.keys();
    }
    
    
    
    /**
     * Transaction manager accessor.
     */
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }
    
    /**
     * Allows for overriding the default transaction manager used by this
     * namespace.
     * @param transactionManager the new transaction manager
     */
    public void initTransactionManager(TransactionManager transactionManager) {
        if (this.transactionManager instanceof SlideTransactionManager) {
            this.transactionManager = transactionManager;
        }
    }

    /**
     * Return the current logger.
     */
    public Logger getLogger() {
        if (logger != null)
            return logger;
        else
            return Domain.getLogger();
    }
    
    
    /**
     * Set the logger used by this namespace.
     */
    public void setLogger(Logger logger) {
        this.logger = logger;
        if (transactionManager instanceof SlideTransactionManager) {
            ((SlideTransactionManager) transactionManager).setLogger(logger);
        }
    }
    
    
    /**
     * Return the current application logger.
     */
    public Logger getApplicationLogger() {
        if (applicationLogger != null)
            return applicationLogger;
        else if (logger != null)
            return logger;
        else
            return Domain.getLogger();
    }
    
    
    /**
     * Set the logger used by this namespace.
     */
    public void setApplicationLogger(Logger logger) {
        this.applicationLogger = logger;
    }
    
    
    // --------------------------------------------------------- Public Methods
    
    /**
     * Checks if all descendants of the given URI are stored in the same store.
     * 
     * @param source
     *            root URI of the tree to check
     * @return <code>true</code> if the whole tree is in a single store,
     *         <code>false</code> otherwise
     */
    public boolean isTreeInSingleStore(Uri source) {
        Store sourceStore = source.getStore();
        String evilPrefix = source.toString();

        for (Iterator it = stores.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            Scope scope = (Scope) entry.getKey();
            Store store = (Store) entry.getValue();

            // there is a store that is not the one of the root, but still stores
            // part of our tree, which means the tree is not in a single store
            if (store != sourceStore && scope.toString().startsWith(evilPrefix)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Used to register a Store in the namespace for the specified scope. First,
     * the function instantiate the Store, then gives it its init parameters. It
     * is then stored in the stores Hashtable, associated with the given scope.
     * 
     * @param storeClass
     *            Class of the Data Source
     * @param parameters
     *            Init parameters for the Data Source
     * @param scope
     *            Scope for which the Data Source is registered
     * @param childStores
     *            Instances of the typed stores
     * @exception ServiceRegistrationFailed
     *                An error occured during instantiation of the service
     * @exception ServiceParameterErrorException
     *                Incorrect service parameter
     * @exception ServiceParameterMissingException
     *                Service parameter missing
     */
    public void registerStore(String storeName, Class storeClass,
                              Hashtable parameters, Scope scope,
                              Hashtable childStores)
        throws ServiceRegistrationFailedException,
        ServiceParameterErrorException, ServiceParameterMissingException {
        if (!stores.containsKey(scope)) {
            try {
                Store store = (Store) storeClass.newInstance();
                store.setName(storeName);
                store.setParameters(parameters);
                stores.put(scope, store);
                
                // assign NodeStore
                NodeStore nodeStore =
                    (NodeStore) dereferenceStore (NODE_STORE, childStores);
                
                store.setNodeStore (nodeStore);
                
                // assign SecurityStore
                SecurityStore securityStore =
                    (SecurityStore) dereferenceStore (SECURITY_STORE, childStores);
                
                store.setSecurityStore (securityStore);
                
                // assign LockStore
                LockStore lockStore =
                    (LockStore) dereferenceStore (LOCK_STORE, childStores);
                
                store.setLockStore (lockStore);
                
                // assign RevisionDescriptorsStore
                RevisionDescriptorsStore revisionDescriptorsStore =
                    (RevisionDescriptorsStore) dereferenceStore
                    (REVISION_DESCRIPTORS_STORE, childStores);
                
                store.setRevisionDescriptorsStore (revisionDescriptorsStore);
                
                // assign RevisionDescriptorStore
                RevisionDescriptorStore revisionDescriptorStore =
                    (RevisionDescriptorStore) dereferenceStore
                    (REVISION_DESCRIPTOR_STORE, childStores);
                
                store.setRevisionDescriptorStore (revisionDescriptorStore);
                
                // assign ContentStore
                ContentStore contentStore =
                    (ContentStore) dereferenceStore (CONTENT_STORE, childStores);
                
                store.setContentStore (contentStore);
                
                // assign PropertiesIndexStore
                IndexStore propertiesIndexer =
                    (IndexStore) dereferenceStore (PROPERTIES_INDEX_STORE, childStores);
                
                // if not configured, take the default indexer
                if (propertiesIndexer == null) {
                    propertiesIndexer = new DefaultIndexer (revisionDescriptorStore);
                    childStores.put (PROPERTIES_INDEX_STORE, propertiesIndexer);
                }
                
                store.setPropertiesIndexer (propertiesIndexer);
                
                // assign ContentIndexStore
                IndexStore contentIndexer =
                    (IndexStore) dereferenceStore (CONTENT_INDEX_STORE, childStores);
                
                // if not configured, take the default indexer
                if (contentIndexer == null) {
                    contentIndexer = new DefaultIndexer (contentStore);
                    childStores.put (CONTENT_INDEX_STORE, contentIndexer);
                }
                
                store.setContentIndexer (contentIndexer);
                
                // assign SequenceStore
                SequenceStore sequenceStore =
                    (SequenceStore) dereferenceStore (SEQUENCE_STORE, childStores);
                
                store.setSequenceStore(sequenceStore);
                
                // assign MacroStore
                MacroStore macroStore =
                    (MacroStore) dereferenceStore (MACRO_STORE, childStores);
                
                store.setMacroStore(macroStore);
                
                // set the scope in the father and child stores
                store.setScope(scope);
                
                // call the create_store_listener
                notifyStoreCreated( this.name, scope.toString(), storeName );
                
            } catch(InstantiationException e) {
                throw new ServiceRegistrationFailedException
                    (storeClass);
            } catch(IllegalAccessException e) {
                throw new ServiceRegistrationFailedException
                    (storeClass);
            } catch(NullPointerException e) {
                throw new ServiceRegistrationFailedException
                    (storeClass);
            } catch(ClassCastException e) {
                // TEMP
                getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
                // --TEMP
                throw new ServiceRegistrationFailedException
                    (storeClass);
            }
            
        }
    }
    
    
    Object dereferenceStore (String storeType, Hashtable childStores) {
        Object result;
        
        Object o = childStores.get(storeType);
        if (o instanceof String) {
            result = childStores.get(o);
        } else {
            result = o;
        }
        return result;
    }
    
    
    /**
     * At the end of the service registration, this service is called to
     * perform any required initialization task.
     *
     * @exception ServicesInitializationFailedException One or more
     * exception occured while initializing services
     */
    public void initializeServices()
        throws ServicesInitializationFailedException {
        
        // We create the nested exception which will hold all thrown exception
        // during the initialization process.
        ServicesInitializationFailedException nestedException
            = new ServicesInitializationFailedException();
        
        // Initializing DesciptorsStores
        Enumeration serviceList = stores.elements();
        while (serviceList.hasMoreElements()) {
            Service service = (Service) serviceList.nextElement();
            try {
                getLogger().log("Initializing Store " + service,LOG_CHANNEL,Logger.INFO);
                service.setNamespace(this);
                service.initialize(new NamespaceAccessTokenImpl(this));
            } catch (ServiceInitializationFailedException e) {
                // We add the exception which just occured to the
                // nested exception
                nestedException.addException(e);
            }
        }
        
        // If the nested exception is not empty, we throw it.
        if (!nestedException.isEmpty()) {
            throw nestedException;
        }
        
    }
    
    
    /**
     * Reinitialize namespace.
     */
    public void clearNamespace() {
        stores.clear();
    }
    
    
    /**
     * Connects a data source on demand.
     *
     * @param service Service on which a connection attempt will be made
     * @param token the credentials token containing e.g. the credential
     * @exception ServiceConnectionFailedException Error connecting service
     * @exception ServiceAccessException Unspecified low level service
     * access exception
     */
    public void connectService(Service service, CredentialsToken token)
        throws ServiceConnectionFailedException, ServiceAccessException {
        // Try to connect ...
        boolean newConnection = service.connectIfNeeded(token);
        
        // If successfull (ie, no exception was thrown), we add it to the list
        // of the connected components.
        if (newConnection) {
            connectedServices.addElement(service);
        }
    }
    
    
    /**
     * Disconnects all services.
     *
     * @exception ServicesShutDownFailedException Error disconnecting one or
     * more services
     */
    public void disconnectServices()
        throws ServicesShutDownFailedException {
        
        // We create the nested exception which will hold all thrown exception
        // during shut down of services.
        ServicesShutDownFailedException nestedException
            = new ServicesShutDownFailedException();
        
        for (int i=0; i<connectedServices.size(); i++) {
            try {
                Service service = (Service) connectedServices.elementAt(i);
                if (service.isConnected()) {
                    getLogger().log("Shutting down service " + service,LOG_CHANNEL,Logger.INFO);
                    service.disconnect();
                }
            } catch (ServiceDisconnectionFailedException e) {
                nestedException.addException(e);
            } catch (ServiceAccessException e) {
                nestedException.addException(e);
            }
        }
        connectedServices.removeAllElements();
        
        // If the nested exception is not empty, we throw it.
        if (!nestedException.isEmpty()) {
            throw nestedException;
        }
        
    }
    
    
    /**
     * Remove a Store from the registry.
     *
     * @param scope Scope to disconnect
     * @exception ServiceDisconnctionFailedException Error disconnecting
     * DescriptorsStore
     * @exception ServiceAccessException Unspecified error during
     * service access
     */
    public void unregisterStore(Scope scope)
        throws ServiceDisconnectionFailedException, ServiceAccessException {
        if (stores.containsKey(scope)) {
            Store store = (Store) stores.get(scope);
            if (store.isConnected()) {
                store.disconnect();
                connectedServices.removeElement(store);
            }
            stores.remove(scope);
            store = null;
        }
    }
    
    
    /**
     * Get the Data Source associated with the given scope, if any.
     * In contrary to the retrieveStore method, this methos does not
     * perform a connection.
     *
     * @param scope Scope to match
     */
    public Store getStore(Scope scope) {
        Store store = null;
        if (stores.containsKey(scope)) {
            store = (Store) stores.get(scope);
        }
        return store;
    }
    
    
    /**
     * Get the Data Source associated with the given scope, if any and
     * connect to the store.
     *
     * @param scope Scope to match
     * @param token the Credeantials token containing e.g. the credential
     * @exception ServiceConnectionFailedException Connection to Store failed
     * @exception ServiceAccessException Unspecified service access exception
     */
    public Store retrieveStore(Scope scope, CredentialsToken token)
        throws ServiceConnectionFailedException, ServiceAccessException {
        Store store = getStore(scope);
        if (store != null) {
            connectService(store, token);
        }
        return store;
    }
    
    
    /**
     * Builds a new uri object to access this namespace. This call will
     * return a Uri which doesn't have its token field set. The store should
     * accept such Uri as valid, and bypass any check that is made based on the
     * state.
     *
     * @param uri Requested Uri
     * @return Uri
     * @deprecated use signature with SlideToken instead
     */
    public Uri getUri(String uri) {
        return getUri(null, uri);
    }
    
    
    /**
     * Builds a new uri object to access this namespace.
     *
     * @param token SlideToken
     * @param uri Requested Uri
     * @return Uri
     */
    public Uri getUri(SlideToken token, String uri) {
        return getUri(token, uri, token==null
                          ?false
                          :token.isForceStoreEnlistment());
    }
    
    
    /**
     * Builds a new uri object to access this namespace.
     *
     * @param token SlideToken
     * @param uri Requested Uri
     * @param forcedEnlistment may differ from the value set in token
     * @return Uri
     */
    public Uri getUri(SlideToken token, String uri, boolean forcedEnlistment) {
        
        Uri result = null;
        Object temp = null;
        temp = uriCache.get(uri);
        if (temp == null) {
            result = new Uri(token, this, uri);
            uriCache.put(uri, result);
            if (uriCache.size() > 10000) {
                clearUriCache();
            }
        } else {
            result = (Uri) temp;
            result = result.cloneObject();
            result.setToken(token);
            result.reconnectServices();
        }
        
        // if a different forceEnlistment value want to be used
        // wrap the used token to reflect the different value
        if (token != null && token.isForceStoreEnlistment() != forcedEnlistment) {
            SlideToken wToken = new SlideTokenWrapper(token);
            wToken.setForceStoreEnlistment(forcedEnlistment);
            result.setToken(wToken);
        }
        
        return result;
        
    }
    
    
    /**
     * Clear uri cache.
     */
    void clearUriCache() {
        uriCache.clear();
    }
    
    
    /**
     * Get content interceptors associated with this namespace.
     */
    public ContentInterceptor[] getContentInterceptors() {
        return config.getContentInterceptors();
    }
    
    
    // -------------------------------------------------------- Package Methods
    
    
    /**
     * Parses the contents of the specified definition object, and uses that
     * info to initialize the namespace.
     *
     * @param definition Definiton of the scopes and stores of
     * the namespace
     * @exception SlideException Something went wrong during registry or
     * services initialization
     * @exception ConfigurationException Error parsing configuration file
     */
    void loadDefinition(Configuration definition)
        throws SlideException, ConfigurationException {
        
        getLogger().log("Loading namespace definition",LOG_CHANNEL,Logger.INFO);
        
        // Loading stores
        
        Hashtable storesClass = new Hashtable();
        Hashtable storesParameters = new Hashtable();
        Hashtable childStores = new Hashtable();
        
        Enumeration storeDefinitions =
            definition.getConfigurations("store");
        
        while (storeDefinitions.hasMoreElements()) {
            loadStoreDefinition
                ((Configuration) storeDefinitions.nextElement(),
                 storesClass, storesParameters, childStores);
        }
        
        Enumeration scopeDefinitions =
            definition.getConfigurations("scope");
        
        while (scopeDefinitions.hasMoreElements()) {
            loadScopeDefinition
                ((Configuration) scopeDefinitions.nextElement(),
                 storesClass, storesParameters, childStores);
        }
        
        // Initialize all loaded services.
        initializeServices();
        
    }
    
    
    /**
     * Parses the contents of the specified reader, and uses that info to
     * initialize the specified Slide namespace.
     *
     * @param namespaceBaseDataDefinition Namespace base data
     * @exception SlideException Something went wrong during registry or
     * services initialization
     */
    void loadBaseData(Configuration namespaceBaseDataDefinition)
        throws SlideException, ConfigurationException {
        
        getLogger().log("Loading namespace " + getName() + " base data",LOG_CHANNEL,Logger.INFO);
        
        // Load Namespace Base Data
        try {
            // start transaction for temp object creation
            getTransactionManager().begin();
            
            SlideToken slideToken = new SlideTokenImpl(new CredentialsToken(""));
            slideToken.setForceStoreEnlistment(true);
            
            // First, we create the root node
            Uri rootUri = getUri(slideToken, "/");
            SubjectNode rootNode = new SubjectNode("/");

            try {
                rootUri.getStore().createObject(rootUri, rootNode);
            } catch (ObjectAlreadyExistsException e) {
                // if it is already there, that's fine with us
            }
            getLogger().log("Init namespace " + getName() + " configuration",LOG_CHANNEL,Logger.INFO);
            
            // Create the dummy configuration
            config.initializeAsDummyConfig(this);
            
            // Create the Access token
            NamespaceAccessToken token = new NamespaceAccessTokenImpl(this);

            getLogger().log("Import data into namespace " + getName(),LOG_CHANNEL,Logger.INFO);
            token.importData(slideToken, namespaceBaseDataDefinition);
            
            getLogger().log("Finish init namespace " + getName() + " configuration",LOG_CHANNEL,Logger.INFO);
            
            // And remove the all permission from the root node
            rootNode = (SubjectNode)rootUri.getStore().retrieveObject(rootUri);
            rootUri.getStore().storeObject(rootUri, rootNode);
            
            // end transaction for temp object removal
            getTransactionManager().commit();
            
        } catch (SlideException e) {
            // If that occurs, then most likely the base config was
            // already done before
            getLogger().log("Namespace base configuration might have been already done before",LOG_CHANNEL,Logger.WARNING);
            getLogger().log(e,LOG_CHANNEL,Logger.WARNING);
            try {
                if (getTransactionManager().getStatus()==Status.STATUS_ACTIVE)
                    getTransactionManager().rollback();
            }
            catch (SystemException ex) {
                getLogger().log("Could not rollback namespace base configuration: " + ex.toString(),LOG_CHANNEL,Logger.WARNING);
            }
        } catch (Exception e) {
            getLogger().log("Unable to read Namespace base configuration file : ",LOG_CHANNEL,Logger.ERROR);
            getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
            // Unable to load the base configuration XML file.
            // Log the event, and hope it was already done before.
            try {
                if (getTransactionManager().getStatus()==Status.STATUS_ACTIVE)
                    getTransactionManager().rollback();
            }
            catch (SystemException ex) {
                getLogger().log("Could not rollback namespace base configuration after load error: " + ex.toString(),LOG_CHANNEL,Logger.WARNING);
            }
        }
    }
    
    
    /**
     * Parses the contents of the specified reader, and uses that info to
     * initialize the specified Slide namespace.
     *
     * @param namespaceConfigurationDefinition The configuration to load.
     * @exception SlideException Something went wrong during registry or
     * services initialization
     */
    void loadConfiguration(Configuration namespaceConfigurationDefinition)
        throws SlideException {
        
        getLogger().log("Loading namespace " + getName() + " configuration",LOG_CHANNEL,Logger.INFO);
        
        // Load Namespace Config
        config = new NamespaceConfig();
        config.initializeNamespaceConfig(this,
                                         namespaceConfigurationDefinition);
        
    }
    
    
    /**
     * Parses the contents of the specified reader, and uses that info to
     * initialize the specified Slide namespace.
     *
     * @param namespaceConfigurationDefinition Namespace configuration
     * @exception SlideException Something went wrong during registry or
     * services initialization
     */
    void loadParameters(Configuration namespaceConfigurationDefinition)
        throws SlideException {
        
        getLogger().log("Loading namespace " + getName() + " parameters",LOG_CHANNEL,Logger.INFO);
        
        // Load Namespace Config
        config = new NamespaceConfig();
        config.initializeNamespaceParameters(this,
                                             namespaceConfigurationDefinition);
        
    }
    
    
    void loadExtractors(Configuration namespaceExtractorsDefinition) 
    	throws SlideException {
        
        getLogger().log("Loading namespace " + getName() + " extractors",LOG_CHANNEL,Logger.INFO);
        
        Enumeration extractorConfigs = namespaceExtractorsDefinition.getConfigurations("extractor");
        while (extractorConfigs.hasMoreElements()) {
            Configuration extractorConfig = (Configuration) extractorConfigs.nextElement();
            String classname = extractorConfig.getAttribute("classname");
            String uri = extractorConfig.getAttribute("uri", null);
            String contentType = extractorConfig.getAttribute("content-type", null);
            String namespace = getName();
            try {
                Class extractorClass = Class.forName(classname);
                Extractor extractor = null;
                Constructor extractorConstructor = extractorClass.getConstructor(new Class[] { String.class, String.class, String.class } );
                extractor = (Extractor)extractorConstructor.newInstance(new String[] { uri, contentType, namespace });
                if ( extractor instanceof Configurable ) {
                    ((Configurable)extractor).configure(extractorConfig.getConfiguration("configuration"));
                }
                ExtractorManager.getInstance().addExtractor(extractor);
            } catch (ClassCastException e) {
                throw new ConfigurationException("Extractor '"+classname+"' is not of type Extractor", namespaceExtractorsDefinition);
            } catch (ConfigurationException e) {
                throw e;
            } catch (Exception e) {
                throw new ConfigurationException("Extractor '"+classname+"' could not be loaded", namespaceExtractorsDefinition);
            }
        }
    }
    
    // -------------------------------------------------------- Private Methods
    
    
    /**
     * Parse the store definition.
     *
     * @param storeDefinition store definition
     * @param storesClass Class names of the stores
     * @param storesParameters Parameters of the stores
     * @param childStores Child stores
     * @exception ConfigurationException Error parsing configuration file
     * @exception SlideException Error loading the specified class
     */
    private void loadStoreDefinition
        (Configuration storeDefinition,
         Hashtable storesClass,
         Hashtable storesParameters,
         Hashtable childStores)
        throws ConfigurationException, SlideException {
        
        String storeName = storeDefinition.getAttribute("name");
        String storeClassname = defaultStoreClassname;
        
        try {
            storeClassname = storeDefinition.getAttribute("classname");
        } catch (ConfigurationException e) {
        }
        
        Enumeration storeParametersDefinitions =
            storeDefinition.getConfigurations("parameter");
        
        // Load descriptors store class
        Class storeClass = null;
        try {
            storeClass = Class.forName(storeClassname);
        } catch (Exception e) {
            getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
            throw new SlideException(e.getMessage());
        }
        storesClass.put(storeName, storeClass);
        
        // Load descriptor store parameters
        Hashtable storeParameters = new Hashtable();
        while (storeParametersDefinitions.hasMoreElements()) {
            Configuration parameterDefinition = (Configuration)
                storeParametersDefinitions.nextElement();
            String parameterName = parameterDefinition.getAttribute("name");
            String parameterValue = parameterDefinition.getValue();
            storeParameters.put(parameterName, parameterValue);
        }
        
        storesParameters.put(storeName, storeParameters);
        
        // Now reading the "child" stores
        
        Hashtable currentStoreChildStores = new Hashtable();
                
        // Loading node store (if any)
        getChildStore (storeDefinition, NODE_STORE, currentStoreChildStores, storeParameters);
        
        // Loading security store (if any)
        getChildStore (storeDefinition, SECURITY_STORE, currentStoreChildStores, storeParameters);
        
        // Loading lock store (if any)
        getChildStore (storeDefinition, LOCK_STORE, currentStoreChildStores, storeParameters);
        
        // Loading revision descriptors store (if any)
        getChildStore (storeDefinition, REVISION_DESCRIPTORS_STORE, currentStoreChildStores, storeParameters);
        
        // Loading revision descriptor store (if any)
        getChildStore (storeDefinition, REVISION_DESCRIPTOR_STORE, currentStoreChildStores, storeParameters);
        
        // Loading content store (if any)
        getChildStore (storeDefinition, CONTENT_STORE, currentStoreChildStores, storeParameters);
        
        // Loading descriptorindexstore store (if any)
        getChildStore (storeDefinition, PROPERTIES_INDEX_STORE, currentStoreChildStores, storeParameters);
        
        // Loading contentindexstore store (if any)
        getChildStore (storeDefinition, CONTENT_INDEX_STORE, currentStoreChildStores, storeParameters);
        
        // load default indexer, if no indexer defined
        
        // Loading sequence store (if any)
        getChildStore (storeDefinition, SEQUENCE_STORE, currentStoreChildStores, storeParameters);

        // Loading macro store (if any)
        getChildStore (storeDefinition, MACRO_STORE, currentStoreChildStores, storeParameters);

        childStores.put(storeName, currentStoreChildStores);
        
    }
    
    private void getChildStore(Configuration storeDefinition, String key, Hashtable currentStoreChildStores, Hashtable storeParameters) throws SlideException
    {
        Configuration localStoreDefinition;
        try {
            localStoreDefinition = storeDefinition.getConfiguration(key);
        } catch (ConfigurationException e) {
            return;
            // silently ignore as this only indicates there is no such store defined
        }
        try {
            try {
                Configuration referenceDefinition =
                    localStoreDefinition.getConfiguration(REFERENCE);
                currentStoreChildStores.put
                    (key, referenceDefinition.getAttribute("store"));
                getLogger().log(key + " references " + referenceDefinition.getAttribute("store"),LOG_CHANNEL,Logger.INFO);
            } catch (ConfigurationException ex) {
                getLogger().log(key + ": " + localStoreDefinition.getAttribute("classname"),LOG_CHANNEL,Logger.INFO);
                Service store =
                    loadChildStore(localStoreDefinition,
                                   storeParameters);
                if (store != null) {
                    currentStoreChildStores.put(key, store);
                }
            }
        } catch (ConfigurationException e) {
            getLogger().log("Exception while loading "+key+"!", e, LOG_CHANNEL, Logger.WARNING);
        }
    }
    
    
    /**
     * Load a child descriptors store.
     *
     * @param childStoreDefinition XML definition of the child store
     * @param fatherParameters     XML parameters defined for the father
     * @return Service Instance of the child store
     * @exception ConfigurationException Error parsing configuration file
     * @exception SlideException Error loading the specified class
     */
    private Service loadChildStore(Configuration childStoreDefinition,
                                   Hashtable fatherParameters)
        throws ConfigurationException, SlideException {
        
        // Load classname
        String childStoreClassname =
            childStoreDefinition.getAttribute("classname");
        
        // Load descriptors store class
        Service childStore = null;
        try {
            Class childStoreClass =
                Class.forName(childStoreClassname);
            childStore = (Service) childStoreClass.newInstance();
        } catch (Exception e) {
            getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
            return null;
        }
        
        // Retrieve parent parameters
        Hashtable childStoreParameters = new Hashtable();
        Enumeration fatherParametersKeys = fatherParameters.keys();
        while (fatherParametersKeys.hasMoreElements()) {
            Object key = fatherParametersKeys.nextElement();
            Object value = fatherParameters.get(key);
            childStoreParameters.put(key, value);
        }
        
        // Load parameters
        Enumeration childStoreParametersDefinitions =
            childStoreDefinition.getConfigurations("parameter");
        while (childStoreParametersDefinitions.hasMoreElements()) {
            Configuration parameterDefinition = (Configuration)
                childStoreParametersDefinitions.nextElement();
            String parameterName = parameterDefinition.getAttribute("name");
            String parameterValue = parameterDefinition.getValue();
            childStoreParameters.put(parameterName, parameterValue);
        }
        childStore.setParameters(childStoreParameters);
        
        // load configurations if any and the store supports them
        if (childStore instanceof Configurable) {
            Configurable configurable = (Configurable)childStore;
            Enumeration childStoreConfigurations = 
                    childStoreDefinition.getConfigurations("configuration");
            while(childStoreConfigurations.hasMoreElements()) {
                configurable.configure(
                        (Configuration)childStoreConfigurations.nextElement());
            }
        }
        
        return childStore;
    }
    
    
    /**
     * Parse the content store definition.
     *
     * @param storesClass Class names of the descriptors stores
     * @param storesParameters Parameters of the descriptors stores
     * @param childStores Child stores instances
     * @exception ConfigurationException Error parsing configuration file
     * @exception UnknownServiceDeclarationException Reference to
     * unknown service
     * @exception ServiceParameterErrorException Service parameter error
     * @exception ServiceParameterMissingException Service parameter missing
     * @exception ServiceRegistrationFailedException Error registering service
     */
    private void loadScopeDefinition(Configuration scopeDefinition,
                                     Hashtable storesClass,
                                     Hashtable storesParameters,
                                     Hashtable childStores)
        throws ConfigurationException, UnknownServiceDeclarationException,
        ServiceParameterErrorException, ServiceParameterMissingException,
        ServiceRegistrationFailedException {
        
        String match = scopeDefinition.getAttribute("match");
        
        // First, we get the correct class and parameters from the Hashtables.
        String storeName = scopeDefinition.getAttribute("store");
        
        if (storeName != null) {
            if ((!storesClass.containsKey(storeName)) ||
                    (!storesParameters.containsKey(storeName))) {
                throw new UnknownServiceDeclarationException(storeName);
            }
            registerStore(storeName,
                              (Class) storesClass.get(storeName),
                              (Hashtable) storesParameters.get(storeName),
                          new Scope(match),
                              (Hashtable) childStores.get(storeName));
            getLogger().log("Registering Store "
                                + storeName
                                + " ("
                                + storesClass.get(storeName)
                                + ") with parameters "
                                + storesParameters.get(storeName)
                                + " on scope " + match,LOG_CHANNEL,Logger.INFO);
        }
        
    }
    
    /**
     *
     */
    private void notifyStoreCreated( String namespaceName, String scope, String storeName ) {
        
        if( createStoreListenerClass != null ) {
            try {
                Method nsc = createStoreListenerClass.getMethod(
                    "notifyStoreCreated", new Class[]{String.class, String.class, String.class} );
                nsc.invoke( null, new Object[]{namespaceName, scope, storeName} ); // obj=null since method is static
            }
            catch( Exception x ) {
                Domain.warn( "Notification of store creation "+
                                "(namespace="+namespaceName+", scope="+scope+", store="+storeName+") failed: "+x.getMessage() );
            }
        }
    }
    
    
    // --------------------------------------------------------- Object Methods
    
    
    /**
     * Get a String representation of this namespace.
     */
    public String toString() {
        return getName();
    }
    
    
}

