Package org.exist.collections.triggers

Source Code of org.exist.collections.triggers.XQueryStartupTrigger

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 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.triggers;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.SecurityManager;
import org.exist.security.xacml.AccessContext;
import org.exist.source.Source;
import org.exist.source.SourceFactory;
import org.exist.storage.DBBroker;
import org.exist.storage.StartupTrigger;
import org.exist.storage.lock.Lock;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Sequence;

/**
* Startup Trigger to fire XQuery scripts during database startup.
*
* Load scripts into /db/system/autostart as DBA.
*
* <pre>
* {@code
* <startup>
*   <triggers>
*     <trigger class="org.exist.collections.triggers.XQueryStartupTrigger"/>
*   </triggers>
* </startup>
* }
* </pre>
*
* Due to security reasons individual scripts cannot be specified anymore. The permissions were not checked per file.
*
* <pre>
* {@code
*       <parameter name="xquery" value="/db/script1.xq"/>
*       <parameter name="xquery" value="/db/script2.xq"/>
* }
* </pre>
*
* @author Dannes Wessels
*/
public class XQueryStartupTrigger implements StartupTrigger {

    protected final static Logger LOG = Logger.getLogger(XQueryStartupTrigger.class);

    private static final String XQUERY = "xquery";
    private static final String AUTOSTART_COLLECTION = "/db/system/autostart";
    private static final String[] XQUERY_EXTENSIONS = {".xq", ".xquery", ".xqy"};
    private static final String REQUIRED_MIMETYPE = "application/xquery";

    @Override
    public void execute(DBBroker broker, Map<String, List<? extends Object>> params) {

        LOG.info("Starting Startup Trigger for stored XQueries");

        for (String path : getScriptsInStartupCollection(broker)) {
            executeQuery(broker, path);
        }

//        for (String path : getParameters(params)) {
//            executeQuery(broker, path);
//        }
    }

    /**
     * List all xquery scripts in /db/system/autostart
     *
     * @param broker The exist-db broker
     * @return List of xquery scripts
     */
    private List<String> getScriptsInStartupCollection(DBBroker broker) {

        // Return values
        List<String> paths = new ArrayList<>();

        XmldbURI uri = XmldbURI.create(AUTOSTART_COLLECTION);
        Collection collection = null;

        try {
            collection = broker.openCollection(uri, Lock.READ_LOCK);

            if (collection == null) {
                LOG.debug(String.format("Collection '%s' not found.", AUTOSTART_COLLECTION));
                createAutostartCollection(broker);

            } else {
                LOG.debug(String.format("Scanning collection '%s'.", AUTOSTART_COLLECTION));

                if (isPermissionsOK(collection)) {

                    Iterator<DocumentImpl> documents = collection.iteratorNoLock(broker);
                    while (documents.hasNext()) {
                        DocumentImpl document = documents.next();
                        String docPath = document.getURI().toString();

                        if (isPermissionsOK(document)) {

                            if (StringUtils.endsWithAny(docPath, XQUERY_EXTENSIONS)) {
                                paths.add(XmldbURI.EMBEDDED_SERVER_URI_PREFIX + docPath);

                            } else {
                                LOG.error(String.format("Skipped document '%s', not an xquery script.", docPath));
                            }

                        } else {
                            LOG.error(String.format("Document %s should be owned by DBA, mode %s, mimetype %s",
                                    docPath, Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM, REQUIRED_MIMETYPE));
                        }
                    }

                } else {
                    LOG.error(String.format("Collection %s should be owned by SYSTEM/DBA, mode %s.", AUTOSTART_COLLECTION,
                            Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM));
                }

            }

            LOG.debug(String.format("Found %s xquery scripts in '%s'.", paths.size(), AUTOSTART_COLLECTION));

        } catch (PermissionDeniedException ex) {
            LOG.error(ex.getMessage());

        } finally {
            // Clean up resources
            if (collection != null) {
                collection.release(Lock.READ_LOCK);
            }
        }

        return paths;

    }

    /**
     * Verify that the permissions for a collection are SYSTEM/DBA/770
     *
     * @param collection The collection
     * @return TRUE if the conditions are met, else FALSE
     */
    private boolean isPermissionsOK(Collection collection) {

        Permission perms = collection.getPermissions();

        return (perms.getOwner().getName().equals(SecurityManager.SYSTEM)
                && perms.getGroup().getName().equals(SecurityManager.DBA_GROUP)
                && perms.getMode() == Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM);

    }

