Index: src/test/java/org/apache/jackrabbit/core/data/FileDataStoreTest.java
===================================================================
--- src/test/java/org/apache/jackrabbit/core/data/FileDataStoreTest.java	(revision 0)
+++ src/test/java/org/apache/jackrabbit/core/data/FileDataStoreTest.java	(revision 0)
@@ -0,0 +1,189 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the {@link FileDataStore} class.
+ */
+public class FileDataStoreTest extends TestCase {
+
+    /**
+     * Test data.
+     */
+    private static final byte[] DATA = "Test content".getBytes();
+
+    /**
+     * The temporary directory used for the test data store.
+     */
+    private File directory;
+
+    /**
+     * Creates the temporary test directory.
+     */
+    protected void setUp() throws Exception {
+        directory = File.createTempFile("FileDataStore", "");
+        directory.delete();
+    }
+
+    /**
+     * Removes the temporary test directory.
+     */
+    protected void tearDown() throws Exception {
+        delete(directory);
+    }
+
+    /**
+     * Recursively deletes the given file or directory.
+     *
+     * @param file file or directory
+     */
+    private void delete(File file) {
+        File[] files = file.listFiles();
+        for (int i = 0; files != null && i < files.length; i++) {
+            delete(files[i]);
+        }
+        file.delete();
+    }
+
+    /**
+     * Tests that an empty stream is correctly stored.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testAddEmptyRecord() throws IOException {
+        DataStore store = new FileDataStore(directory);
+        DataRecord record =
+            store.addRecord(new ByteArrayInputStream(new byte[0]));
+        assertNotNull(record);
+        assertNotNull(record.getIdentifier());
+        assertEquals(0, record.getLength());
+        InputStream stream = record.getStream();
+        try {
+            assertNotNull(stream);
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Tests that a stream is correctly stored.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testAddRecord() throws IOException {
+        DataStore store = new FileDataStore(directory);
+        DataRecord record = store.addRecord(new ByteArrayInputStream(DATA));
+        assertNotNull(record);
+        assertNotNull(record.getIdentifier());
+        assertEquals(DATA.length, record.getLength());
+        InputStream stream = record.getStream();
+        try {
+            assertNotNull(stream);
+            for (int i = 0; i < DATA.length; i++) {
+                assertEquals(DATA[i], stream.read());
+            }
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Tests that a stored stream can be retrieved.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testGetRecord() throws IOException {
+        DataStore store = new FileDataStore(directory);
+        DataRecord record1 = store.addRecord(new ByteArrayInputStream(DATA));
+        DataRecord record2 = store.getRecord(record1.getIdentifier());
+        assertNotNull(record2);
+        assertEquals(record1.getIdentifier(), record2.getIdentifier());
+        assertEquals(record1, record2);
+        assertEquals(DATA.length, record2.getLength());
+        InputStream stream = record2.getStream();
+        try {
+            assertNotNull(stream);
+            for (int i = 0; i < DATA.length; i++) {
+                assertEquals(DATA[i], stream.read());
+            }
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Tests that adding a record in one store can be read in another that is
+     * accessing the same directory.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testGetRecordFromAnotherInstance() throws IOException {
+        DataStore store1 = new FileDataStore(directory);
+        DataStore store2 = new FileDataStore(directory);
+        DataRecord record1 = store1.addRecord(new ByteArrayInputStream(DATA));
+        DataRecord record2 = store2.getRecord(record1.getIdentifier());
+        assertEquals(record1.getIdentifier(), record2.getIdentifier());
+        assertEquals(DATA.length, record2.getLength());
+        InputStream stream = record2.getStream();
+        try {
+            assertNotNull(stream);
+            for (int i = 0; i < DATA.length; i++) {
+                assertEquals(DATA[i], stream.read());
+            }
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Tests that adding two identical streams produces the same records.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testAddSameRecords() throws IOException {
+        DataStore store = new FileDataStore(directory);
+        DataRecord record1 = store.addRecord(new ByteArrayInputStream(DATA));
+        DataRecord record2 = store.addRecord(new ByteArrayInputStream(DATA));
+        assertEquals(record1.getIdentifier(), record2.getIdentifier());
+        assertEquals(record1, record2);
+    }
+
+    /**
+     * Tests that adding two different streams produces different records.
+     *
+     * @throws IOException if an error occurs
+     */
+    public void testAddDifferentRecords() throws IOException {
+        DataStore store = new FileDataStore(directory);
+        DataRecord record1 = store.addRecord(new ByteArrayInputStream(DATA));
+        DataRecord record2 = store.addRecord(new ByteArrayInputStream(new byte[0]));
+        assertTrue(!record1.getIdentifier().equals(record2.getIdentifier()));
+        assertTrue(!record1.equals(record2));
+    }
+
+}

