Author: amitj
Date: Mon Oct 24 04:53:09 2016
New Revision: 1766346

URL: http://svn.apache.org/viewvc?rev=1766346&view=rev
Log:
OAK-4979: Caching sub-system implementation for DataStore

* Fix for getReference for FileCacheDataRecord
* Consolidated calls to the Backend for getOrCreateReference
* Introduced new AbstractDataRecord & AbstractSharedbackend with those 
implementations

Added:
    
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java
   (with props)
    
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java
   (with props)
Modified:
    jackrabbit/oak/trunk/oak-blob/pom.xml
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/AbstractSharedCachingDataStore.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/AbstractDataStoreCacheTest.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/CachingDataStoreTest.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java

Modified: jackrabbit/oak/trunk/oak-blob/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob/pom.xml?rev=1766346&r1=1766345&r2=1766346&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-blob/pom.xml Mon Oct 24 04:53:09 2016
@@ -104,6 +104,11 @@
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
     </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.5</version>
+    </dependency>
 
     <!--  Optional dependency for jclouds s3 to enable s3 cloud store -->
     <dependency>

Added: 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java?rev=1766346&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java
 Mon Oct 24 04:53:09 2016
@@ -0,0 +1,98 @@
+/*
+ * 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.oak.spi.blob;
+
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
+
+/**
+ * Implements {@link DataRecord}
+ */
+public abstract class AbstractDataRecord implements DataRecord {
+
+    /**
+     * The data store that contains this record.
+     */
+    private final AbstractSharedBackend backend;
+
+    /**
+     * The binary identifier;
+     */
+    private final DataIdentifier identifier;
+
+    /**
+     * Creates a data record with the given identifier.
+     *
+     * @param identifier data identifier
+     */
+    public AbstractDataRecord(
+        AbstractSharedBackend backend, DataIdentifier identifier) {
+        this.backend = backend;
+        this.identifier = identifier;
+    }
+
+    /**
+     * Returns the data identifier.
+     *
+     * @return data identifier
+     */
+    public DataIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Delegates the call to the backend to retrieve reference.
+     *
+     * @return
+     */
+    public String getReference() {
+        return backend.getReferenceFromIdentifier(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();
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractDataRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java?rev=1766346&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java
 Mon Oct 24 04:53:09 2016
@@ -0,0 +1,97 @@
+/*
+ * 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.oak.spi.blob;
+
+import java.security.SecureRandom;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.commons.codec.binary.Hex.encodeHexString;
+
+/**
+ */
+public abstract class AbstractSharedBackend implements SharedBackend {
+    private static Logger LOG = 
LoggerFactory.getLogger(AbstractSharedBackend.class);
+
+    private static final String ALGORITHM = "HmacSHA1";
+
+    /**
+     * Cached copy of the reference key of this data store. Initialized in
+     * {@link #getReferenceKey()} when the key is first accessed.
+     */
+    private byte[] referenceKey = null;
+
+
+    protected String getReferenceFromIdentifier(DataIdentifier identifier) {
+        try {
+            String id = identifier.toString();
+
+            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) {
+            LOG.error("Failed to hash identifier using MAC (Message 
Authentication Code) algorithm.", e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the reference key of this backend. 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 backend
+     * 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
+     */
+    public byte[] getOrCreateReferenceKey() throws DataStoreException {
+        byte[] referenceKeyValue = new byte[256];
+        new SecureRandom().nextBytes(referenceKeyValue);
+        return referenceKeyValue;
+    }
+
+    //-----------------------------------------------------------< 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 referenceKey;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-blob/src/main/java/org/apache/jackrabbit/oak/spi/blob/AbstractSharedBackend.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/AbstractSharedCachingDataStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/AbstractSharedCachingDataStore.java?rev=1766346&r1=1766345&r2=1766346&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/AbstractSharedCachingDataStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/AbstractSharedCachingDataStore.java
 Mon Oct 24 04:53:09 2016
@@ -41,13 +41,13 @@ import com.google.common.io.Closeables;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.core.data.AbstractDataRecord;
 import org.apache.jackrabbit.core.data.AbstractDataStore;
 import org.apache.jackrabbit.core.data.DataIdentifier;
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStoreException;
 import org.apache.jackrabbit.core.data.MultiDataStoreAware;
-import org.apache.jackrabbit.oak.spi.blob.SharedBackend;
+import org.apache.jackrabbit.oak.spi.blob.AbstractDataRecord;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
 import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.apache.jackrabbit.util.TransientFileFactory;
 import org.slf4j.Logger;
@@ -129,7 +129,7 @@ public abstract class AbstractSharedCach
     /**
      * The delegate backend
      */
-    protected SharedBackend backend;
+    protected AbstractSharedBackend backend;
 
     protected ListeningExecutorService listeningExecutor;
 
@@ -171,7 +171,19 @@ public abstract class AbstractSharedCach
             }, statisticsProvider, listeningExecutor, schedulerExecutor, 
stagingPurgeInterval);
     }
 
-    protected abstract SharedBackend createBackend();
+    protected abstract AbstractSharedBackend createBackend();
+
+    @Override
+    public DataRecord getRecord(DataIdentifier identifier)
+        throws DataStoreException {
+        DataRecord record = getRecordIfStored(identifier);
+        if (record != null) {
+            return record;
+        } else {
+            throw new DataStoreException(
+                "Record " + identifier + " does not exist");
+        }
+    }
 
     @Override
     @Nullable
@@ -181,7 +193,7 @@ public abstract class AbstractSharedCach
         // This avoids downloading the file for just accessing the meta data
         File cached = cache.getIfPresent(dataIdentifier.toString());
         if (cached != null && cached.exists()) {
-            return new FileCacheDataRecord(this, dataIdentifier, 
cached.length(),
+            return new FileCacheDataRecord(this, backend, dataIdentifier, 
cached.length(),
                 cached.lastModified());
         }
 
@@ -264,9 +276,10 @@ public abstract class AbstractSharedCach
         private final long lastModified;
         private final AbstractSharedCachingDataStore store;
 
-        public FileCacheDataRecord(AbstractSharedCachingDataStore store, 
DataIdentifier identifier, long length,
+        public FileCacheDataRecord(AbstractSharedCachingDataStore store, 
AbstractSharedBackend backend,
+            DataIdentifier identifier, long length,
             long lastModified) {
-            super(store, identifier);
+            super(backend, identifier);
             this.length = length;
             this.lastModified = lastModified;
             this.store = store;
@@ -394,6 +407,11 @@ public abstract class AbstractSharedCach
         return SharedDataStore.Type.SHARED;
     }
 
+    @Override
+    protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+        return backend.getOrCreateReferenceKey();
+    }
+
     /**------------------------ unimplemented methods 
-------------------------------------------**/
 
     @Override

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/AbstractDataStoreCacheTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/AbstractDataStoreCacheTest.java?rev=1766346&r1=1766345&r2=1766346&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/AbstractDataStoreCacheTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/AbstractDataStoreCacheTest.java
 Mon Oct 24 04:53:09 2016
@@ -47,12 +47,12 @@ import com.google.common.util.concurrent
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import org.apache.commons.io.FileUtils;
-import org.apache.jackrabbit.core.data.AbstractDataRecord;
 import org.apache.jackrabbit.core.data.DataIdentifier;
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStoreException;
 import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
-import org.apache.jackrabbit.oak.spi.blob.SharedBackend;
+import org.apache.jackrabbit.oak.spi.blob.AbstractDataRecord;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -228,7 +228,7 @@ public class AbstractDataStoreCacheTest
 
     // A mock Backend implementation that uses a Map to keep track of what
     // records have been added and removed, for test purposes only.
-    static class TestMemoryBackend implements SharedBackend {
+    static class TestMemoryBackend extends AbstractSharedBackend {
         final Map<DataIdentifier, File> _backend = Maps.newHashMap();
 
         @Override public InputStream read(DataIdentifier identifier) throws 
DataStoreException {
@@ -252,7 +252,7 @@ public class AbstractDataStoreCacheTest
         @Override public DataRecord getRecord(DataIdentifier id) throws 
DataStoreException {
             if (_backend.containsKey(id)) {
                 final File f = _backend.get(id);
-                return new AbstractDataRecord(null, id) {
+                return new AbstractDataRecord(this, id) {
                     @Override public long getLength() throws 
DataStoreException {
                         return f.length();
                     }
@@ -320,6 +320,11 @@ public class AbstractDataStoreCacheTest
         @Override public void init() throws DataStoreException {
 
         }
+
+        @Override
+        public String getReferenceFromIdentifier(DataIdentifier identifier) {
+            return super.getReferenceFromIdentifier(identifier);
+        }
     }
 
     static InputStream randomStream(int seed, int size) {

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/CachingDataStoreTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/CachingDataStoreTest.java?rev=1766346&r1=1766345&r2=1766346&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/CachingDataStoreTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/CachingDataStoreTest.java
 Mon Oct 24 04:53:09 2016
@@ -39,7 +39,7 @@ import org.apache.jackrabbit.core.data.D
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStoreException;
 import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
-import org.apache.jackrabbit.oak.spi.blob.SharedBackend;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
 import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider;
 import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.junit.After;
@@ -79,6 +79,7 @@ public class CachingDataStoreTest extend
     private CountDownLatch afterExecuteLatch;
     private ScheduledExecutorService scheduledExecutor;
     private AbstractSharedCachingDataStore dataStore;
+    private TestMemoryBackend backend;
 
     @Before
     public void setup() throws Exception {
@@ -100,9 +101,12 @@ public class CachingDataStoreTest extend
 
         scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
         closer.register(new ExecutorCloser(scheduledExecutor, 500, 
TimeUnit.MILLISECONDS));
+        final TestMemoryBackend testBackend = new TestMemoryBackend();
+        this.backend = testBackend;
+
         dataStore = new AbstractSharedCachingDataStore() {
-            @Override protected SharedBackend createBackend() {
-                return new TestMemoryBackend();
+            @Override protected AbstractSharedBackend createBackend() {
+                return testBackend;
             }
 
             @Override public int getMinRecordLength() {
@@ -274,6 +278,63 @@ public class CachingDataStoreTest extend
         assertTrue(Iterators.contains(dataStore.getAllIdentifiers(), new 
DataIdentifier(id)));
     }
 
+    @Test
+    public void reference() throws Exception {
+        File f = copyToFile(randomStream(0, 4 * 1024), folder.newFile());
+        String id = getIdForInputStream(f);
+        FileInputStream fin = new FileInputStream(f);
+        closer.register(fin);
+
+        // Record still in staging
+        DataRecord rec = dataStore.addRecord(fin);
+        assertEquals(id, rec.getIdentifier().toString());
+        assertFile(rec.getStream(), f, folder);
+        assertEquals(backend.getReferenceFromIdentifier(rec.getIdentifier()),
+            rec.getReference());
+
+        rec = dataStore.getRecordIfStored(new DataIdentifier(id));
+        assertNotNull(rec);
+        assertFile(rec.getStream(), f, folder);
+        assertEquals(backend.getReferenceFromIdentifier(rec.getIdentifier()),
+            rec.getReference());
+
+        //start & finish
+        taskLatch.countDown();
+        callbackLatch.countDown();
+        waitFinish();
+
+        // Now record in download cache
+        rec = dataStore.getRecordIfStored(new DataIdentifier(id));
+        assertNotNull(rec);
+        assertFile(rec.getStream(), f, folder);
+        assertEquals(backend.getReferenceFromIdentifier(rec.getIdentifier()),
+            rec.getReference());
+    }
+
+    @Test
+    public void referenceNoCache() throws Exception {
+        dataStore.close();
+        init(1, 0, 0);
+
+        File f = copyToFile(randomStream(0, 4 * 1024), folder.newFile());
+        String id = getIdForInputStream(f);
+        FileInputStream fin = new FileInputStream(f);
+        closer.register(fin);
+
+        // Record still in staging
+        DataRecord rec = dataStore.addRecord(fin);
+        assertEquals(id, rec.getIdentifier().toString());
+        assertFile(rec.getStream(), f, folder);
+        assertEquals(backend.getReferenceFromIdentifier(rec.getIdentifier()),
+            rec.getReference());
+
+        rec = dataStore.getRecordIfStored(new DataIdentifier(id));
+        assertNotNull(rec);
+        assertFile(rec.getStream(), f, folder);
+        assertEquals(backend.getReferenceFromIdentifier(rec.getIdentifier()),
+            rec.getReference());
+    }
+
     @After
     public void tear() throws Exception {
         closer.close();

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java?rev=1766346&r1=1766345&r2=1766346&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java
 Mon Oct 24 04:53:09 2016
@@ -44,7 +44,7 @@ import org.apache.jackrabbit.oak.commons
 import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.plugins.memory.MultiBinaryPropertyState;
-import org.apache.jackrabbit.oak.spi.blob.SharedBackend;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
 import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -126,7 +126,7 @@ public class ConsolidatedDataStoreStatsT
         scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
         closer.register(new ExecutorCloser(scheduledExecutor, 500, 
TimeUnit.MILLISECONDS));
         dataStore = new AbstractSharedCachingDataStore() {
-            @Override protected SharedBackend createBackend() {
+            @Override protected AbstractSharedBackend createBackend() {
                 return new TestMemoryBackend();
             }
 


Reply via email to