    /**
     * Verify that the owner of the document is DBA, the document is owned by the DBA group and that the permissions are
     * set 0770, and the mimetype is set application/xquery.
     *
     * @param collection The document
     * @return TRUE if the conditions are met, else FALSE
     */
    private boolean isPermissionsOK(DocumentImpl document) {

        Permission perms = document.getPermissions();

        return (perms.getOwner().hasDbaRole()
                && perms.getGroup().getName().equals(SecurityManager.DBA_GROUP)
                && perms.getMode() == Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM
                && document.getMetadata().getMimeType().equals(REQUIRED_MIMETYPE));

    }

    /**
     * Get all XQuery paths from provided parameters in conf.xml
     */
    private List<String> getParameters(Map<String, List<? extends Object>> params) {

        // Return values
        List<String> paths = new ArrayList<>();

        // The complete data map
        Set<Map.Entry<String, List<? extends Object>>> data = params.entrySet();

        // Iterate over all entries
        for (Map.Entry<String, List<? extends Object>> entry : data) {

            // only the 'xpath' parameter is used.
            if (XQUERY.equals(entry.getKey())) {

                // Iterate over all values (object lists)
                List<? extends Object> list = entry.getValue();
                for (Object o : list) {

                    if (o instanceof String) {
                        String value = (String) o;

                        if (value.startsWith("/")) {

                            // Rewrite to URL in database
                            value = XmldbURI.EMBEDDED_SERVER_URI_PREFIX + value;

                            // Prevent double entries
                            if (!paths.contains(value)) {
                                paths.add(value);
                            }

                        } else {
                            LOG.error(String.format("Path '%s' should start with a '/'", value));
                        }
                    }
                }
            }

        }

        LOG.debug(String.format("Found %s 'xquery' entries.", paths.size()));

        return paths;
    }

    /**
     * Execute xquery on path
     *
     * @param broker eXist database broker
     * @param path path to query, formatted as xmldb:exist:///db/...
     */
    private void executeQuery(DBBroker broker, String path) {

        XQueryContext context = null;
        try {
            // Get path to xquery
            Source source = SourceFactory.getSource(broker, null, path, false);

            if (source == null) {
                LOG.info(String.format("No Xquery found at '%s'", path));

            } else {
                // Setup xquery service
                XQuery service = broker.getXQueryService();
                context = service.newContext(AccessContext.TRIGGER);

                // Allow use of modules with relative paths
                String moduleLoadPath = StringUtils.substringBeforeLast(path, "/");
                context.setModuleLoadPath(moduleLoadPath);

                // Compile query
                CompiledXQuery compiledQuery = service.compile(context, source);

                LOG.info(String.format("Starting Xquery at '%s'", path));

                // Finish preparation
                context.prepareForExecution();

                // Execute
                Sequence result = service.execute(compiledQuery, null);

                // Log results
                LOG.info(String.format("Result xquery: '%s'", result.getStringValue()));

            }

        } catch (Throwable t) {
            // Dirty, catch it all
            LOG.error(String.format("An error occured during preparation/execution of the xquery script %s: %s", path, t.getMessage()), t);

        } finally {
            if (context != null) {
                context.runCleanupTasks();
            }
        }
    }

    /**
     * Create autostart collection when not existent
     *
     * @param broker The exist-db broker
     */
    private void createAutostartCollection(DBBroker broker) {

        LOG.info(String.format("Creating %s", AUTOSTART_COLLECTION));

        TransactionManager txnManager = broker.getBrokerPool().getTransactionManager();
        Txn txn = txnManager.beginTransaction();

        try {
            XmldbURI newCollection = XmldbURI.create(AUTOSTART_COLLECTION, true);

            // Create collection
            Collection created = broker.getOrCreateCollection(txn, newCollection);

            // Set ownership
            Permission perms = created.getPermissions();
            perms.setOwner(broker.getBrokerPool().getSecurityManager().getSystemSubject());
            perms.setGroup(broker.getBrokerPool().getSecurityManager().getDBAGroup());
            perms.setMode(Permission.DEFAULT_SYSTEM_SECURITY_COLLECTION_PERM);
            broker.saveCollection(txn, created);
            broker.flush();

            // Commit change
            txnManager.commit(txn);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Finished creation of collection");
            }

        } catch (Throwable ex) {
            LOG.error(ex);
            txnManager.abort(txn);

        } finally {
            txnManager.close(txn);
        }
    }

}
TOP

Related Classes of org.exist.collections.triggers.XQueryStartupTrigger

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.