Return-Path: X-Original-To: apmail-jackrabbit-commits-archive@www.apache.org Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 15065D230 for ; Mon, 20 May 2013 11:53:29 +0000 (UTC) Received: (qmail 84609 invoked by uid 500); 20 May 2013 11:53:29 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 84387 invoked by uid 500); 20 May 2013 11:53:23 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 84356 invoked by uid 99); 20 May 2013 11:53:21 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 20 May 2013 11:53:21 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 20 May 2013 11:53:18 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 9E0E02388962; Mon, 20 May 2013 11:52:55 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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 Date: Mon, 20 May 2013 11:52:55 -0000 To: commits@jackrabbit.apache.org From: jukka@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130520115255.9E0E02388962@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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. + *

+ * 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 getAllIdentifiers() { ArrayList files = new ArrayList(); - listRecursive(files, directory); + for (File file : directory.listFiles()) { + if (file.isDirectory()) { // skip top-level files + listRecursive(files, file); + } + } + ArrayList identifiers = new ArrayList(); 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 {