X-Hive/DB 6.0 - Advanced Topics

November 14 2007

 

 

 

 

 

 

These are the advanced topics of release 6.0.

The list of advanced topics for release 4.0 and before can be found at Advanced topics release 4.0.

 

 

 

 

 

 

 

 

 

Copyright © 2007  X-Hive Corporation B.V. ®, All Rights Reserved.

Rights to the content of this publication

This publication contains proprietary information belonging to X-Hive Corporation. It may not be reproduced without written permission.

Trademarks

X-Hive and X-Hive/DB are registered trademarks of X-Hive Corporation. All other company or product names used in this publication are trademarks of their respective owners.

X-Hive/DB includes software developed by the Apache Software Foundation (http://www.apache.org).

Table of Contents

1 Introduction

2 How to program servlets with X-Hive/DB

    2.1 Classpath Issues

        2.1.1 Common exceptions from Tomcat

    2.2 Session Pooling

3 How to use external (XML-)editors to edit X-Hive/DB documents

    3.1 Introduction and usage

    3.2 Dealing with DTDs, example

    3.3 Dealing with DTDs, explained

    3.4 Other Caveats

4 XSL(T) Transformations without our interface

    4.1 XhiveFormatterIf.formatAsPDF

    4.2 XhiveTransformerIf.transform...

5 Replication tutorial

    5.1 Replication setup

    5.2 Replication application code

Appendix

1 Introduction

This documentation contains several concise topics concerning specific usages and details of X-Hive/DB. You can browse this information yourself, and you may sometimes be directed here by support personnel.

The information in these articles are supposed to give detailed information for specific circumstances, for more detailed information please refer to the manual. Most of this information was compiled based on questions from customers.

If the information given on these pages seems incomplete, or incorrect, do not hesitate to send an email to support@x-hive.com.

2 How to program servlets with X-Hive/DB

Programming a servlet is not really different from programming other applications. We do provide a sample servlet, which shows how to combine them programmatically. In this article two common issues that often come up with servlets are dealt with; using a session pool and classpath issues.

The information in this section only applies to X-Hive/DB 6. When using earlier versions of X-Hive/DB (4 and earlier), the properties for classloading are slightly different because of native libraries, and the session pool optimizations are also different. See previous Advanced topics release 4.0 for information on earlier versions of X-Hive/DB.

Summary of this article:

2.1 Classpath Issues

The information in this section applies mostly to the Tomcat servlet engine. It is about how to how to set up a classpath, specifically how Tomcat deals with this. It does not explain fully how the Tomcat classloader system works, please see the Tomcat documentation for this.

In Tomcat, like other servlet engines, the proper way to deal with the classpath is to create a WEB-INF/lib directory for your servlet and copy all the jars to this directory. This is what is advised in the servlet specification. It is also where jars are placed if an application gets unpacked from a .war file. However, there some serious downsides to placing the X-Hive/DB related jars in WEB-INF/lib:

  • The classloader that loads classes for jars located in the WEB-INF/lib directory has certain special conditions. Specifically, the specification indicates it may not load classes from certain packages. This can give problems in that it might cause classes which are available in multiple jars to be loaded partially from one jar and partially from another jar. In other words, if you place the X-Hive/DB related jars in WEB-INF/lib of specific servlets, you should ensure not to place the same or conflicting jars in common/lib.

  • If you have multiple servlets running within one Tomcat environment. There will be one classloader per servlet running. This will lead to multiple drivers being initialized when you connect, which means that you can only do this if you have an external (dedicated) server running (in simpler terms, when all the servlets connect to the datbase through an xhive://hostname:portnumer URL). If you do decide to deploy xhive.jar in multiple directories, you must also ensure that they all use different session-pools, no objects may be shared between servlets.

    The requirement of not having a dedicated server can be important, because as described in the performance chapter of the manual, running without a dedicated (external) server will give a great performance boost to your applications.

  • If you want Tomcat to automatically reload servlets when they are changed (recompiled), you can turn on 'reloadable=true' on the servlet context in server.xml (consult the Tomcat documentation for this) (or you can also redeploy servlets). However, for each reloading/ redeploying action a new classloader is created, and this will thus has the same issues as described before.

One way to prevent some of these problems is to place all X-Hive/DB jars in what tomcat calls the 'common lib', which is the directory tomcatDir/common/lib. Jars in this directory are shared between all servlets. The classloader which loads the classes from this directory does not unload classes, which means the JNI library will not be loaded multiple times. Here follows a list of jar placement tips:
  • When you want to run a servlet using the X-Hive/DB APIs, place all X-Hive/DB related jars (these are the jars that can be found in the lib subdirectory of your X-Hive/DB installation) in tomcatDir/common/lib.

  • If your Tomcat version also has a tomcatDir/common/endorsed directory, it is best to place the following X-Hive/DB jars in that directory *instead of* in common/lib if you are using JDK 1.4: xercesImpl.jar, xalan.jar and xml-apis.jar. The reason for this is that putting the jars there will mean they override the XML-classes that come with JDK 1.4. For Xalan a lot of bug-fixes were applied since the version that ships default with the JDK. For Xerces it means that the DOM Level 3 interfaces are used. For almost every serious X-Hive/DB application, it is important that 'our' (dom3-)xml-apis.jar is placed in tomcatDir/endorsed, we are sorry for that inconvenience but it will be the same for the default Xerces distribution real soon.

  • Tomcat comes with its own version of Xerces (in xercesImpl.jar or xerces.jar). You should remove those jars and replace them with ours. The reason to use our jars instead of Tomcat is that we make more demanding use of those jars (Tomcat only really uses them to load some configuration files, which will work with just about any parser implementation).

  • One thing to make sure of is that the same jar is not duplicated in multiple directories of Tomcat. Specifically, if you place the X-Hive/DB jars in common/lib, you should not put them in the individual servlets directories. When multiple classloaders load classes with the same name, it may give classcast exceptions or even more obscure errors.

  • When using Tomcat 4.1.x, download/ use the LE version when possible. The LE editions of Tomcat only work with JDK 1.4 and ship with fewer jars, specifically xercesImpl.jar is not included. This means one less jar to worry about, and JDK 1.4 is a requirement for X-Hive/DB 5.0 anyway.

2.1.1 Common exceptions from Tomcat

Because the classloading issues described can sometimes lead to confusing exceptions and error messages, we will list some common ones and their causes here. These errors may be shown in your webbrowser accessing Tomcat, on the console of the running Tomcat process, or in the file tomcatDir/logs/localhost_log.<date>.txt:

  • Load of library: oojava failed java.lang.UnsatisfiedLinkError

    You are using X-Hive/DB 4 or before, and should be reading Advanced topics release 4.0.

  • java.lang.VerifyError: xmlDecl signature: Illegal use of nonvirtual function call

    Means that there is an older version of Xerces somewhere in the classpath. Be sure to remove the existing xerces.jar from tomcatDir/common/lib when you copy the X-Hive/DB jars there.

  • java.lang.ClassNotFoundError: some org.w3.dom class not found

    Means that you have not placed our xml-apis.jar in the endorsed directory, and the DOM Level 3 interfaces cannot be loaded (because of a bug in Tomcat).

  • org.apache.xml.utils.WrappedRuntimeException: The output format must have a '{http://xml.apache.org/xslt}content-handler' property!

    This error is usually caused by the Xalan included with JDK 1.4. Make sure xalan.jar is placed in tomcatDir/common/endorsed (if you run into this error and there is no such directory, we advise you to upgrade to a newer version of Tomcat).

2.2 Session Pooling

It should be noted that although the creation of sessions in X-Hive/DB still takes some time, especially when connecting to an external (dedicated) server (which involves setting up a TCP/IP connection), creating a session for every request does not automatically mean bad performance, as it did in previous versions of X-Hive/DB. Therefor the information in this section could be considered as deprecated, except for the remarks on read-only sessions.

In the sample servlet we create a stack of sessions, from which sessions are taken and returned when needed. Each time a session is used a connection is made on it and a transaction is opened. In this way the session pool can be used in a generic way. You can use this mechanism too in your applications. As the goal of the pool is to increase the responsiveness, we describe three possible ways to increase the reaction speed and explain the merits of each one. Only the first tip on read-only sessions can lead to great improvements of the performance of your system with X-Hive/DB, because the two other items describe bottlenecks that no longer exist in X-Hive/DB 5:

  • When you can determine at the start of a request that that request does not have to make changes in the database (e.g. you can make out from the request-URI that it is a request that will involve an XQuery and stylesheet transformation), you can use a readonly transaction. These have the advantage that A) the request itself does not have to wait for locks of other concurrent transactions and B) that other transactions do not have to wait for locks taken by this transaction. Because of this double effect, the performance gain of using read-only transactions when possible should not be underestimated. When using a pool of sessions, you can still set the readonlymode of the session before each transaction begin.

  • If you always connect to the same database as the same user in your application, you can choose to already connect on each session when you create the pool (our session pool has the method connectAs for this purpose). The overhead of a connect is not very large though, roughly comparable to the retrieval of one tiny document through an index.

  • In previous releases we sometimes recommended to not commit the transaction for every request, but for X-Hive/DB a lot of work was put in to limit the transaction-overhead, so this (flawed) technique should not have to be practiced anymore.