Property changes on: src\test\java\org\apache\jackrabbit\core\data\FileDataStoreTest.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/StringDataIdentifier.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/StringDataIdentifier.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/StringDataIdentifier.java	(revision 0)
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+/**
+ * Data identifier based on a string.
+ */
+public class StringDataIdentifier implements DataIdentifier {
+
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = 4298427655110204208L;
+
+    /**
+     * Array of hexadecimal digits.
+     */
+    private static final char[] HEX = "0123456789abcdef".toCharArray();
+
+    /**
+     * Data identifier.
+     */
+    private final String identifier;
+
+    /**
+     * Creates a data identifier from the given string.
+     *
+     * @param identifier data identifier
+     */
+    public StringDataIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    /**
+     * Creates a data identifier from the hexadecimal string
+     * representation of the given bytes.
+     *
+     * @param identifier data identifier
+     */
+    public StringDataIdentifier(byte[] identifier) {
+        char[] buffer = new char[identifier.length * 2];
+        for (int i = 0; i < identifier.length; i++) {
+            buffer[2*i] = HEX[(identifier[i] >> 4) & 0x0f]; 
+            buffer[2*i + 1] = HEX[identifier[i] & 0x0f]; 
+        }
+        this.identifier = new String(buffer);
+    }
+
+    //-------------------------------------------------------------< Object >
+
+    /**
+     * Returns the identifier string.
+     *
+     * @return identifier string
+     */
+    public String toString() {
+        return identifier;
+    }
+
+    /**
+     * Checks if the given object is a data identifier and has the same
+     * string representation as this one.
+     *
+     * @param object other object
+     * @return <code>true</code> if the given object is the same identifier,
+     *         <code>false</code> otherwise
+     */
+    public boolean equals(Object object) {
+        return (object instanceof DataIdentifier)
+            && identifier.equals(object.toString()); 
+    }
+
+    /**
+     * Returns the hash code of the identifier string.
+     *
+     * @return hash code
+     */
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\StringDataIdentifier.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/DataStore.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/DataStore.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/DataStore.java	(revision 0)
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * Append-only store for binary streams. A data store consists of a number
+ * of identifiable data records that each contain a distinct binary stream.
+ * New binary streams can be added to the data store, but existing streams
+ * are never removed or modified.
+ * <p>
+ * A data store should be fully thread-safe, i.e. it should be possible to
+ * add and access data records concurrently. Optimally even separate processes
+ * should be able to concurrently access the data store with zero interprocess
+ * synchronization.
+ */
+public interface DataStore {
+
+    /**
+     * Returns the identified data record. The given identifier should be
+     * the identifier of a previously saved data record. Since records are
+     * never removed, there should never be cases where the identified record
+     * is not found. Abnormal cases like that are treated as errors and
+     * handled by throwing an exception.
+     *
+     * @param identifier data identifier
+     * @return identified data record
+     * @throws IOException if the data store could not be accessed,
+     *                     or if the given identifier is invalid
+     */
+    DataRecord getRecord(DataIdentifier identifier) throws IOException;
+
+    /**
+     * Creates a new data record. The given binary stream is consumed and
+     * a binary record containing the consumed stream is created and returned.
+     * If the same stream already exists in another record, then that record
+     * is returned instead of creating a new one.
+     * <p>
+     * The given stream is consumed and <strong>not closed</strong> by this
+     * method. It is the responsibility of the caller to close the stream.
+     * A typical call pattern would be:
+     * <pre>
+     *     InputStream stream = ...;
+     *     try {
+     *         record = store.addRecord(stream);
+     *     } finally {
+     *         stream.close();
+     *     }
+     * </pre>
+     *
+     * @param stream binary stream
+     * @return data record that contains the given stream
+     * @throws IOException if the data store could not be accessed
+     */
+    DataRecord addRecord(InputStream stream) throws IOException;
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\DataStore.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java	(revision 0)
@@ -0,0 +1,198 @@
+/*
+ * 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.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Random;
+
+/**
+ * Simple file-based data store. Data records are stored as normal files
+ * named using a message digest of the contained binary stream.
+ * <p>
+ * A three level directory structure is used to avoid placing too many
+ * files in a single directory. The chosen structure is designed to scale
+ * up to billions of distinct records.
+ * <p>
+ * This implementation relies on the underlying file system to support
+ * atomic O(1) move operations with {@link File#renameTo(File)}.
+ */
+public class FileDataStore implements DataStore {
+
+    /**
+     * The digest algorithm used to uniquely identify records.
+     */
+    private static final String DIGEST = "SHA-1";
+
+    /**
+     * Name of the directory used for temporary files.
+     */
+    private static final String TMP = "tmp";
+
+    /**
+     * Temporary file counter used to guarantee that concurrent threads
+     * in this JVM do not accidentally use the same temporary file names.
+     * <p>
+     * This variable is static to allow multiple separate data store
+     * instances in the same JVM to access the same data store directory
+     * on disk. The counter is initialized to a random number based on the
+     * time when this class was first loaded to minimize the chance of two
+     * separate JVM processes (or class loaders within the same JVM) using
+     * the same temporary file names. 
+     */
+    private static long counter = new Random().nextLong();
+
+    /**
+     * Returns the next value of the internal temporary file counter.
+     *
+     * @return next counter value
+     */
+    private static synchronized long nextCount() {
+        return counter++;
+    }
+
+    /**
+     * The directory that contains all the data record files. The structure
+     * of content within this directory is controlled by this class.
+     */
+    private final File directory;
+
+    /**
+     * Creates a data store based on the given directory.
+     *
+     * @param directory data store directory
+     */
+    public FileDataStore(File directory) {
+        this.directory = directory;
+    }
+
+    /**
+     * Returns the record with the given identifier. Note that this method
+     * performs no sanity checks on the given identifier. It is up to the
+     * caller to ensure that only identifiers of previously created data
+     * records are used.
+     *
+     * @param identifier data identifier
+     * @return identified data record
+     */
+    public DataRecord getRecord(DataIdentifier identifier) {
+        return new FileDataRecord(identifier, getFile(identifier));
+    }
+
+    /**
+     * Creates a new record based on the given input stream. The stream
+     * is first consumed and the contents are saved in a temporary file
+     * and the SHA-1 message digest of the stream is calculated. If a
+     * record with the same SHA-1 digest (and length) is found then it is
+     * returned. Otherwise the temporary file is moved in place to become
+     * the new data record that gets returned.
+     *
+     * @param input binary stream
+     * @return data record that contains the given stream
+     * @throws IOException if the record could not be created
+     */
+    public DataRecord addRecord(InputStream input) throws IOException {
+        File temporary = newTemporaryFile();
+        try {
+            // Copy the stream to the temporary file and calculate the
+            // stream length and the message digest of the stream
+            long length = 0;
+            MessageDigest digest = MessageDigest.getInstance(DIGEST);
+            OutputStream output = new FileOutputStream(temporary);
+            try {
+                byte[] b = new byte[4096];
+                for (int n = input.read(b); n != -1; n = input.read(b)) {
+                    output.write(b, 0, n);
+                    digest.update(b, 0, n);
+                    length += n;
+                }
+            } finally {
+                output.close();
+            }
+
+            // Check if the same record already exists, or
+            // move the temporary file in place if needed
+            DataIdentifier identifier =
+                new StringDataIdentifier(digest.digest());
+            File file = getFile(identifier);
+            File parent = file.getParentFile();
+            if (!parent.isDirectory()) {
+                parent.mkdirs();
+            }
+            if (!file.exists()) {
+                temporary.renameTo(file);
+            }
+
+            // Sanity checks on the record file. These should never fail,
+            // but better safe than sorry...
+            if (!file.isFile()) {
+                throw new IOException("Not a file: " + file);
+            }
+            if (file.length() != length) {
+                throw new IOException(DIGEST + " collision: " + file);
+            }
+
+            return new FileDataRecord(identifier, file);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException(DIGEST + " not available: " + e.getMessage());
+        } finally {
+            temporary.delete();
+        }
+    }
+
+    /**
+     * Returns the identified file. This method implements the pattern
+     * used to avoid problems with too many files in a single directory.
+     * <p>
+     * No sanity checks are performed on the given identifier.
+     *
+     * @param identifier data identifier
+     * @return identified file
+     */
+    private File getFile(DataIdentifier identifier) {
+        String string = identifier.toString();
+        File file = directory;
+        file = new File(file, string.substring(0, 2));
+        file = new File(file, string.substring(2, 4));
+        file = new File(file, string.substring(4, 6));
+        return new File(file, string);
+    }
+
+    /**
+     * Returns a unique temporary file to be used for creating a new
+     * data record. A synchronized counter value and the current time are
+     * used to construct the name of the temporary file in a way that
+     * minimizes the chance of collisions across concurrent threads or
+     * processes.
+     *
+     * @return temporary file
+     */
+    private File newTemporaryFile() {
+        File temporary = new File(directory, TMP);
+        if (!temporary.isDirectory()) {
+            temporary.mkdirs();
+        }
+        String name = TMP + "-" + nextCount() + "-" + System.currentTimeMillis();
+        return new File(temporary, name);
+    }
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\FileDataStore.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/DataRecord.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/DataRecord.java	(revision 0)
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Immutable data record that consists of a binary stream.
+ */
+public interface DataRecord {
+
+    /**
+     * Returns the identifier of this record.
+     *
+     * @return data identifier
+     */
+    DataIdentifier getIdentifier();
+
+    /**
+     * Returns the length of the binary stream in this record.
+     *
+     * @return length of the binary stream
+     * @throws IOException if the record could not be accessed
+     */
+    long getLength() throws IOException;
+
+    /**
+     * Returns the the binary stream in this record.
+     *
+     * @return binary stream
+     * @throws IOException if the record could not be accessed
+     */
+    InputStream getStream() throws IOException;
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\DataRecord.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java	(revision 0)
@@ -0,0 +1,66 @@
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Data record that is based on a normal file.
+ */
+class FileDataRecord extends AbstractDataRecord {
+
+    /**
+     * The file that contains the binary stream.
+     */
+    private final File file;
+
+    /**
+     * Creates a data record based on the given identifier and file.
+     *
+     * @param identifier data identifier
+     * @param file file that contains the binary stream
+     */
+    public FileDataRecord(DataIdentifier identifier, File file) {
+        super(identifier);
+        assert file.isFile();
+        this.file = file;
+    }
+
+    /**
+     * Returns the length of the file.
+     *
+     * @return file length
+     */
+    public long getLength() {
+        return file.length();
+    }
+
+    /**
+     * Returns an input stream for reading the file.
+     *
+     * @return file input stream
+     * @throws IOException if the file could not be opened
+     */
+    public InputStream getStream() throws IOException {
+        return new FileInputStream(file);
+    }
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\FileDataRecord.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java	(revision 0)
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+/**
+ * Abstract data record base class. This base class contains only
+ * a reference to the data identifier of the record and implements
+ * the standard {@link Object} equality, hash code, and string
+ * representation methods based on the identifier.
+ */
+public abstract class AbstractDataRecord implements DataRecord {
+
+    /**
+     * The binary identifier;
+     */
+    private final DataIdentifier identifier;
+
+    /**
+     * Creates a data record with the given identifier.
+     *
+     * @param identifier data identifier
+     */
+    public AbstractDataRecord(DataIdentifier identifier) {
+        this.identifier = identifier;
+    }
+
+    /**
+     * Returns the data identifier.
+     *
+     * @param data identifier
+     */
+    public DataIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Returns the string representation of the data identifier.
+     *
+     * @return string representation
+     */
+    public String toString() {
+        return identifier.toString();
+    }
+
+    /**
+     * Checks if the given object is a data record with the same identifier
+     * as this one.
+     *
+     * @param object other object
+     * @return <code>true</code> if the other object is a data record and has
+     *         the same identifier as this one, <code>false</code> otherwise
+     */
+    public boolean equals(Object object) {
+        return (object instanceof DataRecord)
+            && identifier.equals(((DataRecord) object).getIdentifier()); 
+    }
+
+    /**
+     * Returns the hash code of the data identifier.
+     *
+     * @return hash code
+     */
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\AbstractDataRecord.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java	(revision 0)
@@ -0,0 +1,27 @@
+/*
+ * 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.Serializable;
+
+/**
+ * Opaque data identifier used to identify records in a data store. 
+ * All identifiers must be serializable and implement the standard
+ * object equality and hash code methods.
+ */
+public interface DataIdentifier extends Serializable {
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\data\DataIdentifier.java
___________________________________________________________________
Name: svn:eol-style
   + native

