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 2008/04/18 14:27:52 UTC

svn commit: r649493 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/data/ main/java/org/apache/jackrabbit/core/data/db/ test/java/org/apache/jackrabbit/core/data/

Author: thomasm
Date: Fri Apr 18 05:27:48 2008
New Revision: 649493

URL: http://svn.apache.org/viewvc?rev=649493&view=rev
Log:
JCR-1388 Allow concurrent reads from the database data store

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java?rev=649493&r1=649492&r2=649493&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java Fri Apr 18 05:27:48 2008
@@ -46,4 +46,10 @@
      */
     InputStream getStream() throws DataStoreException;
 
+    /**
+     * Returns the last modified of the record.
+     * 
+     * @return last modified time of the binary stream
+     */
+    long getLastModified();
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java?rev=649493&r1=649492&r2=649493&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java Fri Apr 18 05:27:48 2008
@@ -37,7 +37,7 @@
      * @param identifier data identifier
      * @param file file that contains the binary stream
      */
-    protected FileDataRecord(DataIdentifier identifier, File file) {
+    public FileDataRecord(DataIdentifier identifier, File file) {
         super(identifier);
         assert file.isFile();
         this.file = file;
@@ -61,4 +61,10 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public long getLastModified() {
+        return file.lastModified();
+    }
 }

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java?rev=649493&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java Fri Apr 18 05:27:48 2008
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper methods for database operations.
+ */
+public class DatabaseHelper {
+
+    private static Logger log = LoggerFactory.getLogger(DatabaseHelper.class);
+
+    /**
+     * Silently closes a connection.
+     */
+    public static void closeSilently(Connection con) {
+        try {
+            if (con != null) {
+                con.close();
+            }
+        } catch (SQLException e) {
+            log.info("Couldn't close connection: ", e);
+        }
+    }
+
+    /**
+     * Silently closes a result set.
+     */
+    public static void closeSilently(ResultSet rs) {
+        try {
+            if (rs != null) {
+                rs.close();
+            }
+        } catch (SQLException e) {
+            log.info("Couldn't close result set: ", e);
+        }
+    }
+
+    /**
+     * Silently closes a statement.
+     */
+    public static void closeSilently(Statement stmt) {
+        try {
+            if (stmt != null) {
+                stmt.close();
+            }
+        } catch (SQLException e) {
+            log.info("Couldn't close statement: ", e);
+        }
+    }
+}

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DatabaseHelper.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java?rev=649493&r1=649492&r2=649493&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java Fri Apr 18 05:27:48 2008
@@ -33,7 +33,7 @@
 
     /**
      * Creates a data record based on the given identifier and length.
-     *
+     * 
      * @param identifier data identifier
      * @param length the length
      * @param file file that contains the binary stream
@@ -58,7 +58,13 @@
      */
     public InputStream getStream() throws DataStoreException {
         lastModified = store.touch(getIdentifier(), lastModified);
-        return store.getInputStream(getIdentifier());
+        return new DbInputStream(store, getIdentifier());
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public long getLastModified() {
+        return lastModified;
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java?rev=649493&r1=649492&r2=649493&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java Fri Apr 18 05:27:48 2008
@@ -29,6 +29,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -487,6 +488,51 @@
     }
 
     /**
+     * getDatabaseResources() does NOT close the DB resources on success. It's up to the client of
+     * the stream backed by these resources to close it and therefore close the DB resources.
+     *
+     * @param identifier data identifier
+     * @return database resources that will back the stream corresponding
+     *                     to the passed data identifier
+     * @throws DataStoreException if the data store could not be accessed,
+     *                     or if the given identifier is invalid
+     */
+    public DbResources getDatabaseResources(DataIdentifier identifier) throws DataStoreException {
+        ConnectionRecoveryManager conn = null;
+        ResultSet rs = null;
+        try {
+            conn = getConnection();
+            // SELECT ID, DATA FROM DATASTORE WHERE ID = ?
+            PreparedStatement prep = conn.executeStmt(selectDataSQL, new Object[]{identifier.toString()});
+            rs = prep.getResultSet();
+            if (!rs.next()) {
+                throw new DataStoreException("Record not found: " + identifier);
+            }
+            InputStream result = null;
+            InputStream stream = rs.getBinaryStream(2);
+            if (stream == null) {
+                // If the stream is null, go ahead and close resources
+                result = new ByteArrayInputStream(new byte[0]);
+                DatabaseHelper.closeSilently(rs);
+                putBack(conn);
+            } else {
+                result = new BufferedInputStream(stream);
+                if (copyWhenReading) {
+                    File temp = moveToTempFile(result);
+                    result = new TempFileInputStream(temp);
+                }
+            }
+
+            DbResources dbResources = new DbResources(conn, rs, prep, result, this);
+            return dbResources;
+        } catch (Exception e) {
+            DatabaseHelper.closeSilently(rs);
+            putBack(conn);
+            throw convert("Retrieving database resources ", e);
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     public synchronized void init(String homeDir) throws DataStoreException {
@@ -643,32 +689,6 @@
             }
         }
         return lastModified;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public InputStream getInputStream(DataIdentifier identifier) throws DataStoreException {
-        ConnectionRecoveryManager conn = getConnection();
-        try {
-            String id = identifier.toString();
-            // SELECT ID, DATA FROM DATASTORE WHERE ID = ?
-            PreparedStatement prep = conn.executeStmt(selectDataSQL, new Object[]{id});
-            ResultSet rs = prep.getResultSet();
-            if (!rs.next()) {
-                throw new DataStoreException("Record not found: " + identifier);
-            }
-            InputStream in = new BufferedInputStream(rs.getBinaryStream(2));
-            if (copyWhenReading) {
-                File temp = moveToTempFile(in);
-                in = new TempFileInputStream(temp);
-            }
-            return in;
-        } catch (Exception e) {
-            throw convert("Can not read identifier " + identifier, e);
-        } finally {
-            putBack(conn);
-        }
     }
 
     /**

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java?rev=649493&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java Fri Apr 18 05:27:48 2008
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class represents an input stream backed by a database. It allows the stream to be used by
+ * keeping the DB resources open until the stream is closed. When the stream is finished or
+ * close()d, then the resources are freed.
+ */
+public class DbInputStream extends FilterInputStream {
+
+    private static Logger log = LoggerFactory.getLogger(DbInputStream.class);
+
+    protected DbResources resources;
+    protected boolean streamFinished;
+    protected boolean streamClosed;
+    protected DbDataStore store;
+    protected DataIdentifier identifier;
+
+    /**
+     * @param in the stream obtained by a call to ResultSet.getBinaryStream().
+     * @param con the connection to the DB. It must not be closed.
+     * @param rs the result set from wich the stream is obtained. It must not be closed.
+     * @param stmt the statemen that produced the result set. It must not be closed.
+     */
+    protected DbInputStream(DbDataStore store, DataIdentifier identifier) {
+        super(null);
+        streamFinished = false;
+        streamClosed = true;
+        this.store = store;
+        this.identifier = identifier;
+    }
+
+    private void getStream() throws IOException {
+        try {
+            resources = store.getDatabaseResources(identifier);
+            in = resources.getInputStream();
+            streamClosed = false;
+        } catch (DataStoreException e) {
+            IOException e2 = new IOException(e.getMessage());
+            e2.initCause(e);
+            throw e2;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * When the stream is consumed, the database resources held by the instance are closed.
+     */
+    public int read() throws IOException {
+        if (streamFinished) {
+            return -1;
+        }
+        if (in == null) {
+            getStream();
+        }
+        int c = in.read();
+        if (c == -1) {
+            streamFinished = true;
+            close();
+        }
+        return c;
+    }
+
+    /**
+     * {@inheritDoc}
+     * When the stream is consumed, the database resources held by the instance are closed.
+     */
+    public int read(byte[] b) throws IOException {
+        if (streamFinished) {
+            return -1;
+        }
+        int c = read(b, 0, b.length);
+        if (c == -1) {
+            streamFinished = true;
+            close();
+        }
+        return c;
+    }
+
+    /**
+     * {@inheritDoc}
+     * When the stream is consumed, the database resources held by the instance are closed.
+     */
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (streamFinished) {
+            return -1;
+        }
+        if (in == null) {
+            getStream();
+        }
+        int c = in.read(b, off, len);
+        if (c == -1) {
+            streamFinished = true;
+            close();
+        }
+        return c;
+    }
+
+    /**
+     * {@inheritDoc}
+     * When the stream is consumed, the database resources held by the instance are closed.
+     */
+    public void close() throws IOException {
+        if (!streamClosed) {
+            streamClosed = true;
+            // It may be null (see constructor)
+            if (in != null) {
+                super.close();
+            }
+            // resources may be null (if getStream() was not called)
+            if (resources != null) {
+                resources.close();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long skip(long n) throws IOException {
+        if (in == null) {
+            getStream();
+        }
+        return in.skip(n);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int available() throws IOException {
+        if (in == null) {
+            getStream();
+        }
+        return in.available();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void mark(int readlimit) {
+        if (in == null) {
+            try {
+                getStream();
+            } catch (IOException e) {
+                log.info("Error getting underlying stream: ", e);
+            }
+        }
+        in.mark(readlimit);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void reset() throws IOException {
+        if (in == null) {
+            getStream();
+        }
+        in.reset();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean markSupported() {
+        if (in == null) {
+            try {
+                getStream();
+            } catch (IOException e) {
+                log.info("Error getting underlying stream: ", e);
+                return false;
+            }
+        }
+        return in.markSupported();
+    }
+}

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java?rev=649493&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java Fri Apr 18 05:27:48 2008
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.InputStream;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import org.apache.jackrabbit.core.persistence.bundle.util.ConnectionRecoveryManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents the resources used to back a database-based input stream.
+ */
+public class DbResources {
+
+    private static Logger log = LoggerFactory.getLogger(DbResources.class);
+
+    protected final ConnectionRecoveryManager conn;
+    protected final ResultSet rs;
+    protected final Statement stmt;
+    protected final InputStream in;
+    protected final DbDataStore store;
+    protected boolean closed;
+
+    public DbResources(ConnectionRecoveryManager conn, ResultSet rs, Statement stmt, InputStream in, DbDataStore store) {
+        this.conn = conn;
+        this.rs = rs;
+        this.stmt = stmt;
+        this.in = in;
+        this.store = store;
+        this.closed = false;
+    }
+
+    public ConnectionRecoveryManager getConnection() {
+        return conn;
+    }
+
+    public InputStream getInputStream() {
+        return in;
+    }
+
+    public ResultSet getResultSet() {
+        return rs;
+    }
+
+    public Statement getStatement() {
+        return stmt;
+    }
+
+    public void close() {
+        if (!closed) {
+            closed = true;
+            DatabaseHelper.closeSilently(rs);
+            try {
+                store.putBack(conn);
+            } catch (Exception e) {
+                log.info("Closing DbResources: ", e);
+            }
+        }
+    }
+}

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbResources.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java?rev=649493&r1=649492&r2=649493&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java Fri Apr 18 05:27:48 2008
@@ -39,6 +39,7 @@
         suite.addTestSuite(GCEventListenerTest.class);
         suite.addTestSuite(PersistenceManagerIteratorTest.class);
         suite.addTestSuite(CopyValueTest.class);
+        suite.addTestSuite(TestTwoGetStreams.class);
         return suite;
     }
 

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java?rev=649493&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java Fri Apr 18 05:27:48 2008
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test concurrent reads to the data store.
+ */
+public class TestTwoGetStreams extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(TestTwoGetStreams.class);
+
+    private static final int STREAM_LENGTH = 1 * 1024 * 1024;
+
+    /**
+     * Test reading from two concurrently opened streams.
+     */
+    public void testTwoGetStreams() throws Exception {
+        Session session = helper.getSuperuserSession();
+        Node root = session.getRootNode();
+        root.setProperty("p1", new RandomInputStream(1, STREAM_LENGTH));
+        root.setProperty("p2", new RandomInputStream(2, STREAM_LENGTH));
+        session.save();
+
+        InputStream i1 = root.getProperty("p1").getStream();
+        InputStream i2 = root.getProperty("p2").getStream();
+        assertEquals("p1", i1, new RandomInputStream(1, STREAM_LENGTH));
+        assertEquals("p2", i2, new RandomInputStream(2, STREAM_LENGTH));
+        try {
+            i1.close();
+        } catch (IOException e) {
+            log.info("Could not close first input stream: ", e);
+        }
+        try {
+            i2.close();
+        } catch (IOException e) {
+            log.info("Could not close second input stream: ", e);
+        }
+    }
+
+    /**
+     * Tests reading concurrently from two different streams.
+     */
+    public void testTwoStreamsConcurrently() throws Exception {
+        Session session = helper.getSuperuserSession();
+        Node root = session.getRootNode();
+        root.setProperty("p1", new RandomInputStream(1, STREAM_LENGTH));
+        root.setProperty("p2", new RandomInputStream(1, STREAM_LENGTH));
+        session.save();
+
+        InputStream i1 = root.getProperty("p1").getStream();
+        InputStream i2 = root.getProperty("p2").getStream();
+        assertEquals("Streams are different", i1, i2);
+        try {
+            i1.close();
+        } catch (IOException e) {
+            log.info("Could not close first input stream: ", e);
+        }
+        try {
+            i2.close();
+        } catch (IOException e) {
+            log.info("Could not close second input stream: ", e);
+        }
+    }
+
+    /**
+     * Tests reading concurrently from two different streams coming from the
+     * same property.
+     */
+    public void testTwoStreamsFromSamePropertyConcurrently() throws Exception {
+        Session session = helper.getSuperuserSession();
+        Node root = session.getRootNode();
+        root.setProperty("p1", new RandomInputStream(1, STREAM_LENGTH));
+        session.save();
+
+        InputStream i1 = root.getProperty("p1").getStream();
+        InputStream i2 = root.getProperty("p1").getStream();
+        assertEquals("Streams are different", i1, i2);
+        try {
+            i1.close();
+        } catch (IOException e) {
+            log.info("Could not close first input stream: ", e);
+        }
+        try {
+            i2.close();
+        } catch (IOException e) {
+            log.info("Could not close second input stream: ", e);
+        }
+    }
+
+    /**
+     * Asserts that two input streams are equal.
+     */
+    protected void assertEquals(String message, InputStream i1, InputStream i2) {
+        try {
+            int b1 = 0, b2 = 0;
+            int i = 0;
+            while (b1 != -1 || b2 != -1) {
+                b1 = i1.read();
+                b2 = i2.read();
+                assertEquals(message + "; byte #" + i + " mismatch!", b2, b1);
+                ++i;
+            }
+        } catch (Exception e) {
+            fail("Could not read inputstream! " + e);
+        }
+    }
+}

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestTwoGetStreams.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL