<?xml version="1.0" encoding="UTF-8"?>
<!-- $Id: devguide_xmldb.xml 17299 2012-10-10 23:47:33Z joewiz $ --><book><bookinfo><productname>eXist-db – Open Source Native XML Database</productname><title>Developer's Guide</title><date>September 2009</date><author><firstname>Wolfgang M.</firstname><surname>Meier</surname><affiliation><address format="linespecific"><email>wolfgang at exist-db.org</email></address></affiliation></author></bookinfo><chapter id="ch01"><title>Developer's Guide</title><section><title>Writing Java Applications with the XML:DB API</title><para>The preferred way to work with eXist-db when developing Java applications is to use
                the XML:DB API. This API provides a common interface to native or XML-enabled
                databases and supports the development of portable, reusable applications. eXist's
                implementation of the XML:DB standards follows the Xindice implementation, and
                conforms to the latest working drafts put forth by the <ulink url="http://xmldb-org.sourceforge.net/xapi/">XML:DB Initiative</ulink>. For more
                information, refer to the <ulink url="api/index.html">Javadocs for this API</ulink>.</para><para>The basic components employed by the XML:DB API are <emphasis>drivers</emphasis>,
                    <emphasis>collections</emphasis>, <emphasis>resources</emphasis> and
                    <emphasis>services</emphasis>.</para><para><emphasis>Drivers</emphasis> are implementations of the database interface that
                encapsulate the database access logic for specific XML database products. They are
                provided by the product vendor and must be registered with the database manager.</para><para>A <emphasis>collection</emphasis> is a hierarchical container for
                    <emphasis>resources</emphasis> and further sub-collections. Currently two
                different resources are defined by the API: <classname>XMLResource</classname> and
                    <classname>BinaryResource</classname>. An <classname>XMLResource</classname>
                represents an XML document or a document fragment, selected by a previously executed
                XPath query.</para><para>Finally, <emphasis>services</emphasis> are requested for special tasks such as
                querying a collection with XPath, or managing a collection.</para><note><para>There are several XML:DB examples provided in eXist's
                    <filename>samples</filename> directory . To start an example, use the <filename moreinfo="none">start.jar</filename> jar file and pass the name of the
                    example class as the first parameter, for instance:</para><synopsis format="linespecific">java -jar start.jar 
					org.exist.examples.xmldb.Retrieve [- other options]</synopsis></note><para>Programming with the XML:DB API is straightforward. You will find some code
                examples in the <filename>samples/org/exist/examples/xmldb</filename> directory. In
                the following simple example, a document can be retrieved from the eXist server and
                printed to standard output.</para><example><title>Retrieving a Document with XML:DB</title><programlisting format="linespecific" language="java">
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;
import javax.xml.transform.OutputKeys;
import org.exist.xmldb.EXistResource;

public class RetrieveExample {
    
    private static String URI = "xmldb:exist://localhost:8080/exist/xmlrpc";