3 How to use external (XML-)editors to edit X-Hive/DB documents

This article describes the unsupported FTP-and DAV services that can be used to handle the documents that reside in the database with FTP or DAV aware programs.

After an introduction, we describe some issues with using these services.

3.1 Introduction and usage

In certain cases you may want to edit XML documents in the database with an external editor. We provide two services for this:

  • An FTP-server, which is included since X-Hive/DB 2.0, and can be started with the command:

    xhive-ant run-ftpserver -Ddbname=<DatabaseName>

  • Starting with X-Hive/DB 2.0.1, a WebDAV (called DAV in this article) server can be downloaded which is based on the DAV-server that comes with Tomcat 4.x. Installation instructions are included in the download.

Both of these services come with full source code, so that they can be modified to match your environment. After you have started one of these servers, you can access them through FTP and DAV-clients. Here are some examples of such clients:
  • Windows Network Places. Under the 'Windows Network Places' folder, you can add network places for both FTP-sites and DAV-urls. After you have started one of the above described servers, you can view the contents of the database as a folder, and use drag and drop to place documents in the database. You can even open items from such folders in editors, but unfortunately not save them.

  • XML Spy can read/write documents on both FTP- and DAV-servers.

  • XMetaL 3.0 supports WebDAV, but their implementation is incompatible with Apache's WebDAV implementation on which our integration is based and most other WebDAV servers.

  • XMetaL 4.0's WebDAV support does work okay with our WebDAV server. You can use XMetaL 4 with release 1.5 or later of our WebDAV server.

  • Ultraedit supports FTP-editing of documents.

  • MS Word can read and write (binary) files from a DAV-server.

  • WebDrive is a program with which both FTP- and DAV-servers can be mapped as drive letters. Succes with this tool in combination with the X-Hive/services is mixed, it helps to turn off 'Cache Files' in the program settings.

