You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2010/04/23 15:43:37 UTC

svn commit: r937293 [2/3] - in /jackrabbit/sandbox/jackrabbit-j3: ./ src/main/java/org/apache/jackrabbit/j3/ src/main/java/org/apache/jackrabbit/j3/api/ src/main/java/org/apache/jackrabbit/j3/api/management/ src/main/java/org/apache/jackrabbit/j3/api/o...

Added: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/GarbageCollector.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/GarbageCollector.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/GarbageCollector.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/GarbageCollector.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,355 @@
+/*
+ * 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.j3.data;
+
+import org.apache.jackrabbit.j3.RepositoryImpl;
+import org.apache.jackrabbit.j3.SessionImpl;
+import org.apache.jackrabbit.j3.SessionListener;
+import org.apache.jackrabbit.j3.api.management.DataStoreGarbageCollector;
+import org.apache.jackrabbit.j3.api.management.MarkEventListener;
+import org.apache.jackrabbit.j3.api.observation.SynchronousEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.ObservationManager;
+
+/**
+ * Garbage collector for DataStore. This implementation is iterates through all
+ * nodes and reads the binary properties. To detect nodes that are moved while
+ * the scan runs, event listeners are started. Like the well known garbage
+ * collection in Java, the items that are still in use are marked. Currently
+ * this achieved by updating the modified date of the entries. Newly added
+ * entries are detected because the modified date is changed when they are
+ * added.
+ * <p>
+ * Example code to run the data store garbage collection:
+ * <pre>
+ * JackrabbitRepositoryFactory jf = (JackrabbitRepositoryFactory) factory;
+ * RepositoryManager m = factory.getRepositoryManager((JackrabbitRepository) rep);
+ * GarbageCollector gc = m.createDataStoreGarbageCollector();
+ * try {
+ *     gc.mark();
+ *     gc.sweep();
+ * } finally {
+ *     gc.close();
+ * }
+ * </pre>
+ */
+public class GarbageCollector implements DataStoreGarbageCollector {
+
+    /** logger instance */
+    private static final Logger LOG = LoggerFactory.getLogger(GarbageCollector.class);
+
+    private MarkEventListener callback;
+
+    private long sleepBetweenNodes;
+
+    private int testDelay;
+
+    private final DataStore store;
+
+    private long startScanTimestamp;
+
+    private final ArrayList<Listener> listeners = new ArrayList<Listener>();
+
+    private final Session[] sessionList;
+    private SessionListener sessionListener;
+
+    private final AtomicBoolean closed = new AtomicBoolean();
+
+    /**
+     * Create a new garbage collector.
+     * This method is usually not called by the application, it is called
+     * by SessionImpl.createDataStoreGarbageCollector().
+     *
+     * @param rep the repository
+     * @param session the session that created this object (optional)
+     * @param list the persistence managers
+     * @param sessionList the sessions to access the workspaces
+     */
+    public GarbageCollector(RepositoryImpl rep, SessionImpl session, Session[] sessionList) {
+        store = rep.getDataStore();
+        this.sessionList = sessionList;
+
+        if (session != null) {
+            // Auto-close if the main session logs out
+            this.sessionListener = new SessionListener() {
+                public void loggedOut(Session session) {
+                }
+                public void loggingOut(Session session) {
+                    close();
+                }
+            };
+            session.addListener(sessionListener);
+        }
+    }
+
+    public void setSleepBetweenNodes(long millis) {
+        this.sleepBetweenNodes = millis;
+    }
+
+    public long getSleepBetweenNodes() {
+        return sleepBetweenNodes;
+    }
+
+    /**
+     * When testing the garbage collection, a delay is used instead of simulating concurrent access.
+     *
+     * @param testDelay the delay in milliseconds
+     */
+    public void setTestDelay(int testDelay) {
+        this.testDelay = testDelay;
+    }
+
+    public void setMarkEventListener(MarkEventListener callback) {
+        this.callback = callback;
+    }
+
+    public void mark() throws RepositoryException {
+        if (store == null) {
+            throw new RepositoryException("No DataStore configured.");
+        }
+        long now = System.currentTimeMillis();
+        if (startScanTimestamp == 0) {
+            startScanTimestamp = now;
+            store.updateModifiedDateOnAccess(startScanTimestamp);
+        }
+
+        for (Session s : sessionList) {
+            scanNodes(s);
+        }
+    }
+
+    private void scanNodes(Session session) throws RepositoryException {
+
+        // add a listener to get 'new' nodes
+        // actually, new nodes are not the problem, but moved nodes
+        listeners.add(new Listener(session));
+
+        // adding a link to a BLOB updates the modified date
+        // reading usually doesn't, but when scanning, it does
+        recurse(session.getRootNode(), sleepBetweenNodes);
+    }
+
+    /**
+     * Stop the observation listener if any are installed.
+     */
+    public void stopScan() throws RepositoryException {
+        if (listeners.size() > 0) {
+            for (Listener listener : listeners) {
+                try {
+                    listener.stop();
+                } catch (Exception e) {
+                    throw new RepositoryException(e);
+                }
+            }
+            listeners.clear();
+        }
+    }
+
+    public int sweep() throws RepositoryException {
+        if (startScanTimestamp == 0) {
+            throw new RepositoryException("scan must be called first");
+        }
+        stopScan();
+        return store.deleteAllOlderThan(startScanTimestamp);
+    }
+
+    /**
+     * Get the data store if one is used.
+     *
+     * @return the data store, or null
+     */
+    public DataStore getDataStore() {
+        return store;
+    }
+
+    private void recurse(final Node n, long sleep) throws RepositoryException {
+        if (sleep > 0) {
+            try {
+                Thread.sleep(sleep);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+        if (callback != null) {
+            callback.beforeScanning(n);
+        }
+        try {
+            for (PropertyIterator it = n.getProperties(); it.hasNext();) {
+                Property p = it.nextProperty();
+                try {
+                    if (p.getType() == PropertyType.BINARY) {
+                        if (n.hasProperty("jcr:uuid")) {
+                            rememberNode(n.getProperty("jcr:uuid").getString());
+                        } else {
+                            rememberNode(n.getPath());
+                        }
+                        if (p.isMultiple()) {
+                            p.getLengths();
+                        } else {
+                            p.getLength();
+                        }
+                    }
+                } catch (InvalidItemStateException e) {
+                    LOG.debug("Property removed concurrently - ignoring", e);
+                }
+            }
+        } catch (InvalidItemStateException e) {
+            LOG.debug("Node removed concurrently - ignoring", e);
+        }
+        try {
+            for (NodeIterator it = n.getNodes(); it.hasNext();) {
+                recurse(it.nextNode(), sleep);
+            }
+        } catch (InvalidItemStateException e) {
+            LOG.debug("Node removed concurrently - ignoring", e);
+        }
+    }
+
+    private void rememberNode(String path) {
+        // Do nothing at the moment
+        // TODO It may be possible to delete some items early
+        /*
+         * To delete files early in the garbage collection scan, we could do
+         * this:
+         *
+         * A) If garbage collection was run before, see if there a file with the
+         * list of UUIDs ('uuids.txt').
+         *
+         * B) If yes, and if the checksum is ok, read all those nodes first (if
+         * not so many). This updates the modified date of all old files that
+         * are still in use. Afterwards, delete all files with an older modified
+         * date than the last scan! Newer files, and files that are read have a
+         * newer modification date.
+         *
+         * C) Delete the 'uuids.txt' file (in any case).
+         *
+         * D) Iterate (recurse) through all nodes and properties like now. If a
+         * node has a binary property, store the UUID of the node in the file
+         * ('uuids.txt'). Also store the time when the scan started.
+         *
+         * E) Checksum and close the file.
+         *
+         * F) Like now, delete files with an older modification date than this
+         * scan.
+         *
+         * We can't use node path for this, UUIDs are required as nodes could be
+         * moved around.
+         *
+         * This mechanism requires that all data stores update the last modified
+         * date when calling addRecord and that record already exists.
+         *
+         */
+    }
+
+    public void close() {
+        if (!closed.getAndSet(true)) {
+            try {
+                stopScan();
+            } catch (RepositoryException e) {
+                LOG.warn("An error occured when stopping the event listener", e);
+            }
+            for (Session s : sessionList) {
+                s.logout();
+            }
+        }
+    }
+
+    /**
+     * Auto-close in case the application didn't call it explicitly.
+     */
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+
+    /**
+     * Event listener to detect moved nodes.
+     * A SynchronousEventListener is used to make sure this method is called before the main iteration ends.
+     */
+    class Listener implements SynchronousEventListener {
+
+        private final Session session;
+
+        private final ObservationManager manager;
+
+        private Exception lastException;
+
+        Listener(Session session)
+                throws UnsupportedRepositoryOperationException,
+                RepositoryException {
+            this.session = session;
+            Workspace ws = session.getWorkspace();
+            manager = ws.getObservationManager();
+            manager.addEventListener(this, Event.NODE_ADDED, "/", true, null,
+                    null, false);
+        }
+
+        void stop() throws Exception {
+            if (lastException != null) {
+                throw lastException;
+            }
+            manager.removeEventListener(this);
+        }
+
+        public void onEvent(EventIterator events) {
+            if (testDelay > 0) {
+                try {
+                    Thread.sleep(testDelay);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+            while (events.hasNext()) {
+                Event event = events.nextEvent();
+                try {
+                    String path = event.getPath();
+                    try {
+                        Item item = session.getItem(path);
+                        if (item.isNode()) {
+                            Node n = (Node) item;
+                            recurse(n, testDelay);
+                        }
+                    } catch (PathNotFoundException e) {
+                        // ignore
+                    }
+                } catch (Exception e) {
+                    lastException = e;
+                }
+            }
+        }
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/LazyFileInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/LazyFileInputStream.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/LazyFileInputStream.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/data/LazyFileInputStream.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,167 @@
+/*
+ * 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.j3.data;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.commons.io.input.AutoCloseInputStream;
+
+/**
+ * This input stream delays opening the file until the first byte is read, and
+ * closes and discards the underlying stream as soon as the end of input has
+ * been reached or when the stream is explicitly closed.
+ */
+public class LazyFileInputStream extends AutoCloseInputStream {
+
+    /**
+     * The file descriptor to use.
+     */
+    protected final FileDescriptor fd;
+
+    /**
+     * The file to read from.
+     */
+    protected final File file;
+
+    /**
+     * True if the input stream was opened. It is also set to true if the stream
+     * was closed without reading (to avoid opening the file after the stream
+     * was closed).
+     */
+    protected boolean opened;
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
+     * file is unreadable, a FileNotFoundException is thrown.
+     * The file is not opened until the first byte is read from the stream.
+     *
+     * @param file the file
+     * @throws java.io.FileNotFoundException
+     */
+    public LazyFileInputStream(File file)
+            throws FileNotFoundException {
+        super(null);
+        if (!file.canRead()) {
+            throw new FileNotFoundException(file.getPath());
+        }
+        this.file = file;
+        this.fd = null;
+    }
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file
+     * descriptor.
+     * The file is not opened until the first byte is read from the stream.
+     *
+     * @param fdObj
+     */
+    public LazyFileInputStream(FileDescriptor fd) {
+        super(null);
+        this.file = null;
+        this.fd = fd;
+    }
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
+     * file is unreadable, a FileNotFoundException is thrown.
+     *
+     * @param name
+     * @throws java.io.FileNotFoundException
+     */
+    public LazyFileInputStream(String name) throws FileNotFoundException {
+        this(new File(name));
+    }
+
+    /**
+     * Open the stream if required.
+     *
+     * @throws java.io.IOException
+     */
+    protected void open() throws IOException {
+        if (!opened) {
+            opened = true;
+            if (fd != null) {
+                in = new FileInputStream(fd);
+            } else {
+                in = new FileInputStream(file);
+            }
+        }
+    }
+
+    public int read() throws IOException {
+        open();
+        return super.read();
+    }
+
+    public int available() throws IOException {
+        open();
+        return super.available();
+    }
+
+    public void close() throws IOException {
+        // make sure the file is not opened afterwards
+        opened = true;
+        
+        // only close the file if it was in fact opened
+        if (in != null) {
+            super.close();
+        }
+    }
+
+    public synchronized void reset() throws IOException {
+        open();
+        super.reset();
+    }
+
+    public boolean markSupported() {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+        return super.markSupported();
+    }
+
+    public synchronized void mark(int readlimit) {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+        super.mark(readlimit);
+    }
+
+    public long skip(long n) throws IOException {
+        open();
+        return super.skip(n);
+    }
+
+    public int read(byte[] b) throws IOException {
+        open();
+        return super.read(b, 0, b.length);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        open();
+        return super.read(b, off, len);
+    }
+
+}

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/StorageSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/StorageSession.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/StorageSession.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/StorageSession.java Fri Apr 23 13:43:36 2010
@@ -21,11 +21,17 @@ package org.apache.jackrabbit.j3.mc;
  */
 public interface StorageSession {
 
+    int MAIN_WORKSPACE_ID = 0;
+
     Val getRootNodeId();
 
     NodeData getNode(Val nodeId);
 
-    Val newNodeId(Val parentId);
+    Val newNodeId(Val parentNodeId, int workspaceId, Val relPath);
+
+    int getWorkspaceId(Val nodeId);
+
+    Val convertNodeId(Val nodeId, int newWorkspaceId);
 
     void store(long date, NodeData[] nodes, Bundle[] events);
 
@@ -41,6 +47,8 @@ public interface StorageSession {
 
     boolean supportsTemp();
 
+    boolean supportsNodeIdsWithoutPath();
+
     void storeTemp(int block, NodeData[] nodes, Bundle[] events);
 
     NodeData[] getTempNodes(int block);

Added: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Utils.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Utils.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Utils.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Utils.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,75 @@
+/*
+ * 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.j3.mc;
+
+import org.apache.jackrabbit.j3.util.ExceptionFactory;
+
+/**
+ * A utility class.
+ */
+public class Utils {
+
+    static int getWorkspaceId(long nodeId) {
+        if ((nodeId & 1) == 0) {
+            return 0;
+        } else if ((nodeId & 3) == 1) {
+            return (int) ((nodeId >> 2) & ((1 << 4) - 1));
+        } else if ((nodeId & 7) == 3) {
+            return (int) ((nodeId >> 3) & ((1 << 11) - 1));
+        } else if ((nodeId & 15) == 7) {
+            return (int) ((nodeId >> 4) & ((1 << 28) - 1));
+        } else {
+            throw ExceptionFactory.illegalArgument("Node {0}", nodeId);
+        }
+    }
+
+    static long getBaseNodeId(long nodeId) {
+        if ((nodeId & 1) == 0) {
+            return nodeId >> 1;
+        } else if ((nodeId & 3) == 1) {
+            return nodeId >> 6;
+        } else if ((nodeId & 7) == 3) {
+            return nodeId >> 14;
+        } else if ((nodeId & 15) == 7) {
+            return nodeId >> 32;
+        } else {
+            throw ExceptionFactory.illegalArgument("Node {0}", nodeId);
+        }
+    }
+
+    static long getNodeId(int workspaceId, long baseNodeId) {
+        if (workspaceId == 0) {
+            // ... nnnnnnnn nnnnnnn0
+            return baseNodeId << 1;
+        } else if ((workspaceId & ~((1 << 4) - 1)) == 0) {
+            // up to workspace #15
+            // ... nnnnnnnn nnwwww01
+            return (baseNodeId << 6) + (workspaceId << 2) + 1;
+        } else if ((workspaceId & ~((1 << 11) - 1)) == 0) {
+            // up to workspace #2047
+            // ... nnwwwwww wwwww011
+            return (baseNodeId << 14) + (workspaceId << 3) + 3;
+        } else if ((workspaceId & ~((1 << 28) - 1)) == 0) {
+            // up to workspace #268'435'455
+            // ... nnnnnnnn wwwwww ... wwww0111
+            return (baseNodeId << 32) + ((long) workspaceId << 4) + 7;
+        } else {
+            throw ExceptionFactory.illegalArgument("Workspace {0}", workspaceId);
+        }
+    }
+
+}

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Val.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Val.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Val.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/Val.java Fri Apr 23 13:43:36 2010
@@ -432,4 +432,53 @@ public class Val implements Comparable<V
         return Val.get(newArray);
     }
 
+    public static int getWorkspaceId(long nodeId) {
+        if ((nodeId & 1) == 0) {
+            return 0;
+        } else if ((nodeId & 3) == 1) {
+            return (int) ((nodeId >> 2) & ((1 << 4) - 1));
+        } else if ((nodeId & 7) == 3) {
+            return (int) ((nodeId >> 3) & ((1 << 11) - 1));
+        } else if ((nodeId & 15) == 7) {
+            return (int) ((nodeId >> 4) & ((1 << 28) - 1));
+        } else {
+            throw ExceptionFactory.illegalArgument("Node {0}", nodeId);
+        }
+    }
+
+    public static long getBaseNodeId(long nodeId) {
+        if ((nodeId & 1) == 0) {
+            return nodeId >> 1;
+        } else if ((nodeId & 3) == 1) {
+            return nodeId >> 6;
+        } else if ((nodeId & 7) == 3) {
+            return nodeId >> 14;
+        } else if ((nodeId & 15) == 7) {
+            return nodeId >> 32;
+        } else {
+            throw ExceptionFactory.illegalArgument("Node {0}", nodeId);
+        }
+    }
+
+    public static long getNodeId(int workspaceId, long baseNodeId) {
+        if (workspaceId == 0) {
+            // ... nnnnnnnn nnnnnnn0
+            return baseNodeId << 1;
+        } else if ((workspaceId & ~((1 << 4) - 1)) == 0) {
+            // up to workspace #15
+            // ... nnnnnnnn nnwwww01
+            return (baseNodeId << 6) + (workspaceId << 2) + 1;
+        } else if ((workspaceId & ~((1 << 11) - 1)) == 0) {
+            // up to workspace #2047
+            // ... nnwwwwww wwwww011
+            return (baseNodeId << 14) + (workspaceId << 3) + 3;
+        } else if ((workspaceId & ~((1 << 28) - 1)) == 0) {
+            // up to workspace #268'435'455
+            // ... nnnnnnnn wwwwww ... wwww0111
+            return (baseNodeId << 32) + ((long) workspaceId << 4) + 7;
+        } else {
+            throw ExceptionFactory.illegalArgument("Workspace {0}", workspaceId);
+        }
+    }
+
 }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorage.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorage.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorage.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorage.java Fri Apr 23 13:43:36 2010
@@ -17,10 +17,10 @@
 package org.apache.jackrabbit.j3.mc.jdbc;
 
 import java.sql.Connection;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.util.concurrent.atomic.AtomicLong;
 import org.apache.jackrabbit.j3.mc.NodeData;
 import org.apache.jackrabbit.j3.mc.Storage;
 import org.apache.jackrabbit.j3.mc.StorageSession;
@@ -32,58 +32,149 @@ import org.apache.jackrabbit.j3.util.Exc
  */
 public class JdbcStorage implements Storage {
 
+    private static final String SETTING_VERSION_READ = "versionRead";
+    private static final String SETTING_VERSION_WRITE = "versionWrite";
+    private static final String SETTING_NEXT_BASE_NODE_ID = "nextBaseNodeId";
+
+    private static final String VERSION_READ = "1";
+    private static final String VERSION_WRITE = "1";
+    private static final int NODE_ID_INCREMENT = 32;
+
     private final String url;
     private boolean initDone;
     private static final Val rootNodeId = Val.get(0);
-    private AtomicLong nextNodeId;
-    private AtomicLong nextSessionId = new AtomicLong(1);
+    private long nextBaseNodeId;
+    private long lastBaseNodeId;
+    private long nextSessionId = 1;
+
+    private Connection connControl;
+    private Connection connLock;
+    private PreparedStatement prepUpdateSetting;
 
     public JdbcStorage(String url) {
         this.url = url;
     }
 
-    public StorageSession openSession(String user, char[] password) {
+    public synchronized StorageSession openSession(String user, char[] password) {
         try {
             Connection conn = JdbcUtils.getConnection(null, url, user, new String(password));
-            JdbcStorageSession session = new JdbcStorageSession(this, conn, nextSessionId.getAndIncrement());
-            init(conn, session);
+            JdbcStorageSession session = new JdbcStorageSession(this, conn, nextSessionId++);
+            if (connControl == null) {
+                connLock =  JdbcUtils.getConnection(null, url, user, new String(password));
+                connControl = JdbcUtils.getConnection(null, url, user, new String(password));
+                init(session);
+            }
             return session;
         } catch (SQLException e) {
             throw ExceptionFactory.mcException(e);
         }
     }
 
-    private synchronized void init(Connection conn, JdbcStorageSession session) {
+    private String getSetting(String name, String defaultValue) throws SQLException {
+        PreparedStatement prep = connControl.prepareStatement("select value from settings where name = ?");
+        prep.setString(1, name);
+        ResultSet rs = prep.executeQuery();
+        if (rs.next()) {
+            return rs.getString(1);
+        }
+        setSetting(name, defaultValue);
+        return defaultValue;
+    }
+
+    private void setSetting(String name, String value) throws SQLException {
+        if (prepUpdateSetting == null) {
+            prepUpdateSetting = connControl.prepareStatement("update settings set value = ? where name = ?");
+        }
+        PreparedStatement prep = prepUpdateSetting;
+        prep.setString(1, value);
+        prep.setString(2, name);
+        int count = prep.executeUpdate();
+        if (count == 0) {
+            prep = connControl.prepareStatement("insert into settings(name, value) values(?, ?)");
+            prep.setString(1, name);
+            prep.setString(2, value);
+            prep.execute();
+        }
+    }
+
+    private synchronized void init(JdbcStorageSession session) {
         if (initDone) {
             return;
         }
         try {
-            Statement stat = conn.createStatement();
-            stat.execute("create table if not exists bundles(id bigint primary key, version bigint, data binary)");
-            stat.execute("create table if not exists bundles_temp(id bigint, sessionId bigint, block int, version int, data binary, primary key(id, sessionId))");
+            Statement stat = connControl.createStatement();
+            stat.execute("create table if not exists settings(name varchar(255), value varchar(255))");
+            String read = getSetting(SETTING_VERSION_READ, VERSION_READ);
+            if (VERSION_READ.compareTo(read) > 0) {
+                throw ExceptionFactory.mcException("Invalid read version: {0}", read);
+            }
+            String write = getSetting(SETTING_VERSION_WRITE, VERSION_WRITE);
+            if (!VERSION_WRITE.equals(write)) {
+                // could switch to read-only now
+                throw ExceptionFactory.mcException("Invalid write version: {0}", write);
+            }
+            stat.execute("create table if not exists lock(id int primary key)");
+            lock();
+            stat.execute("create table if not exists nodes(id bigint primary key, version bigint, data binary)");
+            stat.execute("create table if not exists nodes_temp(id bigint, sessionId bigint, block int, version int, data binary, primary key(id, sessionId))");
             stat.execute("create table if not exists events(id identity, persistedDate bigint, data binary)");
             stat.execute("create table if not exists events_temp(id identity, sessionId bigint, block int, data binary)");
             stat.execute("create index if not exists events_persisted on events(persistedDate, id)");
-            stat.execute("create index if not exists bundles_temp_block on bundles_temp(sessionId, block, id)");
+            stat.execute("create index if not exists nodes_temp_block on nodes_temp(sessionId, block, id)");
             stat.execute("create index if not exists events_temp_block on events_temp(sessionId, block)");
-            ResultSet rs = stat.executeQuery("select max(id) from bundles");
-            rs.next();
-            nextNodeId = new AtomicLong(rs.getLong(1) + 1);
-            if (rs.wasNull()) {
+            nextBaseNodeId = Long.parseLong(getSetting(SETTING_NEXT_BASE_NODE_ID, "0"));
+            if (nextBaseNodeId == 0) {
                 NodeData root = new NodeData(rootNodeId, null, 0);
                 session.store(0, new NodeData[] { root }, null);
+                nextBaseNodeId++;
             }
+
         } catch (SQLException e) {
             throw ExceptionFactory.mcException(e);
         }
         initDone = true;
     }
 
+    private void lock() {
+        try {
+            connLock.setAutoCommit(false);
+            Statement lock = connLock.createStatement();
+            lock.execute("insert into lock values(1)");
+        } catch (SQLException e) {
+            throw ExceptionFactory.mcException("Repository is already in use", e);
+        }
+    }
+
+    private void unlock() {
+        try {
+            connLock.rollback();
+            connLock.close();
+        } catch (SQLException e) {
+            throw ExceptionFactory.mcException(e);
+        }
+    }
+
     public void close() {
+        try {
+            setSetting(SETTING_NEXT_BASE_NODE_ID, Long.toString(nextBaseNodeId));
+            connControl.close();
+        } catch (SQLException e) {
+            throw ExceptionFactory.mcException(e);
+        }
+        unlock();
     }
 
-    Val newNodeId() {
-        return Val.get(nextNodeId.getAndIncrement());
+    synchronized long newBaseNodeId() {
+        long id = nextBaseNodeId++;
+        if (id > lastBaseNodeId) {
+            lastBaseNodeId += NODE_ID_INCREMENT;
+            try {
+                setSetting(SETTING_NEXT_BASE_NODE_ID, Long.toString(lastBaseNodeId));
+            } catch (SQLException e) {
+                throw ExceptionFactory.mcException(e);
+            }
+        }
+        return id;
     }
 
     Val getRootNodeId() {

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorageSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorageSession.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorageSession.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/jdbc/JdbcStorageSession.java Fri Apr 23 13:43:36 2010
@@ -65,17 +65,17 @@ public class JdbcStorageSession implemen
         try {
             ResultSet rs;
             if (tempNodeBlocks > 0) {
-                PreparedStatement p = prepare("select data from bundles_temp where id = ? and sessionId = ?");
+                PreparedStatement p = prepare("select data from nodes_temp where id = ? and sessionId = ?");
                 p.setLong(1, nodeId.getLong());
                 p.setLong(2, sessionId);
                 rs = p.executeQuery();
                 if (!rs.next()) {
-                    p = prepare("select data from bundles where id = ?");
+                    p = prepare("select data from nodes where id = ?");
                     p.setLong(1, nodeId.getLong());
                     rs = p.executeQuery();
                 }
             } else {
-                PreparedStatement p = prepare("select data from bundles where id = ?");
+                PreparedStatement p = prepare("select data from nodes where id = ?");
                 p.setLong(1, nodeId.getLong());
                 rs = p.executeQuery();
             }
@@ -93,8 +93,18 @@ public class JdbcStorageSession implemen
         }
     }
 
-    public Val newNodeId(Val parentId) {
-        return storage.newNodeId();
+    public Val newNodeId(Val parentNodeId, int workspaceId, Val relPath) {
+        long base = storage.newBaseNodeId();
+        return Val.get(Val.getNodeId(workspaceId, base));
+    }
+
+    public int getWorkspaceId(Val nodeId) {
+        return Val.getWorkspaceId(nodeId.getLong());
+    }
+
+    public Val convertNodeId(Val nodeId, int newWorkspaceId) {
+        long baseNodeId = Val.getBaseNodeId(nodeId.getLong());
+        return Val.get(Val.getNodeId(newWorkspaceId, baseNodeId));
     }
 
     public void store(long date, NodeData[] nodes, Bundle[] events) {
@@ -113,8 +123,8 @@ public class JdbcStorageSession implemen
                     store(date, tempNodes, tempEvents);
                 }
             }
-            PreparedStatement insert = prepare("insert into bundles(id, version, data) values(?, ?, ?)");
-            PreparedStatement update = prepare("update bundles set version = ?, data = ? where id = ?");
+            PreparedStatement insert = prepare("insert into nodes(id, version, data) values(?, ?, ?)");
+            PreparedStatement update = prepare("update nodes set version = ?, data = ? where id = ?");
             for (NodeData n : nodes) {
                 n.incrementVersion();
                 bundle.reset();
@@ -175,8 +185,8 @@ public class JdbcStorageSession implemen
     public void storeTemp(int block, NodeData[] nodes, Bundle[] events) {
         tempNodeBlocks = Math.max(tempNodeBlocks, block + 1);
         try {
-            PreparedStatement insert = prepare("insert into bundles_temp(id, sessionId, block, version, data) values(?, ?, ?, ?, ?)");
-            PreparedStatement update = prepare("update bundles_temp set block = ?, version = ?, data = ? where id = ? and sessionId = ?");
+            PreparedStatement insert = prepare("insert into nodes_temp(id, sessionId, block, version, data) values(?, ?, ?, ?, ?)");
+            PreparedStatement update = prepare("update nodes_temp set block = ?, version = ?, data = ? where id = ? and sessionId = ?");
             for (NodeData n : nodes) {
                 bundle.reset();
                 n.writeTo(bundle);
@@ -230,7 +240,7 @@ public class JdbcStorageSession implemen
         try {
             PreparedStatement prep;
             if (nodes) {
-                prep = prepare("delete from bundles_temp where sessionId = ?");
+                prep = prepare("delete from nodes_temp where sessionId = ?");
                 prep.setLong(1, sessionId);
                 prep.execute();
                 tempNodeBlocks = 0;
@@ -254,10 +264,14 @@ public class JdbcStorageSession implemen
         return true;
     }
 
+    public boolean supportsNodeIdsWithoutPath() {
+        return true;
+    }
+
     public NodeData[] getTempNodes(int block) {
         ArrayList<NodeData> list = new ArrayList<NodeData>();
         try {
-            PreparedStatement get = prepare("select data from bundles_temp where sessionId = ? and block = ? order by id");
+            PreparedStatement get = prepare("select data from nodes_temp where sessionId = ? and block = ? order by id");
             get.setLong(1, sessionId);
             get.setInt(2, block);
             ResultSet rs = get.executeQuery();

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorage.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorage.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorage.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorage.java Fri Apr 23 13:43:36 2010
@@ -30,7 +30,7 @@ public class MemStorage implements Stora
 
     private static final Val rootNodeId = Val.get(0);
     private final HashMap<Val, NodeData> content = new HashMap<Val, NodeData>();
-    private AtomicLong nextNodeId = new AtomicLong(1);
+    private AtomicLong nextBaseNodeId = new AtomicLong(1);
 
     public MemStorage() {
         NodeData root = new NodeData(rootNodeId, null, 0);
@@ -44,8 +44,8 @@ public class MemStorage implements Stora
         return new MemStorageSession(this, content);
     }
 
-    public Val newNodeId() {
-        return Val.get(nextNodeId.getAndIncrement());
+    long newBaseNodeId() {
+        return nextBaseNodeId.getAndIncrement();
     }
 
     Val getRootNodeId() {

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorageSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorageSession.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorageSession.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/mc/mem/MemStorageSession.java Fri Apr 23 13:43:36 2010
@@ -47,8 +47,18 @@ public class MemStorageSession implement
         return storage.getRootNodeId();
     }
 
-    public Val newNodeId(Val parentId) {
-        return storage.newNodeId();
+    public Val newNodeId(Val parentNodeId, int workspaceId, Val relPath) {
+        long base = storage.newBaseNodeId();
+        return Val.get(Val.getNodeId(workspaceId, base));
+    }
+
+    public int getWorkspaceId(Val nodeId) {
+        return Val.getWorkspaceId(nodeId.getLong());
+    }
+
+    public Val convertNodeId(Val nodeId, int newWorkspaceId) {
+        long baseNodeId = Val.getBaseNodeId(nodeId.getLong());
+        return Val.get(Val.getNodeId(newWorkspaceId, baseNodeId));
     }
 
     public void store(long date, NodeData[] nodes, Bundle[] events) {
@@ -76,6 +86,10 @@ public class MemStorageSession implement
         return false;
     }
 
+    public boolean supportsNodeIdsWithoutPath() {
+        return true;
+    }
+
     public Bundle[] getEvents(long date, int size) {
         throw ExceptionFactory.unsupportedOperation();
     }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Constants.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Constants.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Constants.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Constants.java Fri Apr 23 13:43:36 2010
@@ -44,4 +44,7 @@ public class Constants {
     public static final int BUNDLE_MIN_COMPRESS = 3;
     public static final int JOURNAL_LOAD_SIZE = 16;
 
+    public static final String LOG_UNCLOSED_SESSIONS = "logUnclosedSessions";
+    public static final boolean LOG_UNCLOSED_SESSIONS_DEFAULT = true;
+
 }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/LobStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/LobStore.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/LobStore.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/LobStore.java Fri Apr 23 13:43:36 2010
@@ -23,6 +23,7 @@ import java.io.SequenceInputStream;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import org.apache.jackrabbit.j3.RepositoryImpl;
+import org.apache.jackrabbit.j3.data.DataStore;
 import org.apache.jackrabbit.j3.mc.Val;
 
 /**
@@ -41,6 +42,10 @@ public class LobStore {
         return null;
     }
 
+    public DataStore getDataStore() {
+        return null;
+    }
+
     public Val createValue(InputStream in) throws RepositoryException {
         try {
             int min = getMinRecordLength();

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Log.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Log.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Log.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/Log.java Fri Apr 23 13:43:36 2010
@@ -34,9 +34,6 @@ public class Log {
     private static int nextId;
     public boolean debug;
 
-    public Log() {
-    }
-
     private void log(String message) {
         if (message != null && debug) {
             System.out.println(message);
@@ -221,6 +218,11 @@ public class Log {
         return n;
     }
 
+    public void warn(String message, Exception e) {
+        System.err.println(message);
+        e.printStackTrace();
+    }
+
 /*
 
 Find & replace:
@@ -249,6 +251,9 @@ public $1 $2\(\)$3 \{$4log.code\(this, "
 ;// TODO(\R)
 ;$1// TODO$1
 
+(log.code\(this, ")(.*".*\R.* // TODO)
+$1TODO $2
+
 */
 
 }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/StringUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/StringUtils.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/StringUtils.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/main/java/org/apache/jackrabbit/j3/util/StringUtils.java Fri Apr 23 13:43:36 2010
@@ -38,4 +38,18 @@ public class StringUtils {
         return buff.toString();
     }
 
+    /**
+     * Check if two strings are equal. Here, null is equal to null.
+     *
+     * @param a the first value
+     * @param b the second value
+     * @return true if both are null or both are equal
+     */
+    public static boolean equals(String a, String b) {
+        if (a == null) {
+            return b == null;
+        }
+        return a.equals(b);
+    }
+
 }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestAll.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestAll.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestAll.java Fri Apr 23 13:43:36 2010
@@ -35,12 +35,15 @@ public class TestAll {
         TestSuite suite = new TestSuite("org.apache.jackrabbit.j3");
 
         int todo;
-        // repository lock mechanism (required for sessions, journal?)
+        // XA API
+        // integrated lob storage
+        // virtual repository
+        // session attribute to request cache size (only smaller except if in admin group)
         // Repository.OPTION_JOURNALED_OBSERVATION_SUPPORTED
         // external events
-        // auto-close sessions, optionally log open stack trace
         // ability to auto-delete old event journal entries
-        // ability to disable the even journal
+        // ability to disable the even journal (for each session?)
+        // try supporting path as the node id
 
         // events: save space for n=addNode+n.setProperty+... or n.setProperty+n.setProperty+...
 
@@ -57,6 +60,7 @@ public class TestAll {
             suite.addTestSuite(TestLock.class);
             suite.addTestSuite(TestSimple.class);
             suite.addTestSuite(TestObservation.class);
+            suite.addTestSuite(TestSessionGC.class);
 
             suite.addTestSuite(TestNextConfiguration.class);
         }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBase.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBase.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBase.java Fri Apr 23 13:43:36 2010
@@ -37,33 +37,39 @@ public class TestBase extends TestCase {
 
     public static final String[] URL = new String[] {
         // "jdbc:h2:target/repos/test&log=debug",
+        // "jdbc:h2:target/repos/test;TRACE_LEVEL_SYSTEM_OUT=2",
         "jdbc:h2:target/repos/test",
         "mem:",
         "jdbc:h2:mem:test",
     };
 
-    private Repository repository;
+    protected RepositoryFactory repositoryFactory;
+    protected Repository repository;
     protected Session session;
 
-    public void setUp() throws Exception {
+    protected void setUp() throws Exception {
         FileUtils.deleteDirectory(new File("target/repos"));
         String factoryClass = "org.apache.jackrabbit.j3.RepositoryFactoryImpl";
         String url = URL[configurationId];
-        RepositoryFactory factory = (RepositoryFactory) Class.forName(factoryClass).newInstance();
+        repositoryFactory = (RepositoryFactory) Class.forName(factoryClass).newInstance();
         Map<String, String> parameters = new HashMap<String, String>();
         parameters.put("url", url);
-        repository = factory.getRepository(parameters);
+        repository = repositoryFactory.getRepository(parameters);
 
         session = openSession();
     }
 
-    public void tearDown() throws Exception {
+    protected void tearDown() throws Exception {
         session.logout();
     }
 
     protected Session openSession() throws LoginException, RepositoryException {
-        SimpleCredentials credentials = new SimpleCredentials("sa", "sa".toCharArray());
+        SimpleCredentials credentials = getCredentials();
         return repository.login(credentials);
     }
 
+    protected SimpleCredentials getCredentials() {
+        return new SimpleCredentials("sa", "sa".toCharArray());
+    }
+
 }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBundle.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBundle.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBundle.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestBundle.java Fri Apr 23 13:43:36 2010
@@ -28,22 +28,22 @@ public class TestBundle extends TestCase
 
     public void testNodeId() {
         try {
-            RepositoryImpl.getNodeId(268435456, 0);
+            Val.getNodeId(268435456, 0);
             fail();
         } catch (IllegalArgumentException e) {
             // expected
         }
         try {
-            RepositoryImpl.getNodeId(-1, 0);
+            Val.getNodeId(-1, 0);
             fail();
         } catch (IllegalArgumentException e) {
             // expected
         }
         for (int workspaceId = 0; workspaceId < 268435455;) {
             for (long baseNodeId = 0; baseNodeId < Integer.MAX_VALUE;) {
-                long nodeId = RepositoryImpl.getNodeId(workspaceId, baseNodeId);
-                long w = RepositoryImpl.getWorkspaceId(nodeId);
-                long b = RepositoryImpl.getBaseNodeId(nodeId);
+                long nodeId = Val.getNodeId(workspaceId, baseNodeId);
+                long w = Val.getWorkspaceId(nodeId);
+                long b = Val.getBaseNodeId(nodeId);
                 assertEquals(workspaceId, w);
                 assertEquals(baseNodeId, b);
                 if (baseNodeId < 1000) {

Modified: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestEventJournal.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestEventJournal.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestEventJournal.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestEventJournal.java Fri Apr 23 13:43:36 2010
@@ -43,6 +43,8 @@ public class TestEventJournal extends Te
         assertEquals(Event.PROPERTY_ADDED, e.getType());
         assertEquals("userData", e.getUserData());
 
+        session2.logout();
+
         n.remove();
         session.save();
     }

Modified: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestObservation.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestObservation.java?rev=937293&r1=937292&r2=937293&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestObservation.java (original)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestObservation.java Fri Apr 23 13:43:36 2010
@@ -30,6 +30,7 @@ import javax.jcr.observation.EventListen
 public class TestObservation extends TestBase {
 
     private ArrayList<String> eventList = new ArrayList<String>();
+    private boolean ownEvent;
 
     public void test() throws Exception {
         session.getRootNode().addNode("t");
@@ -39,6 +40,14 @@ public class TestObservation extends Tes
                 Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED |
                 Event.PROPERTY_REMOVED, "/", true, null, null, false);
 
+        // testNestedWrite
+        ownEvent = true;
+        Node test = session.getRootNode().addNode("x");
+        session.save();
+        test.remove();
+        session.save();
+        ownEvent = false;
+
         Session session2 = openSession();
         Node n2 = session2.getNode("/t");
         session2.getWorkspace().getObservationManager().setUserData("x");
@@ -46,8 +55,8 @@ public class TestObservation extends Tes
         session2.save();
         session2.logout();
 
-        assertEquals(2, eventList.size());
-        assertEquals("[/t/test:1:x, /t/test/jcr:primaryType:4:x]", eventList.toString());
+        assertEquals(5, eventList.size());
+        assertEquals("[/x:1:null, /x/jcr:primaryType:4:null, null:2:null, /t/test:1:x, /t/test/jcr:primaryType:4:x]", eventList.toString());
     }
 
     /**
@@ -65,6 +74,14 @@ public class TestObservation extends Tes
                     s = e.toString();
                 }
                 eventList.add(s);
+                if (ownEvent) {
+                    try {
+                        session.getRootNode().addNode("testNestedWrite");
+                        fail();
+                    } catch (RepositoryException e) {
+                        // expected
+                    }
+                }
             }
         }
 

Added: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestSessionGC.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestSessionGC.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestSessionGC.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/TestSessionGC.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,82 @@
+/*
+ * 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.j3;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import org.apache.jackrabbit.j3.api.JackrabbitRepositoryFactory;
+import org.apache.jackrabbit.j3.api.management.RepositoryManager;
+import org.apache.jackrabbit.j3.util.Constants;
+
+/**
+ * Test that sessions get garbage collected.
+ */
+public class TestSessionGC extends TestBase {
+
+    public void testSessionsGetGarbageCollected() throws RepositoryException {
+        RepositoryManager manager = ((JackrabbitRepositoryFactory) repositoryFactory).getRepositoryManager(repository);
+        ArrayList<WeakReference<Session>> list = new ArrayList<WeakReference<Session>>();
+        ReferenceQueue<Session> detect = new ReferenceQueue<Session>();
+        Error error = null;
+        int charsPerSession = 100000;
+        try {
+            Runtime rt = Runtime.getRuntime();
+            for (int i = 0;;) {
+                SimpleCredentials sc = getCredentials();
+                sc.setAttribute(Constants.LOG_UNCLOSED_SESSIONS, false);
+                Session s = repository.login(sc);
+                i++;
+                if (i % 100 == 0) {
+                    long mem = rt.totalMemory() - rt.freeMemory();
+                    System.out.println("open: " + i + " mem (kb): " + mem / 1024);
+                }
+                // eat  a lot of memory so it gets garbage collected quickly
+                // (or quickly runs out of memory)
+                Node n = s.getRootNode().addNode("n" + i);
+                // can't use the same content: the value factory would detect it
+                char[] ch = new char[charsPerSession];
+                ch[0] = (char) (i / 256);
+                ch[i] = (char) (i % 256);
+                n.setProperty("x", new String(ch));
+                list.add(new WeakReference<Session>(s, detect));
+                if (detect.poll() != null) {
+                    break;
+                }
+            }
+        } catch (OutOfMemoryError e) {
+            error = e;
+        }
+        for (int i = 0; i < list.size(); i++) {
+            Reference<Session> ref = list.get(i);
+            Session s = ref.get();
+            if (s != null) {
+                s.logout();
+            }
+        }
+        manager.stop();
+        if (error != null) {
+            throw error;
+        }
+    }
+
+}