Jackrabbit Deployment in AS6 and AS7

The following is an extract from my notes about getting JackRabbit deployed as a resource adapter in JBoss AS6 and AS7. No guarantees about accuracy of these instructions, but they have been used by a couple of people to get the resource adapter going and so should give enough info to get you going.

 

The configuration described is using pure JDBC persistence for the repository (no journal files) and using PostgreSQL for the database.

 

 

 

1. Deploying in JBoss

JBoss AS6 and JBoss AS7 are quite different when it comes to configuring the datasources and deploying the Jackrabbit resource adapter. The resource adapter (archive) itself should not need to change although there are some minor changes described below. The configuration of the resource adapter, the deployment of the database JDBC driver and the configuration of the database datasources is quite different.

1.1.  JBoss AS6 Deployment

1.1.1.  JBoss JCR Module

You will need to include the jcr API jar file in the jboss class path, the easiest way to do this is to copy the jcr-2.0.jar file to the $(JBOSS_HOME)/common/lib directory. It is available as part of the JCR-283 specification download.

1.1.2.  Deploy the PostgreSQL JDBC Database Driver

Copy the jdbc driver jar postgresql-9.0-801.jdbc4.jar to the server/default/lib directory (a peer directory to the deploy directory).

1.1.3.  Configure the Datasource in JBoss

Create a PostgreSQL user and DB for JackRabbit:

  1. Create a new user:
$ POSTGRES_HOME/bin/createuser -d -e -E -P -r -s
Enter name of role to add: documentstore
Enter password for new role: xxxxx
Enter it again: xxxxx
## If this doesn't work, try: $POSTGRES_HOME/bin/createuser -d -e -E -P -r -s --username=postgres
  1. Create a Database

$ POSTGRES_HOME/bin/createdb -e --owner=documentstore --username=documentstore -W documentstore

The following datasource descriptor should be deployed to the jboss deploy directory ($JBOSS_HOME/server/default/deploy/). It configures a new datasource connnected to a postgreSQL database. The datasource is configured for local transactions only as required by Jackrabbit.

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
  <jndi-name>jdbc/DocumentStoreDS</jndi-name>
  <connection-url>jdbc:postgresql://localhost/documentstore</connection-url>
  <driver-class>org.postgresql.Driver</driver-class>
<user-name>documentstore</user-name>
<password>xxxxx</password>
  <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<exception-sorter-class-name>
  org.jboss.resource.adapter.jdbc.vendor.PostgreSQLExceptionSorter
</exception-sorter-class-name>
<!--
  <new-connection-sql>select 1</new-connection-sql>
-->
<check-valid-connection-sql>select 1</check-valid-connection-sql>
<max-pool-size>15</max-pool-size>
<min-pool-size>3</min-pool-size>
<prefill>true</prefill>
  <idle-timeout-minutes>15</idle-timeout-minutes>
  <blocking-timeout-millis>60000</blocking-timeout-millis>
  <background-validation>false</background-validation>
  <track-statements>warn</track-statements>
<metadata>
<type-mapping>PostgreSQL 8.0</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>

Table 1 $JBOSS_HOME/server/default/deploy/documentstore-ds.xml

1.1.4.  Deploy the Jackrabbit Resource Adapter to JBoss

A slight change (hack) is required to the shipped jackrabbit rar file. It includes a copy of commons-collections, slf4j-api, logback-classic and logback-core that are incompatible with jboss AS6 so we need to unpack the rar, remove the commons-collections jar file and repack it. It is then copied to the server/default/deploy directory.

1.1.5.  Add the Jackrabbit JCA configuration to JBoss

Create the following deployment descriptor which will configure the resource adapter contained in the rar file and register it with the jndi name shown. Remember to change the filepath to YOUR filepath.

<?xml version="1.0" encoding="UTF-8"?>
<connection-factories>
<tx-connection-factory>
<jndi-name>jca/DocumentStore</jndi-name>
<xa-transaction/>
<rar-name>jackrabbit-jca-2.2.7.rar</rar-name>
  <connection-definition>javax.jcr.Repository</connection-definition>
<config-property name="HomeDir" type="java.lang.String">*/Users/bwallis/InfoMedix/JBoss/jboss-6.0.0.Final/server/default/repository*</config-property>
<config-property name="ConfigFile" type="java.lang.String">*/Users/bwallis/InfoMedix/JBoss/jboss-6.0.0.Final/server/default/repository/repository.xml*</config-property>
<config-property name="bindSessionToTransaction" type="java.lang.Boolean">true</config-property>
</tx-connection-factory>
</connection-factories>