For Windows Network Places and XML Spy, URLs for accessing an FTP-server should look like ftp://localhost/ and for accessing a DAV server (with standard Tomcat settings) would be http://localhost:8080/xhivedav (where localhost is the hostname of the machine running the server).

3.2 Dealing with DTDs, example

This section and the next are only interesting if you want to use the combination of WebDAV (or FTP) and DTDs.

In some cases, you will want to use DTDs connected to the documents in the database from the external editor. This is especially the case with an editor like XMetaL, where a DTD is required to be able to edit the document properly. In X-Hive/DB, the connection between a document and a DTD is made very different from how its done on a file-system. In X-Hive/DB the DTDs are located in a catalog, and identification is done through the Public ID in the doctype declaration. Since the DAV/ FTP servers offer up the libraries and documents as folders and files, DTDs must be located through a file location.

The next section will go into detail about how we solved this problem. However, we will start with a usage example (the sample will use WebDAV, references to XMetaL imply XMetaL 4.0 or better):

  • Make sure the WebDAV server is properly installed and running.

  • If you want to use DTDs stored in X-Hive/DB in your document, the first step is to store a DTD in the database (unless this was already done through another mechanism). One way to do this is to access the database through a webfolders application. For instance on Windows, you can go to your 'Network Places', add a network place with a string depending on your configuration (e.g. http://localhost:8080/xhivedav) and log on.

    You can not store DTDs in any folder, only in folders representing catalogs (in a default configuration, a folder named xhive-catalog). You can place a DTD through WebDAV by placing (dragging) a DTD file in that folder.

    (If you add a DTD in this way, the name of the file will be set as the public id of that DTD in the catalog (this is unusual, but it is best if the item has a public id. In the end the public id as used within X-Hive/DB is just a string identifying the DTD within the catalog. You can also load a DTD through the adminclient (import on catalog), for more control)

  • Next, launch your WebDAV aware editor and create a new file. Since you want to use a DTD, you have to reference it in a document type declaration. This is the general format of such a document type declaration:

    <!DOCTYPE rootElem PUBLIC "publicId" "systemId">
    To make sure a DTD linked to the document in the database can be found, you can do one of the following:
    • Set the publicId string to match a public id of a DTD stored in the catalog of the library where you are going to store the document. If you loaded up the DTD through WebDAV as explained before, the public id would be the same as the filename of the DTD (so for instance play.dtd).

    • An alternative (introduced in our WebDAV support version 1.5) is to set the systemId to the full URL to the DTD as accessible through WebDAV. An example of such an id would be http://localhost:8080/xhivedav/xhive-catalog/play.dtd.

    The second way has the advantage that the editor itself can use it to find DTD information (if it will authenticate itself for the access). In fact, with XMetaL the first step when you create a new 'Blank' XML document is that you will have to browse for a DTD. If you then point to the DTD on the network share representing the WebDAV server, XMetaL will enter the correct document type declaration information automatically (in XMLSpy, you would have to enter the URL yourself).

  • The next step is to save the document to the WebDAV server, which is an editor dependent action. For instance, for XMLSpy you have to use the menu File|Save to URL..., and in XMetaL you have to select the WebDAV folder in the 'Network Places' section and save it there as a normal file (note that this only works for WebDAV aware editors).

  • One final thing to note, is that when you open the file again later from the WebDAV server, the document type declaration will be changed to always have this format:

    <!DOCTYPE rootElem PUBLIC "
      X-Hive/DB public id" "xhive-catalog/filename.dtd">
    This format ensures both the editor and the X-Hive/DB can locate the DTD linked to the document.

3.3 Dealing with DTDs, explained

Note:

The information in this section is mostly targeted towards X-Hive/DB 4.0/5.0 and at least version 1.5 of our WebDAV integration (your mileage with earlier versions may vary).

This section describes how we worked around the problems of using DTDs that are stored within X-Hive/DB, for which an example of usage was given in the previous section..

In X-Hive/DB, every library has access to a catalog (available through XhiveLibraryIf.getCatalog()). That catalog holds a list of stored DTDs. Documents may refer to DTDs in this catalog. This referring is through the the public id in the document type declaration, and the system id in the document type is not used for this.

When parsing with validation, a DTD will also be stored in the catalog, unless a DTD with the same public-id is already in the catalog (if that is the case, the document is validated against that DTD). You can also store a DTD explicitly (through the Abstract Schema interfaces).

With File-based editors, DTD resolution is done based on the file-location specified in the system id of the document type declaration. This can be a relative path. When you present the database as files and folders, and you want the DTD be found, it must be found as a file in some folder, you cannot simply follow the link made in the database. The remainder of this section describes what solution we have taken to implement this, but that solution can best be described as 'a hack'. It could be that in your specific situation another method could work better, in which case you can change the sources. Also, this section describes the general technique, the actual implementation may be different in different versions of the servers.

When you store a file, it parses the document without validation (because some documents may not have a DTD specified for it, and then validation is not possible), which means a DTD is not stored nor is a DTD attached to the document. However, when there is a doctype definition the DTD is still read, as it may contain entity definitions. Therefore, if you have a doctype definition in a document you want to save, or (for WebDAV) you must specify the full location of the DTD as the system id. After the XML document is parsed, the newer versions of the servers will try to find a DTD in the catalog linked to this document based on the public id (or the full system id if it was found), and if one can be found it will be linked to the document in the database. This is done with DocumentAS.setActiveASModel.

When you retrieve a document from the database later, and the DTD was linked to the document as described above, the public id and system id of the document type declaration may have to be changed to now point to the local path.

What we have done is made the catalog available as a folder in each library (whether that library has a local catalog or not). This folder is virtual, in the sense that it is simply added by the server process to the list of file- and directory-names. The name is therefore also arbitrary (xhive-catalog), and can be changed in the sources. When a document is loaded from the database to a DAV/ FTP client, that client may use the system id to look up the DTD. To be able to find the DTD in the database, the system id must then be a relative path, with a format like xhive-catalog/fileName.dtd. In that case the client will request the server for a file fileName.dtd in a folder xhive-catalog relative to the library where the document is found. When the server gets such a request, it will know to look in the catalog, and send the DTD to the client over the network. To get the system id to hold a string of the described structure, you can for instance set it correctly when you first parse the document. When you retrieve the document, the method XhiveDocumentIf.fixupDoctypeIds is used to set this information correctly for the server (so the data is changed!).

So in short, with the default way we provide these servers the following is the case:

  • Each library represented as a folder has a virtual folder to represent the catalog.

  • When you store new documents using FTP or DAV, the DTD is not stored.

  • When you store a new document and want to use a DTD, you must set the public id to something that is available in the catalog or pass a full system id. Otherwise, parsing will fail (some versions of the FTP-server try to fix this problem, by attempting to look up the public id based on the relative system id). If the public id matches something in the catalog, the document will also be linked with this DTD.

  • When receiving documents which have a DTD assigned to them in the database, they will be looked up in the database by 'normal file resolution' if they have a system id specified like xhive-catalog/fileName.dtd. The API-call XhiveDocumentIf.fixupDoctypeIds in X-Hive/DB will try to perform this fixup.

3.4 Other Caveats

  • If a choice can be made between FTP and WebDAV for communication, we advise to use FTP (it is a simpler protocol, and the full implementation is in one classfile not depending on third party solutions so is easier to debug).

  • The WebDAV protocol requires that the 'file'-size of each document is known at all times. The file-size of XML documents can only be determined through serializing the file, so this is currently slow, especially when a directory listing is retrieved (which would show filesizes). Therefor, we employ caching of previously determined file-sizes, and show file-size as a default of 2 KB when we expect file-size is not important.

  • When you parse documents into the database, or create new libraries from a remote client, they are created/ parsed with some default parameters. You may want to check the source code of these servers to check whether they match what you want (and change them otherwise).

  • When storing documents on the FTP/ WebDAV-server, new documents are created during parsing and existing documents are replaced. When using non-live indexes like Context Conditioned Indexes (and pre- X-Hive/DB 4.0 FTI indexes), code should be added to the FTP/ WebDAV sources to update those indexes.

  • When documents or DTDs are stored in the database over FTP/ DAV, the client sends the document contents as a stream to the server. This means that if that document contains local references to external entities (like DTDs), local in the sense that they contain relative paths to files on the client system, these will not be found on the server, and thus parsing might fail.

  • The FTP and DAV servers can store both XML documents and BLOBs. When data comes in, whether it is stored as XML or a BLOB is determined based on the file-extension. You can edit what extensions are seen as XML-files in the sources.

  • On systems that already have a FTP-server running, you will not be able to run the X-Hive/DB ftp-server on the default port number. However, in the sources you can change the port-constant to a number other than 21, and connect to another port (the hostname could then for instance be localhost:2100).

  • With DAV, you should not rename folders, as with the current DAV-server implementation this operation is defined as 'create a new folder, copy all the documents to this folder, and delete the other folder'. Instead, use the Adminclient or the FTP-server for this function (where the command is simply 'setName').

  • When using the combination WebDrive, XMetaL and DTDs, you will run into the problem that XMetaL wants to store BLOBs (dtdname.rlx) in the catalog. We have chosen not to allow this. XMetaL 4's internal WebDAV connection does work directly with X-Hive/DB (including catalogs).

4 XSL(T) Transformations without our interface

The XhiveTransformerIf and XhiveFormatterIf interfaces in com.xhive.util.interfaces are really convenient access methods to Xalan and FOP of Apache project respectively. In some cases you may want to use the more advanced options of these packages. This is why we give you our implementation of these interfaces here, to give you a starting point on how to use the interfaces of those packages.

4.1 XhiveFormatterIf.formatAsPDF

  import org.apache.fop.apps.Driver;
  import org.apache.fop.apps.Version;

  public void formatAsPDFToStream(Document foSource, OutputStream os) throws XhiveException {  
    try {                                                                                                   
      Driver driver = new Driver();
      driver.setRenderer("org.apache.fop.render.pdf.PDFRenderer", Version.getVersion());
      driver.addElementMapping("org.apache.fop.fo.StandardElementMapping");
      driver.addElementMapping("org.apache.fop.svg.SVGElementMapping");
      driver.addPropertyList("org.apache.fop.fo.StandardPropertyListMapping");
      driver.addPropertyList("org.apache.fop.svg.SVGPropertyListMapping");
      driver.setOutputStream(os);
      driver.buildFOTree(foSource);
      driver.format();
      driver.render();               
    } 
    catch(Exception e) {
      throw new XhiveException(XhiveException.FORMAT_EXCEPTION, e);
    }
  }

The sample code above is for a quite an old version (0.20) of FOP, shipped with X-Hive/DB. When you want to use a newer version of FOP in combination with X-Hive/DB, you could use a routine like:

  import org.apache.avalon.framework.logger.ConsoleLogger;
  import org.apache.avalon.framework.logger.Logger;
  import org.apache.fop.apps.Driver;
  import org.apache.fop.messaging.MessageHandler;

  public static void formatAsPDFToStream(DOMImplementation docCreator, Document xmlSource, Document xslSource, OutputStream os)
      throws XhiveException {
    try {
      // XSLT part
      XhiveTransformerIf transformer = XhiveDriverFactory.getDriver().getTransformer();
      Document foDocument = transformer.transformToDocument(docCreator, xmlSource, xslSource);

      // FOP part
      Driver driver = new Driver();
      Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_ERROR);
      MessageHandler.setScreenLogger(logger);
      driver.setLogger(logger);
      driver.setRenderer(Driver.RENDER_PDF);
      driver.setOutputStream(os);
      driver.render(foDocument);
    } catch (Exception e) {
      //e.printStackTrace();
      throw new XhiveException(XhiveException.FORMAT_EXCEPTION, e);
    }
  }

4.2 XhiveTransformerIf.transform...


  import com.xhive.core.interfaces.XhiveSessionIf;
  import com.xhive.error.XhiveException;
  import com.xhive.dom.*;
  import org.w3c.dom.*;
  import javax.xml.transform.*;
  import java.io.*;
  import java.util.Iterator;

  public Document transformToDocument(DOMImplementation docCreator, Node xmlSource, Document xslSource)
      throws XhiveException {

    Document result = docCreator.createDocument("", "Result", null);
    Node rootElem = result.getDocumentElement();
    result.removeChild(rootElem);

    transform(xmlSource, xslSource, new DOMResult(result));
    return result;
  }

  public void transformToStream(Node xmlSource, Document xslSource, Writer writer)
      throws XhiveException {
    transform(xmlSource, xslSource, new StreamResult(writer));
  }

  public String transformToString(Node xmlSource, Document xslSource)
      throws XhiveException {

    StringWriter result = new StringWriter();
    transformToStream(xmlSource, xslSource, result);
    return result.toString();
  }

  private void transform(Node xmlSource, Document xslSource, Result result) {
    try {
      // Try to initialize URI resolver
      URIResolver myResolver;
      try {
        XhiveSessionIf session = ...; // Left as an exercise to the reader
        if (session != null) {
          myResolver = new XhiveURIResolver(session);
        } else {
          myResolver = null;
        }
      } catch (Exception e) {
        // (No session?) Fine, then we don't use a URIResolver
        myResolver = null;
      }

      TransformerFactory tFactory = TransformerFactory.newInstance();
      if (myResolver != null) {
        // Factory resolver must be set before transformer is created
        tFactory.setURIResolver(myResolver);
      }
      Transformer transformer = tFactory.newTransformer(new DOMSource(xslSource));
      if (myResolver != null) {
        transformer.setURIResolver(myResolver);
      }

      transformer.transform(new DOMSource(xmlSource), result);
    } catch (Exception e) {
      throw new XhiveException(XhiveException.TRANSFORM_EXCEPTION, e);
    }
  }




  /**
   * URI resolver that translates
   *  xhive:path#query
   * into an xquery run on path, e.g.
   *  xhive:/plays#//TITLE
   */
  private class XhiveURIResolver implements URIResolver {
    private static final String XHIVE_PREFIX = "xhive:";
    private static final String SEPARATOR = "#";

    private XhiveSessionIf session;

    public XhiveURIResolver(XhiveSessionIf session) {
      this.session = session;
    }

    public Source resolve(String href, String base) throws TransformerException {
      // Do we need to do anything?
      if (((base == null) || (!base.startsWith(XHIVE_PREFIX))) &&
          (!href.startsWith(XHIVE_PREFIX))) {
        return null;
      } else {
        // Process href
        // Up to us to come up with a result
        if (!href.startsWith(XHIVE_PREFIX)) {
          // Create href with base
          href = base + href;
        }
        // Strip xhive: from href
        href = href.substring(XHIVE_PREFIX.length());
        if (!href.startsWith("/")) {
          href = "/" + href;
        }

        // Separate in path and query
        String path = null;
        String query = null;
        if (href.indexOf(SEPARATOR) != -1) {
          path = href.substring(0, href.indexOf(SEPARATOR));
          query = href.substring(href.indexOf(SEPARATOR) + 1);
        } else {
          path = href;
        }
        // Get query context
        XhiveLibraryChildIf contextNode = session.getDatabase().getRoot().getByPath(path);
        if (contextNode == null) {
          // Nothing found, error or null?
          throw new TransformerException("XhiveXalanTransfomer: Could not resolve " + href);
          //return null;
        } else {
          if (query == null) {
            if (contextNode instanceof XhiveDocumentIf) {
              return new DOMSource(contextNode);
            } else if (contextNode instanceof XhiveBlobNodeIf) {
              return new StreamSource(((XhiveBlobNodeIf) contextNode).getContents());
            } else {
              throw new TransformerException("XhiveXalanTransfomer: " + href + " is not a document");
            }
          } else {
            return getExecuteQuerySource(contextNode, query, href);
          }
        }
      }
    }

    private Source getExecuteQuerySource(XhiveLibraryChildIf contextNode, String query, String href) throws TransformerException {
      if (query.startsWith("xpointer(") && query.endsWith(")")) {
        query = query.substring("xpointer(".length(), query.length() - 1);
      }
      Iterator queryResult = null;
      try {
        queryResult = contextNode.executeXQuery(query);
      } catch (XhiveException e) {
        throw new TransformerException("XhiveXalanTransformer: Problem with query " + href + ": " + e.getMessage(), e);
      }
      // We will only use the first result here (otherwise we would have to include a new top-element)
      if (queryResult.hasNext()) {
        XhiveXQueryValueIf queryValue = (XhiveXQueryValueIf) queryResult.next();
        // Is it a node?
        try {
          return new DOMSource(queryValue.asNode());
        } catch (XhiveException e) {
          // must be XQUERY_ERROR_VALUE, so interpret it as a string
          return new StreamSource(new StringReader(queryValue.asString()));
        }
      } else {
        throw new TransformerException("XhiveXalanTransformer: Query " + href + ": " + " has no results");
      }
    }
  }

For improved Xalan XSLT performance, it may be wise to create Templates objects using the newTemplates call on TransformerFactory. What follows is an example of this, see the Xalan documentation for details.

  TransformerFactory tFactory = TransformerFactory.newInstance();
  tFactory.
  translet    = tFactory.newTemplates(new DOMSource(xslSource));
  // Now keep this translet cached somewhere, and for transforming do:
  Transformer transformer = translet.newTransformer();
  transformer.transform( new DOMSource(xmlSource), new StreamResult(writer)) ) ;
