Package org.exist.collections

Source Code of org.exist.collections.CollectionConfigurationManager

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2001-2014 The eXist Project
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.exist.collections;

import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.dom.DocumentImpl;
import org.exist.memtree.SAXAdapter;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.IndexSpec;
import org.exist.storage.lock.Lock;
import org.exist.storage.lock.Locked;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.util.sanity.SanityCheck;
import org.exist.xmldb.XmldbURI;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import java.io.StringReader;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

/**
* Manages index configurations. Index configurations are stored in a collection
* hierarchy below /db/system/config. CollectionConfigurationManager is called
* by {@link org.exist.collections.Collection} to retrieve the
* {@link org.exist.collections.CollectionConfiguration} instance for a given
* collection.
*
* @author wolf
*/
public class CollectionConfigurationManager {

    private static final Logger LOG = Logger.getLogger(CollectionConfigurationManager.class);

    public final static String CONFIG_COLLECTION = XmldbURI.SYSTEM_COLLECTION + "/config";

    /** /db/system/config **/
    public final static XmldbURI CONFIG_COLLECTION_URI = XmldbURI.create(CONFIG_COLLECTION);

    /** /db/system/config/db **/
    public final static XmldbURI ROOT_COLLECTION_CONFIG_URI = CONFIG_COLLECTION_URI.append(XmldbURI.ROOT_COLLECTION_NAME);

    public final static String COLLECTION_CONFIG_FILENAME = "collection.xconf";

    public final static CollectionURI COLLECTION_CONFIG_PATH = new CollectionURI(CONFIG_COLLECTION_URI.getRawCollectionPath());

    private Map<CollectionURI, CollectionConfiguration> configurations = new HashMap<CollectionURI, CollectionConfiguration>();

    private Locked latch = new Locked();

    private CollectionConfiguration defaultConfig;

    public CollectionConfigurationManager(DBBroker broker) throws EXistException, CollectionConfigurationException, PermissionDeniedException, LockException {

        checkCreateCollection(broker, CONFIG_COLLECTION_URI);
        checkCreateCollection(broker, ROOT_COLLECTION_CONFIG_URI);

        loadAllConfigurations(broker);

        defaultConfig = new CollectionConfiguration(broker.getBrokerPool());
        defaultConfig.setIndexConfiguration(broker.getIndexConfiguration());
    }

    /**
     * Add a new collection configuration. The XML document is passed as a
     * string.
     *
     * @param txn
     *            The transaction that will hold the WRITE locks until they are
     *            released by commit()/abort()
     * @param broker
     * @param collection
     *            the collection to which the configuration applies.
     * @param config
     *            the xconf document as a String.
     * @throws CollectionConfigurationException
     */
    public void addConfiguration(Txn txn, final DBBroker broker, Collection collection, String config) throws CollectionConfigurationException {
        try {
            final XmldbURI path = CONFIG_COLLECTION_URI.append(collection.getURI());

            final Collection confCol = broker.getOrCreateCollection(txn, path);
            if (confCol == null) {
                throw new CollectionConfigurationException("Failed to create config collection: " + path);
            }

            XmldbURI configurationDocumentName = null;
            // Replaces the current configuration file if there is one
            final CollectionConfiguration conf = getConfiguration(broker, collection);
            if (conf != null) {
                configurationDocumentName = conf.getDocName();
                if (configurationDocumentName != null) {
                    LOG.warn("Replacing current configuration file '" + configurationDocumentName + "'");
                }
            }
            if (configurationDocumentName == null) {
                configurationDocumentName = CollectionConfiguration.DEFAULT_COLLECTION_CONFIG_FILE_URI;
            }

            broker.saveCollection(txn, confCol);
            final IndexInfo info = confCol.validateXMLResource(txn, broker, configurationDocumentName, config);
            // TODO : unlock the collection here ?
            confCol.store(txn, broker, info, config, false);
            // broker.sync(Sync.MAJOR_SYNC);

            latch.writeE(new Callable<Void>() {
                @Override
                public Void call() throws Exception {

                    configurations.remove(new CollectionURI(path.getRawCollectionPath()));
                    loadConfiguration(broker, confCol);

                    return null;
                }
            });

        } catch (final CollectionConfigurationException e) {
            throw e;
        } catch (final Exception e) {
            throw new CollectionConfigurationException("Failed to store collection configuration: " + e.getMessage(), e);
        }
    }

