jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r517150 [1/4] - in /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core: persistence/bundle/ persistence/bundle/util/ state/
Date Mon, 12 Mar 2007 09:42:13 GMT
Author: tripod
Date: Mon Mar 12 02:42:12 2007
New Revision: 517150

URL: http://svn.apache.org/viewvc?view=rev&rev=517150
Log:
JCR-755 Add Bundle Persistence Managers

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/CachingPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/DerbyPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/H2PersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/MSSqlPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/MySqlPersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/OraclePersistenceManager.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/derby.ddl
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/h2.ddl
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/mssql.ddl
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/mysql.ddl
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/oracle.ddl
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleCache.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/DbNameIndex.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ErrorHandling.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/HashMapIndex.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/LRUNodeIdCache.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NGKDbNameIndex.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NamespaceIndex.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/StringIndex.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/TrackingInputStream.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java?view=auto&rev=517150
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java Mon Mar 12 02:42:12 2007
@@ -0,0 +1,704 @@
+/*
+ * 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.persistence.bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferencesId;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.NamespaceRegistryImpl;
+import org.apache.jackrabbit.core.nodetype.PropDefId;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.persistence.bundle.util.StringIndex;
+import org.apache.jackrabbit.core.persistence.bundle.util.NamespaceIndex;
+import org.apache.jackrabbit.core.persistence.bundle.util.NodePropBundle;
+import org.apache.jackrabbit.core.persistence.bundle.util.BundleCache;
+import org.apache.jackrabbit.core.persistence.bundle.util.LRUNodeIdCache;
+import org.apache.jackrabbit.core.persistence.bundle.util.HashMapIndex;
+import org.apache.jackrabbit.name.QName;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+
+/**
+ * The <code>AbstractBundlePersistenceManager</code> acts as base for all
+ * persistence managers that store the state in a {@link NodePropBundle}.
+ * <p/>
+ * The state and all property states of one node are stored together in one
+ * record. Property values of a certain size can be store outside of the bundle.
+ * This currently only works for binary properties. NodeReferences are not
+ * included in the bundle since they are addressed by the target id.
+ * <p/>
+ * Some strings like namespaces and local names are additionally managed by
+ * seperate indexes. only the index number is serialized to the records which
+ * reduces the amount of memory used.
+ * <p/>
+ * Special treatment is performed for the properties "jcr:uuid", "jcr:primaryType"
+ * and "jcr:mixinTypes". As they are also stored in the node state they are not
+ * included in the bundle but generated when required.
+ * <p/>
+ * In order to increase performance, there are 2 caches maintained. One ist the
+ * {@link BundleCache} that caches already loaded bundles. The other is the
+ * {@link LRUNodeIdCache} that caches non-existent bundles. This is usefull
+ * because a lot of {@link #exists(NodeId)} calls are issued that would result
+ * in a useless SQL execution if the desired bundle does not exist.
+ * <p/>
+ * Configuration:<br>
+ * <ul>
+ * <li>&lt;param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
+ * </ul>
+ */
+abstract public class AbstractBundlePersistenceManager implements 
+        PersistenceManager, CachingPersistenceManager {
+
+    /** the cvs/svn id */
+    static final String CVS_ID = "$URL$ $Rev$ $Date$";
+
+    /** the default logger */
+    private static Logger log = LoggerFactory.getLogger(AbstractBundlePersistenceManager.class);
+
+    /** the prefix of a node file */
+    protected static final String NODEFILENAME = "n";
+
+    /** the prefix of a node references file */
+    protected static final String NODEREFSFILENAME = "r";
+
+    /** the name of the names-index resource */
+    protected static final String RES_NAME_INDEX = "/names.properties";
+
+    /** the name of the namespace-index resource */
+    protected static final String RES_NS_INDEX = "/namespaces.properties";
+
+    /** the index for namespaces */
+    private StringIndex nsIndex;
+
+    /** the index for local names */
+    private StringIndex nameIndex;
+
+    /** the cache of loaded bundles */
+    private BundleCache bundles;
+
+    /** the cahce of non-existent bundles */
+    private LRUNodeIdCache missing;
+
+    /** definition id of the jcr:uuid property */
+    private PropDefId ID_JCR_UUID;
+
+    /** definition id of the jcr:primaryType property */
+    private PropDefId ID_JCR_PRIMARYTYPE;
+
+    /** definition id of the jcr:mixinTypes property */
+    private PropDefId ID_JCR_MIXINTYPES;
+
+    /** the persistence manager context */
+    protected PMContext context;
+
+    /** default size of the bunlde cache */
+    private long bundleCacheSize = 8 * 1024 * 1024;
+
+    /**
+     * Returns the size of the bundlecache in megabytes.
+     * @return the size of the bundlecache in megabytes.
+     */
+    public String getBundleCacheSize() {
+        return String.valueOf(bundleCacheSize/(1024 * 1024));
+    }
+
+    /**
+     * Sets the size of the bundle cache in megabytes.
+     * the default is 8.
+     *
+     * @param bundleCacheSize the bundle cache size in megabytes.
+     */
+    public void setBundleCacheSize(String bundleCacheSize) {
+        this.bundleCacheSize = Long.parseLong(bundleCacheSize)*1024*1024;
+    }
+
+    /**
+     * Creates the folder path for the given node id that is suitable for
+     * storing states in a filesystem.
+     *
+     * @param buf buffer to append to or <code>null</code>
+     * @param id the id of the node
+     * @return the buffer with the appended data.
+     */
+    protected StringBuffer buildNodeFolderPath(StringBuffer buf, NodeId id) {
+        if (buf == null) {
+            buf = new StringBuffer();
+        }
+        char[] chars = id.getUUID().toString().toCharArray();
+        int cnt = 0;
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] == '-') {
+                continue;
+            }
+            //if (cnt > 0 && cnt % 4 == 0) {
+            if (cnt == 2 || cnt == 4) {
+                buf.append(FileSystem.SEPARATOR_CHAR);
+            }
+            buf.append(chars[i]);
+            cnt++;
+        }
+        return buf;
+    }
+
+    /**
+     * Creates the folder path for the given property id that is suitable for
+     * storing states in a filesystem.
+     *
+     * @param buf buffer to append to or <code>null</code>
+     * @param id the id of the property
+     * @return the buffer with the appended data.
+     */
+    protected StringBuffer buildPropFilePath(StringBuffer buf, PropertyId id) {
+        if (buf == null) {
+            buf = new StringBuffer();
+        }
+        buildNodeFolderPath(buf, id.getParentId());
+        buf.append(FileSystem.SEPARATOR);
+        buf.append(getNsIndex().stringToIndex(id.getName().getNamespaceURI()));
+        buf.append('.');
+        buf.append(getNameIndex().stringToIndex(id.getName().getLocalName()));
+        return buf;
+    }
+
+    /**
+     * Creates the file path for the given property id and value index that is
+     * suitable for storing property values in a filesystem.
+     *
+     * @param buf buffer to append to or <code>null</code>
+     * @param id the id of the property
+     * @param i the index of the property value
+     * @return the buffer with the appended data.
+     */
+    protected StringBuffer buildBlobFilePath(StringBuffer buf, PropertyId id,
+                                             int i) {
+        if (buf == null) {
+            buf = new StringBuffer();
+        }
+        buildPropFilePath(buf, id);
+        buf.append('.');
+        buf.append(i);
+        return buf;
+    }
+
+    /**
+     * Creates the file path for the given node id that is
+     * suitable for storing node states in a filesystem.
+     *
+     * @param buf buffer to append to or <code>null</code>
+     * @param id the id of the node
+     * @return the buffer with the appended data.
+     */
+    protected StringBuffer buildNodeFilePath(StringBuffer buf, NodeId id) {
+        if (buf == null) {
+            buf = new StringBuffer();
+        }
+        buildNodeFolderPath(buf, id);
+        buf.append(FileSystem.SEPARATOR);
+        buf.append(NODEFILENAME);
+        return buf;
+    }
+
+    /**
+     * Creates the file path for the given references id that is
+     * suitable for storing reference states in a filesystem.
+     *
+     * @param buf buffer to append to or <code>null</code>
+     * @param id the id of the node
+     * @return the buffer with the appended data.
+     */
+    protected StringBuffer buildNodeReferencesFilePath(StringBuffer buf,
+                                                       NodeReferencesId id) {
+        if (buf == null) {
+            buf = new StringBuffer();
+        }
+        buildNodeFolderPath(buf, id.getTargetId());
+        buf.append(FileSystem.SEPARATOR);
+        buf.append(NODEREFSFILENAME);
+        return buf;
+    }
+
+    /**
+     * Returns the namespace index
+     * @return the namespace index
+     * @throws IllegalStateException if an error occurs.
+     */
+    public StringIndex getNsIndex() {
+        try {
+            if (nsIndex == null) {
+                // load name and ns index
+                FileSystemResource nsFile = new FileSystemResource(context.getFileSystem(), RES_NS_INDEX);
+                if (nsFile.exists()) {
+                    nsIndex = new HashMapIndex(nsFile);
+                } else {
+                    nsIndex = new NamespaceIndex((NamespaceRegistryImpl) context.getNamespaceRegistry());
+                }
+            }
+            return nsIndex;
+        } catch (Exception e) {
+            throw new IllegalStateException("Unable to create nsIndex." + e);
+        }
+    }
+
+    /**
+     * Returns the local name index
+     * @return the local name index
+     * @throws IllegalStateException if an error occurs.
+     */
+    public StringIndex getNameIndex() {
+        try {
+            if (nameIndex == null) {
+                nameIndex = new HashMapIndex(new FileSystemResource(context.getFileSystem(), RES_NAME_INDEX));
+            }
+            return nameIndex;
+        } catch (Exception e) {
+            throw new IllegalStateException("Unable to create nsIndex." + e);
+        }
+    }
+
+    //-----------------------------------------< CacheablePersistenceManager >--
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void onExternalUpdate(ChangeLog changes) {
+        Iterator iter = changes.modifiedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                bundles.remove((NodeId) state.getId());
+            } else {
+                bundles.remove(state.getParentId());
+            }
+        }
+        iter = changes.deletedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                bundles.remove((NodeId) state.getId());
+            } else {
+                bundles.remove(state.getParentId());
+            }
+        }
+        iter = changes.addedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                missing.remove((NodeId) state.getId());
+            } else {
+                missing.remove(state.getParentId());
+            }
+        }
+    }
+
+    //----------------------------------------------------------------< spi >---
+
+    /**
+     * Loads a bundle from the underlying system.
+     *
+     * @param id the node id of the bundle
+     * @return the loaded bunlde or <code>null</code> if the bundle does not
+     *         exist.
+     * @throws ItemStateException if an error while loading occurs.
+     */
+    protected abstract NodePropBundle loadBundle(NodeId id)
+            throws ItemStateException;
+
+    /**
+     * Checks if a bundle exists in the underlying system.
+     *
+     * @param id the node id of the bundle
+     * @return <code>true</code> if the bundle exists;
+     *         <code>false</code> otherwise.
+     * @throws ItemStateException if an error while checking occurs.
+     */
+    protected abstract boolean existsBundle(NodeId id)
+            throws ItemStateException;
+
+    /**
+     * Stores a bundle to the underlying system.
+     *
+     * @param bundle the bundle to store
+     * @throws ItemStateException if an error while storing occurs.
+     */
+    protected abstract void storeBundle(NodePropBundle bundle)
+            throws ItemStateException;
+
+    /**
+     * Deletes the bundle from the underlying system.
+     *
+     * @param bundle the bundle to destroy
+     *
+     * @throws ItemStateException if an error while destroying occurs.
+     */
+    protected abstract void destroyBundle(NodePropBundle bundle)
+            throws ItemStateException;
+
+    /**
+     * {@inheritDoc}
+     */
+    abstract public NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException;
+
+    /**
+     * Deletes the node references from the undelying system.
+     *
+     * @param refs the node references to destroy.
+     * @throws ItemStateException if an error while destroying occurs.
+     */
+    protected abstract void destroy(NodeReferences refs)
+            throws ItemStateException;
+
+    /**
+     * Stores a node references to the underlying system.
+     *
+     * @param refs the node references to store.
+     * @throws ItemStateException if an error while storing occurs.
+     */
+    protected abstract void store(NodeReferences refs)
+            throws ItemStateException;
+
+    //-------------------------------------------------< PersistenceManager >---
+
+    /**
+     * {@inheritDoc}
+     *
+     * Initializes the internal structures of this abstract persistence manager.
+     */
+    public void init(PMContext context) throws Exception {
+        this.context = context;
+        // init bundle cache
+        bundles = new BundleCache(bundleCacheSize);
+        missing = new LRUNodeIdCache();
+
+        // init prop defs
+        if (context.getNodeTypeRegistry() != null) {
+            ID_JCR_UUID = context.getNodeTypeRegistry().getEffectiveNodeType(QName.MIX_REFERENCEABLE).getApplicablePropertyDef(
+                    QName.JCR_UUID, PropertyType.STRING, false).getId();
+            ID_JCR_PRIMARYTYPE = context.getNodeTypeRegistry().getEffectiveNodeType(QName.NT_BASE).getApplicablePropertyDef(
+                    QName.JCR_PRIMARYTYPE, PropertyType.NAME, false).getId();
+            ID_JCR_MIXINTYPES = context.getNodeTypeRegistry().getEffectiveNodeType(QName.NT_BASE).getApplicablePropertyDef(
+                    QName.JCR_MIXINTYPES, PropertyType.NAME, true).getId();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Loads the state via the appropriate NodePropBundle.
+     */
+    public synchronized NodeState load(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        NodePropBundle bundle = getBundle(id);
+        if (bundle == null) {
+            throw new NoSuchItemStateException(id.toString());
+        }
+        return bundle.createNodeState(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Loads the state via the appropriate NodePropBundle.
+     */
+    public synchronized PropertyState load(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        NodePropBundle bundle = getBundle(id.getParentId());
+        if (bundle == null) {
+            throw new NoSuchItemStateException(id.toString());
+        }
+        PropertyState state = bundle.createPropertyState(this, id.getName());
+        if (state == null) {
+            // check if autocreated property state
+            if (id.getName().equals(QName.JCR_UUID)) {
+                state = createNew(id);
+                state.setType(PropertyType.STRING);
+                state.setDefinitionId(ID_JCR_UUID);
+                state.setMultiValued(false);
+                state.setValues(new InternalValue[]{InternalValue.create(id.getParentId().getUUID().toString())});
+            } else if (id.getName().equals(QName.JCR_PRIMARYTYPE)) {
+                state = createNew(id);
+                state.setType(PropertyType.NAME);
+                state.setDefinitionId(ID_JCR_PRIMARYTYPE);
+                state.setMultiValued(false);
+                state.setValues(new InternalValue[]{InternalValue.create(bundle.getNodeTypeName())});
+            } else if (id.getName().equals(QName.JCR_MIXINTYPES)) {
+                Set mixins = bundle.getMixinTypeNames();
+                state = createNew(id);
+                state.setType(PropertyType.NAME);
+                state.setDefinitionId(ID_JCR_MIXINTYPES);
+                state.setMultiValued(true);
+                state.setValues(InternalValue.create((QName[]) mixins.toArray(new QName[mixins.size()])));
+            } else {
+                throw new NoSuchItemStateException(id.toString());
+            }
+            bundle.addProperty(state);
+        }
+        return state;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Loads the state via the appropriate NodePropBundle.
+     */
+    public synchronized boolean exists(PropertyId id) throws ItemStateException {
+        NodePropBundle bundle = getBundle(id.getParentId());
+        return bundle !=null && bundle.hasProperty(id.getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Checks the existance via the appropriate NodePropBundle.
+     */
+    public synchronized boolean exists(NodeId id) throws ItemStateException {
+        // anticipating a load followed by a exists
+        return getBundle(id) != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeState createNew(NodeId id) {
+        return new NodeState(id, null, null, NodeState.STATUS_NEW, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PropertyState createNew(PropertyId id) {
+        return new PropertyState(id, PropertyState.STATUS_NEW, false);
+    }
+
+    /**
+     * Right now, this iterates over all items in the changelog and
+     * calls the individual methods that handle single item states
+     * or node references objects. Properly implemented, this method
+     * should ensure that changes are either written completely to
+     * the underlying persistence layer, or not at all.
+     *
+     * {@inheritDoc}
+     */
+    public synchronized void store(ChangeLog changeLog)
+            throws ItemStateException {
+        // delete bundles
+        HashSet deleted = new HashSet();
+        Iterator iter = changeLog.deletedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                NodePropBundle bundle = getBundle((NodeId) state.getId());
+                if (bundle == null) {
+                    throw new NoSuchItemStateException(state.getId().toString());
+                }
+                deleteBundle(bundle);
+                deleted.add(state.getId());
+            }
+        }
+        // gather added node states
+        HashMap modified = new HashMap();
+        iter = changeLog.addedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                NodePropBundle bundle = new NodePropBundle((NodeState) state);
+                modified.put(state.getId(), bundle);
+            }
+        }
+        // gather modified node states
+        iter = changeLog.modifiedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                NodeId nodeId = (NodeId) state.getId();
+                NodePropBundle bundle = (NodePropBundle) modified.get(nodeId);
+                if (bundle == null) {
+                    bundle = getBundle(nodeId);
+                    if (bundle == null) {
+                        throw new NoSuchItemStateException(nodeId.toString());
+                    }
+                    modified.put(nodeId, bundle);
+                }
+                bundle.update((NodeState) state);
+            } else {
+                PropertyId id = (PropertyId) state.getId();
+                // skip primaryType pr mixinTypes properties
+                if (id.getName().equals(QName.JCR_PRIMARYTYPE)
+                    || id.getName().equals(QName.JCR_MIXINTYPES)
+                    || id.getName().equals(QName.JCR_UUID)) {
+                    continue;
+                }
+                NodeId nodeId = id.getParentId();
+                NodePropBundle bundle = (NodePropBundle) modified.get(nodeId);
+                if (bundle == null) {
+                    bundle = getBundle(nodeId);
+                    if (bundle == null) {
+                        throw new NoSuchItemStateException(nodeId.toString());
+                    }
+                    modified.put(nodeId, bundle);
+                }
+                bundle.addProperty((PropertyState) state);
+            }
+        }
+        // add removed properties
+        iter = changeLog.deletedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (state.isNode()) {
+                // check consistency
+                NodeId parentId = state.getParentId();
+                if (!modified.containsKey(parentId) && !deleted.contains(parentId)) {
+                    log.warn("Deleted node state's parent is not modified or deleted: " + parentId + "/" + state.getId());
+                }
+            } else {
+                PropertyId id = (PropertyId) state.getId();
+                NodeId nodeId = id.getParentId();
+                if (!deleted.contains(nodeId)) {
+                    NodePropBundle bundle = (NodePropBundle) modified.get(nodeId);
+                    if (bundle == null) {
+                        // should actually not happen
+                        log.warn("deleted property state's parent not modified!");
+                        bundle = getBundle(nodeId);
+                        if (bundle == null) {
+                            throw new NoSuchItemStateException(nodeId.toString());
+                        }
+                        modified.put(nodeId, bundle);
+                    }
+                    bundle.removeProperty(id.getName());
+                }
+            }
+        }
+        // add added properties
+        iter = changeLog.addedStates();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            if (!state.isNode()) {
+                PropertyId id = (PropertyId) state.getId();
+                // skip primaryType pr mixinTypes properties
+                if (id.getName().equals(QName.JCR_PRIMARYTYPE)
+                    || id.getName().equals(QName.JCR_MIXINTYPES)
+                    || id.getName().equals(QName.JCR_UUID)) {
+                    continue;
+                }
+                NodeId nodeId = id.getParentId();
+                NodePropBundle bundle = (NodePropBundle) modified.get(nodeId);
+                if (bundle == null) {
+                    // should actually not happen
+                    log.warn("added property state's parent not modified!");
+                    bundle = getBundle(nodeId);
+                    if (bundle == null) {
+                        throw new NoSuchItemStateException(nodeId.toString());
+                    }
+                    modified.put(nodeId, bundle);
+                }
+                bundle.addProperty((PropertyState) state);
+            }
+        }
+
+        // now store all modified bundles
+        iter = modified.values().iterator();
+        while (iter.hasNext()) {
+            NodePropBundle bundle = (NodePropBundle) iter.next();
+            putBundle(bundle);
+        }
+
+        // store the refs
+        iter = changeLog.modifiedRefs();
+        while (iter.hasNext()) {
+            NodeReferences refs = (NodeReferences) iter.next();
+            if (refs.hasReferences()) {
+                store(refs);
+            } else {
+                destroy(refs);
+            }
+        }
+    }
+
+    /**
+     * Gets the bundle for the given nodeid.
+     *
+     * @param id the id of the bundle to retrieve.
+     * @return the bundle or <code>null</code> if the bunlde does not exist
+     *
+     * @throws ItemStateException if an error occurs.
+     */
+    private NodePropBundle getBundle(NodeId id) throws ItemStateException {
+        if (missing.contains(id)) {
+            return null;
+        }
+        NodePropBundle bundle = bundles.get(id);
+        if (bundle == null) {
+            bundle = loadBundle(id);
+            if (bundle != null) {
+                bundle.markOld();
+                bundles.put(bundle);
+            } else {
+                missing.put(id);
+            }
+        }
+        return bundle;
+    }
+
+    /**
+     * Deletes the bundle
+     *
+     * @param bundle the bundle to delete
+     * @throws ItemStateException if an error occurs
+     */
+    private void deleteBundle(NodePropBundle bundle) throws ItemStateException {
+        destroyBundle(bundle);
+        bundles.remove(bundle.getId());
+        missing.put(bundle.getId());
+    }
+
+    /**
+     * Stores the bundle and puts it to the cache.
+     *
+     * @param bundle the bundle to store
+     * @throws ItemStateException if an error occurs
+     */
+    private void putBundle(NodePropBundle bundle) throws ItemStateException {
+        storeBundle(bundle);
+        bundle.markOld();
+        log.debug("stored bundle " + bundle.getId());
+
+        missing.remove(bundle.getId());
+        // only put to cache if already exists. this is to ensure proper overwrite
+        // and not creating big contention during bulk loads
+        if (bundles.contains(bundle.getId())) {
+            bundles.put(bundle);
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url rev

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java?view=auto&rev=517150
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java Mon Mar 12 02:42:12 2007
@@ -0,0 +1,1267 @@
+/*
+ * 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.persistence.bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferencesId;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager;
+import org.apache.jackrabbit.core.persistence.bundle.util.TrackingInputStream;
+import org.apache.jackrabbit.core.persistence.bundle.util.DbNameIndex;
+import org.apache.jackrabbit.core.persistence.bundle.util.NodePropBundle;
+import org.apache.jackrabbit.core.persistence.bundle.util.BundleBinding;
+import org.apache.jackrabbit.core.persistence.bundle.util.ErrorHandling;
+import org.apache.jackrabbit.core.persistence.bundle.util.StringIndex;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.uuid.UUID;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Driver;
+import java.util.Iterator;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * This is a generic persistence manager that stores the {@link NodePropBundle}s
+ * in a database.
+ * <p/>
+ * Configuration:<br>
+ * <ul>
+ * <li>&lt;param name="{@link #setExternalBLOBs(String)} externalBLOBs}" value="false"/>
+ * <li>&lt;param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
+ * <li>&lt;param name="{@link #setConsistencyCheck(String) consistencyCheck}" value="false"/>
+ * <li>&lt;param name="{@link #setMinBlobSize(String) minBlobSize}" value="4096"/>
+ * <li>&lt;param name="{@link #setDriver(String) driver}" value=""/>
+ * <li>&lt;param name="{@link #setUrl(String) url}" value=""/>
+ * <li>&lt;param name="{@link #setUser(String) user}" value=""/>
+ * <li>&lt;param name="{@link #setPassword(String) password}" value=""/>
+ * <li>&lt;param name="{@link #setSchema(String) schema}" value=""/>
+ * <li>&lt;param name="{@link #setSchemaObjectPrefix(String) schemaObjectPrefix}" value=""/>
+ * <li>&lt;param name="{@link #setErrorHandling(String) errorHandling}" value=""/>
+ * </ul>
+ */
+public class BundleDbPersistenceManager extends AbstractBundlePersistenceManager {
+
+    /** the cvs/svn id */
+    static final String CVS_ID = "$URL$ $Rev$ $Date$";
+
+    /** the default logger */
+    private static Logger log = LoggerFactory.getLogger(BundleDbPersistenceManager.class);
+
+    /** the variable for the schema prefix */
+    public static final String SCHEMA_OBJECT_PREFIX_VARIABLE =
+            "${schemaObjectPrefix}";
+
+    /** storage model modifier: binary keys */
+    public static final int SM_BINARY_KEYS = 1;
+
+    /** storage model modifier: longlong keys */
+    public static final int SM_LONGLONG_KEYS = 2;
+
+    /** flag indicating if this manager was initialized */
+    protected boolean initialized = false;
+
+    /** the jdbc driver name */
+    protected String driver;
+
+    /** the jdbc url string */
+    protected String url;
+
+    /** the jdbc user */
+    protected String user;
+
+    /** the jdbc password */
+    protected String password;
+
+    /** the schema identifier */
+    protected String schema;
+
+    /** the prefix for the database objects */
+    protected String schemaObjectPrefix;
+
+    /** flag indicating if a consistency check should be issued during startup */
+    protected boolean consistencyCheck = false;
+
+    /** initial size of buffer used to serialize objects */
+    protected static final int INITIAL_BUFFER_SIZE = 1024;
+
+    /** inidicates if uses (filesystem) blob store */
+    protected boolean externalBLOBs;
+
+
+    /** jdbc conection */
+    protected Connection con;
+
+    // shared prepared statements for bundle management
+    protected PreparedStatement bundleInsert;
+    protected PreparedStatement bundleUpdate;
+    protected PreparedStatement bundleSelect;
+    protected PreparedStatement bundleDelete;
+
+    // shared prepared statements for NodeReference management
+    protected PreparedStatement nodeReferenceInsert;
+    protected PreparedStatement nodeReferenceUpdate;
+    protected PreparedStatement nodeReferenceSelect;
+    protected PreparedStatement nodeReferenceDelete;
+
+    /** file system where BLOB data is stored */
+    protected CloseableBLOBStore blobStore;
+
+    /** the index for local names */
+    private StringIndex nameIndex;
+
+    /**
+     * the minimum size of a property until it gets written to the blob store
+     * @see #setMinBlobSize(String)
+     */
+    private int minBlobSize = 0x1000;
+
+    /**
+     * flag for error handling
+     */
+    protected ErrorHandling errorHandling = new ErrorHandling();
+
+    /**
+     * the bundle binding
+     */
+    protected BundleBinding binding;
+
+    /**
+     * the name of this persistence manager
+     */
+    private String name = super.toString();
+
+
+    /**
+     * Returns the configured JDBC connection url.
+     * @return the configured JDBC connection url.
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Sets the JDBC connection url.
+     * @param url the url to set.
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Returns the configured user that is used to establish JDBC connections.
+     * @return the JDBC user.
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Sets the user name that will be used to establish JDBC connections.
+     * @param user the user name.
+     */
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    /**
+     * Returns the configured password that is used to establish JDBC connections.
+     * @return the password.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the password that will be used to establish JDBC connections.
+     * @param password
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * Returns the class name of the JDBC driver.
+     * @return the class name of the JDBC driver.
+     */
+    public String getDriver() {
+        return driver;
+    }
+
+    /**
+     * Sets the class name of the JDBC driver. The driver class will be loaded
+     * during {@link #init(PMContext) init} in order to assure the existence.
+     *
+     * @param driver the class name of the driver
+     */
+    public void setDriver(String driver) {
+        this.driver = driver;
+    }
+
+    /**
+     * Returns the configured schema object prefix.
+     * @return the configured schema object prefix.
+     */
+    public String getSchemaObjectPrefix() {
+        return schemaObjectPrefix;
+    }
+
+    /**
+     * Sets the schema object prefix. This string is used to prefix all schema
+     * objects, like tables and indexes. this is usefull, if several persistence
+     * managers use the same database.
+     *
+     * @param schemaObjectPrefix the prefix for schema objects.
+     */
+    public void setSchemaObjectPrefix(String schemaObjectPrefix) {
+        // make sure prefix is all uppercase
+        this.schemaObjectPrefix = schemaObjectPrefix.toUpperCase();
+    }
+
+    /**
+     * Returns the configured schema identifier.
+     * @return the schema identifier.
+     */
+    public String getSchema() {
+        return schema;
+    }
+
+    /**
+     * Sets the schema identifier. This identifier is used to load and execute
+     * the respective .ddl resource in order to create the required schema
+     * objects.
+     *
+     * @param schema the schema identifier.
+     */
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    /**
+     * Returns if uses external (filesystem) blob store.
+     * @return if uses external (filesystem) blob store.
+     */
+    public boolean isExternalBLOBs() {
+        return externalBLOBs;
+    }
+
+    /**
+     * Sets the flag for external (filsystem) blob store usage.
+     * @param externalBLOBs a value of "true" indicates that an external blob
+     *        store is to be used.
+     */
+    public void setExternalBLOBs(String externalBLOBs) {
+        this.externalBLOBs = Boolean.valueOf(externalBLOBs).booleanValue();
+    }
+
+    /**
+     * Checks if consistency check is enabled.
+     * @return <code>true</code> if consistenct check is enabled.
+     */
+    public String getConsistencyCheck() {
+        return Boolean.toString(consistencyCheck);
+    }
+
+    /**
+     * Defines if a consistency check is to be performed on initialization.
+     * @param consistencyCheck the consistency check flag.
+     */
+    public void setConsistencyCheck(String consistencyCheck) {
+        this.consistencyCheck = Boolean.valueOf(consistencyCheck).booleanValue();
+    }
+
+    /**
+     * Returns the miminum blob size.
+     * @return the miminum blob size.
+     */
+    public int getMinBlobSize() {
+        return minBlobSize;
+    }
+
+    /**
+     * Sets the minumum blob size. This size defines the threshhold of which
+     * size a property is included in the bundle or is stored in the blob store.
+     *
+     * @param minBlobSize
+     */
+    public void setMinBlobSize(String minBlobSize) {
+        this.minBlobSize = Integer.decode(minBlobSize).intValue();
+    }
+
+    /**
+     * Sets the error handling behaviour of this manager. See {@link ErrorHandling}
+     * for details about the flags.
+     *
+     * @param errorHandling
+     */
+    public void setErrorHandling(String errorHandling) {
+        this.errorHandling = new ErrorHandling(errorHandling);
+    }
+
+    /**
+     * Returns the error handling configuration of this manager
+     * @return the error handling configuration of this manager
+     */
+    public String getErrorHandling() {
+        return errorHandling.toString();
+    }
+
+    /**
+     * Returns <code>true</code> if the blobs are stored in the DB.
+     * @return <code>true</code> if the blobs are stored in the DB.
+     */
+    public boolean useDbBlobStore() {
+        return !externalBLOBs;
+    }
+
+    /**
+     * Returns <code>true</code> if the blobs are stored in the local fs.
+     * @return <code>true</code> if the blobs are stored in the local fs.
+     */
+    public boolean useLocalFsBlobStore() {
+        return externalBLOBs;
+    }
+
+    /**
+     * Checks if the required schema objects exist and creates them if they
+     * don't exist yet.
+     */
+    protected void checkSchema() throws SQLException, RepositoryException {
+        DatabaseMetaData metaData = con.getMetaData();
+        String tableName = schemaObjectPrefix + "BUNDLE";
+        if (metaData.storesLowerCaseIdentifiers()) {
+            tableName = tableName.toLowerCase();
+        } else if (metaData.storesUpperCaseIdentifiers()) {
+            tableName = tableName.toUpperCase();
+        }
+
+        ResultSet rs = metaData.getTables(null, null, tableName, null);
+        boolean schemaExists;
+        try {
+            schemaExists = rs.next();
+        } finally {
+            rs.close();
+        }
+
+        if (!schemaExists) {
+            // read ddl from resources
+            InputStream in = getClass().getResourceAsStream(schema + ".ddl");
+            if (in == null) {
+                String msg = "Configuration error: unknown schema '" + schema + "'";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+            Statement stmt = con.createStatement();
+            try {
+                String sql = reader.readLine();
+                while (sql != null) {
+                    // replace prefix variable
+                    sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix).trim();
+                    if (sql.length() > 0 && (sql.indexOf("BINVAL") < 0 || useDbBlobStore())) {
+                        // only create blob related tables of db blob store configured
+                        // execute sql stmt
+                        stmt.executeUpdate(sql);
+                    }
+                    // read next sql stmt
+                    sql = reader.readLine();
+                }
+                // commit the changes
+                con.commit();
+            } catch (IOException e) {
+                String msg = "Configuration error: unable to read schema '" + schema + "': " + e;
+                log.debug(msg);
+                throw new RepositoryException(msg, e);
+            } finally {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+                stmt.close();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Basically wrapps a JDBC transaction around super.store().
+     */
+    public synchronized void store(ChangeLog changeLog)
+            throws ItemStateException {
+
+        try {
+            con.setAutoCommit(false);
+            super.store(changeLog);
+        } catch (SQLException e) {
+            String msg = "setting autocommit failed.";
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } catch (ItemStateException e) {
+            // storing the changes failed, rollback changes
+            try {
+                con.rollback();
+            } catch (SQLException e1) {
+                String msg = "rollback of change log failed";
+                log.error(msg, e1);
+            }
+            // re-throw original exception
+            throw e;
+        }
+
+        // storing the changes succeeded, now commit the changes
+        try {
+            con.commit();
+            con.setAutoCommit(true);
+        } catch (SQLException e) {
+            String msg = "committing change log failed";
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(PMContext context) throws Exception {
+        if (initialized) {
+            throw new IllegalStateException("already initialized");
+        }
+        super.init(context);
+
+        this.name = context.getHomeDir().getName();
+
+        // setup jdbc connection
+        // Note: Explicit creation of new instance of the driver is required
+        // in order to re-register the driver in the DriverManager after a
+        // repository shutdown.
+        Driver drv = (Driver) Class.forName(driver).newInstance();
+        con = DriverManager.getConnection(url, user, password);
+        con.setAutoCommit(true);
+
+        // make sure schemaObjectPrefix consists of legal name characters only
+        prepareSchemaObjectPrefix();
+
+        // check if schema objects exist and create them if necessary
+        checkSchema();
+
+        // create correct blob store
+        blobStore = createBlobStore();
+
+        // prepare statements
+        if (getStorageModel() == SM_BINARY_KEYS) {
+            bundleInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID) values (?, ?)");
+            bundleUpdate = con.prepareStatement("update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID = ?");
+            bundleSelect = con.prepareStatement("select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?");
+            bundleDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?");
+
+            nodeReferenceInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID) values (?, ?)");
+            nodeReferenceUpdate = con.prepareStatement("update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID = ?");
+            nodeReferenceSelect = con.prepareStatement("select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID = ?");
+            nodeReferenceDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "REFS where NODE_ID = ?");
+        } else {
+            bundleInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)");
+            bundleUpdate = con.prepareStatement("update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?");
+            bundleSelect = con.prepareStatement("select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
+            bundleDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
+
+            nodeReferenceInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)");
+            nodeReferenceUpdate = con.prepareStatement("update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?");
+            nodeReferenceSelect = con.prepareStatement("select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?");
+            nodeReferenceDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?");
+        }
+        // load namespaces
+        binding = new BundleBinding(errorHandling, blobStore, getNsIndex(), getNameIndex());
+        binding.setMinBlobSize(minBlobSize);
+
+        initialized = true;
+
+        if (consistencyCheck) {
+            checkConsistency();
+        }
+    }
+
+    /**
+     * Creates a suitable blobstore
+     * @return a blobstore
+     * @throws Exception
+     */
+    protected CloseableBLOBStore createBlobStore() throws Exception {
+        if (useLocalFsBlobStore()) {
+            return createLocalFSBlobStore(context);
+        } else {
+            return createDBBlobStore(context);
+        }
+    }
+
+    /**
+     * Returns the local name index
+     * @return the local name index
+     * @throws IllegalStateException if an error occurs.
+     */
+    public StringIndex getNameIndex() {
+        try {
+            if (nameIndex == null) {
+                FileSystemResource res = new FileSystemResource(context.getFileSystem(), RES_NAME_INDEX);
+                if (res.exists()) {
+                    nameIndex = super.getNameIndex();
+                } else {
+                    // create db nameindex
+                    nameIndex = createDbNameIndex();
+                }
+            }
+            return nameIndex;
+        } catch (Exception e) {
+            throw new IllegalStateException("Unable to create nsIndex: " + e);
+        }
+    }
+
+    /**
+     * Retruns a new instance of a DbNameIndex.
+     * @return a new instance of a DbNameIndex.
+     * @throws SQLException if an SQL error occurs.
+     */
+    protected DbNameIndex createDbNameIndex() throws SQLException {
+        return new DbNameIndex(con, schemaObjectPrefix);
+    }
+
+    /**
+     * returns the storage model
+     * @return the storage model
+     */
+    public int getStorageModel() {
+        return SM_BINARY_KEYS;
+    }
+
+    /**
+     * Creates a blob store that is based on a local fs. This is called by
+     * init if {@link #useLocalFsBlobStore()} returns <code>true</code>.
+     *
+     * @param context
+     * @return a blob store
+     * @throws Exception if an error occurs.
+     */
+    protected CloseableBLOBStore createLocalFSBlobStore(PMContext context)
+            throws Exception {
+        /**
+         * store blob's in local file system in a sub directory
+         * of the workspace home directory
+         */
+        LocalFileSystem blobFS = new LocalFileSystem();
+        blobFS.setRoot(new File(context.getHomeDir(), "blobs"));
+        blobFS.init();
+        return new FSBlobStore(blobFS);
+    }
+
+    /**
+     * Creates a blob store that uses the database. This is called by
+     * init if {@link #useDbBlobStore()} returns <code>true</code>.
+     *
+     * @param context
+     * @return a blob store
+     * @throws Exception if an error occurs.
+     */
+    protected CloseableBLOBStore createDBBlobStore(PMContext context)
+            throws Exception {
+        return new DbBlobStore();
+    }
+
+    /**
+     * Performs a consistency check.
+     */
+    private void checkConsistency() {
+        int count = 0;
+        int total = 0;
+        log.info("{}: checking workspace consistency...", name);
+
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        DataInputStream din = null;
+        try {
+            if (getStorageModel() == SM_BINARY_KEYS) {
+                stmt = con.prepareStatement("select NODE_ID, BUNDLE_DATA from BUNDLE");
+            } else {
+                stmt = con.prepareStatement("select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from BUNDLE");
+            }
+            stmt.execute();
+            rs = stmt.getResultSet();
+            while (rs.next()) {
+                UUID uuid;
+                Blob blob;
+                if (getStorageModel() == SM_BINARY_KEYS) {
+                    uuid = new UUID(rs.getBytes(1));
+                    blob = rs.getBlob(2);
+                } else {
+                    uuid = new UUID(rs.getLong(1), rs.getLong(2));
+                    blob = rs.getBlob(3);
+                }
+                NodeId id = new NodeId(uuid);
+                TrackingInputStream cin =
+                    new TrackingInputStream(blob.getBinaryStream());
+                din = new DataInputStream(cin);
+                try {
+                    NodePropBundle bundle = binding.readBundle(din, id);
+                    bundle.setSize(cin.getPosition());
+                    Iterator iter = bundle.getChildNodeEntries().iterator();
+                    while (iter.hasNext()) {
+                        NodePropBundle.ChildNodeEntry entry = (NodePropBundle.ChildNodeEntry) iter.next();
+                        try {
+                            NodePropBundle child = loadBundle(entry.getId());
+                            if (child == null) {
+                                log.error("NodeState " + id.getUUID() + " references unexistent child " + entry.getName() + " with id " + entry.getId().getUUID());
+                            } else {
+                                NodeId cp = child.getParentId();
+                                if (cp == null) {
+                                    log.error("ChildNode has invalid parent uuid: null");
+                                } else if (!cp.equals(id)) {
+                                    log.error("ChildNode has invalid parent uuid: " + cp + " (instead of " + id.getUUID() + ")");
+                                }
+                            }
+                        } catch (ItemStateException e) {
+                            log.error("Error while loading child node: " + e);
+                        }
+
+                    }
+                    NodeId parentId = bundle.getParentId();
+                    if (parentId != null) {
+                        if (!exists(parentId)) {
+                            log.error("NodeState " + id + " references unexistent parent id " + parentId);
+                        }
+                    }
+                } catch (IOException e) {
+                    log.error("Error in bundle " + uuid + ": " + e);
+                    din = new DataInputStream(blob.getBinaryStream());
+                    binding.checkBundle(din);
+                }
+                count++;
+                if (count % 1000 == 0) {
+                    log.info(name + ": checked " + count + "/" + total + " bundles...");
+                }
+            }
+        } catch (Exception e) {
+            log.error("Error in bundle", e);
+        } finally {
+            closeStream(din);
+            closeResultSet(rs);
+            closeStatement(stmt);
+        }
+
+        log.info(name + ": checked " + count + "/" + total + " bundles.");
+    }
+
+    /**
+     * Makes sure that <code>schemaObjectPrefix</code> does only consist of
+     * characters that are allowed in names on the target database. Illegal
+     * characters will be escaped as necessary.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void prepareSchemaObjectPrefix() throws Exception {
+        DatabaseMetaData metaData = con.getMetaData();
+        String legalChars = metaData.getExtraNameCharacters();
+        legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+
+        String prefix = schemaObjectPrefix.toUpperCase();
+        StringBuffer escaped = new StringBuffer();
+        for (int i = 0; i < prefix.length(); i++) {
+            char c = prefix.charAt(i);
+            if (legalChars.indexOf(c) == -1) {
+                escaped.append("_x");
+                String hex = Integer.toHexString(c);
+                escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+                escaped.append(hex);
+                escaped.append("_");
+            } else {
+                escaped.append(c);
+            }
+        }
+        schemaObjectPrefix = escaped.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void close() throws Exception {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        try {
+            // close shared prepared statements
+            closeStatement(bundleInsert);
+            closeStatement(bundleUpdate);
+            closeStatement(bundleSelect);
+            closeStatement(bundleDelete);
+
+            closeStatement(nodeReferenceInsert);
+            closeStatement(nodeReferenceUpdate);
+            closeStatement(nodeReferenceSelect);
+            closeStatement(nodeReferenceDelete);
+
+            if (nameIndex instanceof DbNameIndex) {
+                ((DbNameIndex) nameIndex).close();
+            }
+
+            // close jdbc connection
+            con.close();
+
+            // close blob store
+            blobStore.close();
+            blobStore = null;
+        } finally {
+            initialized = false;
+        }
+    }
+
+    /**
+     * Sets the key parameters to the prepared statement, starting at
+     * <code>pos</code> and returns the number of key parameters + pos.
+     *
+     * @param stmt
+     * @param uuid
+     * @param pos
+     * @return the number of key parameters + <code>pos</code>
+     * @throws SQLException
+     */
+    protected int setKey(PreparedStatement stmt, UUID uuid, int pos)
+            throws SQLException {
+        if (getStorageModel() == SM_BINARY_KEYS) {
+            stmt.setBytes(pos++, uuid.getRawBytes());
+        } else {
+            stmt.setLong(pos++, uuid.getMostSignificantBits());
+            stmt.setLong(pos++, uuid.getLeastSignificantBits());
+        }
+        return pos;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized NodePropBundle loadBundle(NodeId id)
+            throws ItemStateException {
+        PreparedStatement stmt = bundleSelect;
+        ResultSet rs = null;
+        DataInputStream din = null;
+        try {
+            setKey(stmt, id.getUUID(), 1);
+            stmt.execute();
+            rs = stmt.getResultSet();
+            if (!rs.next()) {
+                return null;
+            }
+            Blob b = rs.getBlob(1);
+            TrackingInputStream cin = new TrackingInputStream(b.getBinaryStream());
+            din = new DataInputStream(cin);
+            NodePropBundle bundle = binding.readBundle(din, id);
+            bundle.setSize(cin.getPosition());
+            return bundle;
+        } catch (Exception e) {
+            String msg = "failed to read bundle: " + id + ": " + e;
+            log.error(msg);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(din);
+            closeResultSet(rs);
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized boolean existsBundle(NodeId id) throws ItemStateException {
+        PreparedStatement stmt = bundleSelect;
+        ResultSet rs = null;
+        try {
+            setKey(stmt, id.getUUID(), 1);
+            stmt.execute();
+            rs = stmt.getResultSet();
+
+            // a bundle exists, if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of bundle: " + id;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized void storeBundle(NodePropBundle bundle) throws ItemStateException {
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            DataOutputStream dout = new DataOutputStream(out);
+            binding.writeBundle(dout, bundle);
+            dout.close();
+
+            if (bundle.isNew()) {
+                stmt = bundleInsert;
+            } else {
+                stmt = bundleUpdate;
+            }
+            stmt.setBytes(1, out.toByteArray());
+            setKey(stmt, bundle.getId().getUUID(), 2);
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to write bundle: " + bundle.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized void destroyBundle(NodePropBundle bundle) throws ItemStateException {
+        PreparedStatement stmt = bundleDelete;
+        try {
+            setKey(stmt, bundle.getId().getUUID(), 1);
+            stmt.execute();
+            // also delete all
+            bundle.removeAllProperties();
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to delete bundle: " + bundle.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        PreparedStatement stmt = nodeReferenceSelect;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            setKey(stmt, targetId.getTargetId().getUUID(), 1);
+            stmt.execute();
+            rs = stmt.getResultSet();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(targetId.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeReferences refs = new NodeReferences(targetId);
+            Serializer.deserialize(refs, in);
+
+            return refs;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read references: " + targetId;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * This method uses shared <code>PreparedStatements</code>, which must
+     * be used strictly sequentially. Because this method synchronizes on the
+     * persistence manager instance, there is no need to synchronize on the
+     * shared statement. If the method would not be sychronized, the shared
+     * statement must be synchronized.
+     *
+     * @see AbstractPersistenceManager#store(NodeReferences)
+     */
+    public synchronized void store(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        PreparedStatement stmt = null;
+        try {
+            // check if insert or update
+            if (exists(refs.getId())) {
+                stmt = nodeReferenceUpdate;
+            } else {
+                stmt = nodeReferenceInsert;
+            }
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize references
+            Serializer.serialize(refs, out);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the preparedStatement
+
+            stmt.setBytes(1, out.toByteArray());
+            setKey(stmt, refs.getTargetId().getUUID(), 2);
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + refs.getTargetId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void destroy(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        PreparedStatement stmt = nodeReferenceDelete;
+        try {
+            setKey(stmt, refs.getTargetId().getUUID(), 1);
+            stmt.execute();
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to delete references: " + refs.getTargetId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized boolean exists(NodeReferencesId targetId) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        PreparedStatement stmt = nodeReferenceSelect;
+        ResultSet rs = null;
+        try {
+            setKey(stmt, targetId.getTargetId().getUUID(), 1);
+            stmt.execute();
+            rs = stmt.getResultSet();
+
+            // a reference exists, if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of node references: " + targetId;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            resetStatement(stmt);
+        }
+    }
+
+    /**
+     * Resets the given <code>PreparedStatement</code> by clearing the
+     * parameters and warnings contained.
+     *
+     * @param stmt The <code>PreparedStatement</code> to reset. If
+     *             <code>null</code> this method does nothing.
+     */
+    protected synchronized void resetStatement(PreparedStatement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.clearParameters();
+                stmt.clearWarnings();
+            } catch (SQLException se) {
+                logException("Failed resetting PreparedStatement", se);
+            }
+        }
+    }
+
+    /**
+     * Closes the result set
+     * @param rs the result set
+     */
+    protected void closeResultSet(ResultSet rs) {
+        if (rs != null) {
+            try {
+                rs.close();
+            } catch (SQLException se) {
+                logException("Failed closing ResultSet", se);
+            }
+        }
+    }
+
+    /**
+     * closes the input stream
+     * @param ins
+     */
+    protected void closeStream(InputStream ins) {
+        if (ins != null) {
+            try {
+                ins.close();
+            } catch (IOException ignore) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * closes the statement
+     * @param stmt
+     */
+    protected void closeStatement(PreparedStatement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.close();
+            } catch (SQLException se) {
+                logException("Failed closing PreparedStatement", se);
+            }
+        }
+    }
+
+    /**
+     * logs an sql exception
+     * @param message
+     * @param se
+     */
+    protected void logException(String message, SQLException se) {
+        if (message != null) {
+            log.error(message);
+        }
+        log.error("       Reason: " + se.getMessage());
+        log.error("   State/Code: " + se.getSQLState() + "/" +
+                se.getErrorCode());
+        log.debug("   dump:", se);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Helper interface for closeable stores
+     */
+    protected static interface CloseableBLOBStore extends BLOBStore {
+        void close();
+    }
+
+    /**
+     * own implementation of the filesystem blob store that uses a different
+     * blob-id scheme.
+     */
+    protected class FSBlobStore extends FileSystemBLOBStore implements CloseableBLOBStore {
+
+        private FileSystem fs;
+
+        public FSBlobStore(FileSystem fs) {
+            super(fs);
+            this.fs = fs;
+        }
+
+        public String createId(PropertyId id, int index) {
+            return buildBlobFilePath(null, id, index).toString();
+        }
+
+        public void close() {
+            try {
+                fs.close();
+                fs = null;
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Implementation of a blob store that stores the data inside the database
+     */
+    protected class DbBlobStore implements CloseableBLOBStore {
+
+        protected PreparedStatement blobInsert;
+        protected PreparedStatement blobUpdate;
+        protected PreparedStatement blobSelect;
+        protected PreparedStatement blobSelectExist;
+        protected PreparedStatement blobDelete;
+
+        public DbBlobStore() throws SQLException {
+            blobInsert =
+                    con.prepareStatement("insert into "
+                    + schemaObjectPrefix + "BINVAL (BINVAL_DATA, BINVAL_ID) values (?, ?)");
+            blobUpdate =
+                    con.prepareStatement("update "
+                    + schemaObjectPrefix + "BINVAL set BINVAL_DATA = ? where BINVAL_ID = ?");
+            blobSelect =
+                    con.prepareStatement("select BINVAL_DATA from "
+                    + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
+            blobSelectExist =
+                    con.prepareStatement("select 1 from "
+                    + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
+            blobDelete =
+                    con.prepareStatement("delete from "
+                    + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public String createId(PropertyId id, int index) {
+            StringBuffer buf = new StringBuffer();
+            buf.append(id.getParentId().toString());
+            buf.append('.');
+            buf.append(getNsIndex().stringToIndex(id.getName().getNamespaceURI()));
+            buf.append('.');
+            buf.append(getNameIndex().stringToIndex(id.getName().getLocalName()));
+            buf.append('.');
+            buf.append(index);
+            return buf.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public InputStream get(String blobId) throws Exception {
+            PreparedStatement stmt = blobSelect;
+            synchronized (stmt) {
+                try {
+                    stmt.setString(1, blobId);
+                    stmt.execute();
+                    final ResultSet rs = stmt.getResultSet();
+                    if (!rs.next()) {
+                        closeResultSet(rs);
+                        throw new Exception("no such BLOB: " + blobId);
+                    }
+                    InputStream in = rs.getBinaryStream(1);
+                    if (in == null) {
+                        // some databases treat zero-length values as NULL;
+                        // return empty InputStream in such a case
+                        closeResultSet(rs);
+                        return new ByteArrayInputStream(new byte[0]);
+                    }
+
+                    /**
+                     * return an InputStream wrapper in order to
+                     * close the ResultSet when the stream is closed
+                     */
+                    return new FilterInputStream(in) {
+                        public void close() throws IOException {
+                            in.close();
+                            // now it's safe to close ResultSet
+                            closeResultSet(rs);
+                        }
+                    };
+                } finally {
+                    resetStatement(stmt);
+                }
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized void put(String blobId, InputStream in, long size)
+                throws Exception {
+            PreparedStatement stmt = blobSelectExist;
+            try {
+                stmt.setString(1, blobId);
+                stmt.execute();
+                ResultSet rs = stmt.getResultSet();
+                // a BLOB exists if the result has at least one entry
+                boolean exists = rs.next();
+                resetStatement(stmt);
+                closeResultSet(rs);
+
+                stmt = (exists) ? blobUpdate : blobInsert;
+                stmt.setBinaryStream(1, in, (int) size);
+                stmt.setString(2, blobId);
+                stmt.executeUpdate();
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized boolean remove(String blobId) throws Exception {
+            PreparedStatement stmt = blobDelete;
+            try {
+                stmt.setString(1, blobId);
+                return stmt.executeUpdate() == 1;
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+
+        public void close() {
+            closeStatement(blobInsert);
+            closeStatement(blobUpdate);
+            closeStatement(blobSelect);
+            closeStatement(blobSelectExist);
+            closeStatement(blobDelete);
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url rev



Mime
View raw message