    /**
     * args[0] Should be the name of the collection to access
     * args[1] Should be the name of the resource to read from the collection
     */
    public static void main(String args[]) throws Exception {
        
        final String driver = "org.exist.xmldb.DatabaseImpl";
        
        // initialize database driver
        Class cl = Class.forName(driver);
        Database database = (Database) cl.newInstance();
        database.setProperty("create-database", "true");
        DatabaseManager.registerDatabase(database);
        
        Collection col = null;
        XMLResource res = null;
        try {    
            // get the collection
            col = DatabaseManager.getCollection(URI + args[0]);
            col.setProperty(OutputKeys.INDENT, "no");
            res = (XMLResource)col.getResource(args[1]);
            
            if(res == null) {
                System.out.println("document not found!");
            } else {
                System.out.println(res.getContent());
            }
        } finally {
            //dont forget to clean up!
            
            if(res != null) {
                try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
            
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
    }
}
        </programlisting></example><para>With this example, the database driver class for eXist
                    (<classname>org.exist.xmldb.DatabaseImpl</classname>) is first registered with
                the <classname>DatabaseManager</classname>. Next we obtain a
                <classname>Collection</classname> object from the database manager by calling the
                static method <methodname>DatabaseManger.getCollection()</methodname>. The method
                expects a fully qualified URI for its parameter value, which identifies the desired
                collection. The format of this URI should look like the following:</para><synopsis format="linespecific">xmldb:[DATABASE-ID]://[HOST-ADDRESS]/db/collection</synopsis><para>Because more than one database driver can be registered with the database manager,
                the first part of the URI (<option>xmldb:exist</option>) is required to determine
                which driver class to use. The <emphasis>database-id</emphasis> is used by the
                database manager to select the correct driver from its list of available drivers. To
                use eXist, this ID should always be "exist" (unless you have set up multiple
                database instances; additional instances may have other names).</para><para>The final part of the URI identifies the collection path, and optionally the host
                address of the database server on the network. Internally, eXist uses two different
                driver implementations: The first talks to a remote database engine using XML-RPC
                calls, the second has direct access to a local instance of eXist. The root
                collection is always identified by <option>/db</option>. For example, the URI</para><synopsis format="linespecific">xmldb:exist://localhost:8080/exist/xmlrpc/db/shakespeare/plays</synopsis><para>references the Shakespeare collection on a remote server running the XML-RPC
                interface as a servlet at <option>localhost:8080/exist/xmlrpc</option>. If we leave
                out the host address, the XML:DB driver will try to connect to a locally attached
                database instance, e.g.:</para><synopsis format="linespecific">xmldb:exist:///db/shakespeare/plays</synopsis><para>In this case, we have to tell the XML:DB driver that it should create a new
                database instance if none has been started. This is done by setting the
                    <option>create-database</option> property of class
                <classname>Database</classname> to "true" (more information on embedded use of eXist
                can be found in the <ulink url="deployment.xml">deployment guide</ulink>.</para><para>The <methodname>setProperty</methodname> calls are used to set database-specific
                parameters. In this case, pretty-printing of XML output is turned on for the
                collection. eXist uses the property keys defined in the standard Java package
                    <filename>javax.xml.transform</filename>. Thus, in Java you can simply use class
                    <classname>OutputKeys</classname> to get the correct keys.</para><para>Calling <methodname>col.getResource()</methodname> finally retrieves the document,
                which is returned as an <classname>XMLResource</classname>. All resources have a
                method <methodname>getContent()</methodname>, which returns the resource's content,
                depending on it's type. In this case we retrieve the content as type
                    <classname>String</classname>.</para><para>To query the repository, we may either use the standard
                    <classname>XPathQueryService</classname> or eXist's
                <classname>XQueryService</classname> class. The XML:DB API defines different kinds
                of services, which may or may not be provided by the database. The
                    <methodname>getService</methodname> method of class
                <classname>Collection</classname> calls a service if it is available. The method
                expects the service name as the first parameter, and the version (as a string) as
                the second, which is used to distinguish between different versions of the service
                defined by the XML:DB API.</para><para>The following is an example of using the XML:DB API to execute a database query:</para><example><title>Querying the Database with XPath(XML:DB API)</title><programlisting format="linespecific" language="java">
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;
import org.exist.xmldb.EXistResource;

public class XPathExample {
    
    private static String URI = "xmldb:exist://localhost:8080/exist/xmlrpc";

    /**
     * args[0] Should be the name of the collection to access
     * args[1] Should be the XPath expression to execute
     */
    public static void main(String args[]) throws Exception {
        
        final String driver = "org.exist.xmldb.DatabaseImpl";
        
        // initialize database driver
        Class cl = Class.forName(driver);
        Database database = (Database) cl.newInstance();
        database.setProperty("create-database", "true");
        DatabaseManager.registerDatabase(database);
        
        Collection col = null;
        try { 
            col = DatabaseManager.getCollection(URI + args[0]);
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");

            ResourceSet result = xpqs.query(args[1]);
            ResourceIterator i = result.getIterator();
            Resource res = null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    System.out.println(res.getContent());
                } finally {
                    //dont forget to cleanup resources
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        } finally {
            //dont forget to cleanup
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
    }
}
        </programlisting></example><para>To execute the query, method <methodname>service.query(xpath)</methodname> is
                called. This method returns a <classname>ResourceSet</classname>, containing the
                Resources found by the query. <methodname>ResourceSet.getIterator()</methodname>
                gives us an iterator over these resources. Every Resource contains a single document
                fragment or value selected by the XPath expression.</para><para>Internally, eXist does not distinguish between XPath and XQuery expressions.
                    <classname>XQueryService</classname> thus maps to the same implementation class
                as <classname>XPathQueryService</classname>. However, it provides a few additional
                methods. Most important, when talking to an embedded database,
                    <classname>XQueryService</classname> allows for the XQuery expression to be
                compiled as an internal representation, which can then be reused. With compilation,
                the previous example code would look as follows:</para><example><title>Compiling a Query (XML:DB API)</title><programlisting format="linespecific" language="java">
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;
import org.exist.xmldb.EXistResource;

public class XQueryExample {
    
    private static String URI = "xmldb:exist://localhost:8080/exist/xmlrpc";

    /**
     * args[0] Should be the name of the collection to access
     * args[1] Should be the XQuery to execute
     */
    public static void main(String args[]) throws Exception {
        
        final String driver = "org.exist.xmldb.DatabaseImpl";
        
        // initialize database driver
        Class cl = Class.forName(driver);
        Database database = (Database) cl.newInstance();
        database.setProperty("create-database", "true");
        DatabaseManager.registerDatabase(database);
        
        Collection col = null;
        try { 
            col = DatabaseManager.getCollection(URI + args[0]);
            XQueryService xqs = (XQueryService) col.getService("XQueryService", "1.0");
            xqs.setProperty("indent", "yes");
        
            CompiledExpression compiled = xqs.compile(args[1]);
            ResourceSet result = xqs.execute(compiled);
            ResourceIterator i = result.getIterator();
            Resource res = null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    System.out.println(res.getContent());
                } finally {
                    //dont forget to cleanup resources
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        } finally {
            //dont forget to cleanup
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
    }
}
        </programlisting></example><para>The XML-RPC server automatically caches compiled expressions, and so calling
                    <methodname>compile</methodname> through the remote driver produces no effect if
                the expression is already cached.</para><para>Next, we would like to store a new document into the repository. This is done by
                creating a new <classname>XMLResource</classname>, assigning it the content of the
                new document, and calling the <methodname>storeResource</methodname> method of class
                    <classname>Collection</classname>. First, a new Resource is created by method
                    <methodname>Collection.createResource()</methodname>, and expects two
                parameters: the id and type of resource being created. If the id-parameter is null,
                a unique resource-id will be automatically generated .</para><para>In some cases, the collection may not yet exist, and so we must create it. To
                create a new collection, call the <methodname>createCollection</methodname> method
                of the <classname>CollectionManagementService</classname> service. In the following
                example, we simply start at the root-collection object to get the
                    <classname>CollectionManagementService</classname> service.</para><example><title>Adding a File (XML:DB API)</title><programlisting format="linespecific" language="java">
import java.io.File;
import org.exist.xmldb.EXistResource;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;

public class StoreExample {
    
    private static String URI = "xmldb:exist://localhost:8080/exist/xmlrpc";
    
    /**
     * args[0] Should be the name of the collection to access
     * args[1] Should be the name of the file to read and store in the collection
     */
    public static void main(String args[]) throws Exception {
       
        if(args.length &lt; 2) {
            System.out.println("usage: StoreExample collection-path document");
            System.exit(1);
        }

        final String driver = "org.exist.xmldb.DatabaseImpl";
        
        // initialize database driver
        Class cl = Class.forName(driver);
        Database database = (Database) cl.newInstance();
        database.setProperty("create-database", "true");
        DatabaseManager.registerDatabase(database);

        Collection col = null;
        XMLResource res = null;
        try { 
            col = getOrCreateCollection(args[0]);
            
            // create new XMLResource; an id will be assigned to the new resource
            res = (XMLResource)col.createResource(null, "XMLResource");
            File f = new File(args[1]);
            if(!f.canRead()) {
                System.out.println("cannot read file " + args[1]);
                return;
            }
            
            res.setContent(f);
            System.out.print("storing document " + res.getId() + "...");
            col.storeResource(res);
            System.out.println("ok.");
        } finally {
            //dont forget to cleanup
            if(res != null) {
                try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
            
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
    }
    
    private static Collection getOrCreateCollection(String collectionUri) throws XMLDBException {
        return getOrCreateCollection(collectionUri, 0);
    }
    
    private static Collection getOrCreateCollection(String collectionUri, int pathSegmentOffset) throws XMLDBException {
        
        Collection col = DatabaseManager.getCollection(URI + collectionUri);
        if(col == null) {
            if(collectionUri.startsWith("/")) {
                collectionUri = collectionUri.substring(1);
            }

            String pathSegments[] = collectionUri.split("/");
            if(pathSegments.length &gt; 0) {

                StringBuilder path = new StringBuilder();
                for(int i = 0; i &lt;= pathSegmentOffset; i++) {
                    path.append("/" + pathSegments[i]);
                }

                Collection start = DatabaseManager.getCollection(URI + path);
                if(start == null) {
                    //collection does not exist, so create
                    String parentPath = path.substring(0, path.lastIndexOf("/"));
                    Collection parent = DatabaseManager.getCollection(URI + parentPath);
                    CollectionManagementService mgt = (CollectionManagementService) parent.getService("CollectionManagementService", "1.0");
                    col = mgt.createCollection(pathSegments[pathSegmentOffset]);
                    col.close();
                    parent.close();
                } else {
                    start.close();
                }
            }
            return getOrCreateCollection(collectionUri, ++pathSegmentOffset);
        } else {
            return col;
        }
    }
}
</programlisting></example><para>Please note that the <classname>XMLResource.setContent()</classname> method takes
                a Java object as its parameter. The eXist driver checks if the object is a File.
                Otherwise, the object is transformed into a String by calling the object's
                    <methodname>toString()</methodname> method. Passing a File has one big
                advantage: If the database is running in the embedded mode, the file will be
                directly passed to the indexer. Thus, the file's content does not have to be loaded
                into the main memory. This is handy if your files are very large.</para></section><section><title>Extensions to XML:DB</title><section><title>Additional Services</title><para>eXist provides several services in addition to those defined by the XML:DB
                    specification:</para><para>The <ulink url="api/org/exist/xmldb/UserManagementService.html">UserManagementService</ulink> service contains methods to manage users and
                    handle permissions. These methods resemble common Unix commands such as
                        <methodname>chown</methodname> or <methodname>chmod</methodname>. As with
                    other services, <classname>UserManagementService</classname> can be retrieved
                    from a collection object, as in:</para><synopsis format="linespecific">UserManagementService service = 
                    (UserManagementService)collection.getService("UserManagementService", "1.0");</synopsis><para>Another service called <ulink url="api/org/exist/xmldb/DatabaseInstanceManager.html">DatabaseInstanceManager</ulink>, provides a single method to shut down the
                    database instance accessed by the driver. You have to be a member of the
                        <option>dba</option> user group to use this method or an exception will be
                    thrown. See the <ulink url="deployment.xml#embedded">Deployment Guide</ulink>
                    for an example.</para><para>Finally, interface <ulink url="api/org/exist/xmldb/IndexQueryService.html">IndexQueryService</ulink> supports access to the terms and elements
                    contained in eXist's internal index. Method getIndexedElements() returns a list
                    of element occurrences for the current collection. For each occurring element,
                    the element's name and a frequency count is returned.</para><para>Method scanIndexTerms() allows for a retrieval of the list of occurring words
                    for the current collection. This might be useful, for example, to provide users
                    a list of searchable terms together with their frequency.</para></section><section id="multidb"><title>Multiple Database Instances</title><para>As explained above, passing a local XML:DB URI to the
                        <classname>DatabaseManager</classname> means that the driver will try to
                    start or access an embedded database instance. You can configure more than one
                    database instance by setting the location of the central configuration file. The
                    configuration file is set through the <option>configuration</option> property of
                    the <classname>DatabaseImpl</classname> driver class. If you would like to use
                    different drivers for different database instances, specify a name for the
                    created instance through the <option>database-id</option> property. You may
                    later use this name in the URI to refer to a database instance. The following
                    fragment sets up two instances:</para><example><title>Multiple Database Instances</title><programlisting format="linespecific" language="java">
// initialize driver
String driver = "org.exist.xmldb.DatabaseImpl";
Class cl = Class.forName(driver);			
Database database1 = (Database)cl.newInstance();
database1.setProperty("create-database", "true");
database1.setProperty("configuration", "/home/exist/test/conf.xml");
database1.setProperty("database-id", "test");
DatabaseManager.registerDatabase(database1);

Database database2 = (Database)cl.newInstance();
database2.setProperty("create-database", "true");
database2.setProperty("configuration", "/home/exist/production/conf.xml");
database2.setProperty("database-id", "exist");
DatabaseManager.registerDatabase(database1);
</programlisting></example><para>With the above example, the URI</para><synopsis format="linespecific">xmldb:test:///db</synopsis><para>selects the test database instance. Both instances should have their own data
                    and log directory as specified in the configuration files.</para></section></section></chapter></book>