You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ck...@apache.org on 2012/11/14 13:21:05 UTC

svn commit: r1409177 - in /jackrabbit: site/trunk/content/dtd/ trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/ trunk/jackrabbit-core/src/main/java/org/apache/ja...

Author: ckoell
Date: Wed Nov 14 12:21:03 2012
New Revision: 1409177

URL: http://svn.apache.org/viewvc?rev=1409177&view=rev
Log:
JCR-3389 Implement a MultiDataStore

Added:
    jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd   (with props)
    jackrabbit/site/trunk/content/dtd/repository-2.6.dtd   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java

Added: jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd
URL: http://svn.apache.org/viewvc/jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd?rev=1409177&view=auto
==============================================================================
--- jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd (added)
+++ jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd Wed Nov 14 12:21:03 2012
@@ -0,0 +1,254 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % jackrabbit-repository-elements
+         "DataSources|Cluster|FileSystem|DataStore|Security|Workspaces|Workspace|Versioning|SearchIndex|RepositoryLockMechanism">
+
+<!--
+    The DataSources element configures the data sources of the repository.
+-->
+<!ELEMENT DataSources (DataSource*)>
+<!ELEMENT DataSource (param*)>
+<!ATTLIST DataSource name CDATA #REQUIRED>
+
+<!--
+    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 various security related managers to be used.
+-->
+<!ELEMENT Security (SecurityManager?, AccessManager?, LoginModule?)>
+<!ATTLIST Security appName CDATA #REQUIRED>
+
+<!--
+    the SecurityManager element configures the general security manager to be
+    used by this repository instance; the class attribute specifies the FQN of the
+    class implementing the JackrabbitSecurityManager interface
+-->
+<!ELEMENT SecurityManager (WorkspaceAccessManager?,UserManager?,UserIdClass?, param*)>
+<!ATTLIST SecurityManager class CDATA #REQUIRED
+                          workspaceName CDATA #IMPLIED>
+
+<!--
+    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)
+    this element can also have custom objects 
+-->
+<!ELEMENT param (param*)>
+<!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 WorkspaceAccessManager element optionally configures the manager
+    to be used by this repository instance to determine if access to a specific
+    workspace is granted for a specific subject;
+    the class attribute specifies the FQN of the class implementing the
+    WorkspaceAccessManager interface
+-->
+<!ELEMENT WorkspaceAccessManager EMPTY>
+<!ATTLIST WorkspaceAccessManager class CDATA #REQUIRED>
+
+<!--
+    the Workspaces element specifies the physical workspaces root directory
+    (rootPath attribute), the name of the default workspace (defaultWorkspace 
+    attribute), the (optional) maximum amount of time in seconds before an idle 
+    workspace is automatically shutdown (maxIdleTime attribute) and the 
+    (optional) workspace configuration root directory within the virtual 
+    repository file system (configRootPath attribute).
+
+    individual workspaces are configured through individual workspace.xml files 
+    located in a subfolder each of either
+
+    a) the physical workspaces root directory
+
+    or, if configRootPath had been specified,
+
+    b) the configuration root directory within the virtual repository file 
+    system.
+-->
+<!ELEMENT Workspaces EMPTY>
+<!ATTLIST Workspaces rootPath         CDATA #REQUIRED
+                     defaultWorkspace CDATA #REQUIRED
+                     defaultLockTimeout CDATA #IMPLIED
+                     configRootPath   CDATA #IMPLIED
+                     maxIdleTime      CDATA #IMPLIED>
+
+<!--
+    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?,ISMLocking?,WorkspaceSecurity?,Import?)>
+<!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 WorkspaceSecurity element specifies the workspace specific security
+    configuration.
+-->
+<!ELEMENT WorkspaceSecurity (AccessControlProvider?)>
+
+<!--
+    the AccessControlProvider element defines a class attribute specifying the
+    FQN of the class implementing the AccessControlProvider interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT AccessControlProvider (param*)>
+<!ATTLIST AccessControlProvider class CDATA #REQUIRED>
+
+<!--
+    the Versioning element configures the persistence manager
+    to be used for persisting version state
+-->
+<!ELEMENT Versioning (FileSystem, PersistenceManager, ISMLocking?)>
+<!ATTLIST Versioning rootPath CDATA #REQUIRED>
+
+<!--
+    the Cluster element configures the optional participation of this
+    repository in a clustered environment. a literal id may be
+    specified that uniquely identifies this node in a cluster, as well
+    as the delay in milliseconds before changes to the journal are
+    automatically detected.
+-->
+<!ELEMENT Cluster (Journal)>
+<!ATTLIST Cluster id        CDATA #IMPLIED
+                  syncDelay CDATA #IMPLIED>
+
+<!--
+    the Journal element configures the journal used in clustering; the
+    class attribute specifies the FQN of the class implementing the
+    Journal interface.
+-->
+<!ELEMENT Journal (param*)>
+<!ATTLIST Journal class CDATA #REQUIRED>
+
+<!--
+    the ISMLocking element configures the locking implementation
+    to be used for the workspace and version storage; the class
+    attribute specifies the FQN of the class implementing the
+    ISMLocking interface.
+-->
+<!ELEMENT ISMLocking (param*)>
+<!ATTLIST ISMLocking class CDATA #REQUIRED>
+
+<!--
+    the RepositoryLockMechanism element configures the mechanism
+    that is used to ensure only one process writes to the 
+    backend (file system or database) at any time; the class
+    attribute specifies the FQN of the class implementing the
+    RepositoryLockMechanism interface.
+-->
+<!ELEMENT RepositoryLockMechanism (param*)>
+<!ATTLIST RepositoryLockMechanism class CDATA #REQUIRED>
+
+<!--
+    the DataStore element configures the data store
+    to be used for the workspace; the class attribute specifies the
+    FQN of the class implementing the DataStore interface
+-->
+<!ELEMENT DataStore (param*)>
+<!ATTLIST DataStore class CDATA #REQUIRED>
+
+<!--
+    The Import element configures how protected items are imported into a
+    workspace.
+-->
+<!ELEMENT Import (ProtectedNodeImporter|ProtectedPropertyImporter)*>
+
+<!--
+    The ProtectedNodeImporter element configures an importer for protected
+    nodes. The class attribute specifies the FQN of the class implementing the
+    ProtectedNodeImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedNodeImporter (param*)>
+<!ATTLIST ProtectedNodeImporter class CDATA #REQUIRED>
+
+<!--
+    The ProtectedPropertyImporter element configures an importer for protected
+    properties. The class attribute specifies the FQN of the class implementing
+    the ProtectedPropertyImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedPropertyImporter (param*)>
+<!ATTLIST ProtectedPropertyImporter class CDATA #REQUIRED>
+
+<!--
+    The UserManager element configures the user manager implementation that is
+    used in Jackrabbit. The class attribute specifies the FQN of the class
+    implementing the UserManager interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT UserManager (param*,AuthorizableAction*)>
+<!ATTLIST UserManager class CDATA #REQUIRED>
+
+<!--
+   The optional AuthorizableAction element(s) configure additional custom
+   actions to be executed upon authorizable creation and removal. The 'class'
+   attribute specifies the FQN of a class implementing AuthorizableAction interface.
+   The parameter(s) define the implementation specific configuration.
+-->
+<!ELEMENT AuthorizableAction (param*)>
+<!ATTLIST AuthorizableAction class CDATA #REQUIRED>
+
+
+<!--
+    The UserIdClass element specifies the class of principals used to retrieve
+    the userID out of a Subject. The class attribute specifies the FQN of a
+    class implementing the java.security.Principal interface.
+-->
+<!ELEMENT UserIdClass EMPTY>
+<!ATTLIST UserIdClass class CDATA #REQUIRED>