One advantage of a compiled stylesheet is that it no longer has references to any X-Hive/DB persistent data, so you can use the compiled version with any session.

5 Replication tutorial

This article uses the replication information given in the manual to give an example on how to set up a simple replicating system based on an existing single server application. In this example use case the specific goal of the replicating is to improve performance by duplicating the application (+ federation) on different hosts.

This article only applies to X-Hive/DB release 6 and up.

5.1 Replication setup

The first step is to prepare the databases for replication. We create a temporary segment in each database, so that temporary results can be created in the replica-databases. This can be done in the adminclient, by connecting to a database and:

  • Right-click 'Segments' and add a new one (make sure an id is entered and the temporary flag is checked.
  • Right-click the database root node, and in the properties set the just created segment as temporary segment.
It can also be done in Java code, with something like:
  // Creating a temporary segment in Java code
  session.getDatabase().createTemporaryDataSegment("tempSegmentName", null, 0);
  session.getDatabase().setTemporaryDataSegment("tempSegmentName");

Next, the actual replica is made, assuming the master server is running a page server accessible from other hosts, you can run the following command on the replica host (assuming X-Hive/DB is installed there too):

  XHCreateReplica -debug -federation xhive://masterhost:1235 -replicaid newOrExistingReplicaId 
    -replicabootstrappath /path/to/new/replicadata/XhiveDatabase.bootstrap SuperUserPassword
(XHCreateReplica is not much more than a wrapper around the XhiveFederationIf.registerReplicator(...) and XhiveFederationIf.replicateFully(...) API calls). This will register the replica in the master federation, and copy the complete federation over to the replica host. You can now connect to the federation copy on the replica host, although it is not wise to do so directly (since it should only be accessed as a replica). If something would happen to your replica for whatever reason, you can always remove the files on the replica host and issue this command again.

5.2 Replication application code

On the master host, you can access the federation in the normal way, as long as you make sure it acts as a page server to the replicating host. So assuming that for performance you access the database files directly the driver initialization code in your application would be:

  XhiveDriverIf driver = XhiveDriverFactory.getDriver("/path/to/XhiveDatabase.bootstrap");
  driver.init(numCachePages);
  driver.startListenerThread(new ServerSocket(1235));
(Of course, in a real use situation you want really one code-base for your application, with a configuration switch to indicate whether the machine is acting as a replica or master, with corresponding driver configuration and usage code, but that is out of the scope of this tutorial)

The driver configuration code on the replica host depends on the application. If you only want to run read-only transactions on your replica federation only an extra call to XhiveDriverIf.configureReplicator(...) is needed. However, for our example we want the replica host to act as a full copy of the application, including allowing for read-write transactions. Since X-Hive/DB replication only allows for updates on the master copy of the federation, this means some extra configuration is needed on the replica to allow for update transactions. Specifically, on the replica a distinction is made between readonly transactions (which can act on the replication federation, and thus can have direct access to (a copy of) the database files), and update transactions which have to access the master federation. This results in much slower performance for these update transactions (relative to readonly transactions on the replica or to updates on the master host itself), so this setup is only advised if the majority of your application's transactions are readonly transactions.

In code, this means you have to create two driver objects, one that acts on the replica and one that connects to the master-server directly:

  // On replica-host, use two drivers
  XhiveDriverIf masterDriver = XhiveDriverFactory.getDriver("xhive://masterhost:1235");
  masterDriver.init(numCachePages / 2);
  XhiveDriverIf replicaDriver = XhiveDriverFactory.getDriver("/path/to/replicadir/XhiveDatabase.bootstrap");
  driver.configureReplicator("xhive://masterhost:1235", "previouslySetupId");
  replicaDriver.init(numCachePages / 2);

In your session pooling code, you decide which driver to get the session from based on whether it is a readonly transaction or not. For performance, it is advisable to have your session pool code (and the code that uses the session pool) already make a distinction between readonly and readwrite transactions (using XhiveSessionIf.setReadOnlyMode(...)) for concurrency performance. Once you have that, this could be the getSession code:

  synchronized XhiveSessionIf getSession(boolean readOnlyTransaction) {
    if (readOnlyTransaction) {
      return waitForUpdates(replicaDriver.createSession());
    } else {
      return masterDriver.createSession();
    }
  }
waitForUpdates will be explained shortly, the createSession call should be replaced by code that pools the sessions. Since there are two drivers you also need to set up two collections of sessions.

With the setup above you will be able to perform both readonly and update transactions, with the readonly transactions having improved performance since they use the replica. However, X-Hive/DB replication is 'lazy', which means that when an update transaction on the master federation finishes it is not guaranteed that a transaction started directly after on the replica will 'see' the changes. This can be problematic if in your application you e.g. add a document to a library, and then immediately after that show the contents of the library to the user in a readonly transaction, the library contents might not be updated on the replica yet in such a case. To overcome this, it is possible to have a session on the replica wait for updates made in a session on the master server. In the session pool code you could register a timestamp each time a readwrite session completes:

  XhiveSessionIf.TimeStamp currentWaitTimeStamp = null;

  synchronized returnSession(XhiveSessionIf session) {
    // Regular session pool code
    ....
    if (session.getDriver().equals(masterDriver)) {
      currentWaitTimeStamp = session.getUpdateTimeStamp();
    }
  }
and then from getSession call a routine that waits for the updates to be available on the replica before continuing:
  XhiveSessionIf waitForUpdates(XhiveSessionIf session) {
    if (currentWaitTimeStamp != null) {
      session.waitForTimeStamp(currentWaitTimeStamp);
      currentWaitTimeStamp = null;
    }
    return session;
  }
(Please note that in practice, 'lazy' does not mean it will take long for the changes to be replicated, only that it is not guaranteed, so in practice XhiveSessionIf.waitForTimeStamp(...) should complete immediately or quickly).