You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2004/10/27 17:47:15 UTC

svn commit: rev 55707 - in incubator/jackrabbit/trunk: . src/java/org/apache/jackrabbit/core src/java/org/apache/jackrabbit/core/state src/java/org/apache/jackrabbit/core/state/obj src/java/org/apache/jackrabbit/core/state/xml xdocs

Author: stefan
Date: Wed Oct 27 08:47:13 2004
New Revision: 55707

Added:
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/BLOBStore.java   (contents, props changed)
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/ObjectPersistenceManager.java   (contents, props changed)
Modified:
   incubator/jackrabbit/trunk/ToDo.txt
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/InternalValue.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/RepositoryImpl.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/SessionImpl.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Test.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PersistenceManager.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/xml/XMLPersistenceManager.java
   incubator/jackrabbit/trunk/xdocs/tasks.xml
Log:
- added new files-system based PersistenceManager 
  (org.apache.jackrabbit.core.state.obj.ObjectPersistenceManager)
  implementation that uses more efficient serialization format
- misc. minor fixes
- updated todo.txt and tasks.xml


Modified: incubator/jackrabbit/trunk/ToDo.txt
==============================================================================
--- incubator/jackrabbit/trunk/ToDo.txt	(original)
+++ incubator/jackrabbit/trunk/ToDo.txt	Wed Oct 27 08:47:13 2004
@@ -5,7 +5,6 @@
 is it complete :(
 
 - locking
-- jta support
 - access control (jaas)
 - current persistence model (nodes and properties are stored in separate
   files) leads to *very* slow performance in a normal filesystem;
@@ -25,7 +24,6 @@
 - HierarchyManager: cache Path objects (key: ItemId, value: Path[]);
   update cache on hierarchy changes (move, hardlink, remove, etc) 
 - inline @todo comments: resolve/implement
-- ItemState: hide STATUS_* flags, use set* and is* methods instead
 - javaDoc, javaDoc, javaDoc
 - logging: use commons logging instead of log4j
 - logging: remove unnecessary output, check log categories/verbosity, 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/InternalValue.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/InternalValue.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/InternalValue.java	Wed Oct 27 08:47:13 2004
@@ -220,7 +220,7 @@
      */
     public static InternalValue[] create(QName[] values) {
         InternalValue[] ret = new InternalValue[values.length];
-        for (int i=0; i<values.length; i++) {
+        for (int i = 0; i < values.length; i++) {
             ret[i] = new InternalValue(values[i]);
         }
         return ret;
@@ -232,7 +232,7 @@
      */
     public static InternalValue[] create(String[] values) {
         InternalValue[] ret = new InternalValue[values.length];
-        for (int i=0; i<values.length; i++) {
+        for (int i = 0; i < values.length; i++) {
             ret[i] = new InternalValue(values[i]);
         }
         return ret;

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java	Wed Oct 27 08:47:13 2004
@@ -1089,7 +1089,6 @@
          */
         ReferenceManager refMgr = wsp.getReferenceManager();
         synchronized (refMgr) {
-
             /**
              * build list of transient descendents in the attic
              * (i.e. those marked as 'removed')

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/RepositoryImpl.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/RepositoryImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/RepositoryImpl.java	Wed Oct 27 08:47:13 2004
@@ -185,15 +185,7 @@
                             // ignore
                         }
                     }
-                    /**
-                     * use hard-coded uuid for root node rather than generating
-                     * a different uuid per repository instance; using a
-                     * hard-coded uuid makes it easier to copy/move entire
-                     * workspaces from one repository instance to another.
-                     */
-                    //rootNodeUUID = new UUID(new String(chars)).toString();
-                    rootNodeUUID = ROOT_NODE_UUID;
-
+                    rootNodeUUID = new UUID(new String(chars)).toString();
                 } catch (Exception e) {
                     String msg = "failed to load persisted repository state";
                     log.error(msg, e);
@@ -201,8 +193,17 @@
                 }
             } else {
                 // create new uuid
+/*
                 UUID rootUUID = UUID.randomUUID();     // version 4 uuid
                 rootNodeUUID = rootUUID.toString();
+*/
+                /**
+                 * use hard-coded uuid for root node rather than generating
+                 * a different uuid per repository instance; using a
+                 * hard-coded uuid makes it easier to copy/move entire
+                 * workspaces from one repository instance to another.
+                 */
+                rootNodeUUID = ROOT_NODE_UUID;
                 try {
                     // persist uuid of the repository's root node
                     OutputStream out = uuidFile.getOutputStream();
@@ -221,7 +222,7 @@
                     // store uuid in text format for better readability
                     OutputStreamWriter writer = new OutputStreamWriter(out);
                     try {
-                        writer.write(rootUUID.toString());
+                        writer.write(rootNodeUUID);
                     } finally {
                         try {
                             writer.close();
@@ -536,13 +537,6 @@
             return;
         }
 
-        // persist repository properties
-        try {
-            storeRepProps();
-        } catch (RepositoryException e) {
-            log.error("failed to persist repository properties", e);
-        }
-
         // stop / dispose all ObservationManagers
         for (Iterator it = wspObsMgrFactory.values().iterator(); it.hasNext();) {
             ObservationManagerFactory obsMgr = (ObservationManagerFactory) it.next();
@@ -556,14 +550,37 @@
         }
 
         /**
-         * todo free resources, shutdown workspaces, close sessions,
-         * shutdown item state mgr's, persistence mgr's, etc.
+         * todo close sessions, close item state mgr's, free resources, etc.
          */
+
+        for (Iterator it = wspConfigs.values().iterator(); it.hasNext();) {
+            WorkspaceConfig wspConfig = (WorkspaceConfig) it.next();
+            try {
+                // close workspace file system
+                wspConfig.getFileSystem().close();
+            } catch (FileSystemException e) {
+                log.error("Error while closing filesystem of workspace " + wspConfig.getName(), e);
+            }
+            try {
+                // close persistence manager
+                wspConfig.getPersistenceManager().close();
+            } catch (Exception e) {
+                log.error("Error while closing persistence manager of workspace " + wspConfig.getName(), e);
+            }
+        }
+
+        // persist repository properties
+        try {
+            storeRepProps();
+        } catch (RepositoryException e) {
+            log.error("failed to persist repository properties", e);
+        }
+
         try {
-            // close master file system (this will also invalidate sub file systems)
+            // close repository file system
             repStore.close();
         } catch (FileSystemException e) {
-            log.error("Error while closing filesystem", e);
+            log.error("Error while closing repository filesystem", e);
         }
 
         // make sure this instance is not used anymore

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/SessionImpl.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/SessionImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/SessionImpl.java	Wed Oct 27 08:47:13 2004
@@ -121,7 +121,6 @@
                           WorkspaceConfig wspConfig)
             throws RepositoryException {
         this.rep = rep;
-
         if (credentials instanceof SimpleCredentials) {
             SimpleCredentials sc = (SimpleCredentials) credentials;
             // clear password for security reasons
@@ -139,14 +138,11 @@
         } else {
             userId = null;
         }
-
         nsMappings = new TransientNamespaceMappings(rep.getNamespaceRegistry());
-
         ntMgr = new NodeTypeManagerImpl(rep.getNodeTypeRegistry(), getNamespaceResolver());
         String wspName = wspConfig.getName();
         wsp = new WorkspaceImpl(wspConfig, rep.getWorkspaceStateManager(wspName),
                 rep.getWorkspaceReferenceManager(wspName), rep, this);
-
         itemStateMgr = createSessionItemStateManager(wsp.getPersistentStateManager());
         hierMgr = itemStateMgr.getHierarchyMgr();
         itemMgr = createItemManager(itemStateMgr, hierMgr);
@@ -173,11 +169,8 @@
     protected SessionImpl(RepositoryImpl rep, String userId, WorkspaceConfig wspConfig)
             throws RepositoryException {
         this.rep = rep;
-
         this.userId = userId;
-
         nsMappings = new TransientNamespaceMappings(rep.getNamespaceRegistry());
-
         ntMgr = new NodeTypeManagerImpl(rep.getNodeTypeRegistry(), getNamespaceResolver());
         String wspName = wspConfig.getName();
         wsp = new WorkspaceImpl(wspConfig, rep.getWorkspaceStateManager(wspName),
@@ -203,7 +196,6 @@
      * @return session item state manager
      */
     protected SessionItemStateManager createSessionItemStateManager(PersistentItemStateProvider provider) {
-
         return new SessionItemStateManager(rep.getRootNodeUUID(), provider, getNamespaceResolver());
     }
 
@@ -214,7 +206,6 @@
      */
     protected ItemManager createItemManager(SessionItemStateManager itemStateMgr,
                                             HierarchyManager hierMgr) {
-
         return new ItemManager(itemStateMgr, hierMgr, this,
                 ntMgr.getRootNodeDefinition(), rep.getRootNodeUUID());
     }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Test.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Test.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Test.java	Wed Oct 27 08:47:13 2004
@@ -243,7 +243,7 @@
             imported = root.getNode("imported");
         }
 
-        importNode(new File("d:/dev/jsr170/jackrabbit/src/java"), imported);
+        //importNode(new File("d:/dev/jsr170/jackrabbit/src/java"), imported);
 
         if (root.hasNode("foo")) {
             root.getNode("foo").remove();
@@ -294,7 +294,7 @@
         System.out.println("repository properties:");
         System.out.println(repProps);
 
-        //((RepositoryImpl) r).shutdown();
+        ((RepositoryImpl) r).shutdown();
     }
 
     public static void importNode(File file, Node parent) throws Exception {

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PersistenceManager.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PersistenceManager.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PersistenceManager.java	Wed Oct 27 08:47:13 2004
@@ -29,6 +29,11 @@
     public void init(WorkspaceConfig wspConfig) throws Exception;
 
     /**
+     * @throws Exception
+     */
+    public void close() throws Exception;
+
+    /**
      * @param state
      * @throws NoSuchItemStateException
      * @throws ItemStateException

Added: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/BLOBStore.java
==============================================================================
--- (empty file)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/BLOBStore.java	Wed Oct 27 08:47:13 2004
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * Licensed 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.state.obj;
+
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.PropertyId;
+
+import java.io.InputStream;
+
+/**
+ * <code>BLOBStore</code> ...
+ */
+public interface BLOBStore {
+    /**
+     *
+     * @param id id of the property associated with the blob data
+     * @param index subscript of the value holding the blob data
+     * @param in
+     * @param size
+     * @return a string identifying the blob data
+     * @throws Exception
+     */
+    public String put(PropertyId id, int index, InputStream in, long size) throws Exception;
+
+    /**
+     *
+     * @param blobId
+     * @return
+     * @throws Exception
+     */
+    public FileSystemResource get(String blobId) throws Exception;
+
+    /**
+     *
+     * @param blobId
+     * @return
+     * @throws Exception
+     */
+    public boolean remove(String blobId) throws Exception;
+}

Added: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/ObjectPersistenceManager.java
==============================================================================
--- (empty file)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/obj/ObjectPersistenceManager.java	Wed Oct 27 08:47:13 2004
@@ -0,0 +1,746 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * Licensed 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.state.obj;
+
+import org.apache.jackrabbit.core.BLOBFileValue;
+import org.apache.jackrabbit.core.InternalValue;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.QName;
+import org.apache.jackrabbit.core.config.WorkspaceConfig;
+import org.apache.jackrabbit.core.fs.*;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
+import org.apache.jackrabbit.core.nodetype.NodeDefId;
+import org.apache.jackrabbit.core.nodetype.PropDefId;
+import org.apache.jackrabbit.core.state.*;
+import org.apache.log4j.Logger;
+
+import javax.jcr.PropertyType;
+import java.io.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+/**
+ * <code>ObjectPersistenceManager</code> ...
+ */
+public class ObjectPersistenceManager implements BLOBStore, PersistenceManager {
+
+    private static Logger log = Logger.getLogger(ObjectPersistenceManager.class);
+
+    /**
+     * hexdigits for toString
+     */
+    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
+
+    /**
+     * The default encoding used in serialization
+     */
+    private static final String NODEFILENAME = ".node";
+
+    private static final String NODEREFSFILENAME = ".references";
+
+    private boolean initialized;
+
+    // file system where the item state is stored
+    private FileSystem itemStateFS;
+    // file system where BLOB data is stored
+    private FileSystem blobFS;
+
+    /**
+     * Creates a new <code>ObjectPersistenceManager</code> instance.
+     */
+    public ObjectPersistenceManager() {
+        initialized = false;
+    }
+
+    private static String buildNodeFolderPath(String uuid) {
+        StringBuffer sb = new StringBuffer();
+        char[] chars = uuid.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) {
+                sb.append('/');
+            }
+            sb.append(chars[i]);
+            cnt++;
+        }
+        return sb.toString();
+    }
+
+    private static String buildPropFilePath(String parentUUID, QName propName) {
+        String fileName;
+        try {
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            md5.update(propName.getNamespaceURI().getBytes());
+            md5.update(propName.getLocalName().getBytes());
+            byte[] bytes = md5.digest();
+            char[] chars = new char[32];
+            for (int i = 0, j = 0; i < 16; i++) {
+                chars[j++] = HEXDIGITS[(bytes[i] >> 4) & 0x0f];
+                chars[j++] = HEXDIGITS[bytes[i] & 0x0f];
+            }
+            fileName = new String(chars);
+        } catch (NoSuchAlgorithmException nsae) {
+            // should never get here as MD5 should always be available in the JRE
+            String msg = "MD5 not available: ";
+            log.fatal(msg, nsae);
+            throw new InternalError(msg + nsae);
+        }
+        return buildNodeFolderPath(parentUUID) + "/" + fileName;
+    }
+
+    private static String buildBlobFilePath(String parentUUID, QName propName, int i) {
+        return buildNodeFolderPath(parentUUID) + "/"
+                + FileSystemPathUtil.escapeName(propName.toString()) + "." + i + ".bin";
+    }
+
+    private static String buildNodeFilePath(String uuid) {
+        return buildNodeFolderPath(uuid) + "/" + NODEFILENAME;
+    }
+
+    private static String buildNodeReferencesFilePath(String uuid) {
+        return buildNodeFolderPath(uuid) + "/" + NODEREFSFILENAME;
+    }
+
+    //------------------------------------------------< static helper methods >
+    /**
+     * Serializes the specified <code>state</code> object to the given
+     * <code>stream</code>.
+     *
+     * @param state  <code>state</code> to serialize
+     * @param stream the stream where the <code>state</code> should be serialized to
+     * @throws Exception if an error occurs during the serialization
+     * @see #deserialize(PersistentNodeState, InputStream)
+     */
+    public static void serialize(PersistentNodeState state, OutputStream stream)
+            throws Exception {
+        DataOutputStream out = new DataOutputStream(stream);
+        // uuid
+        out.writeUTF(state.getUUID());
+        // primaryType
+        out.writeUTF(state.getNodeTypeName().toString());
+        // parentUUID
+        out.writeUTF(state.getParentUUID() == null ? "" : state.getParentUUID());
+        // definitionId
+        out.writeUTF(state.getDefinitionId().toString());
+        // parentUUIDs
+        Collection c = state.getParentUUIDs();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            out.writeUTF((String) iter.next()); // uuid
+        }
+        // mixin types
+        c = state.getMixinTypeNames();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            out.writeUTF(iter.next().toString());   // name
+        }
+        // properties (names)
+        c = state.getPropertyEntries();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            NodeState.PropertyEntry entry = (NodeState.PropertyEntry) iter.next();
+            out.writeUTF(entry.getName().toString());   // name
+        }
+        // child nodes (list of name/uuid pairs)
+        c = state.getChildNodeEntries();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) iter.next();
+            out.writeUTF(entry.getName().toString());   // name
+            out.writeUTF(entry.getUUID());  // uuid
+        }
+    }
+
+    /**
+     * Deserializes a <code>state</code> object from the given <code>stream</code>.
+     *
+     * @param state  <code>state</code> to deserialize
+     * @param stream the stream where the <code>state</code> should be deserialized from
+     * @throws Exception if an error occurs during the deserialization
+     * @see #serialize(PersistentNodeState, OutputStream)
+     */
+    public static void deserialize(PersistentNodeState state, InputStream stream)
+            throws Exception {
+        DataInputStream in = new DataInputStream(stream);
+        // check uuid
+        String s = in.readUTF();
+        if (!state.getUUID().equals(s)) {
+            String msg = "invalid serialized state: uuid mismatch";
+            log.error(msg);
+            throw new ItemStateException(msg);
+        }
+
+        // deserialize node state
+
+        // primaryType
+        s = in.readUTF();
+        state.setNodeTypeName(QName.valueOf(s));
+        // parentUUID
+        s = in.readUTF();
+        if (s.length() > 0) {
+            state.setParentUUID(s);
+        }
+        // definitionId
+        s = in.readUTF();
+        state.setDefinitionId(NodeDefId.valueOf(s));
+        // parentUUIDs
+        int count = in.readInt();   // count
+        List list = new ArrayList(count);
+        for (int i = 0; i < count; i++) {
+            list.add(in.readUTF()); // uuid
+        }
+        if (list.size() > 0) {
+            state.setParentUUIDs(list);
+        }
+        // mixin types
+        count = in.readInt();   // count
+        Set set = new HashSet(count);
+        for (int i = 0; i < count; i++) {
+            set.add(QName.valueOf(in.readUTF())); // name
+        }
+        if (set.size() > 0) {
+            state.setMixinTypeNames(set);
+        }
+        // properties (names)
+        count = in.readInt();   // count
+        for (int i = 0; i < count; i++) {
+            state.addPropertyEntry(QName.valueOf(in.readUTF())); // name
+        }
+        // child nodes (list of name/uuid pairs)
+        count = in.readInt();   // count
+        for (int i = 0; i < count; i++) {
+            QName name = QName.valueOf(in.readUTF());    // name
+            String s1 = in.readUTF();   // uuid
+            state.addChildNodeEntry(name, s1);
+        }
+    }
+
+    /**
+     * Serializes the specified <code>state</code> object to the given
+     * <code>stream</code>.
+     *
+     * @param state     <code>state</code> to serialize
+     * @param stream    the stream where the <code>state</code> should be serialized to
+     * @param blobStore handler for blob data
+     * @throws Exception if an error occurs during the serialization
+     * @see #deserialize(PersistentPropertyState, InputStream, BLOBStore)
+     */
+    public static void serialize(PersistentPropertyState state,
+                                 OutputStream stream,
+                                 BLOBStore blobStore)
+            throws Exception {
+        DataOutputStream out = new DataOutputStream(stream);
+        // type
+        out.writeInt(state.getType());
+        // definitionId
+        out.writeUTF(state.getDefinitionId().toString());
+        // values
+        InternalValue[] values = state.getValues();
+        out.writeInt(values.length); // count
+        for (int i = 0; i < values.length; i++) {
+            InternalValue val = values[i];
+            if (state.getType() == PropertyType.BINARY) {
+                // special handling required for binary value:
+                // spool binary value to file in blob store
+                BLOBFileValue blobVal = (BLOBFileValue) val.internalValue();
+                String blobId = blobStore.put((PropertyId) state.getId(), i, blobVal.getStream(), blobVal.getLength());
+                // store id of blob as property value
+                out.writeUTF(blobId);   // value
+                // replace value instance with value
+                // backed by resource in blob store and delete temp file
+                values[i] = InternalValue.create(blobStore.get(blobId));
+                if (blobVal.isTempFile()) {
+                    blobVal.delete();
+                    blobVal = null;
+                }
+            } else {
+                out.writeUTF(val.toString());   // value
+            }
+        }
+    }
+
+    /**
+     * Deserializes a <code>state</code> object from the given <code>stream</code>.
+     *
+     * @param state     <code>state</code> to deserialize
+     * @param stream    the stream where the <code>state</code> should be deserialized from
+     * @param blobStore handler for blob data
+     * @throws Exception if an error occurs during the deserialization
+     * @see #serialize(PersistentPropertyState, OutputStream, BLOBStore)
+     */
+    public static void deserialize(PersistentPropertyState state,
+                                   InputStream stream,
+                                   BLOBStore blobStore)
+            throws Exception {
+        DataInputStream in = new DataInputStream(stream);
+
+        // type
+        int type = in.readInt();
+        state.setType(type);
+        // definitionId
+        String s = in.readUTF();
+        state.setDefinitionId(PropDefId.valueOf(s));
+        // values
+        int count = in.readInt();   // count
+        InternalValue[] values = new InternalValue[count];
+        for (int i = 0; i < count; i++) {
+            InternalValue val;
+            s = in.readUTF();   // value
+            if (type == PropertyType.BINARY) {
+                // special handling required for binary value:
+                // the value stores the id of the blob resource in the blob store
+                val = InternalValue.create(blobStore.get(s));
+            } else {
+                val = InternalValue.valueOf(s, type);
+            }
+            values[i] = val;
+        }
+        state.setValues(values);
+    }
+
+    /**
+     * Serializes the specified <code>NodeReferences</code> object to the given
+     * <code>stream</code>.
+     *
+     * @param refs   object to serialize
+     * @param stream the stream where the object should be serialized to
+     * @throws IOException if an error occurs during the serialization
+     * @see #deserialize(NodeReferences, InputStream)
+     */
+    public static void serialize(NodeReferences refs, OutputStream stream)
+            throws IOException {
+        DataOutputStream out = new DataOutputStream(stream);
+
+        // references
+        Collection c = refs.getReferences();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            PropertyId propId = (PropertyId) iter.next();
+            out.writeUTF(propId.toString());   // propertyId
+        }
+    }
+
+    /**
+     * Deserializes a <code>NodeReferences</code> object from the given
+     * <code>stream</code>.
+     *
+     * @param refs   object to deserialize
+     * @param stream the stream where the object should be deserialized from
+     * @throws Exception if an error occurs during the deserialization
+     * @see #serialize(NodeReferences, OutputStream)
+     */
+    public static void deserialize(NodeReferences refs, InputStream stream)
+            throws Exception {
+        DataInputStream in = new DataInputStream(stream);
+
+        refs.clearAllReferences();
+
+        // references
+        int count = in.readInt();   // count
+        for (int i = 0; i < count; i++) {
+            refs.addReference(PropertyId.valueOf(in.readUTF()));    // propertyId
+        }
+    }
+
+    //------------------------------------------------------------< BLOBStore >
+    /**
+     * @see BLOBStore#get
+     */
+    public FileSystemResource get(String blobId) throws Exception {
+        return new FileSystemResource(blobFS, blobId);
+    }
+
+    /**
+     * @see BLOBStore#put
+     */
+    public String put(PropertyId id, int index, InputStream in, long size) throws Exception {
+        String path = buildBlobFilePath(id.getParentUUID(), id.getName(), index);
+        OutputStream out = null;
+        FileSystemResource internalBlobFile = new FileSystemResource(blobFS, path);
+        internalBlobFile.makeParentDirs();
+        try {
+            out = new BufferedOutputStream(internalBlobFile.getOutputStream());
+            byte[] buffer = new byte[8192];
+            int read = 0;
+            while ((read = in.read(buffer)) > 0) {
+                out.write(buffer, 0, read);
+            }
+        } finally {
+            out.close();
+        }
+        return path;
+    }
+
+    /**
+     * @see BLOBStore#remove
+     */
+    public boolean remove(String blobId) throws Exception {
+        FileSystemResource res = new FileSystemResource(blobFS, blobId);
+        if (!res.exists()) {
+            return false;
+        }
+        res.delete();
+        // prune empty folders
+        String parentDir = FileSystemPathUtil.getParentDir(blobId);
+        while (!parentDir.equals(FileSystem.SEPARATOR)
+                && !blobFS.hasChildren(parentDir)) {
+            blobFS.deleteFolder(parentDir);
+            parentDir = FileSystemPathUtil.getParentDir(parentDir);
+        }
+        return true;
+    }
+
+    //---------------------------------------------------< PersistenceManager >
+    /**
+     * @see PersistenceManager#init
+     */
+    public void init(WorkspaceConfig wspConfig) throws Exception {
+        FileSystem wspFS = wspConfig.getFileSystem();
+        itemStateFS = new BasedFileSystem(wspFS, "/data");
+
+        /**
+         * store blob's in local file system in a sub directory
+         * of the workspace home directory
+         */
+        LocalFileSystem blobFS = new LocalFileSystem();
+        blobFS.setPath(wspConfig.getHomeDir() + "/blobs");
+        blobFS.init();
+        this.blobFS = blobFS;
+
+        initialized = true;
+    }
+
+    /**
+     * @see PersistenceManager#close
+     */
+    public synchronized void close() throws Exception {
+        if (!initialized) {
+            return;
+        }
+
+        try {
+            // close blob store
+            blobFS.close();
+            /**
+             * there's no need close the item state store because it
+             * is based in the workspace's file system which is
+             * closed by the repository
+             */
+        } finally {
+            initialized = false;
+        }
+    }
+
+    /**
+     * @see PersistenceManager#load(PersistentNodeState)
+     */
+    public synchronized void load(PersistentNodeState state)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = state.getUUID();
+        String nodeFilePath = buildNodeFilePath(uuid);
+
+        try {
+            if (!itemStateFS.isFile(nodeFilePath)) {
+                throw new NoSuchItemStateException(uuid);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to read node state: " + uuid;
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+
+        try {
+            BufferedInputStream in =
+                    new BufferedInputStream(itemStateFS.getInputStream(nodeFilePath));
+            try {
+                deserialize(state, in);
+                return;
+            } catch (Exception e) {
+                String msg = "failed to read node state: " + uuid;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                in.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to read node state: " + uuid;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#load(PersistentPropertyState)
+     */
+    public synchronized void load(PersistentPropertyState state)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String parentUUID = state.getParentUUID();
+        QName propName = state.getName();
+        String propFilePath = buildPropFilePath(parentUUID, propName);
+
+        try {
+            if (!itemStateFS.isFile(propFilePath)) {
+                throw new NoSuchItemStateException(parentUUID + "/" + propName);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to read property state: " + parentUUID + "/" + propName;
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+
+        try {
+            BufferedInputStream in =
+                    new BufferedInputStream(itemStateFS.getInputStream(propFilePath));
+            try {
+                deserialize(state, in, this);
+                return;
+            } finally {
+                in.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to read property state: " + parentUUID + "/" + propName;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#store
+     */
+    public synchronized void store(PersistentNodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = state.getUUID();
+        String nodeFilePath = buildNodeFilePath(uuid);
+        FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath);
+        try {
+            nodeFile.makeParentDirs();
+            BufferedOutputStream out = new BufferedOutputStream(nodeFile.getOutputStream());
+            try {
+                // serialize node state
+                serialize(state, out);
+                return;
+            } finally {
+                out.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + uuid;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#store
+     */
+    public synchronized void store(PersistentPropertyState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String propFilePath = buildPropFilePath(state.getParentUUID(), state.getName());
+        FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath);
+        try {
+            propFile.makeParentDirs();
+            BufferedOutputStream out = new BufferedOutputStream(propFile.getOutputStream());
+            try {
+                // serialize property state
+                serialize(state, out, this);
+                return;
+            } finally {
+                out.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to store property state: " + state.getParentUUID() + "/" + state.getName();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#destroy
+     */
+    public synchronized void destroy(PersistentNodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = state.getUUID();
+        String nodeFilePath = buildNodeFilePath(uuid);
+        FileSystemResource nodeFile = new FileSystemResource(itemStateFS, nodeFilePath);
+        try {
+            nodeFile.delete();
+            // prune empty folders
+            String parentDir = FileSystemPathUtil.getParentDir(nodeFilePath);
+            while (!parentDir.equals(FileSystem.SEPARATOR)
+                    && !itemStateFS.hasChildren(parentDir)) {
+                itemStateFS.deleteFolder(parentDir);
+                parentDir = FileSystemPathUtil.getParentDir(parentDir);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to delete node state: " + uuid;
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#destroy
+     */
+    public synchronized void destroy(PersistentPropertyState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // delete binary values (stored as files)
+        InternalValue[] values = state.getValues();
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                if (val != null) {
+                    if (val.getType() == PropertyType.BINARY) {
+                        BLOBFileValue blobVal = (BLOBFileValue) val.internalValue();
+                        blobVal.delete();
+                    }
+                }
+            }
+        }
+        // delete property file
+        String propFilePath = buildPropFilePath(state.getParentUUID(), state.getName());
+        FileSystemResource propFile = new FileSystemResource(itemStateFS, propFilePath);
+        try {
+            propFile.delete();
+            // prune empty folders
+            String parentDir = FileSystemPathUtil.getParentDir(propFilePath);
+            while (!parentDir.equals(FileSystem.SEPARATOR)
+                    && !itemStateFS.hasChildren(parentDir)) {
+                itemStateFS.deleteFolder(parentDir);
+                parentDir = FileSystemPathUtil.getParentDir(parentDir);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to delete property state: " + state.getParentUUID() + "/" + state.getName();
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#load(NodeReferences)
+     */
+    public void load(NodeReferences refs)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = refs.getTargetId().getUUID();
+        String refsFilePath = buildNodeReferencesFilePath(uuid);
+
+        try {
+            if (!itemStateFS.isFile(refsFilePath)) {
+                throw new NoSuchItemStateException(uuid);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to load references: " + uuid;
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+
+        try {
+            BufferedInputStream in =
+                    new BufferedInputStream(itemStateFS.getInputStream(refsFilePath));
+            try {
+                deserialize(refs, in);
+                return;
+            } finally {
+                in.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to load references: " + uuid;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#store(NodeReferences)
+     */
+    public void store(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = refs.getTargetId().getUUID();
+        String refsFilePath = buildNodeReferencesFilePath(uuid);
+        FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath);
+        try {
+            refsFile.makeParentDirs();
+            OutputStream out = new BufferedOutputStream(refsFile.getOutputStream());
+            try {
+                serialize(refs, out);
+            } finally {
+                out.close();
+            }
+        } catch (Exception e) {
+            String msg = "failed to store references: " + uuid;
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * @see PersistenceManager#destroy(NodeReferences)
+     */
+    public void destroy(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        String uuid = refs.getTargetId().getUUID();
+        String refsFilePath = buildNodeReferencesFilePath(uuid);
+        FileSystemResource refsFile = new FileSystemResource(itemStateFS, refsFilePath);
+        try {
+            refsFile.delete();
+            // prune empty folders
+            String parentDir = FileSystemPathUtil.getParentDir(refsFilePath);
+            while (!parentDir.equals(FileSystem.SEPARATOR)
+                    && !itemStateFS.hasChildren(parentDir)) {
+                itemStateFS.deleteFolder(parentDir);
+                parentDir = FileSystemPathUtil.getParentDir(parentDir);
+            }
+        } catch (FileSystemException fse) {
+            String msg = "failed to delete references: " + uuid;
+            log.error(msg, fse);
+            throw new ItemStateException(msg, fse);
+        }
+    }
+}

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/xml/XMLPersistenceManager.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/xml/XMLPersistenceManager.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/xml/XMLPersistenceManager.java	Wed Oct 27 08:47:13 2004
@@ -267,7 +267,7 @@
                     try {
                         val = InternalValue.create(new FileSystemResource(blobStore, text));
                     } catch (IOException ioe) {
-                        String msg = "error while reading serialized binary valuey";
+                        String msg = "error while reading serialized binary value";
                         log.error(msg, ioe);
                         throw new ItemStateException(msg, ioe);
                     }
@@ -306,6 +306,27 @@
     }
 
     /**
+     * @see PersistenceManager#close
+     */
+    public synchronized void close() throws Exception {
+        if (!initialized) {
+            return;
+        }
+
+        try {
+            // close blob store
+            blobStore.close();
+            /**
+             * there's no need close the item state store because it
+             * is based in the workspace's file system which is
+             * closed by the repository
+             */
+        } finally {
+            initialized = false;
+        }
+    }
+
+    /**
      * @see PersistenceManager#load(PersistentNodeState)
      */
     public synchronized void load(PersistentNodeState state)
@@ -333,7 +354,6 @@
 
                 readState(rootElement, state);
                 return;
-
             } finally {
                 in.close();
             }
@@ -377,7 +397,6 @@
                 props.load(in);
                 readState(props, state);
                 return;
-
             } finally {
                 in.close();
             }
@@ -636,7 +655,6 @@
                             }
                             // store path to binary file as property value
                             props.setProperty(Integer.toString(i), binPath);
-                            // FIXME: hack!
                             // replace value instance with value
                             // backed by internal file and delete temp file
                             values[i] = InternalValue.create(internalBlobFile);

Modified: incubator/jackrabbit/trunk/xdocs/tasks.xml
==============================================================================
--- incubator/jackrabbit/trunk/xdocs/tasks.xml	(original)
+++ incubator/jackrabbit/trunk/xdocs/tasks.xml	Wed Oct 27 08:47:13 2004
@@ -10,9 +10,6 @@
         	locking
         </task>
         <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
-        	jta support
-        </task>
-        <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
         	access control (jaas)
         </task>
         <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
@@ -43,9 +40,6 @@
   		update cache on hierarchy changes (move, hardlink, remove, etc) 
         <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
         	inline @todo comments: resolve/implement
-        </task>
-        <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
-    		ItemState: hide STATUS_* flags, use set* and is* methods instead
         </task>
         <task creator="stefan" assignedto="" startdate="" enddate="" effort="" status="">
     		javaDoc, javaDoc, javaDoc