Table 2 $JBOSS_HOME/server/default/deploy/jcr-ds.xml

1.2. JBoss AS7 Deployment

1.2.1. JBoss JCR Module

You will need to add the jcr API jar to the jboss modules (it isn't currently included with jboss). It is available as part of the JCR-283 specification download. Create a new directory ${JBOSS_HOME)/modules/javax/jcr/main and copy the jcr api jar file into that directory, the file is jcr-2.0.jar (should be in your maven repository at javax/jcr/jcr/2.0). Then you need to create two more files as shown

META-INF
META-INF/maven
META-INF/maven/javax.jcr
META-INF/maven/javax.jcr/jcr
javax
javax/jcr
javax/jcr/lock
javax/jcr/nodetype
javax/jcr/observation
javax/jcr/query
javax/jcr/query/qom
javax/jcr/retention
javax/jcr/security
javax/jcr/util
javax/jcr/version

 

Table 3 jcr-2.0.jar.index

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.0" name="javax.jcr">
  <dependencies>
  <module name="javax.transaction.api" export="true"/>
  </dependencies>

<resources>
  <resource-root path="jcr-2.0.jar"/>
  <!-- Insert resources here -->
</resources>
</module>

Table 4 module.xml

Now the jcr api jar file is available to deployed applications that have a dependency on it.

Next we need to modify the jackrabbit jcr rar file to include the dependency on the jcr module we just defined.

1.2.2. Deploy the PostgreSQL JDBC Database Driver

If you don't already have postgres drivers installed in jboss7 then you need to deploy them. This is relatively easy if you have the driver jar file at hand. Just copy it into the $(JBOSS_HOME)/standalone/deployments directory. Make sure you have the latest one for PostgreSQL 9.0 and the JDBC4 compliant one (the JDBC3 one will probably work as well).

An alternative would be to create a JBoss 7 module in the modules directory (similar to the jcr module described above). Deploying the JDBC jar file seems to be a good solution for now.

1.2.3. Configure the Datasource in JBoss

You need to add this to the $JBOSS_HOME)/standalone/configuration/standalone.xml in the datasources section. Note that the document store needs its own datasource configured and should not share the datasource configured for the hibernate persistence or used for the JMS persistence. The reason for this is that jackrabbit implements the XA protocol internally and requires a non-XA datasource underneath rather than relying on the datasource's XA implementation to coordinate commits across multiple providers (in essence, jackrabbit is a persistence provider and as such coordinates its own end of the transactions).

<datasource jndi-name="jdbc/DocumentStoreDS" pool-name="DocumentStoreDS_Pool" enabled="true"
  jta="true" use-java-context="false" use-ccm="true">
  <connection-url>
  jdbc:postgresql://localhost/documentstore
  </connection-url>
<driver>
  postgresql-9.0-801.jdbc4.jar
</driver>
  <transaction-isolation>
  TRANSACTION_READ_COMMITTED
  </transaction-isolation>
<pool>
  <min-pool-size>
  3
  </min-pool-size>
  <max-pool-size>
  15
  </max-pool-size>
  <prefill>
  true
  </prefill>
  <use-strict-min>
  false
  </use-strict-min>
  <flush-strategy>
  FailingConnectionOnly
  </flush-strategy>
</pool>
<security>
  <user-name>
  documentstore
  </user-name>
  <password>
  xxxxx
  </password>
</security>
<validation>
  <check-valid-connection-sql>
  select 1
  </check-valid-connection-sql>
  <validate-on-match>
  false
  </validate-on-match>
  <background-validation>
  false
  </background-validation>
  <useFastFail>
  false
  </useFastFail>
  <exception-sorter
  class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter"/>
</validation>
<timeout>
  <blocking-timeout-millis>
  60000
  </blocking-timeout-millis>
  <idle-timeout-minutes>
  15
  </idle-timeout-minutes>
</timeout>
<statement>
  <track-statements>
  TRUE
  </track-statements>
</statement>
</datasource>

1.2.4. Deploy the Jackrabbit Resource Adapter to JBoss

It should be possible to just copy the resource adapter to the deploy directory but it requires a couple of modifications first before it can be used in JBoss AS7.

RAR Modifications

Unpack the rar file (jackrabbit-jca-2.2.7.rar) to make a couple of configuration changes (these should not really be necessary but are for now)

Dependency on JCR module

