<?xml version="1.0" encoding="UTF-8"?>
<!-- $Id: triggers.xml 17299 2012-10-10 23:47:33Z joewiz $ --><book><bookinfo><productname>eXist-db – Open Source Native XML Database</productname><title>Configuring Database Triggers</title><date>August 2012</date><orgname>The eXist-db Project</orgname></bookinfo><chapter id="configuring-db-triggers"><title>Configuring Database Triggers</title><para>This document has been reviewed for eXist-db 2.0.</para><section><title>Overview</title><para>In this section, we discuss the types of database events that may be Triggered by eXist-db, as well
                as how Triggers are created and configured. It assumes readers have a basic
                understanding of XML, Java and XQuery.</para><para>Triggers may be configured by the User to respond to document and/or collection events.
                From version 1.5, eXist defines five events, all of which are applicable to the collection configured with the Trigger:</para><itemizedlist><listitem><para><emphasis>create</emphasis>: Fired when a collection or document is created</para></listitem><listitem><para><emphasis>update</emphasis>: Fired when a collection or document is updated</para></listitem><listitem><para><emphasis>copy</emphasis>: Fired when a collection or document is copied</para></listitem><listitem><para><emphasis>move</emphasis>: Fired when a collection or document is moved</para></listitem><listitem><para><emphasis>delete</emphasis>: Fired when a collection or document is deleted</para></listitem></itemizedlist></section><section><title>Trigger Types</title><para>Triggers may be written in either XQuery or Java and will be Triggered
            once by before-event and once by after-event. The Trigger may respond to either or both the before
            and after events.</para><section><title>XQuery Triggers</title><para>Triggers written in XQuery may be configured by using the org.exist.collections.triggers. XQueryTrigger to fire the XQuery.
                The XQuery to be executed when the trigger is fired may either be placed in the collection.xconf itself or indicated by a URL.</para><para>The XQuery functions mapped to trigger event:</para><itemizedlist><listitem><para><emphasis>trigger:before-create-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-create-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-update-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-update-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-copy-collection($uri as xs:anyURI, $new-uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-copy-collection($new-uri as xs:anyURI, $uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-move-collection($uri as xs:anyURI, $new-uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-move-collection($new-uri as xs:anyURI, $uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-delete-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-delete-collection($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-create-document($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-create-document($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-update-document($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-update-document($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-copy-document($uri as xs:anyURI, $new-uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-copy-document($new-uri as xs:anyURI, $uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-move-document($uri as xs:anyURI, $new-uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-move-document($new-uri as xs:anyURI, $uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:before-delete-document($uri as xs:anyURI)</emphasis></para></listitem><listitem><para><emphasis>trigger:after-delete-document($uri as xs:anyURI)</emphasis></para></listitem></itemizedlist><para>The 'trigger' prefix must be mapped to "http://exist-db.org/xquery/trigger" namespace.</para></section><section><title>Java Triggers</title><para>Triggers written in Java must implement the org.exist.collections.triggers.CollectionTrigger or 
                	org.exist.collections.triggers.DocumentTrigger interface. The Java class for your trigger must be 
                	available on the class path, lib/user is a good place to copy your custom trigger to.</para><para>The DocumentTrigger interface provides a convenient starting place and provides the methods.</para></section></section><section id="trigger-config"><title>Configuring Triggers</title><para>Triggers are configured using collection-specific configuration files. These files are stored
                as standard XML documents in the system collection:
                <filename>/db/system/config</filename>, which can be accessed using the Admin
                interface or Java Client. In addition to defining settings for Triggers
                the configuration document specifies other collection-specific settings such as
                indexes or default permissions.</para><para>The hierarchy of the system collection (<filename>/db/system/config</filename>)
                mirrors the hierarchical structure of the main collection. Configurations are
                therefore "inherited" by descendants in the hierarchy, (i.e. the configuration
                settings for the child collection are added to or override those set for the
                parent). It is furthermore possible for each collection in the hierarchy to have its
                own <emphasis>trigger creation policy</emphasis> defined by a configuration file.</para><para>To configure triggers for a given collection - for example:
                <filename>/db/foo</filename> - create a new <filename>.xconf</filename>
                configuration file and store it in the system collection (e.g.
                    <filename>/db/system/config/db/foo/collection.xconf</filename>). You may choose any name for this
                document so long as it has the <filename>.xconf</filename> extension, although
                <filename>collection.xconf</filename> is recommended. Note that
                since subcollections will inherit the configuration policy of their parent
                collections, you are not required to specify a configuration for every collection.</para><note><para>You can store only ONE <filename>.xconf</filename> configuration document per
                    collection in the system collection <filename>/db/system/config</filename>. For
                    example, the collection <filename>/db/system/config/foo</filename> would contain
                    one configuration file and/or other subcollections.</para></note><section><title>Configuration Structure and Syntax</title><para>Trigger configuration files are standard XML documents that have their elements
                    and attributes defined by the eXist namespace:</para><synopsis>http://exist-db.org/collection-config/1.0</synopsis><para>All configuration documents have the <sgmltag>collection</sgmltag> root
                    element. These documents also have a <sgmltag>triggers</sgmltag> element 
                    below the root element, which encloses the trigger configuration. Only ONE
                        <sgmltag>triggers</sgmltag> element is permitted in a document. </para><para>In the <sgmltag>triggers</sgmltag> element are elements that define each trigger
                and the event(s) that it is fired for.</para><para>Each <sgmltag>trigger</sgmltag> element has two attributes, <emphasis>event</emphasis> and <emphasis>class</emphasis>.</para><para>The <emphasis>event</emphasis> attribute is a comma
                    separated list of events (<option>create</option>, <option>update</option>, <option>copy</option>, <option>move</option>, <option>delete</option>)
                    to fire on.
                    If the <emphasis>event</emphasis> attribute is not present for an Xquery trigger then the Xquery will never be invoked. 
                    For other (Java) triggers the <emphasis>event</emphasis> attribute may or may not have any effect depending on the
                    implementation of the <methodname>configure()</methodname> method and so on.
                </para><para>The <emphasis>class</emphasis> is the name of the Java Class to fire when an event occurs. 
                    XQuery triggers are handled by a built-in Java trigger: <classname>org.exist.collections.triggers.XQueryTrigger</classname>.</para><para>The trigger definition may also contain zero or more <sgmltag>parameter</sgmltag> elements defining any parameters to send to the trigger.</para><section><title>Configuring an XQuery Trigger</title><para>When configuring an XQuery trigger there are a few parameters that may need to be set:</para><itemizedlist><listitem><para><emphasis>url</emphasis>: The URL to the XQuery that the XQuery Trigger should execute</para></listitem><listitem><para><emphasis>query</emphasis>: Can be used instead of <emphasis>url</emphasis>. And should contain the XQuery itself.</para></listitem><listitem><para><emphasis>bindingPrefix</emphasis>: This should be the namespace that the XQuery resides in. If this is omitted then a default of "local" is assumed.</para></listitem><listitem><para>Any other parameters will be passed to the XQuery as external variables of type xs:string</para></listitem></itemizedlist><para>The <emphasis>event</emphasis> attribute must be present and list each of the event types that the XQuery trigger should
                        process. If the attribute is not present or it is an empty list, then the XQuery will never be invoked.
                        Note also that the <emphasis>event</emphasis> names from prior eXist versions may also be used 
                        (<option>store</option>, <option>update</option>, <option>remove</option>).
                    </para><para>The following example shows two XQuery Triggers configured, the first executes an XQuery stored in the database
                    whereas the second executes XQuery placed inline in the collection.xconf:</para><example><title>XQuery Trigger Configuration</title><programlisting><markup>&lt;collection xmlns="http://exist-db.org/collection-config/1.0"&gt;
    &lt;triggers&gt;
		&lt;trigger class="org.exist.collections.triggers.XQueryTrigger"&gt;
			&lt;parameter name="url" value="xmldb:exist://localhost/db/myTrigger.xql"/&gt;
		&lt;/trigger&gt;
		
		&lt;trigger event="create" class="org.exist.collections.triggers.XQueryTrigger"&gt;
			&lt;parameter name="query" value="
				module namespace trigger='http://exist-db.org/xquery/trigger';
				
				declare function trigger:before-create-collection($uri as xs:anyURI) {
					util:log('debug', concat('Trigger fired at ', current-dateTime()))
				};"
			/&gt;
		&lt;/trigger&gt;
    &lt;/triggers&gt;
&lt;/collection&gt;</markup></programlisting></example></section><section><title>Configuring a Java Trigger</title><para>When configuring a Java Trigger any parameters defined will be passed in a named map to the <emphasis>configure</emphasis> function of the trigger.</para><para>The following example shows a Java trigger configured:</para><example><title>Java Trigger Configuration</title><programlisting><markup>&lt;collection xmlns="http://exist-db.org/collection-config/1.0"&gt;
    &lt;triggers&gt;
		&lt;trigger class="my.domain.testTrigger"&gt;
			&lt;parameter name="myParam" value="myValue"/&gt;
		&lt;/trigger&gt;
    &lt;/triggers&gt;
&lt;/collection&gt;</markup></programlisting></example></section></section></section><section><title>Example Triggers</title><para>Here are some simple code examples of triggers</para><section><title>XQuery</title><example><title>Simple Logging Trigger</title><programlisting language="xquery">

xquery version "1.0";

(:
	A simple XQuery for an XQueryTrigger that
	logs all trigger events for which it is executed
	in the file /db/triggersLogs.xml
:)

module namespace trigger='http://exist-db.org/xquery/trigger';

declare namespace  xmldb="http://exist-db.org/xquery/xmldb";

declare variable $local:triggersLogFile := "triggersLog.xml";

declare function trigger:before-create-collection($uri as xs:anyURI) {
	local:logEvent('before', 'create', 'collection', $uri)
};

declare function trigger:after-create-collection($uri as xs:anyURI) {
	local:logEvent('after', 'create', 'collection', $uri)" +
};

declare function trigger:before-update-collection($uri as xs:anyURI) {
	local:logEvent('before', 'update', 'collection', $uri)
};

declare function trigger:after-update-collection($uri as xs:anyURI) {
	local:logEvent('after', 'update', 'collection', $uri)
};

declare function trigger:before-copy-collection($uri as xs:anyURI, $new-uri as xs:anyURI) {
	local:logEvent('before', 'copy', 'collection', $uri)
};

declare function trigger:after-copy-collection($new-uri as xs:anyURI, $uri as xs:anyURI) {
	local:logEvent('after', 'copy', 'collection', $new-uri)
};

declare function trigger:before-move-collection($uri as xs:anyURI, $new-uri as xs:anyURI) {
	local:logEvent('before', 'move', 'collection', $uri)
};

declare function trigger:after-move-collection($new-uri as xs:anyURI, $uri as xs:anyURI) {
	local:logEvent('after', 'move', 'collection', $new-uri)
};

declare function trigger:before-delete-collection($uri as xs:anyURI) {
	local:logEvent('before', 'delete', 'collection', $uri)
};

declare function trigger:after-delete-collection($uri as xs:anyURI) {
	local:logEvent('after', 'delete', 'collection', $uri)
};

declare function trigger:before-create-document($uri as xs:anyURI) {
	local:logEvent('before', 'create', 'document', $uri)
};

declare function trigger:after-create-document($uri as xs:anyURI) {
	local:logCollectionEvent('after', 'create', 'document', $uri)
};

declare function trigger:before-update-document($uri as xs:anyURI) {
	local:logEvent('before', 'update', 'document', $uri)
};

declare function trigger:after-update-document($uri as xs:anyURI) {
	local:logEvent('after', 'update', 'document', $uri)
};

declare function trigger:before-copy-document($uri as xs:anyURI, $new-uri as xs:anyURI) {
	local:logEvent('before', 'copy', 'document', $uri)
};

declare function trigger:after-copy-document($new-uri as xs:anyURI, $uri as xs:anyURI) {
	local:logEvent('after', 'copy', 'document', $new-uri)
};

declare function trigger:before-move-document($uri as xs:anyURI, $new-uri as xs:anyURI) {
	local:logEvent('before', 'move', 'document', $uri)
};

declare function trigger:after-move-document($new-uri as xs:anyURI, $uri as xs:anyURI) {
	local:logEvent('after', 'move', 'document', $new-uri)
};

declare function trigger:before-delete-document($uri as xs:anyURI) {
	local:logEvent('before', 'delete', 'document', $uri)
};

declare function trigger:after-delete-document($uri as xs:anyURI) {
	local:logCollectionEvent('after', 'delete', 'document', $uri)
};

declare function local:logEvent($type as xs:string, $event as xs:string, $objectType as xs:string, $uri as xs:anyURI) {
	(: create the log file if it does not exist :)
	if(not(doc-available($local:triggersLogFile)))then
	(
		xmldb:store("/db", $local:triggersLogFile, &lt;triggers/&gt;)
	)
	else(),

	(: log the trigger details to the log file :)
	update insert &lt;trigger event="{$local:triggerEvent}" eventType="{$local:eventType}" collectionName="{$local:collectionName}" documentName="{$local:documentName}" timestamp="{current-dateTime()}"&gt;{$local:document}&lt;/trigger&gt;  into doc("/db/triggersLog.xml")/triggers
};
</programlisting></example></section><section><title>Java</title><example><title>Simple Logging Trigger</title><programlisting language="java">

import java.io.File;
import java.io.FileOutputStream;

import org.exist.collections.triggers.FilteringTrigger;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.storage.DBBroker;
import org.exist.storage.txn.Txn;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.value.DateTimeValue;

/**
 	A simple Java Trigger that
	logs all trigger events for which it is executed
	in the file triggersLog.xml in the systems temporary folder
*/

public class LoggingTrigger extends FilteringTrigger implements DocumentTrigger
{
    private final static String TEMPLATE = "&lt;?xml version=\"1.0\"?&gt;&lt;events&gt;&lt;/events&gt;";

    private DocumentImpl doc;

    public void configure(DBBroker broker, org.exist.collections.Collection parent, Map&lt;String, List&lt;?&gt;&gt; parameters) throws CollectionConfigurationException {
        super.configure(broker, parent, parameters);
        XmldbURI docPath = XmldbURI.create("messages.xml");
        System.out.println("TestTrigger prepares");
        this.doc = parent.getDocument(broker, docPath);
        if (this.doc == null) {
            TransactionManager transactMgr = broker.getBrokerPool().getTransactionManager();
            Txn transaction = transactMgr.beginTransaction();
            try {
                getLogger().debug("creating new file for collection contents");

                // IMPORTANT: temporarily disable triggers on the collection.
                // We would end up in infinite recursion if we don't do that
                parent.setTriggersEnabled(false);
                IndexInfo info = parent.validateXMLResource(transaction, broker, docPath, TEMPLATE);

                parent.store(transaction, broker, info, TEMPLATE, false);
                this.doc = info.getDocument();

                transactMgr.commit(transaction);
            } catch (Exception e) {
                transactMgr.abort(transaction);
                throw new CollectionConfigurationException(e.getMessage(), e);
            } finally {
                parent.setTriggersEnabled(true);
            }
        }
    }

	@Deprecated
    public void prepare(int event, DBBroker broker, Txn transaction, XmldbURI documentPath, DocumentImpl existingDocument) throws TriggerException {
    }

	@Deprecated
	public void finish(int event, DBBroker broker, Txn transaction, XmldbURI documentPath, DocumentImpl document) {
	}
	
	private void addRecord(DBBroker broker, String xupdate) throws TriggerException {
        MutableDocumentSet docs = new DefaultDocumentSet();
        docs.add(doc);
        try {
            // IMPORTANT: temporarily disable triggers on the collection.
            // We would end up in infinite recursion if we don't do that
            getCollection().setTriggersEnabled(false);
            // create the XUpdate processor
            XUpdateProcessor processor = new XUpdateProcessor(broker, docs, AccessContext.TRIGGER);
            // process the XUpdate
            Modification modifications[] = processor.parse(new InputSource(new StringReader(xupdate)));
            for (int i = 0; i &lt; modifications.length; i++)
                modifications[i].process(null);
            broker.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new TriggerException(e.getMessage(), e);
        } finally {
            // IMPORTANT: reenable trigger processing for the collection.
            getCollection().setTriggersEnabled(true);
        }

	}

	@Override
	public void beforeCreateDocument(DBBroker broker, Txn transaction, XmldbURI uri) throws TriggerException {
        String xupdate = "&lt;?xml version=\"1.0\"?&gt;" +
        "&lt;xu:modifications version=\"1.0\" xmlns:xu=\"" + XUpdateProcessor.XUPDATE_NS + "\"&gt;" +
        "   &lt;xu:append select='/events'&gt;" +
        "       &lt;xu:element name='event'&gt;" +
        "           &lt;xu:attribute name='id'&gt;STORE-DOCUMENT&lt;/xu:attribute&gt;" +
        "           &lt;xu:attribute name='collection'&gt;" + doc.getCollection().getURI() + "&lt;/xu:attribute&gt;" +
        "       &lt;/xu:element&gt;" +
        "   &lt;/xu:append&gt;" +
        "&lt;/xu:modifications&gt;";

        addRecord(broker, xupdate);
	}

	@Override
	public void afterCreateDocument(DBBroker broker, Txn transaction, DocumentImpl document) {
		//ignore this event
	}

	@Override
	public void beforeUpdateDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
		//ignore this event
	}

	@Override
	public void afterUpdateDocument(DBBroker broker, Txn transaction, DocumentImpl document) {
		//ignore this event
	}

	@Override
	public void beforeCopyDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
		//ignore this event
	}

	@Override
	public void afterCopyDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) {
		//ignore this event
	}

	@Override
	public void beforeMoveDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
		//ignore this event
	}

	@Override
	public void afterMoveDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) {
		//ignore this event
	}

	@Override
	public void beforeDeleteDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
        String xupdate = "&lt;?xml version=\"1.0\"?&gt;" +
        "&lt;xu:modifications version=\"1.0\" xmlns:xu=\"" + XUpdateProcessor.XUPDATE_NS + "\"&gt;" +
        "   &lt;xu:append select='/events'&gt;" +
        "       &lt;xu:element name='event'&gt;" +
        "           &lt;xu:attribute name='id'&gt;REMOVE-DOCUMENT&lt;/xu:attribute&gt;" +
        "           &lt;xu:attribute name='collection'&gt;" + doc.getCollection().getURI() + "&lt;/xu:attribute&gt;" +
        "       &lt;/xu:element&gt;" +
        "   &lt;/xu:append&gt;" +
        "&lt;/xu:modifications&gt;";
        
        addRecord(broker, xupdate);
	}

	@Override
	public void afterDeleteDocument(DBBroker broker, Txn transaction, XmldbURI uri) {
	}
}
</programlisting></example></section></section><section><title>Provided Triggers</title><para>eXist provides some Triggers out of the box that may be used</para><section><title>HistoryTrigger</title><para>This collection trigger will save all old versions of documents before they are overwritten or removed. The old versions are kept in the
                    'history root' which is by default <code>/db/history</code>, but can be changed with the parameter <code>root</code>.
                    You need to configure this trigger for every collection whose history you want to preserve.</para><para>The <emphasis>event</emphasis> attribute is ignored by <classname>HistoryTrigger</classname></para><example><title>History Trigger collection.xconf</title><programlisting><markup>&lt;collection xmlns='http://exist-db.org/collection-config/1.0'&gt;
     &lt;triggers&gt;
       &lt;trigger class="org.exist.collections.triggers.HistoryTrigger"/&gt;
     &lt;/triggers&gt;
&lt;/collection&gt;</markup></programlisting></example></section><section><title>STX Transformer Trigger</title><para>STXTransformerTrigger applies an STX stylesheet to the input SAX stream, using <a href="http://joost.sourceforge.net">Joost</a>.
                    The stylesheet location is identified by parameter "src". If the src parameter is just a path, the stylesheet will be loaded from the database,
                    otherwise, it is interpreted as an URI.</para><para>The <emphasis>event</emphasis> attribute is ignored by <classname>STXTransformerTrigger</classname></para></section></section></chapter></book>