    /**
     * Check the passed collection configuration. Throws an exception if errors
     * are detected in the configuration document. Note: some configuration
     * settings depend on the current environment, in particular the
     * availability of trigger or index classes.
     *
     * @param broker
     *            DBBroker
     * @param config
     *            the configuration to test
     * @throws CollectionConfigurationException
     *             if errors were detected
     */
    public void testConfiguration(DBBroker broker, String config) throws CollectionConfigurationException {
        try {
            final SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true);
            final InputSource src = new InputSource(new StringReader(config));
            final SAXParser parser = factory.newSAXParser();
            final XMLReader reader = parser.getXMLReader();
            final SAXAdapter adapter = new SAXAdapter();
            reader.setContentHandler(adapter);
            reader.parse(src);

            final Document doc = adapter.getDocument();
            final CollectionConfiguration conf = new CollectionConfiguration(broker.getBrokerPool());
            conf.read(broker, doc, true, null, null);
        } catch (final Exception e) {
            throw new CollectionConfigurationException(e);
        }
    }

    public List<Object> getCustomIndexSpecs(final String customIndexId) {
       
        return latch.read(new Callable<List<Object>>() {

            @Override
            public List<Object> call() throws Exception {

                List<Object> configs = new ArrayList<Object>(10);

                for (CollectionConfiguration config: configurations.values()) {
                    IndexSpec spec = config.getIndexConfiguration();
                    if (spec != null) {
                        Object customConfig = spec.getCustomIndexSpec(customIndexId);
                        if (customConfig != null) {
                            configs.add(customConfig);
                        }
                    }
                }

                return configs;
            }
        });
    }

    /**
     * Retrieve the collection configuration instance for the given collection.
     * This creates a new CollectionConfiguration object and recursively scans
     * the collection hierarchy for available configurations.
     *
     * @param broker
     * @param collection
     * @return The collection configuration
     * @throws CollectionConfigurationException
     */
    protected CollectionConfiguration getConfiguration(DBBroker broker, Collection collection) throws CollectionConfigurationException {

        final CollectionURI path = new CollectionURI(COLLECTION_CONFIG_PATH);
        path.append(collection.getURI().getRawCollectionPath());

        /*
         * This used to go from the root collection (/db), and continue all the
         * way to the end of the path, checking each collection on the way. I
         * modified it to start at the collection path and work its way back to
         * the root, stopping at the first config file it finds. This should be
         * more efficient, and fit more appropriately will the XmldbURI api
         */
        return latch.read(new Callable<CollectionConfiguration>() {

            @Override
            public CollectionConfiguration call() throws Exception {

                CollectionConfiguration conf = null;

                while(!path.equals(COLLECTION_CONFIG_PATH)) {
                    conf = configurations.get(path);
                    if (conf != null) {
                        return conf;
                    }
                    path.removeLastSegment();
                }

                // use default configuration
                return defaultConfig;
            }
        });
    }

    protected void loadAllConfigurations(DBBroker broker) throws CollectionConfigurationException, PermissionDeniedException, LockException {
        final Collection root = broker.getCollection(CONFIG_COLLECTION_URI);
        loadAllConfigurations(broker, root);
    }

    protected void loadAllConfigurations(DBBroker broker, Collection configCollection) throws CollectionConfigurationException, PermissionDeniedException,
            LockException {
        if (configCollection == null) {
            return;
        }
        loadConfiguration(broker, configCollection);
        final XmldbURI path = configCollection.getURI();
        for (final Iterator<XmldbURI> i = configCollection.collectionIterator(broker); i.hasNext();) {
            final XmldbURI childName = i.next();
            final Collection child = broker.getCollection(path.appendInternal(childName));
            if (child == null) {
                LOG.error("Collection is registered but could not be loaded: " + childName);
            }
            loadAllConfigurations(broker, child);
        }
    }

    protected void loadConfiguration(DBBroker broker, final Collection configCollection) throws CollectionConfigurationException, PermissionDeniedException,
            LockException {
        if (configCollection != null && configCollection.getDocumentCount(broker) > 0) {
            for (final Iterator<DocumentImpl> i = configCollection.iterator(broker); i.hasNext();) {
                final DocumentImpl confDoc = i.next();
                if (confDoc.getFileURI().endsWith(CollectionConfiguration.COLLECTION_CONFIG_SUFFIX_URI)) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Reading collection configuration from '" + confDoc.getURI() + "'");
                    }
                    final CollectionConfiguration conf = new CollectionConfiguration(broker.getBrokerPool());

                    // [ 1807744 ] Invalid collection.xconf causes a non startable database
                    // http://sourceforge.net/tracker/index.php?func=detail&aid=1807744&group_id=17691&atid=117691
                    try {
                        conf.read(broker, confDoc, false, configCollection.getURI(), confDoc.getFileURI());
                    } catch (final CollectionConfigurationException e) {
                        final String message = "Failed to read configuration document " + confDoc.getFileURI() + " in " + configCollection.getURI() + ". "
                                + e.getMessage();
                        LOG.error(message);
                        System.out.println(message);
                    }
                   
                    latch.write(new Callable<Void>() {
                        @Override
                        public Void call() throws Exception {

                            configurations.put(new CollectionURI(configCollection.getURI().getRawCollectionPath()), conf);

                            return null;
                        }
                    });

                    // Allow just one configuration document per collection
                    // TODO : do not break if a system property allows several ones -pb
                    break;
                }
            }
        }
    }

    public CollectionConfiguration getOrCreateCollectionConfiguration(final DBBroker broker, Collection collection) {
        final CollectionURI path = new CollectionURI(COLLECTION_CONFIG_PATH);
        path.append(collection.getURI().getRawCollectionPath());
       
        CollectionConfiguration conf = latch.read(new Callable<CollectionConfiguration>() {
            @Override
            public CollectionConfiguration call() {
                return configurations.get(path);
            }
        });

        if (conf != null) {
            return conf;
        }
       
        return latch.write(new Callable<CollectionConfiguration>() {
            @Override
            public CollectionConfiguration call() {
               
                CollectionConfiguration conf = configurations.get(path);
               
                if (conf != null) {
                    return conf;
                }

                conf = new CollectionConfiguration(broker.getBrokerPool());
                configurations.put(path, conf);

                return conf;
            }
        });
    }

    /**
     * Notify the manager that a collection.xconf file has changed. All cached
     * configurations for the corresponding collection and its sub-collections
     * will be cleared.
     *
     * @param collectionPath
     */
    public void invalidateAll(final XmldbURI collectionPath) {

        if (!collectionPath.startsWith(CONFIG_COLLECTION_URI)) {
            return;
        }
       
        latch.write(new Callable<Void>() {
            @Override
            public Void call() {

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Invalidating collection " + collectionPath + " and subcollections");
                }

                CollectionURI uri = new CollectionURI(collectionPath.getRawCollectionPath());

                configurations.remove(uri);

                String str = uri.toString();

                Iterator<Entry<CollectionURI, CollectionConfiguration>> it = configurations.entrySet().iterator();

                while (it.hasNext()) {
                    Entry<CollectionURI, CollectionConfiguration> entry = it.next();

                    if (entry.getKey().toString().startsWith(str)) {
                        it.remove();
                    }
                }

                return null;
            }
        });
    }

    /**
     * Called by the collection cache if a collection is removed from the cache.
     * This will delete the cached configuration instance for this collection.
     *
     * @param collectionPath
     * @param pool if not null: clear query cache
     */
    public void invalidate(final XmldbURI collectionPath, final BrokerPool pool) {
        if (!collectionPath.startsWith(CONFIG_COLLECTION_URI)) {
            return;
        }

        latch.write(new Callable<Void>() {
            @Override
            public Void call() {

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Invalidating collection " + collectionPath);
                }

                configurations.remove(new CollectionURI(collectionPath.getRawCollectionPath()));
                return null;
            }
        });
    }

    /**
     * Check if the collection exists below the system collection. If not,
     * create it.
     *
     * @param broker
     * @param uri
     * @throws EXistException
     */
    private void checkCreateCollection(DBBroker broker, XmldbURI uri) throws EXistException {
        final TransactionManager transact = broker.getDatabase().getTransactionManager();
        Txn txn = null;
        try {
            Collection collection = broker.getCollection(uri);
            if (collection == null) {
                txn = transact.beginTransaction();
                collection = broker.getOrCreateCollection(txn, uri);
                SanityCheck.THROW_ASSERT(collection != null);
                broker.saveCollection(txn, collection);
                transact.commit(txn);
            }
        } catch (final Exception e) {
            transact.abort(txn);
            throw new EXistException("Failed to initialize '" + uri + "' : " + e.getMessage());
        } finally {
            transact.close(txn);
        }
    }

    /**
     * Create a stored default configuration document for the root collection
     *
     * @param broker
     *            The broker which will do the operation
     * @throws EXistException
     */
    public void checkRootCollectionConfig(DBBroker broker) throws EXistException, PermissionDeniedException {
        // Copied from the legacy conf.xml in order to make the test suite work
        // TODO : backward compatibility could be ensured by copying the
        // relevant parts of conf.xml

        final String configuration =
          "<collection xmlns=\"http://exist-db.org/collection-config/1.0\">"
        + "    <index>"
        + "        <fulltext attributes=\"true\" default=\"all\">"
        + "            <exclude path=\"/auth\" />"
        + "        </fulltext>"
        + "    </index>"
        + "</collection>";

        final TransactionManager transact = broker.getDatabase().getTransactionManager();
        final Txn txn = transact.beginTransaction();
        try {
            Collection collection = null;
            try {
                collection = broker.openCollection(XmldbURI.ROOT_COLLECTION_URI, Lock.READ_LOCK);
                if (collection == null) {
                    transact.abort(txn);
                    throw new EXistException("collection " + XmldbURI.ROOT_COLLECTION_URI + " not found!");
                }
                final CollectionConfiguration conf = getConfiguration(broker, collection);
                if (conf != null) {
                    // We already have a configuration document : do not erase
                    // it
                    if (conf.getDocName() != null) {
                        transact.abort(txn);
                        return;
                    }
                }
            } finally {
                if (collection != null) {
                    collection.release(Lock.READ_LOCK);
                }
            }
            // Configure the root collection
            addConfiguration(txn, broker, collection, configuration);
            transact.commit(txn);
            LOG.info("Configured '" + collection.getURI() + "'");
        } catch (final CollectionConfigurationException e) {
            transact.abort(txn);
            throw new EXistException(e.getMessage());
        } finally {
            transact.close(txn);
        }
    }

    /*
     * private void debugCache() { StringBuilder buf = new StringBuilder(); for
     * (Iterator i = configurations.keySet().iterator(); i.hasNext(); ) {
     * buf.append(i.next()).append(' '); } LOG.debug(buf.toString()); }
     */
}
TOP

Related Classes of org.exist.collections.CollectionConfigurationManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.