diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/JCRSessionFactory.java osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/JCRSessionFactory.java
--- osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/JCRSessionFactory.java	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/JCRSessionFactory.java	2006-12-22 14:50:23.000000000 +0100
@@ -0,0 +1,30 @@
+package com.opensymphony.workflow.spi.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Factory used by JCRWorkflowStore for accessing a JCR repository.<br>
+ * Namespaces and node types must be registered before any call to
+ * <code>getSession()</code> or <code>getWorkflowStoreNodePath()</code>.
+ *  
+ * @author <a href="mailto:sebastien.launay.no.spam@anyware-tech.com">Sébastien Launay</a>
+ */
+public interface JCRSessionFactory {
+	/**
+	 * Get the absolute workflow store node path to root node.<br>
+	 * Called once by JCRWorkflowStore and then cached. 
+	 * 
+	 * @return The workflow store  node path relative to root node.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	public String getWorkflowStoreNodePath() throws RepositoryException;
+
+	/**
+	 * Get a new session for accessing the JCR repository.
+	 * 
+	 * @return A new session.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	public Session getSession() throws RepositoryException;
+}
\ No newline at end of file
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/JCRWorkflowStore.java osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/JCRWorkflowStore.java
--- osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/JCRWorkflowStore.java	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/JCRWorkflowStore.java	2007-01-25 12:45:11.000000000 +0100
@@ -0,0 +1,1275 @@
+package com.opensymphony.workflow.spi.jcr;
+
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.opensymphony.module.propertyset.PropertySet;
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.query.Expression;
+import com.opensymphony.workflow.query.FieldExpression;
+import com.opensymphony.workflow.query.NestedExpression;
+import com.opensymphony.workflow.query.WorkflowExpressionQuery;
+import com.opensymphony.workflow.query.WorkflowQuery;
+import com.opensymphony.workflow.spi.SimpleStep;
+import com.opensymphony.workflow.spi.SimpleWorkflowEntry;
+import com.opensymphony.workflow.spi.Step;
+import com.opensymphony.workflow.spi.WorkflowEntry;
+import com.opensymphony.workflow.spi.WorkflowStore;
+import com.opensymphony.workflow.util.PropertySetDelegate;
+
+
+/**
+ * JCR (JSR-170) implementation.
+ * <p>
+ * When manipulating a workflow of a content stored into a JCR repository, it
+ * may be interesting to store the workflow state in the JCR repository as well.
+ * Indeed, the integrity of the content and the workflow is ensured unlike storing
+ * the workflow in a database.
+ * Furthermore, it will be possible to query a content on both his metadatas and
+ * his workflow state by referencing the entry node and by using XPath
+ * <code>/jcr:root//content[...]/jcr:deref(@workflowEntry, 'oswf:entry')[...]</code>.
+ * <p>
+ * The following property is <b>required</b>:
+ * <ul>
+ *  <li><b>sessionFactory</b> - an JCRSessionFactory implementation for getting sessions.
+ * </ul>
+ * The following property is <b>optional</b>:
+ * <ul>
+ *  <li><b>propertySetDelegate</b> - Delegation for getting a PropertySet associated with a workflow entry.
+ * </ul>
+ * 
+ * To select an JCR implementation, pass to the
+ * DefaultConfiguration.persistenceArgs the JCRSessionFactory to use.
+ * <p>
+ * For example, with a Jackrabbit repository :<br>
+ * <code>DefaultConfiguration.persistenceArgs.put("sessionFactory", new SimpleJackrabbitSessionFactory(...));</code>
+ * <p>
+ * The underlying JCR implementation must support the following features:
+ * <ul>
+ *  <li>referenceable node using mixin type <code>mix:referenceable</code>,
+ *  <li>child axis support for XPath predicates (<code>//node[child/@property = 'value']<code>,
+ *      for executing all workflow queries).
+ * </ul>
+ *
+ * @see com.opensymphony.workflow.spi.jcr.JCRSessionFactory
+ * @see com.opensymphony.workflow.spi.jcr.SimpleJackrabbitSessionFactory
+ * @author <a href="mailto:sebastien.launay.no.spam@anyware-tech.com">Sébastien Launay</a>
+ */
+public class JCRWorkflowStore implements WorkflowStore {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+	/** Namespace for node type and attribute. */
+    public static final String NAMESPACE = "http://www.opensymphony.com/osworkflow/1.0";
+	/** Prefix used for this namespace. */
+    public static final String NAMESPACE_PREFIX = "oswf";
+    private static final String NM_PREFIX = NAMESPACE_PREFIX + ":";
+    /** Root node type. */
+    public static final String ROOT_NT = NAMESPACE_PREFIX + ":root";
+    /** Entry node type. */
+    public static final String ENTRY_NT = NAMESPACE_PREFIX + ":entry";
+    /** Step node type. */
+    public static final String STEP_NT = NAMESPACE_PREFIX + ":step";
+    
+    /** Entry node name. */
+    public static final String ENTRY_NODE = NM_PREFIX + "entry";
+    /** Current step node name. */
+    public static final String CURRENT_STEP_NODE = NM_PREFIX + "currentStep";
+    /** History step node name. */
+    public static final String HISTORY_STEP_NODE = NM_PREFIX + "historyStep";
+    /** PropertySet node name. */
+    public static final String PROPERTY_SET_NODE = NM_PREFIX + "propertySet";
+    
+    
+    /** Next entry id property for root node. */
+    public static final String NEXT_ENTRY_ID_PROPERTY = NM_PREFIX + "nextEntryId";
+    /** ID for entry and step node. */
+    public static final String ID_PROPERTY = NM_PREFIX + "id";
+    /** Workflow name property for entry node. */
+    public static final String WF_NAME_PROPERTY = NM_PREFIX + "workflowName";
+    /** State property for entry node. */
+    public static final String STATE_PROPERTY = NM_PREFIX + "state";
+    /** Next step id property for entry node. */
+    public static final String NEXT_STEP_ID_PROPERTY = NM_PREFIX + "nextStepId";
+    /** Step id property for step node. */
+    public static final String STEP_ID_PROPERTY = NM_PREFIX + "stepId";
+    /** Action id property for step node. */
+    public static final String ACTION_ID_PROPERTY = NM_PREFIX + "actionId";
+    /** Owner property for step node. */
+    public static final String OWNER_PROPERTY = NM_PREFIX + "owner";
+    /** Caller property for step node. */
+    public static final String CALLER_PROPERTY = NM_PREFIX + "caller";
+    /** Start date property for step node. */
+    public static final String START_DATE_PROPERTY = NM_PREFIX + "startDate";
+    /** Due date property for step node. */
+    public static final String DUE_DATE_PROPERTY = NM_PREFIX + "dueDate";
+    /** Finish date property for step node. */
+    public static final String FINISH_DATE_PROPERTY = NM_PREFIX + "finishDate";
+    /** Status property for step node. */
+    public static final String STATUS_PROPERTY = NM_PREFIX + "status";
+    /** Previous steps ids property for step node. */
+    public static final String PREVIOUS_STEPS_PROPERTY = NM_PREFIX + "previousSteps";
+    
+
+    /** Property key for JCR session factory. */
+    public static final String SESSION_FACTORY_KEY = "sessionFactory";
+    /** Property key for PropertySet delagation. */
+    public static final String PS_DELEGATE_KEY = "propertySetDelegate";
+    
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    /** Log instance for logging events, errors, warnigs, etc. */
+    protected final Log log = LogFactory.getLog(getClass());
+    /** JCR Session factory.*/
+    protected JCRSessionFactory sessionFactory;
+    /** PropertySet delegation.*/
+    protected PropertySetDelegate propertySetDelegate;
+    /** Path to the root node.*/
+    protected String rootNodePath;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+	public void setEntryState(long entryId, int state) throws StoreException {
+		Session session =  null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve existing entry
+			Node entry = getEntry(session, entryId);
+			
+			// Modoify state
+			entry.setProperty(STATE_PROPERTY, state);
+			
+			session.save();
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to change entry state for entryId: " + entryId, e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	public PropertySet getPropertySet(long entryId) throws StoreException {
+        if (getPropertySetDelegate() == null) {
+        	// TODO use internal JCRPropertySet:
+        	// property set is stored as a child of a workflow entry.
+            throw new StoreException("PropertySetDelegate is not properly configured");
+        }
+
+        return getPropertySetDelegate().getPropertySet(entryId);
+	}
+	
+	/**
+	 * Set the PropertySet factory to use.
+	 * 
+	 * @param propertySetDelegate the PropertySet factory to use.
+	 */
+    public void setPropertySetDelegate(PropertySetDelegate propertySetDelegate) {
+        this.propertySetDelegate = propertySetDelegate;
+    }
+
+	/**
+	 * Get the PropertySet factory used.
+	 * 
+	 * @return propertySetDelegate the PropertySet factory used or <code>null</code>
+	 *                             if internal JCR PropertySet factory is used.
+	 */
+    public PropertySetDelegate getPropertySetDelegate() {
+        return propertySetDelegate;
+    }
+
+	public Step createCurrentStep(long entryId, int stepId, String owner, Date startDate, Date dueDate, String status, long[] previousIds) throws StoreException {
+		Session session =  null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve existing entry
+			Node entry = getEntry(session, entryId);
+			
+			// Generate an unique step id inside the entry
+			long id = getNextStepId(entry.getUUID());
+			int actionId = 0;
+			
+			// Create a new current step node
+			Node step = entry.addNode(CURRENT_STEP_NODE);
+			
+			step.setProperty(ID_PROPERTY, id);
+			step.setProperty(STEP_ID_PROPERTY, stepId);
+			step.setProperty(ACTION_ID_PROPERTY, actionId);
+			step.setProperty(OWNER_PROPERTY, owner);
+			step.setProperty(START_DATE_PROPERTY, toCalendar(startDate));
+			
+			if (dueDate != null) {
+				step.setProperty(DUE_DATE_PROPERTY, toCalendar(dueDate));
+			}
+			
+			step.setProperty(STATUS_PROPERTY, status);
+			
+			// Set references to previous steps, default empty
+			Value[] previousStepsRefs = new Value[] {};
+			
+			if (previousIds != null) {
+				previousStepsRefs = new Value[previousIds.length];
+			
+				for (int i = 0; i < previousIds.length; i++) {
+					long previousId = previousIds[i];
+					// Retrieve existing step for this entry
+					Node previousStep = getStep(session, entryId, previousId);
+					// Use the ValueFactory to set a reference to this step
+					previousStepsRefs[i] = session.getValueFactory().createValue(previousStep);
+				}
+			}
+			
+			step.setProperty(PREVIOUS_STEPS_PROPERTY, previousStepsRefs);
+			
+			session.save();
+			
+			return new SimpleStep(id, entryId, stepId, actionId, null, startDate, dueDate, null, status, previousIds, null);
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to store new entry", e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+	
+	public WorkflowEntry createEntry(String workflowName) throws StoreException {
+		Session session =  null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve root node containing entries
+			Node root = session.getRootNode().getNode(rootNodePath);
+			
+			// Generate an unique entry id
+			long id = getNextEntryId();
+			int state = WorkflowEntry.CREATED;
+			
+			Node entry = root.addNode(ENTRY_NODE);
+			
+			entry.setProperty(ID_PROPERTY, id);
+			entry.setProperty(WF_NAME_PROPERTY,workflowName);
+			entry.setProperty(STATE_PROPERTY, state);
+			
+			session.save();
+			
+			return new SimpleWorkflowEntry(id, workflowName, state);
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to store new entry", e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+	
+	/**
+	 * Convert a Date to a Calendar.
+	 * 
+	 * @param date The date to convert.
+	 * @return The date converted as a Calendar.
+	 */
+	protected static Calendar toCalendar(Date date) {
+		if (date == null) {
+			return null;
+		}
+		
+		Calendar calendar = new GregorianCalendar();
+		calendar.setTime(date);
+		return calendar;
+	}
+
+	/**
+	 * Retrieve an entry node from its id.
+	 * 
+	 * @param session The session to use.
+	 * @param entryId The id of the entry.
+	 * @return The entry node. 
+	 * @throws RepositoryException if no entry matches or multiple entries match.
+	 */
+	protected Node getEntry(Session session, long entryId) throws RepositoryException {
+		QueryManager queryManager = session.getWorkspace().getQueryManager();
+		StringBuffer xPathQuery = new StringBuffer("/jcr:root/");
+		xPathQuery.append(rootNodePath);
+		xPathQuery.append("/");
+		xPathQuery.append(ENTRY_NODE);
+		xPathQuery.append("[@");
+		xPathQuery.append(ID_PROPERTY);
+		xPathQuery.append(" = ");
+		xPathQuery.append(entryId);
+		xPathQuery.append("]");
+
+		if (log.isDebugEnabled()) {
+			log.debug("XPathQuery: " + xPathQuery);
+		}
+		
+		Query query = queryManager.createQuery(xPathQuery.toString(), Query.XPATH);
+		QueryResult result = query.execute();
+		NodeIterator nodeIterator =  result.getNodes();
+		
+		if (nodeIterator.hasNext()) {
+			Node entry = nodeIterator.nextNode();
+			
+			if (nodeIterator.hasNext()) {
+				throw new RepositoryException("Multiple entry node for entryId: " + entryId);
+			}
+			
+			return entry;
+		}
+		
+		throw new PathNotFoundException("Unknown entry node for entryId: " + entryId);
+	}
+	
+	/**
+	 * Retrieve an entry node UUID from its id.<br>
+	 * Can be used to reference a workflow entry like a content node
+	 * associated with a workflow.
+	 * 
+	 * @param entryId The id of the entry.
+	 * @return The entry node UUID. 
+	 * @throws RepositoryException if no entry matches or multiple entries match.
+	 */
+	public String getEntryUUID(long entryId)  throws RepositoryException {
+		return getEntry(sessionFactory.getSession(), entryId).getUUID();
+	}
+	
+	/**
+	 * Retrieve an step node from its id for a particular entry.
+	 * 
+	 * @param session The session to use.
+	 * @param entryId The id of the entry.
+	 * @param stepId The id of the step.
+	 * @return The step node. 
+	 * @throws RepositoryException if no step matches or multiple steps match.
+	 */
+	protected Node getStep(Session session, long entryId, long stepId) throws RepositoryException {
+		QueryManager queryManager = session.getWorkspace().getQueryManager();
+		StringBuffer xPathQuery = new StringBuffer("/jcr:root/");
+		xPathQuery.append(rootNodePath);
+		xPathQuery.append("/");
+		xPathQuery.append(ENTRY_NODE);
+		xPathQuery.append("[@");
+		xPathQuery.append(ID_PROPERTY);
+		xPathQuery.append(" = ");
+		xPathQuery.append(entryId);
+		xPathQuery.append("]/*[@");
+		xPathQuery.append(ID_PROPERTY);
+		xPathQuery.append(" = ");
+		xPathQuery.append(stepId);
+		xPathQuery.append("]");
+		
+		if (log.isDebugEnabled()) {
+			log.debug("XPathQuery: " + xPathQuery);
+		}
+		
+		Query query = queryManager.createQuery(xPathQuery.toString(), Query.XPATH);
+		QueryResult result = query.execute();
+		NodeIterator nodeIterator =  result.getNodes();
+		
+		if (nodeIterator.hasNext()) {
+			Node step = nodeIterator.nextNode();
+			
+			if (nodeIterator.hasNext()) {
+				throw new RepositoryException("Multiple entry node for entryId: " + entryId + " and stepId: " + stepId);
+			}
+			
+			return step;
+		}
+		
+		throw new RepositoryException("Unknown entry node for entryId: " + entryId + " and stepId: " + stepId);
+	}
+
+	/**
+	 * Generate an unique entry id.
+	 * @return A new entry id.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	protected synchronized long getNextEntryId() throws RepositoryException {
+		Session session = null;
+		
+		try {
+			// Use a new session to ensure that just this property is saved.
+			session = sessionFactory.getSession();
+			// Retrieve root node containing entries
+			Node root = session.getRootNode().getNode(rootNodePath);
+			long nextEntryId = root.getProperty(NEXT_ENTRY_ID_PROPERTY).getLong();
+			
+			// Set the next entry id
+			root.setProperty(NEXT_ENTRY_ID_PROPERTY, nextEntryId + 1);
+			session.save();
+			
+			// Return the previous entry id
+			return nextEntryId;
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	/**
+	 * Generate an unique step id inside an entry.
+	 * @param entryUUID The entry UUID.
+	 * @return A new step id.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	protected synchronized long getNextStepId(String entryUUID) throws RepositoryException {
+		Session session = null;
+		
+		try {
+			// Use a new session to ensure that just this property is saved.
+			session = sessionFactory.getSession();
+			// Retrieve entry node
+			Node entry = session.getNodeByUUID(entryUUID);
+			long nextStepId = entry.getProperty(NEXT_STEP_ID_PROPERTY).getLong();
+			
+			// Set the next step id
+			entry.setProperty(NEXT_STEP_ID_PROPERTY, nextStepId + 1);
+			session.save();
+			
+			// Return the previous step id
+			return nextStepId;
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	public List findCurrentSteps(long entryId) throws StoreException {
+		List currentSteps = new ArrayList();
+		Session session =  null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve existing entry
+			Node entry = getEntry(session, entryId);
+			// Get current steps
+			NodeIterator nodeIterator = entry.getNodes(CURRENT_STEP_NODE);
+			
+			while (nodeIterator.hasNext()) {
+				// Convert each step node to a Step
+				currentSteps.add(getStep(session, nodeIterator.nextNode()));
+			}
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to change entry state for entryId: " + entryId, e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+
+		return currentSteps;
+	}
+
+	/**
+	 * Convert a step node into a Step.
+	 * @param session The session to use.
+	 * @param stepNode The step node.
+	 * @return the step node converting into a Step.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	protected Step getStep(Session session, Node stepNode) throws RepositoryException {
+		long id = stepNode.getProperty(ID_PROPERTY).getLong();
+		long entryId = stepNode.getParent().getProperty(ID_PROPERTY).getLong();
+		int stepId = (int) stepNode.getProperty(STEP_ID_PROPERTY).getLong();
+		int actionId = 0;
+		
+		if (stepNode.hasProperty(ACTION_ID_PROPERTY)) {
+			actionId = (int) stepNode.getProperty(ID_PROPERTY).getLong();
+		}
+		
+		String owner = stepNode.getProperty(OWNER_PROPERTY).getString();
+		Date startDate = stepNode.getProperty(START_DATE_PROPERTY).getDate().getTime();
+		Date dueDate = null;
+		
+		if (stepNode.hasProperty(DUE_DATE_PROPERTY)) {
+			dueDate = stepNode.getProperty(DUE_DATE_PROPERTY).getDate().getTime();
+		}
+		
+		Date finishDate = null;
+		
+		if (stepNode.hasProperty(FINISH_DATE_PROPERTY)) {
+			finishDate = stepNode.getProperty(FINISH_DATE_PROPERTY).getDate().getTime();
+		}
+
+		String status = stepNode.getProperty(STATUS_PROPERTY).getString();
+		
+		String caller = null;
+		
+		if (stepNode.hasProperty(CALLER_PROPERTY)) {
+			caller = stepNode.getProperty(CALLER_PROPERTY).getString();
+		}
+		
+		List previousStepsIds = new ArrayList();
+		Value[] previous = stepNode.getProperty(PREVIOUS_STEPS_PROPERTY).getValues();
+		
+		for (int i = 0; i < previous.length; i++) {
+			String uuid = previous[i].getString();
+			Node historyStep = session.getNodeByUUID(uuid);
+			
+			long previousStepId = historyStep.getProperty(ID_PROPERTY).getLong();
+			previousStepsIds.add(new Long(previousStepId));
+		}
+		
+		long[] previousIds = new long[previousStepsIds.size()];
+		
+		for (int i = 0; i < previousIds.length; i++) {
+			previousIds[i] = ((Long) previousStepsIds.get(i)).longValue();
+		}
+		
+		// FIXME pas de setDueDate sur SimpleStep
+		return new SimpleStep(id, entryId, stepId, actionId,
+                              owner, startDate, dueDate, finishDate,
+                              status, previousIds, caller);
+	}
+
+	public WorkflowEntry findEntry(long entryId) throws StoreException {
+		Session session =  null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve existing entry
+			Node entry = getEntry(session, entryId);
+			
+			String workflowName = entry.getProperty(WF_NAME_PROPERTY).getString();
+			int state = (int) entry.getProperty(STATE_PROPERTY).getLong();
+			
+			return new SimpleWorkflowEntry(entryId, workflowName, state);
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to change entry state for entryId: " + entryId, e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	public List findHistorySteps(long entryId) throws StoreException {
+		List historySteps = new ArrayList();
+		Session session = null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve existing entry
+			Node entry = getEntry(session, entryId);
+			// Get history steps
+			NodeIterator nodeIterator = entry.getNodes(HISTORY_STEP_NODE);
+			
+			while (nodeIterator.hasNext()) {
+				// Convert each step node to a Step
+				historySteps.add(getStep(session, nodeIterator.nextNode()));
+			}
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to change entry state for entryId: " + entryId, e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+		
+		return historySteps;
+	}
+	
+	public void init(Map props) throws StoreException {
+        sessionFactory = (JCRSessionFactory) props.get(SESSION_FACTORY_KEY);
+        if (sessionFactory == null) {
+        	throw new StoreException("No JCRSessionFactory found");
+        }
+        
+        setPropertySetDelegate((PropertySetDelegate) props.get(PS_DELEGATE_KEY));
+        
+        try {
+        	boolean isValid = false;
+        	rootNodePath = sessionFactory.getWorkflowStoreNodePath();
+        	
+        	if (rootNodePath != null && rootNodePath.length() > 0) {
+        		if (rootNodePath.charAt(0) == '/') {
+        			// Use relative path to the root
+        			rootNodePath = rootNodePath.substring(1);
+        			isValid = true;
+        		}
+        	}
+        	
+        	if (!isValid) {
+        		throw new StoreException("Invalid workflow store node path: " + rootNodePath);
+        	}
+        }
+        catch (RepositoryException e) {
+        	throw new StoreException("Unable to get workflow store root node path", e);
+        }
+        
+        // Test root node presence
+        Session session = null;
+        
+        try {
+            session = sessionFactory.getSession();
+            
+            if (!session.getRootNode().hasNode(rootNodePath)) {
+            	throw new PathNotFoundException("Unable to get workflow store node: " + rootNodePath);
+            }
+            
+            Workspace workspace = session.getWorkspace();
+            
+            // Assert UUID is supported
+            workspace.getNodeTypeManager().getNodeType("mix:referenceable");
+            
+            // Assert namespace is registered
+            String[] uris = workspace.getNamespaceRegistry().getURIs();
+            
+            if (!Arrays.asList(uris).contains(NAMESPACE)) {
+                throw new StoreException("Namespace " + NAMESPACE + " not registered");
+            }
+                
+            // Assert nodetypes are registered
+            workspace.getNodeTypeManager().getNodeType(ROOT_NT);
+            workspace.getNodeTypeManager().getNodeType(ENTRY_NT);
+            workspace.getNodeTypeManager().getNodeType(STEP_NT);
+        }
+        catch (RepositoryException e) {
+        	throw new StoreException("Unable to get workflow store root node path", e);
+        }
+        finally {
+        	if (session != null) {
+        		session.logout();
+        	}
+        }
+	}
+
+	public Step markFinished(Step step, int actionId, Date finishDate, String status, String caller) throws StoreException {
+		Session session = null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve the step node
+			Node currentStep = getStep(session, step.getEntryId(), step.getId());
+			
+			// Change properties
+			currentStep.setProperty(ACTION_ID_PROPERTY, actionId);
+			currentStep.setProperty(FINISH_DATE_PROPERTY, toCalendar(finishDate));
+			currentStep.setProperty(STATUS_PROPERTY, status);
+			currentStep.setProperty(CALLER_PROPERTY, caller);
+			
+			session.save();
+			
+			// Add new properties
+			SimpleStep theStep = (SimpleStep) step;
+            
+			theStep.setActionId(actionId);
+            theStep.setFinishDate(finishDate);
+            theStep.setStatus(status);
+            theStep.setCaller(caller);
+
+            return theStep;
+			
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to modify step for entryId: " + step.getEntryId()
+					               + " and stepId: " + step.getStepId(), e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+
+	}
+
+	public void moveToHistory(Step step) throws StoreException {
+		Session session = null;
+		
+		try {
+			session = sessionFactory.getSession();
+			// Retrieve the step node
+			Node stepNode = getStep(session, step.getEntryId(), step.getId());
+			Node entry = stepNode.getParent();
+			
+			// Get the existing path of the current node
+			String currentStepPath = stepNode.getPath();
+			// Set the destination path to history steps
+			String historyStepPath =  entry.getPath() + "/" + HISTORY_STEP_NODE;
+			// Move node
+			session.move(currentStepPath, historyStepPath);
+			
+			session.save();
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to move step to history for entryId: " + step.getEntryId()
+					               + " and stepId: " + step.getStepId(), e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	public List query(WorkflowQuery query) throws StoreException {
+		List results = new ArrayList();
+		Session session = null;
+        
+		try {
+			session = sessionFactory.getSession();
+	        QueryManager queryManager = session.getWorkspace().getQueryManager();
+	        // Build XPath query
+			StringBuffer xPathQuery = new StringBuffer("/jcr:root/");
+			xPathQuery.append(rootNodePath);
+			xPathQuery.append("/");
+			xPathQuery.append(ENTRY_NODE);
+			xPathQuery.append("[");
+			xPathQuery.append(getCondition(query));
+			xPathQuery.append("]");
+			
+			if (log.isDebugEnabled()) {
+            	log.debug("XPathQuery: " + xPathQuery);
+            }
+			
+			Query jcrQuery = queryManager.createQuery(xPathQuery.toString(), Query.XPATH);
+			QueryResult result = jcrQuery.execute();
+			NodeIterator nodeIterator =  result.getNodes();
+			
+			// Add matching entries
+			while (nodeIterator.hasNext()) {
+				Node entry = nodeIterator.nextNode();
+				results.add(new Long(entry.getProperty(ID_PROPERTY).getLong()));
+			}
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to query entries", e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+		
+		return results;
+	}
+	
+	/**
+	 * Get the condition from the query.
+	 * 
+	 * @param query The query.
+	 * @return The resulting condition.
+	 * @throws StoreException if the query is not valid or not supported.
+	 */
+    protected String getCondition(WorkflowQuery query) throws StoreException {
+        if (query.getLeft() == null) {
+            // leaf node
+            return buildFieldExpression(query);
+        }
+        else {
+            int operator = query.getOperator();
+            WorkflowQuery left = query.getLeft();
+            WorkflowQuery right = query.getRight();
+
+            switch (operator) {
+            case WorkflowQuery.AND:
+                return '(' + getCondition(left) + ") and (" + getCondition(right) + ')';
+
+            case WorkflowQuery.OR:
+                return '(' + getCondition(left) + ") or (" + getCondition(right) + ')';
+
+            case WorkflowQuery.XOR:
+            default:
+            	throw new StoreException("Not supported operator: " + operator);
+            }
+        }
+    }
+
+    /**
+     * Build a field expression.
+     * 
+     * @param query The query.
+     * @return The resulting field expression.
+     * @throws StoreException if the query is not valid.
+     */
+	protected String buildFieldExpression(WorkflowQuery query) throws StoreException {
+		StringBuffer condition = new StringBuffer();
+		
+		int qtype = query.getType();
+
+        if (qtype == 0) { // then not set, so look in sub queries
+            if (query.getLeft() != null) {
+                qtype = query.getLeft().getType();
+            }
+        }
+
+        if (qtype == WorkflowQuery.CURRENT) {
+        	condition.append(CURRENT_STEP_NODE + "/");
+        } else {
+        	condition.append(HISTORY_STEP_NODE + "/");
+        }
+		
+        Object value = query.getValue();
+        int operator = query.getOperator();
+        int field = query.getField();
+
+        String oper;
+
+        switch (operator) {
+        case WorkflowQuery.EQUALS:
+            oper = " = ";
+            break;
+
+        case WorkflowQuery.NOT_EQUALS:
+            oper = " != ";
+            break;
+
+        case WorkflowQuery.GT:
+            oper = " > ";
+            break;
+
+        case WorkflowQuery.LT:
+            oper = " < ";
+            break;
+
+        default:
+            oper = " = ";
+        }
+
+        String left = getPropertyName(field);
+        String right = "";
+
+        if (value == null) {
+        	if (operator == WorkflowQuery.EQUALS) {
+        		oper = "";
+        	}
+        	else if (operator == WorkflowQuery.NOT_EQUALS) {
+        		left = "not(" + left + ")";
+        		oper = "";
+        	}
+        	else {
+        		right = "null";
+        	}
+        }
+        else {
+        	right = translateValue(value);
+        }
+
+        condition.append(left);
+        condition.append(oper);
+        condition.append(right);
+        
+        return condition.toString();
+    }
+	
+	public List query(WorkflowExpressionQuery query) throws StoreException {
+		List results = new ArrayList();
+		Session session = null;
+        
+		try {
+			session = sessionFactory.getSession();
+	        QueryManager queryManager = session.getWorkspace().getQueryManager();
+	        // Build XPath query
+			StringBuffer xPathQuery = new StringBuffer("/jcr:root/");
+			xPathQuery.append(rootNodePath);
+			xPathQuery.append("/");
+			xPathQuery.append(ENTRY_NODE);
+			xPathQuery.append(getPredicate(query));
+			xPathQuery.append(getSortCriteria(query));
+			
+			if (log.isDebugEnabled()) {
+            	log.debug("XPathQuery: " + xPathQuery);
+            }
+			
+			Query jcrQuery = queryManager.createQuery(xPathQuery.toString(), Query.XPATH);
+			QueryResult result = jcrQuery.execute();
+			NodeIterator nodeIterator =  result.getNodes();
+			
+			// Add matching entries
+			while (nodeIterator.hasNext()) {
+				Node entry = nodeIterator.nextNode();
+				results.add(new Long(entry.getProperty(ID_PROPERTY).getLong()));
+			}
+		}
+		catch (RepositoryException e) {
+			throw new StoreException("Unable to query entries", e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+		
+		return results;
+	}
+	
+	/**
+	 * Get the JCR sort criteria from the query.
+	 * 
+	 * @param query The query.
+	 * @return The sort criteria.
+	 * @throws StoreException if the criteria is not valid.
+	 */
+	protected String getSortCriteria(WorkflowExpressionQuery query) throws StoreException {
+        StringBuffer criteria = new StringBuffer();
+		
+		if (query.getSortOrder() != WorkflowExpressionQuery.SORT_NONE) {
+			criteria.append(" order by ");
+
+			// Set child axis for specific context (not entry context)
+			// FIXME Use first field expression found for determining context
+			FieldExpression fieldExpr = getFirstFieldExpression(query.getExpression());
+			
+			if (fieldExpr == null) {
+				// No field expression, no sorting
+				return "";
+			}
+			
+			if (fieldExpr.getContext() == FieldExpression.CURRENT_STEPS) {
+				criteria.append(CURRENT_STEP_NODE + "/");
+	        } else if (fieldExpr.getContext() == FieldExpression.HISTORY_STEPS) {
+	        	criteria.append(HISTORY_STEP_NODE + "/");
+	        }
+			
+            criteria.append(getPropertyName(query.getOrderBy()));
+
+            if (query.getSortOrder() == WorkflowExpressionQuery.SORT_DESC) {
+            	criteria.append(" descending");
+            } else {
+            	criteria.append(" ascending");
+            }
+        }
+
+		return criteria.toString();
+	}
+
+	private FieldExpression getFirstFieldExpression(Expression expression) {
+		if (expression.isNested()) {
+			NestedExpression nestedExpr = (NestedExpression) expression;
+			
+			for (int i = 0; i < nestedExpr.getExpressionCount(); i++) {
+				FieldExpression fieldExpr = getFirstFieldExpression(nestedExpr.getExpression(i));
+				
+				if (fieldExpr != null) {
+					// Field expression found
+					return fieldExpr;
+				}
+			}
+		}
+		else {
+			// Field expression found
+			return (FieldExpression) expression;
+		}
+		
+		// No field expression found
+		return null;
+	}
+
+	/**
+	 * Build a predicate from the query.
+	 * 
+	 * @param query The query.
+	 * @return The predicate.
+	 * @throws StoreException if the query is not valid.
+	 */
+	protected String getPredicate(WorkflowExpressionQuery query) throws StoreException {
+		Expression expression = query.getExpression();
+		String predicate;
+		
+		if (expression.isNested()) {
+			predicate = buildNestedExpression((NestedExpression) expression);
+		}
+		else {
+			predicate = buildFieldExpression((FieldExpression) expression);
+		}
+		
+		return "[" + predicate + "]";
+	}
+
+	/**
+	 * Build a nested expression.
+	 * 
+	 * @param nestedExpr The nested expression.
+	 * @return The resulting condition.
+	 * @throws StoreException if the expression is not valid.
+	 */
+	protected String buildNestedExpression(NestedExpression nestedExpr) throws StoreException {
+		StringBuffer query = new StringBuffer();
+		int exprCount = nestedExpr.getExpressionCount();
+		
+		for (int i = 0; i < exprCount; i++) {
+			Expression subExpression = nestedExpr.getExpression(i);
+			
+			query.append("(");
+			
+			if (subExpression instanceof NestedExpression) {
+				query.append(buildNestedExpression((NestedExpression) subExpression));
+			}
+			else if (subExpression instanceof FieldExpression) {
+				query.append(buildFieldExpression((FieldExpression) subExpression));
+			}
+			
+			query.append(")");
+			
+			if (i != exprCount - 1) {
+				switch (nestedExpr.getExpressionOperator()) {
+				case NestedExpression.AND:
+					query.append(" and ");
+					break;
+					
+				case NestedExpression.OR:
+					query.append(" or ");
+					break;
+				}
+			}
+		}
+		
+		return query.toString();
+	}
+
+	/**
+	 * Build a field expression.
+	 * 
+	 * @param nestedExpr The field expression.
+	 * @return The resulting condition.
+	 * @throws StoreException if the expression is not valid.
+	 */
+	private String buildFieldExpression(FieldExpression expr) throws StoreException {
+		StringBuffer query = new StringBuffer();
+		
+		// Set child axis for specific context (not entry context)
+		if (expr.getContext() == FieldExpression.CURRENT_STEPS) {
+			query.append(CURRENT_STEP_NODE + "/");
+        } else if (expr.getContext() == FieldExpression.HISTORY_STEPS) {
+        	query.append(HISTORY_STEP_NODE + "/");
+        }
+		
+		Object value = expr.getValue();
+        int operator = expr.getOperator();
+        int field = expr.getField();
+
+        String oper;
+
+        switch (operator) {
+        case FieldExpression.EQUALS:
+            oper = " = ";
+            break;
+
+        case FieldExpression.NOT_EQUALS:
+            oper = " != ";
+            break;
+
+        case FieldExpression.GT:
+            oper = " > ";
+            break;
+
+        case FieldExpression.LT:
+            oper = " < ";
+            break;
+
+        default:
+            oper = " = ";
+        }
+
+        String left = getPropertyName(field);
+        String right = "";
+
+        if (value == null) {
+        	if (operator == FieldExpression.EQUALS) {
+        		oper = "";
+        	}
+        	else if (operator == FieldExpression.NOT_EQUALS) {
+        		left = "not(" + left + ")";
+        		oper = "";
+        	}
+        	else {
+        		right = "null";
+        	}
+        }
+        else {
+        	right = translateValue(value);
+        }
+
+        if (expr.isNegate()) {
+        	query.append("not(");
+        }
+
+        query.append(left);
+        query.append(oper);
+        query.append(right);
+        
+        
+        if (expr.isNegate()) {
+        	query.append(")");
+        }
+        
+        return query.toString();
+	}
+	
+	/**
+	 * Convert and maybe escape a value to support XPath grammar.
+	 * 
+	 * @param value The value to translate.
+	 * @return The translated value.
+	 * @throws StoreException if the value cannot be translated.
+	 */
+	protected String translateValue(Object value) throws StoreException {
+		if (value instanceof Date) {
+			Date date = (Date) value;
+			TimeZone timeZone = TimeZone.getDefault();
+            int offset = timeZone.getOffset(date.getTime());
+            
+            String sign = "+";
+            
+            if (offset < 0) {
+                offset = -offset;
+                sign = "-";
+            }
+            
+            int hours = offset / 3600000;
+            int minutes = (offset - hours * 3600000) / 60000;
+            
+            if (offset != hours * 3600000 + minutes * 60000) {
+                // E.g. TZ=Asia/Riyadh87
+				throw new StoreException("TimeZone offset (" + sign + offset + " ms) is not an exact number of minutes");
+            }
+            
+            DecimalFormat twoDigits = new DecimalFormat("00");
+            DateFormat iso8601Local = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+            iso8601Local.setTimeZone(timeZone);
+            
+            String iso8601DateAsString = iso8601Local.format(date) + sign + twoDigits.format(hours) + ":" + twoDigits.format(minutes);
+
+            return " xs:dateTime('" + iso8601DateAsString + "')";
+		}
+		else if ((value instanceof Integer) || (value instanceof Long)) {
+			// No need of escaping
+			return value.toString();
+		}
+	
+		// Escape single quote into two successive single quote		 
+		return "'" + value.toString().replaceAll("'", "''") + "'";
+	}
+
+	/**
+	 * Retrieve a propertyName from a field id.
+	 * @param field The field id.
+	 * @return The corresponding property name.
+	 * @throws StoreException if the field is not valid.
+	 * @see {@link com.opensymphony.workflow.query.FieldExpression}
+	 */
+	protected String getPropertyName(int field) throws StoreException {
+        switch (field) {
+        case FieldExpression.ACTION:
+            return "@" + ACTION_ID_PROPERTY;
+
+        case FieldExpression.CALLER:
+            return "@" + CALLER_PROPERTY;
+
+        case FieldExpression.FINISH_DATE:
+            return "@" + FINISH_DATE_PROPERTY;
+
+        case FieldExpression.OWNER:
+            return "@" + OWNER_PROPERTY;
+
+        case FieldExpression.START_DATE:
+            return "@" + START_DATE_PROPERTY;
+
+        case FieldExpression.STEP:
+            return "@" + STEP_ID_PROPERTY;
+
+        case FieldExpression.STATUS:
+            return "@" + STATUS_PROPERTY;
+
+        case FieldExpression.STATE:
+            return "@" + STATE_PROPERTY;
+
+        case FieldExpression.NAME:
+            return "@" + WF_NAME_PROPERTY;
+
+        case FieldExpression.DUE_DATE:
+            return "@" + DUE_DATE_PROPERTY;
+
+        default:
+        	throw new StoreException("Invalid field: " + field);
+        }
+    }
+	
+    /* TODO Provide internal property set implementation.
+	private class JCRPropertySet implements PropertySet {
+		private long entryId;
+		
+		/**
+		 * Create a JCRPropertySet for an entry.
+		 * 
+		 * @param entry The entry node.
+		 */
+		/*public JCRPropertySet(long entryId) {
+			this.entryId = entryId;
+		}*/
+		
+		/**
+		 * Retrieve property set node.
+		 * 
+		 * @param session The session to use.
+		 * @return The property set node.
+		 * @throws RepositoryException if an error occurs.
+		 */
+		/*protected Node getPropertySetNode(Session session) throws RepositoryException {
+			return getEntry(session, entryId).getNode(PROPERTY_SET_NODE);
+		}*/
+		
+		/*public boolean exists(String key) throws PropertyException {
+			Session session = null;
+			
+			try {
+				session = sessionFactory.getSession();
+				Node propertySet = getPropertySetNode(session);
+				
+				// Test property existence
+				return propertySet.hasProperty(key);
+			}
+			catch (RepositoryException e) {
+				String msg = "Unable to get property set";
+				log.error(msg, e);
+				throw new PropertyException(msg);
+			}
+			finally {
+				if (session != null) {
+					session.logout();
+				}
+			}
+		}
+	}*/
+}
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/SimpleJackrabbitSessionFactory.java osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/SimpleJackrabbitSessionFactory.java
--- osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/SimpleJackrabbitSessionFactory.java	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/SimpleJackrabbitSessionFactory.java	2007-01-25 13:54:59.000000000 +0100
@@ -0,0 +1,177 @@
+package com.opensymphony.workflow.spi.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.jcr.Credentials;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException;
+import org.apache.jackrabbit.core.nodetype.NodeTypeDefStore;
+import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.name.QName;
+
+/**
+ * Implementation for using a Jackrabbit repository.<br>
+ * Simple Jackrabbit session factory can be created like this:<br>
+ * <pre>RepositoryConfig config = RepositoryConfig.create("repository.xml", "/tmp/repository");
+ *TransientRepository repository = new TransientRepository(config)
+ *new SimpleJackrabbitSessionFactory(repository);</pre>
+ * 
+ * This implementation needs at least Jackrabbit 1.1.<br>
+ * Use Jackrabbit 1.2.1 or later for fully supports of queries (child axis in predicate).
+ * @author <a href="mailto:sebastien.launay.nospam@anyware-tech.com">Sébastien Launay</a>
+ */
+public class SimpleJackrabbitSessionFactory implements JCRSessionFactory {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+	/** Workflow store root node name. */
+	protected static final String WFS_ROOT_NODE_NAME = "osworkflow";
+	/** Cutom nodetypes definition filename. */
+	protected static final String CUSTOM_NODE_TYPES_FILENAME = "oswf_nodetypes.xml";
+	/** Default credentials. */
+	protected static final Credentials DEFAULT_CREDENTIALS = new SimpleCredentials("osworkflow", new char[0]);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+	
+	/** Log instance for logging events, errors, warnigs, etc. */
+	protected final Log log = LogFactory.getLog(getClass());
+	/** Repository implementation used. */
+	protected JackrabbitRepository repository;
+	/** Credentials used to login. */
+	protected Credentials credentials;
+	
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+	/**
+	 * Creating a Jackrabbit session factory using specific repository and
+	 * default credentials.
+	 * 
+	 * @param repository the repository to use.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	public SimpleJackrabbitSessionFactory(JackrabbitRepository repository) throws RepositoryException {
+		this(repository, DEFAULT_CREDENTIALS);
+	}
+
+	/**
+	 * Creating a Jackrabbit session factory using specific repository and
+	 * credentials.
+	 * 
+	 * @param repository the repository to use.
+	 * @param credentials credentials to use for login.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	public SimpleJackrabbitSessionFactory(JackrabbitRepository repository, Credentials credentials) throws RepositoryException {
+		this.repository = repository;
+		this.credentials = credentials;
+		
+		if (log.isDebugEnabled()) {
+			log.debug("Registering namespaces and node types");
+		}
+
+		Session session = null;
+			
+		try {
+        	session = getSession();
+        	initNamespace(session);
+	        initNodetypes(session);
+        	Node root = session.getRootNode();
+
+        	if (!root.hasNode(WFS_ROOT_NODE_NAME)) {
+        		if (log.isDebugEnabled()) {
+        			log.debug("Creating workflow node");
+        		}
+        		
+        		root.addNode(WFS_ROOT_NODE_NAME, JCRWorkflowStore.ROOT_NT);
+        	}
+        	else if (log.isDebugEnabled()) {
+    			log.debug("Existing workflow node. Skipping creation");
+    		}
+
+    		session.save();
+		}
+		catch (InvalidNodeTypeDefException e) {
+        	throw new RepositoryException("Invalid definition of osworkflow nodetypes", e);
+		}
+		catch (IOException e) {
+        	throw new RepositoryException("Unable to read custom osworkflow nodetypes", e);
+		}
+		finally {
+			if (session != null) {
+				session.logout();
+			}
+		}
+	}
+
+	// ~ Methods
+	
+	/**
+	 * Registrer osworkflow namespace.
+	 * @param session session to the repository.
+	 * @throws RepositoryException if an error occurs.
+	 */
+	protected void initNamespace(Session session) throws RepositoryException
+    {
+        NamespaceRegistry registry =  session.getWorkspace().getNamespaceRegistry();
+        Collection prefixes = Arrays.asList(registry.getPrefixes());
+
+        if (!prefixes.contains(JCRWorkflowStore.NAMESPACE_PREFIX)) {
+            registry.registerNamespace(JCRWorkflowStore.NAMESPACE_PREFIX, JCRWorkflowStore.NAMESPACE);
+        }
+    }
+    
+	/**
+	 * Registrer osworkflow nodetypes.
+	 * @param session session to the repository.
+	 * @throws RepositoryException if an error occurs.
+	 * @throws InvalidNodeTypeDefException if the node types definition are invalid.
+	 * @throws IOException if an error occurs while reading the definitions file. 
+	 */
+	protected void initNodetypes(Session session) throws RepositoryException, InvalidNodeTypeDefException, IOException
+    {
+        NodeTypeManagerImpl ntManager = (NodeTypeManagerImpl) session.getWorkspace().getNodeTypeManager();
+        NodeTypeRegistry registry = ntManager.getNodeTypeRegistry();
+
+        String rootNodetypeName = JCRWorkflowStore.ROOT_NT.substring(JCRWorkflowStore.NAMESPACE_PREFIX.length() + 1);
+        if (!registry.isRegistered(new QName(JCRWorkflowStore.NAMESPACE, rootNodetypeName))) {
+            NodeTypeDefStore store = new NodeTypeDefStore();
+            InputStream is = null;
+            
+            try {
+                // Use embedded node types definition
+            	is = getClass().getResourceAsStream(CUSTOM_NODE_TYPES_FILENAME);
+                store.load(is);
+                registry.registerNodeTypes(store.all());
+            }
+            finally {
+            	if (is != null) {
+            		is.close();
+            	}
+            }
+        }
+    }
+
+	public String getWorkflowStoreNodePath() throws RepositoryException {
+		return getSession().getRootNode().getNode(WFS_ROOT_NODE_NAME).getPath();
+	}
+
+	public Session getSession() throws RepositoryException {
+		if (log.isDebugEnabled()) {
+			log.debug("Creating new session");
+		}
+		
+		return repository.login(credentials);
+	}
+}
+
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/oswf_nodetypes.xml osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/oswf_nodetypes.xml
--- osworkflow-20060124/src/java/com/opensymphony/workflow/spi/jcr/oswf_nodetypes.xml	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/java/com/opensymphony/workflow/spi/jcr/oswf_nodetypes.xml	2006-12-22 18:33:20.000000000 +0100
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE nodeTypes [
+  <!ELEMENT nodeTypes (nodeType)*>
+    <!ELEMENT nodeType (supertypes?|propertyDefinition*|childNodeDefinition*)>
+
+    <!ATTLIST nodeType
+      name CDATA #REQUIRED
+      isMixin (true|false) #REQUIRED
+      hasOrderableChildNodes (true|false) #REQUIRED
+      primaryItemName CDATA #REQUIRED
+    >
+    <!ELEMENT supertypes (supertype+)>
+    <!ELEMENT supertype (#PCDATA)>
+
+    <!ELEMENT propertyDefinition (valueConstraints?|defaultValues?)>
+    <!ATTLIST propertyDefinition
+      name CDATA #REQUIRED
+      requiredType (String|Date|Path|Name|Reference|Binary|Double|Long|Boolean|undefined) #REQUIRED
+      autoCreated (true|false) #REQUIRED
+      mandatory (true|false) #REQUIRED
+      onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED
+      protected (true|false) #REQUIRED
+      multiple  (true|false) #REQUIRED
+    >
+    <!ELEMENT valueConstraints (valueConstraint+)>
+    <!ELEMENT valueConstraint (#PCDATA)>
+    <!ELEMENT defaultValues (defaultValue+)>
+    <!ELEMENT defaultValue (#PCDATA)>
+
+    <!ELEMENT childNodeDefinition (requiredPrimaryTypes)>
+    <!ATTLIST childNodeDefinition
+      name CDATA #REQUIRED
+      defaultPrimaryType  CDATA #REQUIRED
+      autoCreated (true|false) #REQUIRED
+      mandatory (true|false) #REQUIRED
+      onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED
+      protected (true|false) #REQUIRED
+      sameNameSiblings (true|false) #REQUIRED
+    >
+    <!ELEMENT requiredPrimaryTypes (requiredPrimaryType+)>
+    <!ELEMENT requiredPrimaryType (#PCDATA)>
+]>
+<nodeTypes xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    xmlns:oswf="http://www.opensymphony.com/osworkflow/1.0"
+    xmlns:mix="http://www.jcp.org/jcr/mix/1.0">
+
+    <nodeType name="oswf:root" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
+        <supertypes>
+            <supertype>nt:base</supertype>
+        </supertypes>
+        <propertyDefinition name="oswf:nextEntryId" requiredType="Long" autoCreated="true" mandatory="true" multiple="false" onParentVersion="COPY" protected="false">
+            <defaultValues>
+                <defaultValue>1</defaultValue>
+            </defaultValues>
+        </propertyDefinition>
+        <childNodeDefinition name="oswf:entry" defaultPrimaryType="oswf:entry" sameNameSiblings="true" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false">
+            <requiredPrimaryTypes>
+                <requiredPrimaryType>oswf:entry</requiredPrimaryType>
+            </requiredPrimaryTypes>
+        </childNodeDefinition>
+    </nodeType>
+
+    <nodeType name="oswf:entry" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
+        <supertypes>
+            <supertype>nt:base</supertype>
+            <supertype>mix:referenceable</supertype>
+        </supertypes>
+        <propertyDefinition name="oswf:id" requiredType="Long" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:workflowName" requiredType="String" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:state" requiredType="Long" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:nextStepId" requiredType="Long" autoCreated="true" mandatory="true" multiple="false" onParentVersion="COPY" protected="false">
+            <defaultValues>
+                <defaultValue>1</defaultValue>
+            </defaultValues>
+        </propertyDefinition>
+        <childNodeDefinition name="oswf:currentStep" defaultPrimaryType="oswf:step" sameNameSiblings="true" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false">
+            <requiredPrimaryTypes>
+                <requiredPrimaryType>oswf:step</requiredPrimaryType>
+            </requiredPrimaryTypes>
+        </childNodeDefinition>
+        <childNodeDefinition name="oswf:historyStep" defaultPrimaryType="oswf:step" sameNameSiblings="true" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false">
+            <requiredPrimaryTypes>
+                <requiredPrimaryType>oswf:step</requiredPrimaryType>
+            </requiredPrimaryTypes>
+        </childNodeDefinition>
+        <childNodeDefinition name="oswf:propertySet" defaultPrimaryType="oswf:propertyset" sameNameSiblings="false" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false">
+            <requiredPrimaryTypes>
+                <requiredPrimaryType>oswf:propertyset</requiredPrimaryType>
+            </requiredPrimaryTypes>
+        </childNodeDefinition>
+    </nodeType>
+
+    <nodeType name="oswf:step" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
+        <supertypes>
+            <supertype>nt:base</supertype>
+            <supertype>mix:referenceable</supertype>
+        </supertypes>
+        <propertyDefinition name="oswf:id" requiredType="Long" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:stepId" requiredType="Long" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:actionId" requiredType="Long" autoCreated="false" mandatory="false" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:owner" requiredType="String" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:startDate" requiredType="Date" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:dueDate" requiredType="Date" autoCreated="false" mandatory="false" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:finishDate" requiredType="Date" autoCreated="false" mandatory="false" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:status" requiredType="String" autoCreated="false" mandatory="true" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:caller" requiredType="String" autoCreated="false" mandatory="false" multiple="false" onParentVersion="COPY" protected="false"/>
+        <propertyDefinition name="oswf:previousSteps" requiredType="Reference" autoCreated="false" mandatory="false" multiple="true" onParentVersion="COPY" protected="false">
+            <valueConstraints>
+                <valueConstraint>oswf:step</valueConstraint>
+            </valueConstraints>
+        </propertyDefinition>
+    </nodeType>
+
+    <nodeType name="oswf:propertyset" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
+        <supertypes>
+            <supertype>nt:base</supertype>
+        </supertypes>
+        <propertyDefinition name="*" requiredType="undefined" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false" multiple="false"/>
+    </nodeType>
+</nodeTypes>
\ No newline at end of file
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/test/com/opensymphony/workflow/spi/jcr/JCRFunctionalWorkflowTestCase.java osworkflow-20060124-jcr/src/test/com/opensymphony/workflow/spi/jcr/JCRFunctionalWorkflowTestCase.java
--- osworkflow-20060124/src/test/com/opensymphony/workflow/spi/jcr/JCRFunctionalWorkflowTestCase.java	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/test/com/opensymphony/workflow/spi/jcr/JCRFunctionalWorkflowTestCase.java	2007-01-25 12:46:31.000000000 +0100
@@ -0,0 +1,123 @@
+package com.opensymphony.workflow.spi.jcr;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.jcr.Repository;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.core.TransientRepository;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.fs.local.FileUtil;
+
+import com.opensymphony.workflow.config.Configuration;
+import com.opensymphony.workflow.config.DefaultConfiguration;
+import com.opensymphony.workflow.spi.AbstractFunctionalWorkflowTest;
+import com.opensymphony.workflow.spi.Step;
+import com.opensymphony.workflow.util.PropertySetDelegateImpl;
+
+
+/**
+ * This test case is functional in that it attempts to validate the entire
+ * lifecycle of a workflow using JCRWorkflowStore and Jackrabbit.
+ *
+ * @see com.opensymphony.workflow.spi.jcr.JCRWorkflowStore
+ * @see com.opensymphony.workflow.spi.jcr.SimpleJackrabbitSessionFactory
+ * @author <a href="mailto:sebastien.launay.no.spam@anyware-tech.com">Sébastien Launay</a>
+ */
+public class JCRFunctionalWorkflowTestCase extends AbstractFunctionalWorkflowTest {
+    //~ Static fields ///////////////////////////////////////////////////////////
+    private static final String REP_DIR = System.getProperty("java.io.tmpdir") + File.separator + "repository";
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+    // Keep repository in order to shut it down
+    private JackrabbitRepository repository;
+    
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public JCRFunctionalWorkflowTestCase(String s) {
+        super(s);
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Use temporary directory for storing the repository
+        File repDir = new File(REP_DIR);
+        
+        if (repDir.exists()) {
+        	FileUtil.delete(new File(REP_DIR));
+        }
+        
+        Configuration config = new DefaultConfiguration();
+        config.load(getClass().getResource("/osworkflow-jcr.xml"));
+        
+        InputStream is = null;
+        
+        // Use XML file system persistence manager for workspaces
+        // and object persistence manager for versions 
+        try {
+        	is = getClass().getResourceAsStream("/jackrabbit/repository.xml");
+            repository = new TransientRepository(RepositoryConfig.create(is, REP_DIR));
+        	JCRSessionFactory sessionFactory = new SimpleJackrabbitSessionFactory(repository);
+            
+            config.getPersistenceArgs().put(JCRWorkflowStore.SESSION_FACTORY_KEY, sessionFactory);
+            config.getPersistenceArgs().put(JCRWorkflowStore.PS_DELEGATE_KEY, new PropertySetDelegateImpl());
+            workflow.setConfiguration(config);
+        }
+        finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+    
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        
+        // Shutdown repository
+        repository.shutdown();
+    }
+    
+    /**
+     * Test workflow's history.
+     */
+    public void testHistory() throws Exception {
+        String workflowName = getWorkflowName();
+        assertTrue("canInitialize for workflow " + workflowName + " is false", workflow.canInitialize(workflowName, 100));
+
+        // Action 100
+        long workflowId = workflow.initialize(workflowName, 100, new HashMap());
+        
+        List currentSteps = workflow.getCurrentSteps(workflowId);
+        assertEquals("Unexpected number of current steps", 1, currentSteps.size());
+        
+        Step step = (Step) currentSteps.get(0);
+        assertNotNull(step.getPreviousStepIds());
+        // First step, then no history
+        assertEquals(0, step.getPreviousStepIds().length);
+        
+        // Action 1, result in a split
+        workflow.doAction(workflowId, 1, Collections.EMPTY_MAP);
+        
+        currentSteps = workflow.getCurrentSteps(workflowId);
+        assertEquals("Unexpected number of history steps", 2, currentSteps.size());
+        
+        // Previours steps of the first current step
+        long[] previousSteps = ((Step) currentSteps.get(0)).getPreviousStepIds();
+        assertNotNull(previousSteps);
+        assertEquals(1, previousSteps.length);
+        assertEquals(step.getId(), previousSteps[0]);
+        
+        // Previours steps of the second current step
+        previousSteps = ((Step) currentSteps.get(1)).getPreviousStepIds();
+        assertNotNull(previousSteps);
+        assertEquals(1, previousSteps.length);
+        assertEquals(step.getId(), previousSteps[0]);
+	}
+}
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/test/jackrabbit/repository.xml osworkflow-20060124-jcr/src/test/jackrabbit/repository.xml
--- osworkflow-20060124/src/test/jackrabbit/repository.xml	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/test/jackrabbit/repository.xml	2007-01-25 12:47:05.000000000 +0100
@@ -0,0 +1,226 @@
+<?xml version="1.0"?>
+<!DOCTYPE Repository [
+    <!--
+        the Repository element configures a repository instance;
+        individual workspaces of the repository are configured through
+        separate configuration files called workspace.xml which are
+        located in a subfolder of the workspaces root directory
+        (see Workspaces element).
+
+        it consists of
+
+            a FileSystem element (the virtual file system
+            used by the repository to persist global state such as
+            registered namespaces, custom node types, etc..
+
+            a Security element that specifies the name of the app-entry
+            in the JAAS config and the access manager
+
+            a Workspaces element that specifies to the location of
+            workspaces root directory and the name of default workspace
+
+            a Workspace element that is used as a workspace configuration
+            template; it is used to create the initial workspace if there's
+            no workspace yet and for creating additional workspaces through
+            the api
+
+            a Versioning element that is used for configuring
+            versioning-related settings
+
+            a SearchIndex element that is used for configuring Indexing-related
+            settings on the /jcr:system tree.
+    -->
+    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace,Versioning,SearchIndex?)>
+
+    <!--
+        a virtual file system
+    -->
+    <!ELEMENT FileSystem (param*)>
+    <!ATTLIST FileSystem
+      class CDATA #REQUIRED>
+
+    <!--
+        the Security element specifies the name (appName attribute)
+        of the JAAS configuration app-entry for this repository. 
+
+        it also specifies the access manager to be used (AccessManager element).
+    -->
+    <!ELEMENT Security (AccessManager, LoginModule?)>
+    <!ATTLIST Security
+      appName CDATA #REQUIRED>
+
+    <!--
+        the AccessManager element configures the access manager to be used by
+        this repository instance; the class attribute specifies the FQN of the
+        class implementing the AccessManager interface
+    -->
+    <!ELEMENT AccessManager (param*)>
+    <!ATTLIST AccessManager
+      class CDATA #REQUIRED>
+
+    <!--
+        generic parameter (name/value pair)
+    -->
+    <!ELEMENT param EMPTY>
+    <!ATTLIST param
+      name CDATA #REQUIRED
+      value CDATA #REQUIRED>
+
+     <!--
+        the LoginModule element optionally specifies a JAAS login module to
+        authenticate users. This feature allows the use of Jackrabbit in a
+        non-JAAS environment.
+    -->
+    <!ELEMENT LoginModule (param*)>
+    <!ATTLIST LoginModule
+      class CDATA #REQUIRED>
+
+   <!--
+        the Workspaces element specifies the workspaces root directory
+        (rootPath attribute) and the name of the default workspace
+        (defaultWorkspace attribute).
+
+        individual workspaces are configured through individual workspace.xml
+        files located in a subfolder each of the workspaces root directory.
+    -->
+    <!ELEMENT Workspaces EMPTY>
+    <!ATTLIST Workspaces
+      rootPath CDATA #REQUIRED
+      defaultWorkspace CDATA #REQUIRED>
+
+    <!--
+        the Workspace element serves as a workspace configuration template;
+        it is used to create the initial workspace if there's no workspace yet
+        and for creating additional workspaces through the api
+    -->
+    <!ELEMENT Workspace (FileSystem,PersistenceManager,SearchIndex?)>
+    <!ATTLIST Workspace
+      name CDATA #REQUIRED>
+
+    <!--
+        the PersistenceManager element configures the persistence manager
+        to be used for the workspace; the class attribute specifies the
+        FQN of the class implementing the PersistenceManager interface
+    -->
+    <!ELEMENT PersistenceManager (param*)>
+    <!ATTLIST PersistenceManager
+      class CDATA #REQUIRED>
+
+    <!--
+        the SearchIndex element specifies the locaction of the search index
+        (used by the QueryHandler); the class attribute specifies the
+        FQN of the class implementing the QueryHandler interface.
+    -->
+    <!ELEMENT SearchIndex (param*,FileSystem?)>
+    <!ATTLIST SearchIndex
+      class CDATA #REQUIRED>
+
+    <!--
+        the Versioning element configures the persistence manager
+        to be used for persisting version state
+    -->
+    <!ELEMENT Versioning (FileSystem, PersistenceManager)>
+    <!ATTLIST Versioning
+      rootPath CDATA #REQUIRED
+    >
+]>
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule">
+           <!-- anonymous user name ('anonymous' is the default value) -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              default user name to be used instead of the anonymous user
+              when no login credentials are provided (unset by default)
+           -->
+           <!-- <param name="defaultUserId" value="superuser"/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.state.xml.XMLPersistenceManager"/>
+        <!--PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager-->
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.state.obj.ObjectPersistenceManager"/>
+        <!--PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager-->
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+</Repository>
diff -x .classpath -x 'build**' -x 'lib**' -x '.settings**' -ruN osworkflow-20060124/src/test/osworkflow-jcr.xml osworkflow-20060124-jcr/src/test/osworkflow-jcr.xml
--- osworkflow-20060124/src/test/osworkflow-jcr.xml	1970-01-01 01:00:00.000000000 +0100
+++ osworkflow-20060124-jcr/src/test/osworkflow-jcr.xml	2006-12-21 13:48:56.000000000 +0100
@@ -0,0 +1,6 @@
+<osworkflow>
+  <persistence class="com.opensymphony.workflow.spi.jcr.JCRWorkflowStore"/>
+  <factory class="com.opensymphony.workflow.loader.URLWorkflowFactory">
+    <property key="cache" value="true" />
+  </factory>
+</osworkflow>