Propchange: jackrabbit/site/trunk/content/dtd/repository-2.6-elements.dtd
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/site/trunk/content/dtd/repository-2.6.dtd
URL: http://svn.apache.org/viewvc/jackrabbit/site/trunk/content/dtd/repository-2.6.dtd?rev=1409177&view=auto
==============================================================================
--- jackrabbit/site/trunk/content/dtd/repository-2.6.dtd (added)
+++ jackrabbit/site/trunk/content/dtd/repository-2.6.dtd Wed Nov 14 12:21:03 2012
@@ -0,0 +1,64 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % repository-elements
+         PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.4 Elements//EN"
+         "http://jackrabbit.apache.org/dtd/repository-2.4-elements.dtd">
+%repository-elements;
+
+<!--
+    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
+
+      - an optional Cluster element that is used for configuring a
+        clustering node that synchronizes changes made in a cluster
+       
+      - a FileSystem element (the virtual file system
+        used by the repository to persist global state such as
+        registered namespaces, custom node types, etc.
+        
+      - an optional DataStore element to configure the component
+        to use for storing large binary objects
+
+      - a Security element that specifies the name of the app-entry
+        in the JAAS config and the access manager
+
+      - a Workspaces element that specifies the location of the 
+        workspaces root directory, the name of the default workspace,
+        the maximum idle time before a workspace is automatically
+        shutdown (optional) and the workspace configuration root directory
+        within the virtual repository file system (optional)
+
+      - 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
+
+      - an optional SearchIndex element that is used for configuring Indexing-related
+        settings on the /jcr:system tree.
+
+-->
+<!ELEMENT Repository (%jackrabbit-repository-elements;)*> 

Propchange: jackrabbit/site/trunk/content/dtd/repository-2.6.dtd
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java?rev=1409177&r1=1409176&r2=1409177&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java Wed Nov 14 12:21:03 2012
@@ -20,6 +20,8 @@ import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.core.cluster.ClusterNode;
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.data.DataStoreFactory;
+import org.apache.jackrabbit.core.data.MultiDataStore;
+import org.apache.jackrabbit.core.data.MultiDataStoreAware;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.apache.jackrabbit.core.fs.FileSystemFactory;
@@ -38,9 +40,15 @@ import org.apache.jackrabbit.core.util.R
 import org.apache.jackrabbit.core.util.RepositoryLockMechanismFactory;
 import org.apache.jackrabbit.core.util.db.ConnectionFactory;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
+import org.w3c.dom.TypeInfo;
+import org.w3c.dom.UserDataHandler;
 import org.xml.sax.InputSource;
 
 import java.io.File;
@@ -180,6 +188,12 @@ public class RepositoryConfigurationPars
     /** Name of the clustered configuration attribute. */
     public static final String CLUSTERED_ATTRIBUTE = "clustered";
 
+    /** Name of the primary DataStore class attribute. */
+    public static final String PRIMARY_DATASTORE_ATTRIBUTE = "primary";
+
+    /** Name of the archive DataStore class attribute. */
+    public static final String ARCHIVE_DATASTORE_ATTRIBUTE = "archive";
+
     /** Default synchronization delay, in milliseconds. */
     public static final String DEFAULT_SYNC_DELAY = "5000";
 
@@ -973,6 +987,19 @@ public class RepositoryConfigurationPars
      *     ...
      *   &lt;/DataStore&gt;
      * </pre>
+     * Its also possible to configure a multi data store. The configuration uses following format:
+     * <pre>
+     *   &lt;DataStore class="org.apache.jackrabbit.core.data.MultiDataStore"&gt;
+     *     &lt;param name="primary" value="org.apache.jackrabbit.core.data.db.XXDataStore"&gt;
+     *         &lt;param name="..." value="..."&gt;
+     *         ...
+     *     &lt;/param&gt;
+     *     &lt;param name="archive" value="org.apache.jackrabbit.core.data.db.XXDataStore"&gt;
+     *         &lt;param name="..." value="..."&gt;
+     *         ...
+     *     &lt;/param&gt;
+     *   &lt;/DataStore&gt;
+     * </pre>
      * <p/>
      * <code>DataStore</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
      * element.
@@ -992,9 +1019,50 @@ public class RepositoryConfigurationPars
                     Node child = children.item(i);
                     if (child.getNodeType() == Node.ELEMENT_NODE
                             && DATA_STORE_ELEMENT.equals(child.getNodeName())) {
-                        BeanConfig bc =
-                            parseBeanConfig(parent, DATA_STORE_ELEMENT);
+                        BeanConfig bc = 
+                        		parseBeanConfig(parent, DATA_STORE_ELEMENT);
+                        bc.setValidate(false);
                         DataStore store = bc.newInstance(DataStore.class);
+                        if (store instanceof MultiDataStore) {
+                        	DataStore primary = null;
+                        	DataStore archive = null;
+                        	NodeList subParamNodes = child.getChildNodes();
+                            for (int x = 0; x < subParamNodes.getLength(); x++) {
+                                Node paramNode = subParamNodes.item(x);
+                                if (paramNode.getNodeType() == Node.ELEMENT_NODE 
+                                		&& (PRIMARY_DATASTORE_ATTRIBUTE.equals(paramNode.getAttributes().getNamedItem("name").getNodeValue())
+                                		|| ARCHIVE_DATASTORE_ATTRIBUTE.equals(paramNode.getAttributes().getNamedItem("name").getNodeValue()))) {
+                                    final ElementImpl datastoreElement = new ElementImpl(DATA_STORE_ELEMENT, Node.ELEMENT_NODE, paramNode.getAttributes(), paramNode.getChildNodes());
+                                	ElementImpl parent = new ElementImpl("parent", Node.ELEMENT_NODE, null, new NodeList() {
+										
+										@Override
+										public Node item(int index) {
+											return datastoreElement;
+										}
+										
+										@Override
+										public int getLength() {
+											return 1;
+										}
+									});
+                                	DataStore subDataStore = getDataStoreFactory(parent, directory).getDataStore();
+                                	if (!MultiDataStoreAware.class.isAssignableFrom(subDataStore.getClass())) {
+                                		throw new ConfigurationException("Only MultiDataStoreAware datastore's can be used within a MultiDataStore.");
+                                	}
+                                	String type = getAttribute((Element) paramNode, NAME_ATTRIBUTE);
+                                	if (PRIMARY_DATASTORE_ATTRIBUTE.equals(type)) {
+                                        primary = subDataStore;
+                                	} else if (ARCHIVE_DATASTORE_ATTRIBUTE.equals(type)) {
+                                        archive = subDataStore;
+                                	}
+                                }
+                            }
+                            if (primary == null || archive == null) {
+                        		throw new ConfigurationException("A MultiDataStore must have configured a primary and archive datastore");
+                            }
+                        	((MultiDataStore) store).setPrimaryDataStore(primary);
+                        	((MultiDataStore) store).setArchiveDataStore(archive);
+                        }
                         store.init(directory);
                         return store;
                     }
@@ -1100,4 +1168,305 @@ public class RepositoryConfigurationPars
     public void setConfigVisitor(BeanConfigVisitor configVisitor) {
         this.configVisitor = configVisitor;
     }
+    
+    private class ElementImpl implements org.w3c.dom.Element {
+
+    	private String nodeName;
+    	private short nodeType;
+    	private NodeList childNodes;
+    	private NamedNodeMap params;
+    	
+    	public ElementImpl(String nodeName, short nodeType, NamedNodeMap params, NodeList nodeList) {
+    		this.nodeName = nodeName;
+    		this.nodeType = nodeType;
+    		this.childNodes = nodeList;
+    		this.params = params;
+    	}
+    	
+		@Override
+		public Node appendChild(Node newChild) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Node cloneNode(boolean deep) {
+			return null;
+		}
+
+		@Override
+		public short compareDocumentPosition(Node other) throws DOMException {
+			return 0;
+		}
+
+		@Override
+		public NamedNodeMap getAttributes() {
+			return null;
+		}
+
+		@Override
+		public String getBaseURI() {
+			return null;
+		}
+
+		@Override
+		public NodeList getChildNodes() {
+			return childNodes;
+		}
+
+		@Override
+		public Object getFeature(String feature, String version) {
+			return null;
+		}
+
+		@Override
+		public Node getFirstChild() {
+			return null;
+		}
+
+		@Override
+		public Node getLastChild() {
+			return null;
+		}
+
+		@Override
+		public String getLocalName() {
+			return null;
+		}
+
+		@Override
+		public String getNamespaceURI() {
+			return null;
+		}
+
+		@Override
+		public Node getNextSibling() {
+			return null;
+		}
+
+		@Override
+		public String getNodeName() {
+			return nodeName;
+		}
+
+		@Override
+		public short getNodeType() {
+			return nodeType;
+		}
+
+		@Override
+		public String getNodeValue() throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Document getOwnerDocument() {
+			return null;
+		}
+
+		@Override
+		public Node getParentNode() {
+			return null;
+		}
+
+		@Override
+		public String getPrefix() {
+			return null;
+		}
+
+		@Override
+		public Node getPreviousSibling() {
+			return null;
+		}
+
+		@Override
+		public String getTextContent() throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Object getUserData(String key) {
+			return null;
+		}
+
+		@Override
+		public boolean hasAttributes() {
+			return false;
+		}
+
+		@Override
+		public boolean hasChildNodes() {
+			return false;
+		}
+
+		@Override
+		public Node insertBefore(Node newChild, Node refChild)
+				throws DOMException {
+			return null;
+		}
+
+		@Override
+		public boolean isDefaultNamespace(String namespaceURI) {
+			return false;
+		}
+
+		@Override
+		public boolean isEqualNode(Node arg) {
+			return false;
+		}
+
+		@Override
+		public boolean isSameNode(Node other) {
+			return false;
+		}
+
+		@Override
+		public boolean isSupported(String feature, String version) {
+			return false;
+		}
+
+		@Override
+		public String lookupNamespaceURI(String prefix) {
+			return null;
+		}
+
+		@Override
+		public String lookupPrefix(String namespaceURI) {
+			return null;
+		}
+
+		@Override
+		public void normalize() {
+		}
+
+		@Override
+		public Node removeChild(Node oldChild) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Node replaceChild(Node newChild, Node oldChild)
+				throws DOMException {
+			return null;
+		}
+
+		@Override
+		public void setNodeValue(String nodeValue) throws DOMException {
+		}
+
+		@Override
+		public void setPrefix(String prefix) throws DOMException {
+		}
+
+		@Override
+		public void setTextContent(String textContent) throws DOMException {
+		}
+
+		@Override
+		public Object setUserData(String key, Object data,
+				UserDataHandler handler) {
+			return null;
+		}
+
+		@Override
+		public String getAttribute(String name) {
+			return null;
+		}
+
+		@Override
+		public String getAttributeNS(String namespaceURI, String localName)
+				throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Attr getAttributeNode(String name) {
+			return (Attr) params.getNamedItem(VALUE_ATTRIBUTE);
+		}
+
+		@Override
+		public Attr getAttributeNodeNS(String namespaceURI, String localName)
+				throws DOMException {
+			return null;
+		}
+
+		@Override
+		public NodeList getElementsByTagName(String name) {
+			return null;
+		}
+
+		@Override
+		public NodeList getElementsByTagNameNS(String namespaceURI,
+				String localName) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public TypeInfo getSchemaTypeInfo() {
+			return null;
+		}
+
+		@Override
+		public String getTagName() {
+			return null;
+		}
+
+		@Override
+		public boolean hasAttribute(String name) {
+			return false;
+		}
+
+		@Override
+		public boolean hasAttributeNS(String namespaceURI, String localName)
+				throws DOMException {
+			return false;
+		}
+
+		@Override
+		public void removeAttribute(String name) throws DOMException {
+		}
+
+		@Override
+		public void removeAttributeNS(String namespaceURI, String localName)
+				throws DOMException {
+		}
+
+		@Override
+		public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public void setAttribute(String name, String value) throws DOMException {
+		}
+
+		@Override
+		public void setAttributeNS(String namespaceURI, String qualifiedName,
+				String value) throws DOMException {
+		}
+
+		@Override
+		public Attr setAttributeNode(Attr newAttr) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
+			return null;
+		}
+
+		@Override
+		public void setIdAttribute(String name, boolean isId)
+				throws DOMException {
+		}
+
+		@Override
+		public void setIdAttributeNS(String namespaceURI, String localName,
+				boolean isId) throws DOMException {
+		}
+
+		@Override
+		public void setIdAttributeNode(Attr idAttr, boolean isId)
+				throws DOMException {
+		}
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java?rev=1409177&r1=1409176&r2=1409177&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java Wed Nov 14 12:21:03 2012
@@ -59,7 +59,7 @@ import org.slf4j.LoggerFactory;
  * This implementation relies on the underlying file system to support
  * atomic O(1) move operations with {@link File#renameTo(File)}.
  */
-public class FileDataStore implements DataStore {
+public class FileDataStore implements DataStore, MultiDataStoreAware {
 
     /**
      * Logger instance
@@ -292,6 +292,18 @@ public class FileDataStore implements Da
         minModifiedDate = before;
     }
 
+    public void deleteRecord(DataIdentifier identifier)
+			throws DataStoreException {
+        File file = getFile(identifier);
+        synchronized (this) {
+            if (file.exists()) {
+                if (!file.delete()) {
+                    log.warn("Failed to delete file " + file.getAbsolutePath());
+                }
+            }
+        }
+	}
+
     public int deleteAllOlderThan(long min) {
         return deleteOlderRecursive(directory, min);
     }
@@ -455,5 +467,4 @@ public class FileDataStore implements Da
             }
         }
     }
-
 }

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java?rev=1409177&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java Wed Nov 14 12:21:03 2012
@@ -0,0 +1,651 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A MultiDataStore can handle two independent DataStores.
+ * <p>
+ * <b>Attention:</b> You will lost the global single instance mechanism !
+ * </p>
+ * It can be used if you have two storage systems. One for fast access and a
+ * other one like a archive DataStore on a slower storage system. All Files will
+ * be added to the primary DataStore. On read operations first the primary
+ * dataStore will be used and if no Record is found the archive DataStore will
+ * be used. The GarabageCollector will only remove files from the archive DataStore.
+ * <p>
+ * The internal MoveDataTask will be started automatically and could be
+ * configured with the following properties.
+ * <p/>
+ * The Configuration:
+ * 
+ * <pre>
+ * &lt;DataStore class="org.apache.jackrabbit.core.data.MultiDataStore">
+ *     &lt;param name="{@link #setMaxAge(int) maxAge}" value="60"/>
+ *     &lt;param name="{@link #setMoveDataTaskSleep(int) moveDataTaskSleep}" value="604800"/>
+ *     &lt;param name="{@link #setMoveDataTaskFirstRunHourOfDay(int) moveDataTaskFirstRunHourOfDay}" value="1"/>
+ *     &lt;param name="{@link #setSleepBetweenRecords(long) sleepBetweenRecords}" value="100"/>
+ *     &lt;param name="{@link #setDelayedDelete(boolean) delayedDelete}" value="false"/>
+ *     &lt;param name="{@link #setDelayedDeleteSleep(long) delayedDeleteSleep}" value="86400"/>
+ *     &lt;param name="primary" value="org.apache.jackrabbit.core.data.db.DbDataStore">
+ *        &lt;param .../>
+ *     &lt;/param>
+ *     &lt;param name="archive" value="org.apache.jackrabbit.core.data.FileDataStore">
+ *        &lt;param .../>
+ *     &lt;/param>
+ * &lt/DataStore>
+ * </pre>
+ * 
+ * <ul>
+ * <li><code>maxAge</code>: defines how many days the content will reside in the
+ * primary data store. DataRecords that have been added before this time span
+ * will be moved to the archive data store. (default = <code>60</code>)</li>
+ * <li><code>moveDataTaskSleep</code>: specifies the sleep time of the moveDataTaskThread
+ * in seconds. (default = 60 * 60 * 24 * 7, which equals 7 days)</li>
+ * <li><code>moveDataTaskNextRunHourOfDay</code>: specifies the hour at which the
+ * moveDataTaskThread initiates its first run (default = <code>1</code> which means
+ * 01:00 at night)</li>
+ * <li><code>sleepBetweenRecords</code>: specifies the delay in milliseconds
+ * between scanning data records (default = <code>100</code>)</li>
+ * <li><code>delayedDelete</code>: its possible to delay the delete operation
+ * on the primary data store. The DataIdentifiers will be written to a temporary file.
+ * The file will be processed after a defined sleep (see <code>delayedDeleteSleep</code>) 
+ * It's useful if you like to create a snapshot of the primary data store backend
+ * in the meantime before the data will be deleted. (default = <code>false</code>)</li>
+ * <li><code>delayedDeleteSleep</code>: specifies the sleep time of the delayedDeleteTaskThread
+ * in seconds. (default = 60 * 60 * 24, which equals 1 day). This means the delayed delete 
+ * from the primary data store will be processed after one day.</li>
+ * </ul>
+ */
+public class MultiDataStore implements DataStore {
+
+	/**
+	 * Logger instance
+	 */
+	private static Logger log = LoggerFactory.getLogger(MultiDataStore.class);
+
+	private DataStore primaryDataStore;
+	private DataStore archiveDataStore;
+
+	/**
+	 * Max Age in days.
+	 */
+	private int maxAge = 60;
+
+	/**
+	 * ReentrantLock that is used while the MoveDataTask is running.
+	 */
+	private ReentrantLock moveDataTaskLock = new ReentrantLock();
+	private boolean moveDataTaskRunning = false;
+	private Thread moveDataTaskThread;
+
+	/**
+	 * The sleep time in seconds of the MoveDataTask, 7 day default.
+	 */
+	private int moveDataTaskSleep = 60 * 60 * 24 * 7;
+	
+	/**
+	 * Indicates when the next run of the move task is scheduled. The first run is
+	 * scheduled by default at 01:00 hours.
+	 */
+	private Calendar moveDataTaskNextRun = Calendar.getInstance();
+
+	/**
+	 * Its possible to delay the delete operation on the primary data store
+	 * while move task is running. The delete will be executed after defined
+	 * delayDeleteSleep.
+	 */
+	private boolean delayedDelete = false;
+	
+	/**
+	 * The sleep time in seconds to delay remove operation 
+	 * on the primary data store, 1 day default.
+	 */
+	private long delayedDeleteSleep = 60 * 60 * 24;
+    
+    /**
+     * File that holds the data identifiers if delayDelete is enabled.
+     */
+    private FileSystemResource identifiersToDeleteFile = null;
+
+	private Thread deleteDelayedIdentifiersTaskThread;
+
+	/**
+     * Name of the file which holds the identifiers if deleayed delete is enabled
+     */
+    private final String IDENTIFIERS_TO_DELETE_FILE_KEY = "identifiersToDelete";
+    
+
+	/**
+	 * The delay time in milliseconds between scanning data records, 100 default.
+	 */
+	private long sleepBetweenRecords = 100;
+
+	{
+		if (moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY) >= 1) {
+			moveDataTaskNextRun.add(Calendar.DAY_OF_MONTH, 1);
+		}
+		moveDataTaskNextRun.set(Calendar.HOUR_OF_DAY, 1);
+		moveDataTaskNextRun.set(Calendar.MINUTE, 0);
+		moveDataTaskNextRun.set(Calendar.SECOND, 0);
+		moveDataTaskNextRun.set(Calendar.MILLISECOND, 0);
+	}
+	
+	/**
+	 * Setter for the primary dataStore
+	 * 
+	 * @param dataStore
+	 */
+	public void setPrimaryDataStore(DataStore dataStore) {
+		this.primaryDataStore = dataStore;
+	}
+
+	/**
+	 * Setter for the archive dataStore
+	 * 
+	 * @param dataStore
+	 */
+	public void setArchiveDataStore(DataStore dataStore) {
+		this.archiveDataStore = dataStore;
+	}
+
+	/**
+	 * Check if a record for the given identifier exists in the primary data
+	 * store. If not found there it will be returned from the archive data
+	 * store. If no record exists, this method returns null.
+	 * 
+	 * @param identifier
+	 *            data identifier
+	 * @return the record if found, and null if not
+	 */
+	public DataRecord getRecordIfStored(DataIdentifier identifier)
+			throws DataStoreException {
+		if (moveDataTaskRunning) {
+			moveDataTaskLock.lock();
+		}
+		try {
+			DataRecord dataRecord = primaryDataStore
+					.getRecordIfStored(identifier);
+			if (dataRecord == null) {
+				dataRecord = archiveDataStore.getRecordIfStored(identifier);
+			}
+			return dataRecord;
+		} finally {
+			if (moveDataTaskRunning) {
+				moveDataTaskLock.unlock();
+			}
+		}
+	}
+
+	/**
+	 * Returns the identified data record from the primary data store. If not
+	 * found there it will be returned from the archive data store. The given
+	 * identifier should be the identifier of a previously saved data record.
+	 * Since records are never removed, there should never be cases where the
+	 * identified record is not found. Abnormal cases like that are treated as
+	 * errors and handled by throwing an exception.
+	 * 
+	 * @param identifier
+	 *            data identifier
+	 * @return identified data record
+	 * @throws DataStoreException
+	 *             if the data store could not be accessed, or if the given
+	 *             identifier is invalid
+	 */
+	public DataRecord getRecord(DataIdentifier identifier)
+			throws DataStoreException {
+		if (moveDataTaskRunning) {
+			moveDataTaskLock.lock();
+		}
+		try {
+			return primaryDataStore.getRecord(identifier);
+		} catch (DataStoreException e) {
+			return archiveDataStore.getRecord(identifier);
+		} finally {
+			if (moveDataTaskRunning) {
+				moveDataTaskLock.unlock();
+			}
+		}
+	}
+
+	/**
+	 * Creates a new data record in the primary data store. The given binary
+	 * stream is consumed and a binary record containing the consumed stream is
+	 * created and returned. If the same stream already exists in another
+	 * record, then that record is returned instead of creating a new one.
+	 * <p>
+	 * The given stream is consumed and <strong>not closed</strong> by this
+	 * method. It is the responsibility of the caller to close the stream. A
+	 * typical call pattern would be:
+	 * 
+	 * <pre>
+	 *     InputStream stream = ...;
+	 *     try {
+	 *         record = store.addRecord(stream);
+	 *     } finally {
+	 *         stream.close();
+	 *     }
+	 * </pre>
+	 * 
+	 * @param stream
+	 *            binary stream
+	 * @return data record that contains the given stream
+	 * @throws DataStoreException
+	 *             if the data store could not be accessed
+	 */
+	public DataRecord addRecord(InputStream stream) throws DataStoreException {
+		return primaryDataStore.addRecord(stream);
+	}
+
+	/**
+	 * From now on, update the modified date of an object even when accessing it
+	 * in the archive data store. Usually, the modified date is only updated
+	 * when creating a new object, or when a new link is added to an existing
+	 * object. When this setting is enabled, even getLength() will update the
+	 * modified date.
+	 * 
+	 * @param before
+	 *            - update the modified date to the current time if it is older
+	 *            than this value
+	 */
+	public void updateModifiedDateOnAccess(long before) {
+		archiveDataStore.updateModifiedDateOnAccess(before);
+	}
+
+	/**
+	 * Delete objects that have a modified date older than the specified date
+	 * from the archive data store.
+	 * 
+	 * @param min
+	 *            the minimum time
+	 * @return the number of data records deleted
+	 * @throws DataStoreException
+	 */
+	public int deleteAllOlderThan(long min) throws DataStoreException {
+		return archiveDataStore.deleteAllOlderThan(min);
+	}
+
+	/**
+	 * Get all identifiers from the archive data store.
+	 * 
+	 * @return an iterator over all DataIdentifier objects
+	 * @throws DataStoreException
+	 *             if the list could not be read
+	 */
+	public Iterator<DataIdentifier> getAllIdentifiers()
+			throws DataStoreException {
+		return archiveDataStore.getAllIdentifiers();
+	}
+
+	public void init(String homeDir) throws RepositoryException {
+		if (delayedDelete) {
+			// First initialize the identifiersToDeleteFile
+			LocalFileSystem fileSystem = new LocalFileSystem();
+			fileSystem.setRoot(new File(homeDir));
+	        identifiersToDeleteFile = new FileSystemResource(fileSystem, FileSystem.SEPARATOR + IDENTIFIERS_TO_DELETE_FILE_KEY);
+		}
+		moveDataTaskThread = new Thread(new MoveDataTask(), "Jackrabbit-MulitDataStore-MoveDataTaskThread");
+		moveDataTaskThread.setDaemon(true);
+		moveDataTaskThread.start();
+		log.info("MultiDataStore-MoveDataTask thread started; first run scheduled at " + moveDataTaskNextRun.getTime());
+		if (delayedDelete) {
+	        try {
+	        	// Run on startup the DeleteDelayedIdentifiersTask only if the file exists and modify date is older than the delayedDeleteSleep timeout ...
+		        if (identifiersToDeleteFile != null && identifiersToDeleteFile.exists() && (identifiersToDeleteFile.lastModified() + (delayedDeleteSleep * 1000)) < System.currentTimeMillis()) {
+		        	deleteDelayedIdentifiersTaskThread = new Thread(new DeleteDelayedIdentifiersTask(), "Jackrabbit-MultiDataStore-DeleteDelayedIdentifiersTaskThread");
+		        	deleteDelayedIdentifiersTaskThread.setDaemon(true);
+		        	deleteDelayedIdentifiersTaskThread.start();
+		    		log.info("Old entries in the " + IDENTIFIERS_TO_DELETE_FILE_KEY + " File found. DeleteDelayedIdentifiersTask-Thread started now.");
+		        }
+	        } catch (FileSystemException e) {
+	            throw new RepositoryException("I/O error while reading from '"
+	                    + identifiersToDeleteFile.getPath() + "'", e);
+	        }
+		}
+	}
+
+	/**
+	 * Get the minimum size of an object that should be stored in the primary
+	 * data store.
+	 * 
+	 * @return the minimum size in bytes
+	 */
+	public int getMinRecordLength() {
+		return primaryDataStore.getMinRecordLength();
+	}
+
+	public void close() throws DataStoreException {
+		DataStoreException lastException = null;
+		// 1. close the primary data store
+		try {
+			primaryDataStore.close();
+		} catch (DataStoreException e) {
+			lastException = e;
+		}
+		// 2. close the archive data store
+		try {
+			archiveDataStore.close();
+		} catch (DataStoreException e) {
+			if (lastException != null) {
+				lastException = new DataStoreException(lastException);
+			}
+		}
+		// 3. if moveDataTaskThread is running interrupt it
+		try {
+			if (moveDataTaskRunning) {
+				moveDataTaskThread.interrupt();
+			}
+		} catch (Exception e) {
+			if (lastException != null) {
+				lastException = new DataStoreException(lastException);
+			}
+		}
+		// 4. if deleteDelayedIdentifiersTaskThread is running interrupt it
+		try {
+			if (deleteDelayedIdentifiersTaskThread != null && deleteDelayedIdentifiersTaskThread.isAlive()) {
+				deleteDelayedIdentifiersTaskThread.interrupt();
+			}
+		} catch (Exception e) {
+			if (lastException != null) {
+				lastException = new DataStoreException(lastException);
+			}
+		}
+		if (lastException != null) {
+			throw lastException;
+		}
+	}
+
+	public void clearInUse() {
+		archiveDataStore.clearInUse();
+	}
+
+	public int getMaxAge() {
+		return maxAge;
+	}
+
+	public void setMaxAge(int maxAge) {
+		this.maxAge = maxAge;
+	}
+
+	public int getMoveDataTaskSleep() {
+		return moveDataTaskSleep;
+	}
+
+	public int getMoveDataTaskFirstRunHourOfDay() {
+		return moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY);
+	}
+
+	public void setMoveDataTaskSleep(int sleep) {
+		this.moveDataTaskSleep = sleep;
+	}
+
+	public void setMoveDataTaskFirstRunHourOfDay(int hourOfDay) {
+		moveDataTaskNextRun = Calendar.getInstance();
+		if (moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY) >= hourOfDay) {
+			moveDataTaskNextRun.add(Calendar.DAY_OF_MONTH, 1);
+		}
+		moveDataTaskNextRun.set(Calendar.HOUR_OF_DAY, hourOfDay);
+		moveDataTaskNextRun.set(Calendar.MINUTE, 0);
+		moveDataTaskNextRun.set(Calendar.SECOND, 0);
+		moveDataTaskNextRun.set(Calendar.MILLISECOND, 0);
+	}
+
+	public void setSleepBetweenRecords(long millis) {
+		this.sleepBetweenRecords = millis;
+	}
+
+	public long getSleepBetweenRecords() {
+		return sleepBetweenRecords;
+	}
+
+	public boolean isDelayedDelete() {
+		return delayedDelete;
+	}
+
+	public void setDelayedDelete(boolean delayedDelete) {
+		this.delayedDelete = delayedDelete;
+	}
+
+	public long getDelayedDeleteSleep() {
+		return delayedDeleteSleep;
+	}
+
+	public void setDelayedDeleteSleep(long delayedDeleteSleep) {
+		this.delayedDeleteSleep = delayedDeleteSleep;
+	}
+
+	/**
+	 * Writes the given DataIdentifier to the delayedDeletedFile.
+	 * 
+	 * @param identifier
+	 * @return boolean true if it was successful otherwise false
+	 */
+	private boolean writeDelayedDataIdentifier(DataIdentifier identifier) {
+	    BufferedWriter writer = null;
+	    try {
+	    	File identifierFile = new File(((LocalFileSystem)identifiersToDeleteFile.getFileSystem()).getPath(), 
+	    			identifiersToDeleteFile.getPath());
+	        writer = new BufferedWriter(
+	                new FileWriter(identifierFile, true));
+	        return true;
+	    } catch (Exception e) {
+	        log.warn("I/O error while saving DataIdentifier (stacktrace on DEBUG log level) to '"
+	                + identifiersToDeleteFile.getPath() + "': " + e.getMessage());
+	        log.debug("Root cause: ", e);
+	        return false;
+	    } finally {
+	        IOUtils.closeQuietly(writer);
+	    }
+	}
+
+	/**
+	 * Purges the delayedDeletedFile.
+	 * @return boolean true if it was successful otherwise false
+	 */
+	private boolean purgeDelayedDeleteFile() {
+		BufferedWriter writer = null;
+	    try {
+	        writer = new BufferedWriter(
+	                new OutputStreamWriter(identifiersToDeleteFile.getOutputStream()));
+	        writer.write("");
+	        return true;
+	    } catch (Exception e) {
+	        log.warn("I/O error while purging (stacktrace on DEBUG log level) the " + IDENTIFIERS_TO_DELETE_FILE_KEY + " file '"
+	    	         + identifiersToDeleteFile.getPath() + "': " + e.getMessage());
+	        log.debug("Root cause: ", e);
+	        return false;
+	    } finally {
+	        IOUtils.closeQuietly(writer);
+	    }
+	}
+
+	/**
+	 * Class for maintaining the MultiDataStore. It will be used to move the
+	 * content of the primary data store to the archive data store.
+	 */
+	public class MoveDataTask implements Runnable {
+
+		/**
+		 * {@inheritDoc}
+		 */
+		public void run() {
+			while (!Thread.currentThread().isInterrupted()) {
+				try {
+					log.info("Next move-data task run scheduled at " + moveDataTaskNextRun.getTime());
+					long sleepTime = moveDataTaskNextRun.getTimeInMillis() - System.currentTimeMillis();
+					if (sleepTime > 0) {
+						Thread.sleep(sleepTime);
+					}
+					moveDataTaskRunning = true;
+					moveOutdatedData();
+					moveDataTaskRunning = false;
+					moveDataTaskNextRun.add(Calendar.SECOND, moveDataTaskSleep);
+					if (delayedDelete) {
+						if (deleteDelayedIdentifiersTaskThread != null && deleteDelayedIdentifiersTaskThread.isAlive()) {
+							log.warn("The DeleteDelayedIdentifiersTask-Thread is already running.");
+						} else {
+							deleteDelayedIdentifiersTaskThread = new Thread(new DeleteDelayedIdentifiersTask(), "Jackrabbit-MultiDataStore-DeleteDelayedIdentifiersTaskThread");
+							deleteDelayedIdentifiersTaskThread.setDaemon(true);
+							deleteDelayedIdentifiersTaskThread.start();
+						}
+					}
+				} catch (InterruptedException e) {
+					Thread.currentThread().interrupt();
+				}
+			}
+			log.warn("Interrupted: stopping move-data task.");
+		}
+
+		/**
+		 * Moves outdated data from primary to archive data store
+		 */
+		protected void moveOutdatedData() {
+			try {
+				long now = System.currentTimeMillis();
+				long maxAgeMilli = 1000 * 60 * 60 * 24 * maxAge;
+				Iterator<DataIdentifier> allIdentifiers = primaryDataStore.getAllIdentifiers();
+				int moved = 0;
+				while (allIdentifiers.hasNext()) {
+					DataIdentifier identifier = allIdentifiers.next();
+					DataRecord dataRecord = primaryDataStore.getRecord(identifier);
+					if ((dataRecord.getLastModified() + maxAgeMilli) < now) {
+						try {
+							moveDataTaskLock.lock();
+							if (delayedDelete) {
+								// first write it to the file and then add it to the archive data store ...
+								if (writeDelayedDataIdentifier(identifier)) {
+									archiveDataStore.addRecord(dataRecord.getStream());
+									moved++;
+								}
+							} else {
+								// first add it and then delete it .. not really atomic ...
+								archiveDataStore.addRecord(dataRecord.getStream());
+								((MultiDataStoreAware) primaryDataStore).deleteRecord(identifier);
+								moved++;
+							}
+						} catch (DataStoreException e) {
+							log.error("Failed to move DataRecord. DataIdentifier: " + identifier, e);
+						} finally {
+							moveDataTaskLock.unlock();
+						}
+					}
+					// Give other threads time to use the MultiDataStore while
+					// MoveDataTask is running..
+					Thread.sleep(sleepBetweenRecords);
+				}
+				if (delayedDelete) {
+					log.info("Moved " + moved + " DataRecords to the archive data store. The DataRecords in the primary data store will be removed in " + delayedDeleteSleep +" seconds.");
+				} else {
+					log.info("Moved " + moved + " DataRecords to the archive data store.");
+				}
+			} catch (Exception e) {
+				log.warn("Failed to run move-data task.", e);
+			}
+		}
+	}
+
+	/**
+	 * Class to clean up the delayed DataRecords from the primary data store.
+	 */
+	public class DeleteDelayedIdentifiersTask implements Runnable {
+
+		boolean run = true;
+		
+		@Override
+		public void run() {
+			if (moveDataTaskRunning) {
+				log.warn("It's not supported to run the DeleteDelayedIdentifiersTask while the MoveDataTask is running.");
+				return;
+			}
+			while (run && !Thread.currentThread().isInterrupted()) {
+		        BufferedReader reader = null;
+		        ArrayList<DataIdentifier> problemIdentifiers = new ArrayList<DataIdentifier>();
+				try {
+					int deleted = 0;
+		            reader = new BufferedReader(
+		                    new InputStreamReader(identifiersToDeleteFile.getInputStream()));
+		            while (true) {
+		                String s = reader.readLine();
+		                if (s == null || s.equals("")) {
+		                    break;
+		                }
+		                DataIdentifier identifier = new DataIdentifier(s);
+						try {
+							moveDataTaskLock.lock();
+							((MultiDataStoreAware) primaryDataStore).deleteRecord(identifier);
+							deleted++;
+						} catch (DataStoreException e) {
+							log.error("Failed to delete DataRecord. DataIdentifier: " + identifier, e);
+							problemIdentifiers.add(identifier);
+						} finally {
+							moveDataTaskLock.unlock();
+						}
+						// Give other threads time to use the MultiDataStore while
+						// DeleteDelayedIdentifiersTask is running..
+						Thread.sleep(sleepBetweenRecords);
+		            }
+					log.info("Deleted " + deleted+ " DataRecords from the primary data store.");
+	            	if (problemIdentifiers.isEmpty()) {
+			            try {
+		            		identifiersToDeleteFile.delete();
+			            } catch (FileSystemException e) {
+			            	log.warn("Unable to delete the " + IDENTIFIERS_TO_DELETE_FILE_KEY + " File.");
+			            	if (!purgeDelayedDeleteFile()) {
+				            	log.error("Unable to purge the " + IDENTIFIERS_TO_DELETE_FILE_KEY + " File.");
+			            	}
+			            }
+	            	} else {
+	            		if (purgeDelayedDeleteFile()) {
+		            		for (int x = 0; x < problemIdentifiers.size(); x++) {
+		            			writeDelayedDataIdentifier(problemIdentifiers.get(x));
+		            		}
+	            		}
+	            	}
+				} catch (InterruptedException e) {
+					log.warn("Interrupted: stopping delayed-delete task.");
+					Thread.currentThread().interrupt();
+				} catch (Exception e) {
+					log.warn("Failed to run delayed-delete task.", e);
+				} finally {
+					IOUtils.closeQuietly(reader);
+					run = false;
+				}
+			}
+		}
+	}
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java?rev=1409177&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java Wed Nov 14 12:21:03 2012
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import org.apache.jackrabbit.core.data.MultiDataStore.MoveDataTask;
+
+/**
+ * To use a DataStore within a MultiDataStore it must implement this
+ * MultiDataStoreAware Interface. It extends a DataStore to delete a
+ * single DataRecord.
+ */
+public interface MultiDataStoreAware {
+
+	/**
+     * Deletes a single DataRecord based on the given identifier. Delete
+     * will only be used by the {@link MoveDataTask}.
+     *  
+     * @param identifier data identifier
+     * @throws DataStoreException if the data store could not be accessed,
+     *                     or if the given identifier is invalid
+     */
+    void deleteRecord(DataIdentifier identifier) throws DataStoreException;
+
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java?rev=1409177&r1=1409176&r2=1409177&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java Wed Nov 14 12:21:03 2012
@@ -21,6 +21,7 @@ import org.apache.jackrabbit.core.data.D
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.MultiDataStoreAware;
 import org.apache.jackrabbit.core.util.db.CheckSchemaOperation;
 import org.apache.jackrabbit.core.util.db.ConnectionFactory;
 import org.apache.jackrabbit.core.util.db.ConnectionHelper;
@@ -97,7 +98,7 @@ import javax.sql.DataSource;
  * The tablePrefix can be used to specify a schema and / or catalog name:
  * &lt;param name="tablePrefix" value="ds.">
  */
-public class DbDataStore implements DataStore, DatabaseAware {
+public class DbDataStore implements DataStore, DatabaseAware, MultiDataStoreAware {
 
     /**
      * The default value for the minimum object size.
@@ -470,6 +471,14 @@ public class DbDataStore implements Data
         return temp;
     }
 
+    public synchronized void deleteRecord(DataIdentifier identifier) throws DataStoreException {
+    	try {
+            conHelper.exec(deleteSQL, identifier.toString());
+    	} catch (Exception e) {
+    		throw convert("Can not delete record", e);
+    	}
+	}
+
     public synchronized int deleteAllOlderThan(long min) throws DataStoreException {
         try {
             ArrayList<String> touch = new ArrayList<String>();

Added: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd?rev=1409177&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd Wed Nov 14 12:21:03 2012
@@ -0,0 +1,254 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % jackrabbit-repository-elements
+         "DataSources|Cluster|FileSystem|DataStore|Security|Workspaces|Workspace|Versioning|SearchIndex|RepositoryLockMechanism">
+
+<!--
+    The DataSources element configures the data sources of the repository.
+-->
+<!ELEMENT DataSources (DataSource*)>
+<!ELEMENT DataSource (param*)>
+<!ATTLIST DataSource name CDATA #REQUIRED>
+
+<!--
+    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 various security related managers to be used.
+-->
+<!ELEMENT Security (SecurityManager?, AccessManager?, LoginModule?)>
+<!ATTLIST Security appName CDATA #REQUIRED>
+
+<!--
+    the SecurityManager element configures the general security manager to be
+    used by this repository instance; the class attribute specifies the FQN of the
+    class implementing the JackrabbitSecurityManager interface
+-->
+<!ELEMENT SecurityManager (WorkspaceAccessManager?,UserManager?,UserIdClass?, param*)>
+<!ATTLIST SecurityManager class CDATA #REQUIRED
+                          workspaceName CDATA #IMPLIED>
+
+<!--
+    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)
+    this element can also have custom objects 
+-->
+<!ELEMENT param (param*)>
+<!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 WorkspaceAccessManager element optionally configures the manager
+    to be used by this repository instance to determine if access to a specific
+    workspace is granted for a specific subject;
+    the class attribute specifies the FQN of the class implementing the
+    WorkspaceAccessManager interface
+-->
+<!ELEMENT WorkspaceAccessManager EMPTY>
+<!ATTLIST WorkspaceAccessManager class CDATA #REQUIRED>
+
+<!--
+    the Workspaces element specifies the physical workspaces root directory
+    (rootPath attribute), the name of the default workspace (defaultWorkspace 
+    attribute), the (optional) maximum amount of time in seconds before an idle 
+    workspace is automatically shutdown (maxIdleTime attribute) and the 
+    (optional) workspace configuration root directory within the virtual 
+    repository file system (configRootPath attribute).
+
+    individual workspaces are configured through individual workspace.xml files 
+    located in a subfolder each of either
+
+    a) the physical workspaces root directory
+
+    or, if configRootPath had been specified,
+
+    b) the configuration root directory within the virtual repository file 
+    system.
+-->
+<!ELEMENT Workspaces EMPTY>
+<!ATTLIST Workspaces rootPath         CDATA #REQUIRED
+                     defaultWorkspace CDATA #REQUIRED
+                     defaultLockTimeout CDATA #IMPLIED
+                     configRootPath   CDATA #IMPLIED
+                     maxIdleTime      CDATA #IMPLIED>
+
+<!--
+    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?,ISMLocking?,WorkspaceSecurity?,Import?)>
+<!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 WorkspaceSecurity element specifies the workspace specific security
+    configuration.
+-->
+<!ELEMENT WorkspaceSecurity (AccessControlProvider?)>
+
+<!--
+    the AccessControlProvider element defines a class attribute specifying the
+    FQN of the class implementing the AccessControlProvider interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT AccessControlProvider (param*)>
+<!ATTLIST AccessControlProvider class CDATA #REQUIRED>
+
+<!--
+    the Versioning element configures the persistence manager
+    to be used for persisting version state
+-->
+<!ELEMENT Versioning (FileSystem, PersistenceManager, ISMLocking?)>
+<!ATTLIST Versioning rootPath CDATA #REQUIRED>
+
+<!--
+    the Cluster element configures the optional participation of this
+    repository in a clustered environment. a literal id may be
+    specified that uniquely identifies this node in a cluster, as well
+    as the delay in milliseconds before changes to the journal are
+    automatically detected.
+-->
+<!ELEMENT Cluster (Journal)>
+<!ATTLIST Cluster id        CDATA #IMPLIED
+                  syncDelay CDATA #IMPLIED>
+
+<!--
+    the Journal element configures the journal used in clustering; the
+    class attribute specifies the FQN of the class implementing the
+    Journal interface.
+-->
+<!ELEMENT Journal (param*)>
+<!ATTLIST Journal class CDATA #REQUIRED>
+
+<!--
+    the ISMLocking element configures the locking implementation
+    to be used for the workspace and version storage; the class
+    attribute specifies the FQN of the class implementing the
+    ISMLocking interface.
+-->
+<!ELEMENT ISMLocking (param*)>
+<!ATTLIST ISMLocking class CDATA #REQUIRED>
+
+<!--
+    the RepositoryLockMechanism element configures the mechanism
+    that is used to ensure only one process writes to the 
+    backend (file system or database) at any time; the class
+    attribute specifies the FQN of the class implementing the
+    RepositoryLockMechanism interface.
+-->
+<!ELEMENT RepositoryLockMechanism (param*)>
+<!ATTLIST RepositoryLockMechanism class CDATA #REQUIRED>
+
+<!--
+    the DataStore element configures the data store
+    to be used for the workspace; the class attribute specifies the
+    FQN of the class implementing the DataStore interface
+-->
+<!ELEMENT DataStore (param*)>
+<!ATTLIST DataStore class CDATA #REQUIRED>
+
+<!--
+    The Import element configures how protected items are imported into a
+    workspace.
+-->
+<!ELEMENT Import (ProtectedNodeImporter|ProtectedPropertyImporter)*>
+
+<!--
+    The ProtectedNodeImporter element configures an importer for protected
+    nodes. The class attribute specifies the FQN of the class implementing the
+    ProtectedNodeImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedNodeImporter (param*)>
+<!ATTLIST ProtectedNodeImporter class CDATA #REQUIRED>
+
+<!--
+    The ProtectedPropertyImporter element configures an importer for protected
+    properties. The class attribute specifies the FQN of the class implementing
+    the ProtectedPropertyImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedPropertyImporter (param*)>
+<!ATTLIST ProtectedPropertyImporter class CDATA #REQUIRED>
+
+<!--
+    The UserManager element configures the user manager implementation that is
+    used in Jackrabbit. The class attribute specifies the FQN of the class
+    implementing the UserManager interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT UserManager (param*,AuthorizableAction*)>
+<!ATTLIST UserManager class CDATA #REQUIRED>
+
+<!--
+   The optional AuthorizableAction element(s) configure additional custom
+   actions to be executed upon authorizable creation and removal. The 'class'
+   attribute specifies the FQN of a class implementing AuthorizableAction interface.
+   The parameter(s) define the implementation specific configuration.
+-->
+<!ELEMENT AuthorizableAction (param*)>
+<!ATTLIST AuthorizableAction class CDATA #REQUIRED>
+
+
+<!--
+    The UserIdClass element specifies the class of principals used to retrieve
+    the userID out of a Subject. The class attribute specifies the FQN of a
+    class implementing the java.security.Principal interface.
+-->
+<!ELEMENT UserIdClass EMPTY>
+<!ATTLIST UserIdClass class CDATA #REQUIRED>

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd?rev=1409177&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd Wed Nov 14 12:21:03 2012
@@ -0,0 +1,64 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % repository-elements
+         PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.4 Elements//EN"
+         "http://jackrabbit.apache.org/dtd/repository-2.4-elements.dtd">
+%repository-elements;
+
+<!--
+    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
+
+      - an optional Cluster element that is used for configuring a
+        clustering node that synchronizes changes made in a cluster
+       
+      - a FileSystem element (the virtual file system
+        used by the repository to persist global state such as
+        registered namespaces, custom node types, etc.
+        
+      - an optional DataStore element to configure the component
+        to use for storing large binary objects
+
+      - a Security element that specifies the name of the app-entry
+        in the JAAS config and the access manager
+
+      - a Workspaces element that specifies the location of the 
+        workspaces root directory, the name of the default workspace,
+        the maximum idle time before a workspace is automatically
+        shutdown (optional) and the workspace configuration root directory
+        within the virtual repository file system (optional)
+
+      - 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
+
+      - an optional SearchIndex element that is used for configuring Indexing-related
+        settings on the /jcr:system tree.
+
+-->
+<!ELEMENT Repository (%jackrabbit-repository-elements;)*> 

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd
------------------------------------------------------------------------------
    svn:eol-style = native