You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2013/05/20 13:52:55 UTC
svn commit: r1484440 - in /jackrabbit/trunk/jackrabbit-core/src:
main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java
main/java/org/apache/jackrabbit/core/data/FileDataStore.java
test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
Author: jukka
Date: Mon May 20 11:52:55 2013
New Revision: 1484440
URL: http://svn.apache.org/r1484440
Log:
JCR-3534: Efficient copying of binaries across repositories with the same data store
Revised version of Tommaso's patch. Including a test case.
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java?rev=1484440&r1=1484439&r2=1484440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java Mon May 20 11:52:55 2013
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.core.data;
+import java.security.SecureRandom;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -28,12 +29,29 @@ public abstract class AbstractDataStore
*/
private static final char[] HEX = "0123456789abcdef".toCharArray();
- private String secret;
+ /**
+ * Cached copy of the reference key of this data store. Initialized in
+ * {@link #getReferenceKey()} when the key is first accessed.
+ */
+ private byte[] referenceKey = null;
+
+ //---------------------------------------------------------< DataStore >--
- public void setSecret(String secret) {
- this.secret = secret;
+ @Override
+ public DataIdentifier getIdentifierFromReference(String reference) {
+ int colon = reference.indexOf(':');
+ if (colon != -1) {
+ DataIdentifier identifier =
+ new DataIdentifier(reference.substring(0, colon));
+ if (reference.equals(getReferenceFromIdentifier(identifier))) {
+ return identifier;
+ }
+ }
+ return null;
}
+ //---------------------------------------------------------< protected >--
+
/**
* Returns the hex encoding of the given bytes.
*
@@ -49,37 +67,55 @@ public abstract class AbstractDataStore
return new String(buffer);
}
+ protected String getReferenceFromIdentifier(DataIdentifier identifier) {
+ try {
+ String id = identifier.toString();
- @Override
- public DataIdentifier getIdentifierFromReference(String reference) {
- int colon = reference.indexOf(':');
- if (colon != -1) {
- DataIdentifier identifier =
- new DataIdentifier(reference.substring(0, colon));
- if (reference.equals(getReferenceFromIdentifier(identifier))) {
- return identifier;
- }
+ Mac mac = Mac.getInstance(ALGORITHM);
+ mac.init(new SecretKeySpec(getReferenceKey(), ALGORITHM));
+ byte[] hash = mac.doFinal(id.getBytes("UTF-8"));
+
+ return id + ':' + encodeHexString(hash);
+ } catch (Exception e) {
+ // TODO: log a warning about this exception
}
return null;
}
- //---------------------------------------------------------< protected >--
+ /**
+ * Returns the reference key of this data store. If one does not already
+ * exist, it is automatically created in an implementation-specific way.
+ * The default implementation simply creates a temporary random key that's
+ * valid only until the data store gets restarted. Subclasses can override
+ * and/or decorate this method to support a more persistent reference key.
+ * <p>
+ * This method is called only once during the lifetime of a data store
+ * instance and the return value is cached in memory, so it's no problem
+ * if the implementation is slow.
+ *
+ * @return reference key
+ * @throws DataStoreException if the key is not available
+ */
+ protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+ byte[] referenceKeyValue = new byte[256];
+ new SecureRandom().nextBytes(referenceKeyValue);
+ return referenceKeyValue;
+ }
- protected String getReferenceFromIdentifier(DataIdentifier identifier) {
- if (secret != null) {
- try {
- String id = identifier.toString();
-
- Mac mac = Mac.getInstance(ALGORITHM);
- mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), ALGORITHM));
- byte[] hash = mac.doFinal(id.getBytes("UTF-8"));
-
- return id + ':' + encodeHexString(hash);
- } catch (Exception e) {
- // TODO: log a warning about this exception
- }
+ //-----------------------------------------------------------< private >--
+
+ /**
+ * Returns the reference key of this data store. Synchronized to
+ * control concurrent access to the cached {@link #referenceKey} value.
+ *
+ * @return reference key
+ * @throws DataStoreException if the key is not available
+ */
+ private synchronized byte[] getReferenceKey() throws DataStoreException {
+ if (referenceKey == null) {
+ referenceKey = getOrCreateReferenceKey();
}
- return null;
+ return referenceKey;
}
-}
+}
\ No newline at end of file
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java?rev=1484440&r1=1484439&r2=1484440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java Mon May 20 11:52:55 2013
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -307,7 +308,13 @@ public class FileDataStore extends Abstr
}
public int deleteAllOlderThan(long min) {
- return deleteOlderRecursive(directory, min);
+ int count = 0;
+ for (File file : directory.listFiles()) {
+ if (file.isDirectory()) { // skip top-level files
+ count += deleteOlderRecursive(file, min);
+ }
+ }
+ return count;
}
private int deleteOlderRecursive(File file, long min) {
@@ -348,11 +355,9 @@ public class FileDataStore extends Abstr
// JCR-1396: FileDataStore Garbage Collector and empty directories
// Automatic removal of empty directories (but not the root!)
synchronized (this) {
- if (file != directory) {
- list = file.listFiles();
- if (list != null && list.length == 0) {
- file.delete();
- }
+ list = file.listFiles();
+ if (list != null && list.length == 0) {
+ file.delete();
}
}
}
@@ -374,14 +379,16 @@ public class FileDataStore extends Abstr
public Iterator<DataIdentifier> getAllIdentifiers() {
ArrayList<File> files = new ArrayList<File>();
- listRecursive(files, directory);
+ for (File file : directory.listFiles()) {
+ if (file.isDirectory()) { // skip top-level files
+ listRecursive(files, file);
+ }
+ }
+
ArrayList<DataIdentifier> identifiers = new ArrayList<DataIdentifier>();
for (File f: files) {
String name = f.getName();
- if (!name.startsWith(TMP)) {
- DataIdentifier id = new DataIdentifier(name);
- identifiers.add(id);
- }
+ identifiers.add(new DataIdentifier(name));
}
log.debug("Found " + identifiers.size() + " identifiers.");
return identifiers.iterator();
@@ -426,6 +433,27 @@ public class FileDataStore extends Abstr
// nothing to do
}
+ //---------------------------------------------------------< protected >--
+
+ @Override
+ protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+ File file = new File(directory, "reference.key");
+ try {
+ if (file.exists()) {
+ return FileUtils.readFileToByteArray(file);
+ } else {
+ byte[] key = super.getOrCreateReferenceKey();
+ FileUtils.writeByteArrayToFile(file, key);
+ return key;
+ }
+ } catch (IOException e) {
+ throw new DataStoreException(
+ "Unable to access reference key file " + file.getPath(), e);
+ }
+ }
+
+ //-----------------------------------------------------------< private >--
+
/**
* Get the last modified date of a file.
*
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java?rev=1484440&r1=1484439&r2=1484440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java Mon May 20 11:52:55 2013
@@ -17,6 +17,7 @@
package org.apache.jackrabbit.core.data;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.core.data.db.DbDataStore;
import org.apache.jackrabbit.test.JUnitTest;
@@ -25,6 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.security.SecureRandom;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -102,6 +104,53 @@ public class DataStoreTest extends JUnit
}
}
+ public void testReference() throws Exception {
+ byte[] data = new byte[12345];
+ new Random(12345).nextBytes(data);
+ String reference;
+
+ FileDataStore store = new FileDataStore();
+ store.init(testDir + "/reference");
+ try {
+ DataRecord record = store.addRecord(new ByteArrayInputStream(data));
+ reference = record.getReference();
+
+ DataIdentifier identifier = store.getIdentifierFromReference(reference);
+ record = store.getRecord(identifier);
+ assertEquals(data.length, record.getLength());
+ InputStream stream = record.getStream();
+ try {
+ for (int i = 0; i < data.length; i++) {
+ assertEquals(data[i] & 0xff, stream.read());
+ }
+ assertEquals(-1, stream.read());
+ } finally {
+ stream.close();
+ }
+ } finally {
+ store.close();
+ }
+
+ store = new FileDataStore();
+ store.init(testDir + "/reference");
+ try {
+ DataIdentifier identifier = store.getIdentifierFromReference(reference);
+ DataRecord record = store.getRecord(identifier);
+ assertEquals(data.length, record.getLength());
+ InputStream stream = record.getStream();
+ try {
+ for (int i = 0; i < data.length; i++) {
+ assertEquals(data[i] & 0xff, stream.read());
+ }
+ assertEquals(-1, stream.read());
+ } finally {
+ stream.close();
+ }
+ } finally {
+ store.close();
+ }
+ }
+
private void shutdownDatabase(String url) {
if (url.startsWith("jdbc:derby:") || url.startsWith("jdbc:hsqldb:")) {
try {