Edit the META-INF/MANIFEST.MF file. Add the Dependencies line (and make sure it has a blank line at the end or it doesn't work). You should end up with a MANIFEST.MF file that looks a bit like this:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: bwallis
Build-Jdk: 1.6.0_26
Dependencies: javax.jcr export,org.slf4j

Table 5 MANIFEST.MF

Most of the entries are not used and can be ignored if different from the above, the manifest version and dependencies lines should be as shown.

Note that this is also including a dependency on the org.slf4j logging package so we can get the logging from the rar integrated into the jboss logging.

If you are recreating the rar with the jar utility don't forget the -M flag so that the MANIFEST.MF file is auto-generated (overwriting your changes).

Remove logging Jars

When deployed into jboss we want to use the jboss logging framework, not write to standard output so we need to remove the logging implementation included in the rar file to get it to use the jboss logging framework (otherwise logging ends up to stdout). The two jars to remove are  logback-classic-0.9.20.jar and logback-core-0.9.20.jar. They are in the root directory of the rar file.

Add Repository Paths to ra.xml

This is temporary to work around a problem in jboss 7 for builds prior to August 5th (see above for jboss bug report link). Find the config-property definitions for HomeDir and ConfigFile and add the values as shown (use appropriate paths to your installation). These values should be defined in standalone.xml/domain.xml but for earlier builds including 7.0.0.Final that doesn't work.

<config-property>
  <config-property-name>HomeDir</config-property-name>
<config-property-type>java.lang.String</config-property-type>
  <config-property-value>/Users/bwallis/InfoMedix/JBoss/jboss-as-7.1.0.Alpha1-SNAPSHOT/repository</config-property-value>
</config-property>
<config-property>
  <config-property-name>ConfigFile</config-property-name>
  <config-property-type>java.lang.String</config-property-type>
  <config-property-value>/Users/bwallis/InfoMedix/JBoss/jboss-as-7.1.0.Alpha1-SNAPSHOT/repository/repository.xml</config-property-value>
</config-property>

1.2.5. Add the Jackrabbit JCA configuration to JBoss

You need to add the JCA configuration to $(JBOSS_HOME)/standalone/configuration/standalone.xml (or the domain equivalent if running the domain version of jboss)

Something like this seems to work for a simple test case (see below for test case code)

<subsystem xmlns="urn:jboss:domain:resource-adapters:1.0">
<resource-adapters>
<resource-adapter>
<archive>
  jackrabbit-jca-2.2.7.rar
</archive>
<transaction-support>
XATransaction
</transaction-support>
<connection-definitions>
  <connection-definition class-name="org.apache.jackrabbit.jca.JCAManagedConnectionFactory"
  jndi-name="java:/jca/DocumentStore" enabled="true" use-java-context="true"
  pool-name="jackrabbit-jca-2_2_7_rar-Pool" use-ccm="true">
<!-- There is a bug in JBoss that prevents the following configuration of the
properties from working in builds of JBoss AS 7.1.0 prior to August 5th
so these two property definitions should be left out and configured in the
ra.xml file in the resource adapter for builds earlier than that (including
the 7.0.0.Final version). See below and also the bug report at
https://issues.jboss.org/browse/AS7-1452
-->
<config-property name="HomeDir">
/Users/bwallis/InfoMedix/JBoss/jboss-as-7.1.0.Alpha1-SNAPSHOT/repository
</config-property>
<config-property name="ConfigFile">
/Users/bwallis/InfoMedix/JBoss/jboss-as-7.1.0.Alpha1-SNAPSHOT/repository/repository.xml
</config-property>
  <pool> 
  <min-pool-size>
  3
  </min-pool-size>
  <max-pool-size>
  15
  </max-pool-size>
  <prefill>
  true   
  </prefill>
  <use-strict-min>
  true   
  </use-strict-min>
  </pool>
  </connection-definition>
</connection-definitions>
</resource-adapter>
</resource-adapters>
</subsystem>

2. Configure the Repository

The path configured in ra.xml or standalone.xml points to a directory for the repository and also to a configuration file for the repository. Two files are required to configure the repository. One is the repository.xml file and the other is a schema (optional).

The schema is used once to initialise the data model similar in the way that an SQL DDL file is used in a relational database. Once the data model has been loaded you don't need to do it again (ie: not every time you start) unless you are recreating the repository from scratch. The following is an example of a valid schema that has been used during initial evaluation.

<inf = 'http://infomedix.com.au/inf'>

[inf:metadata] mixin
- inf:description (STRING)='' mandatory

[inf:attribute] > nt:base
- inf:key (STRING) mandatory
- inf:value (STRING) mandatory

[inf:externalReference] > nt:base
- inf:date (DATE) mandatory
- inf:endpoint (STRING) mandatory
- inf:address (STRING) mandatory
- inf:state (STRING) mandatory

[inf:content] > inf:metadata, mix:referenceable
+ inf:data (nt:resource) mandatory multiple primary

[inf:document] > inf:metadata, mix:referenceable
+ inf:pages (inf:content) mandatory multiple primary
+ inf:attributes (inf:attribute) multiple

[inf:documentGroup] > nt:folder, inf:metadata, mix:referenceable
+ inf:documents (inf:document) mandatory multiple primary
+ inf:sources (inf:externalReference) multiple
+ inf:destinations (inf:externalReference) multiple
+ inf:attributes (inf:attribute) multiple

Table 6 schema.cnd

The repository.xml file is used every time the document store is started up. It defines where things are stored (databases, filesystem paths) and configuration for the stores and for the lucene search engine. This example defines a datastore that is persisted in the database using a standard jdbc DataSource.

When deployed in jboss AS7 the JNDI name of the datasource (the url parameter) is specified as jdbc/DocumentStoreDS but in JBoss AS6 you need to add java: to the front giving java:jdbc/DocumentStoreDS. Note that this is specified in about 6 different places in the repository.xml file and is also specified in any workspace.xml files that JackRabbit may have created after first use (in repository/workspaces/*/workspace.xml).

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.5//EN"
  "http://jackrabbit.apache.org/dtd/repository-1.5.dtd">
<Repository>
<FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
  <param name="driver" value="javax.naming.InitialContext"/>
  <param name="url" value="jdbc/DocumentStoreDS"/>
  <param name="schema" value="postgresql"/>
  <param name="schemaObjectPrefix" value="fsrep_"/>
</FileSystem>
<Security appName="Jackrabbit">
<AccessManager class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager"></AccessManager>
<LoginModule class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
  <param name="anonymousId" value="anonymous" />
</LoginModule>
</Security>
<DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
<param name="driver" value="javax.naming.InitialContext"/>
<param name="url" value="jdbc/DocumentStoreDS"/>
<param name="databaseType" value="postgresql"/>
  <param name="schemaObjectPrefix" value="ds_" />
</DataStore>
<Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" />
<Workspace name="default">
  <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
  <param name="driver" value="javax.naming.InitialContext"/>
  <param name="url" value="jdbc/DocumentStoreDS"/>
  <param name="schema" value="postgresql"/>
<param name="schemaObjectPrefix" value="fsws_${wsp.name}_"/>
  </FileSystem>
  <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
<param name="driver" value="javax.naming.InitialContext"/>
<param name="url" value="jdbc/DocumentStoreDS"/>
  <param name="schema" value="postgresql" />
  <param name="schemaObjectPrefix" value="pm_${wsp.name}_" />
  <param name="externalBLOBs" value="false" />
  </PersistenceManager>
<SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
  <param name="path" value="${wsp.home}/index" />
  <param name="useCompoundFile" value="true" />
  <param name="minMergeDocs" value="100" />
  <param name="volatileIdleTime" value="3" />
<param name="maxMergeDocs" value="100000" />
  <param name="mergeFactor" value="10" />
  <param name="maxFieldLength" value="10000" />
  <param name="bufferSize" value="10" />
  <param name="cacheSize" value="1000" />
  <param name="forceConsistencyCheck" value="false" />
  <param name="autoRepair" value="true" />
  <param name="analyzer" value="org.apache.lucene.analysis.standard.StandardAnalyzer" />
  <param name="queryClass" value="org.apache.jackrabbit.core.query.QueryImpl" />
  <param name="respectDocumentOrder" value="true" />
  <param name="resultFetchSize" value="2147483647" />
  <param name="extractorPoolSize" value="3" />
  <param name="extractorTimeout" value="100" />
  <param name="extractorBackLogSize" value="100" />
  <param name="textFilterClasses"
  value="org.apache.jackrabbit.extractor.MsWordTextExtractor,
  org.apache.jackrabbit.extractor.MsExcelTextExtractor,
  org.apache.jackrabbit.extractor.MsPowerPointTextExtractor,
  org.apache.jackrabbit.extractor.PdfTextExtractor,
  org.apache.jackrabbit.extractor.OpenOfficeTextExtractor,
  org.apache.jackrabbit.extractor.RTFTextExtractor,
  org.apache.jackrabbit.extractor.HTMLTextExtractor,
  org.apache.jackrabbit.extractor.PlainTextExtractor,
  org.apache.jackrabbit.extractor.XMLTextExtractor" />
  </SearchIndex>
</Workspace>
<Versioning rootPath="${rep.home}/version">
  <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
  <param name="driver" value="javax.naming.InitialContext"/>
  <param name="url" value="jdbc/DocumentStoreDS"/>
  <param name="schema" value="postgresql"/>
<param name="schemaObjectPrefix" value="fsver_"/>
  </FileSystem>
  <PersistenceManager class="org.apache.jackrabbit.core.persistence.bundle.PostgreSQLPersistenceManager">
<param name="driver" value="javax.naming.InitialContext"/>
<param name="url" value="jdbc/DocumentStoreDS"/>
  <param name="schema" value="postgresql" />
  <param name="schemaObjectPrefix" value="version_" />
  <param name="externalBLOBs" value="false" />
</PersistenceManager>
</Versioning>
</Repository>

Table 7 repository.xml

3. Debug Test Code

To keep the testing simple I just used a JSP in a simple war file

The war file structure is:

  1. jndi.jsp
    META-INF/
    META-INF/MANIFEST.MF
    test.jsp
    WEB-INF/
    WEB-INF/lib/
    WEB-INF/lib/jackrabbit-jcr-client-2.2.7.jar

The manifest contains the following

Manifest-Version: 1.0
Dependencies: javax.jcr,org.jboss.as.naming,deployment.jackrabbit-jca-2.2.7.rar

Table 8 MANIFEST.MF

the lib directory needs the jackrabbit client jar file as we need some of the classes from the test jsp file

The test.jsp file contains the following little test program. This installs a schema written in CND notation and then inserts and retrieves a couple of nodes.

 

Note: this code depends on using the CND schema given above. You could do something similar without the schema but that is an exercise left to the reader :-)

 

 

<%@ page  language="java" import="java.util.*,java.io.*,javax.naming.*,javax.jcr.*,org.apache.jackrabbit.api.*,org.apache.jackrabbit.core.*" errorPage="" %>
<html>
<body>
<%
Context ctx = new InitialContext();
Repository repository = (Repository) ctx.lookup("java:/jca/DocumentStore") ;  //For JBoss 6
//  Repository repository = (Repository) ctx.lookup("jca/DocumentStore") ;  //For JBoss 7

%>
<pre>
Have Repository Reference: <%=repository.toString()%>
<%
Session ses = repository.login(new SimpleCredentials("username", "password".toCharArray()));
%>
Have logged in
<%
try {
  JackrabbitNodeTypeManager manager = (JackrabbitNodeTypeManager)ses.getWorkspace().getNodeTypeManager();
// Register the custom node types defined in the CND file
manager.registerNodeTypes(new FileInputStream("/Users/bwallis/InfoMedix/JBoss/jboss-as-7.1.0.Alpha1-SNAPSHOT/repository/schema.cnd"),JackrabbitNodeTypeManager.TEXT_X_JCR_CND);

%>
Loaded Node Definitions
<%
Node root = ses.getRootNode();

// Store content
Node docGroup = root.addNode("documents_1","inf:documentGroup");
Node attr = docGroup.addNode("inf:attributes","inf:attribute");
  attr.setProperty("inf:key","name");
  attr.setProperty("inf:value","a value");
Node doc = docGroup.addNode("inf:documents","inf:document");
Node dpage = doc.addNode("inf:pages","inf:content");
Node data = dpage.addNode("inf:data","nt:resource");
  data.setProperty("jcr:data","stuff");
  docGroup.setProperty("inf:description", "Hello, World, this is a Document Group");
  doc.setProperty("inf:description", "Hello, World, this is a Document");
  dpage.setProperty("inf:description", "Hello, World, this is a Page");
%>
Created nodes, about to persist...
<%
ses.save();
%>
Persisted nodes. Retrieve one back.
<%
// Retrieve content
Node node = root.getNode("documents_1/inf:documents");
%>
node.getPath() = <%=node.getPath()%>
  node.getProperty("message").getString() = <%=node.getProperty("inf:description").getString()%>
<%
// Remove content
  root.getNode("documents_1").remove();
ses.save();
}
catch(Throwable e) {
%>
Exception: <%=e.toString()%>
<%
}
finally {
ses.logout();
}
%>
</body>
</html>

Table 9 test.jsp

and on running via the url http://localhost:8080/test/test.jsp you should get output similar to that shown below.

Have Repository Reference: org.apache.jackrabbit.jca.JCARepositoryHandle@75eee7b7

Have logged in

node.getPath() = /hello/world
  node.getProperty("message").getString() = Hello, World!