You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by am...@apache.org on 2016/10/24 04:51:47 UTC

svn commit: r1766339 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ oak-it/src/test/jav...

Author: amitj
Date: Mon Oct 24 04:51:47 2016
New Revision: 1766339

URL: http://svn.apache.org/viewvc?rev=1766339&view=rev
Log:
OAK-4870: Implement caching for S3DataStore

- Consolidated cache stats including download and staging cache
- OSGi component registration and tests

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java   (with props)
    jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java   (with props)
    jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java   (with props)
    jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java   (with props)

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java?rev=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,38 @@
+/*
+ * 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.api.jmx;
+
+import javax.management.openmbean.TabularData;
+
+public interface ConsolidatedDataStoreCacheStatsMBean {
+    String TYPE = "ConsolidatedDataStoreCacheStats";
+
+    TabularData getCacheStats();
+
+    /**
+     * Determines whether a file-like entity with the given name
+     * has been "synced" (completely copied) to S3.
+     *
+     * @param nodePathName - Path to the entity to check.  This is
+     *                       the repository node path, not an external file path.
+     * @return true if the file is synced to S3.
+     */
+    boolean isFileSynced(final String nodePathName);
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/ConsolidatedDataStoreCacheStatsMBean.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java?rev=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,307 @@
+/*
+ * 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.plugins.blob;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
+import org.apache.jackrabbit.oak.api.jmx.ConsolidatedDataStoreCacheStatsMBean;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.InMemoryDataRecord;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.osgi.framework.BundleContext;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
+import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
+
+/**
+ * Stats for caching data store.
+ */
+@Component
+public class ConsolidatedDataStoreCacheStats implements ConsolidatedDataStoreCacheStatsMBean {
+
+    private final List<Registration> registrations = newArrayList();
+
+    private final List<DataStoreCacheStatsMBean> cacheStats = newArrayList();
+
+    @Reference public AbstractSharedCachingDataStore cachingDataStore;
+
+    @Reference public NodeStore nodeStore;
+
+    @Override
+    public TabularData getCacheStats() {
+        TabularDataSupport tds;
+        try {
+            TabularType tt = new TabularType(CacheStatsData.class.getName(),
+                    "Consolidated DataStore Cache Stats", CacheStatsData.TYPE, new String[]{"name"});
+            tds = new TabularDataSupport(tt);
+            for(DataStoreCacheStatsMBean stats : cacheStats){
+                tds.put(new CacheStatsData(stats).toCompositeData());
+            }
+        } catch (OpenDataException e) {
+            throw new IllegalStateException(e);
+        }
+        return tds;
+    }
+
+    @Activate
+    private void activate(BundleContext context){
+        Whiteboard wb = new OsgiWhiteboard(context);
+        List<DataStoreCacheStatsMBean> allStats = cachingDataStore.getStats();
+        for (DataStoreCacheStatsMBean stat : allStats) {
+            registrations.add(
+                registerMBean(wb, CacheStatsMBean.class, stat, CacheStatsMBean.TYPE,
+                    stat.getName()));
+            cacheStats.add(stat);
+        }
+        registrations.add(
+            registerMBean(wb, ConsolidatedDataStoreCacheStatsMBean.class, this,
+                ConsolidatedDataStoreCacheStatsMBean.TYPE,
+                "Consolidated DataStore Cache statistics"));
+    }
+
+    @Deactivate
+    private void deactivate(){
+        for (Registration r : registrations) {
+            r.unregister();
+        }
+        registrations.clear();
+    }
+
+    /**
+     * Determines whether a file-like entity with the given name
+     * has been "synced" (completely copied) to S3.
+     *
+     * Determination of "synced":
+     * - A nodeName of null or "" is always "not synced".
+     * - A nodeName that does not map to a valid node is always "not synced".
+     * - If the node for this nodeName does not have a binary property,
+     * this node is always "not synced" since such a node would never be
+     * copied to S3.
+     * - If the node for this nodeName is not in the nodeStore, this node is
+     * always "not synced".
+     * - Otherwise, the state is "synced" if the corresponding blob is
+     * completely stored in S3.
+     *
+     * @param nodePathName - Path to the entity to check.  This is
+     *                       a node path, not an external file path.
+     * @return true if the file is synced to S3.
+     */
+    @Override
+    public boolean isFileSynced(final String nodePathName) {
+        if (Strings.isNullOrEmpty(nodePathName)) {
+            return false;
+        }
+
+        if (null == nodeStore) {
+            return false;
+        }
+
+        final NodeState leafNode = findLeafNode(nodePathName);
+        if (!leafNode.exists()) {
+            return false;
+        }
+
+        boolean nodeHasBinaryProperties = false;
+        for (final PropertyState propertyState : leafNode.getProperties()) {
+            nodeHasBinaryProperties |= (propertyState.getType() == Type.BINARY || propertyState.getType() == Type.BINARIES);
+            try {
+                if (propertyState.getType() == Type.BINARY) {
+                    final Blob blob = (Blob) propertyState.getValue(propertyState.getType());
+                    if (null == blob || !haveRecordForBlob(blob)) {
+                        return false;
+                    }
+                } else if (propertyState.getType() == Type.BINARIES) {
+                    final List<Blob> blobs = (List<Blob>) propertyState.getValue(propertyState.getType());
+                    if (null == blobs) {
+                        return false;
+                    }
+                    for (final Blob blob : blobs) {
+                        if (!haveRecordForBlob(blob)) {
+                            return false;
+                        }
+                    }
+                }
+            }
+            catch (ClassCastException e) {
+                return false;
+            }
+        }
+
+        // If we got here and nodeHasBinaryProperties is true,
+        // it means at least one binary property was found for
+        // the leaf node and that we were able to locate a
+        // records for binaries found.
+        return nodeHasBinaryProperties;
+    }
+
+    private NodeState findLeafNode(final String nodePathName) {
+        final Iterable<String> pathNodes = PathUtils.elements(PathUtils.getParentPath(nodePathName));
+        final String leafNodeName = PathUtils.getName(nodePathName);
+
+        NodeState currentNode = nodeStore.getRoot();
+        for (String pathNodeName : pathNodes) {
+            if (pathNodeName.length() > 0) {
+                NodeState childNode = currentNode.getChildNode(pathNodeName);
+                if (!childNode.exists()) {
+                    break;
+                }
+                currentNode = childNode;
+            }
+        }
+        return currentNode.getChildNode(leafNodeName);
+    }
+
+    private boolean haveRecordForBlob(final Blob blob) {
+        final String fullBlobId = blob.getContentIdentity();
+        if (!Strings.isNullOrEmpty(fullBlobId)
+            && !InMemoryDataRecord.isInstance(fullBlobId)) {
+            String blobId = DataStoreBlobStore.BlobId.of(fullBlobId).getBlobId();
+            return cachingDataStore.exists(new DataIdentifier(blobId));
+        }
+        return false;
+    }
+
+    private static class CacheStatsData {
+        static final String[] FIELD_NAMES = new String[]{
+                "name",
+                "requestCount",
+                "hitCount",
+                "hitRate",
+                "missCount",
+                "missRate",
+                "loadCount",
+                "loadSuccessCount",
+                "loadExceptionCount",
+                "totalLoadTime",
+                "averageLoadPenalty",
+                "evictionCount",
+                "elementCount",
+                "totalWeight",
+                "totalMemWeight",
+                "maxWeight",
+        };
+
+        static final String[] FIELD_DESCRIPTIONS = FIELD_NAMES;
+
+        @SuppressWarnings("rawtypes")
+        static final OpenType[] FIELD_TYPES = new OpenType[]{
+                SimpleType.STRING,
+                SimpleType.LONG,
+                SimpleType.LONG,
+                SimpleType.BIGDECIMAL,
+                SimpleType.LONG,
+                SimpleType.BIGDECIMAL,
+                SimpleType.LONG,
+                SimpleType.LONG,
+                SimpleType.LONG,
+                SimpleType.STRING,
+                SimpleType.STRING,
+                SimpleType.LONG,
+                SimpleType.LONG,
+                SimpleType.STRING,
+                SimpleType.STRING,
+                SimpleType.STRING,
+        };
+
+        static final CompositeType TYPE = createCompositeType();
+
+        static CompositeType createCompositeType() {
+            try {
+                return new CompositeType(
+                        CacheStatsData.class.getName(),
+                        "Composite data type for Cache statistics",
+                        CacheStatsData.FIELD_NAMES,
+                        CacheStatsData.FIELD_DESCRIPTIONS,
+                        CacheStatsData.FIELD_TYPES);
+            } catch (OpenDataException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        private final DataStoreCacheStatsMBean stats;
+
+        public CacheStatsData(DataStoreCacheStatsMBean stats){
+            this.stats = stats;
+        }
+
+        CompositeDataSupport toCompositeData() {
+            Object[] values = new Object[]{
+                    stats.getName(),
+                    stats.getRequestCount(),
+                    stats.getHitCount(),
+                    new BigDecimal(stats.getHitRate(),new MathContext(2)),
+                    stats.getMissCount(),
+                    new BigDecimal(stats.getMissRate(), new MathContext(2)),
+                    stats.getLoadCount(),
+                    stats.getLoadSuccessCount(),
+                    stats.getLoadExceptionCount(),
+                    timeInWords(stats.getTotalLoadTime()),
+                    TimeUnit.NANOSECONDS.toMillis((long) stats.getAverageLoadPenalty()) + "ms",
+                    stats.getEvictionCount(),
+                    stats.getElementCount(),
+                    humanReadableByteCount(stats.estimateCurrentWeight()),
+                    humanReadableByteCount(stats.estimateCurrentMemoryWeight()),
+                    humanReadableByteCount(stats.getMaxTotalWeight()),
+            };
+            try {
+                return new CompositeDataSupport(TYPE, FIELD_NAMES, values);
+            } catch (OpenDataException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    static String timeInWords(long nanos) {
+        long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
+        return String.format("%d min, %d sec",
+            TimeUnit.MILLISECONDS.toMinutes(millis),
+            TimeUnit.MILLISECONDS.toSeconds(millis) -
+                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
+        );
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreCacheStats.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,504 @@
+/*
+ * 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.plugins.blob;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closer;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+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.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.DefaultStatisticsProvider;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.commons.codec.binary.Hex.encodeHexString;
+import static org.apache.commons.io.FileUtils.copyInputStreamToFile;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ConsolidatedDataStoreStatsTest extends AbstractDataStoreCacheTest {
+    private static final Logger LOG = LoggerFactory.getLogger(ConsolidatedDataStoreStatsTest.class);
+    private static final String ID_PREFIX = "12345";
+    private static String testNodePathName = "test/node/path/name";
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
+
+    private final Closer closer = Closer.create();
+    private File root;
+    private File testFile;
+
+    private CountDownLatch taskLatch;
+    private CountDownLatch callbackLatch;
+    private CountDownLatch afterExecuteLatch;
+    private TestExecutor executor;
+    private StatisticsProvider statsProvider;
+    private ScheduledExecutorService scheduledExecutor;
+    private ConsolidatedDataStoreCacheStats stats;
+    private NodeStore nodeStore;
+    private AbstractSharedCachingDataStore dataStore;
+    private static Blob mockBlob;
+
+    @Before
+    public void setup() throws Exception {
+        root = folder.newFolder();
+        init(1);
+    }
+
+    private void init(int i) throws Exception {
+        testFile = folder.newFile();
+        copyInputStreamToFile(randomStream(0, 16384), testFile);
+        String testNodeId = getIdForInputStream(new FileInputStream(testFile));
+        mockBlob = mock(Blob.class);
+        when(mockBlob.getContentIdentity()).thenReturn(testNodeId);
+
+        nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.<List<Blob>>absent());
+
+        // create executor
+        taskLatch = new CountDownLatch(1);
+        callbackLatch = new CountDownLatch(1);
+        afterExecuteLatch = new CountDownLatch(i);
+        executor = new TestExecutor(1, taskLatch, callbackLatch, afterExecuteLatch);
+
+        // stats
+        ScheduledExecutorService statsExecutor = Executors.newSingleThreadScheduledExecutor();
+        closer.register(new ExecutorCloser(statsExecutor, 500, TimeUnit.MILLISECONDS));
+        statsProvider = new DefaultStatisticsProvider(statsExecutor);
+
+        scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
+        closer.register(new ExecutorCloser(scheduledExecutor, 500, TimeUnit.MILLISECONDS));
+        dataStore = new AbstractSharedCachingDataStore() {
+            @Override protected SharedBackend createBackend() {
+                return new TestMemoryBackend();
+            }
+
+            @Override public int getMinRecordLength() {
+                return 0;
+            }
+        };
+        dataStore.setStatisticsProvider(statsProvider);
+        dataStore.listeningExecutor = executor;
+        dataStore.schedulerExecutor = scheduledExecutor;
+        dataStore.init(root.getAbsolutePath());
+
+        stats = new ConsolidatedDataStoreCacheStats();
+        stats.nodeStore = nodeStore;
+        stats.cachingDataStore = dataStore;
+    }
+
+    @After
+    public void tear() throws Exception {
+        closer.close();
+        dataStore.close();
+    }
+
+    private String getIdForInputStream(final InputStream in)
+        throws Exception {
+        MessageDigest digest = MessageDigest.getInstance("SHA-1");
+        OutputStream output = new DigestOutputStream(new NullOutputStream(), digest);
+        try {
+            IOUtils.copyLarge(in, output);
+        } finally {
+            IOUtils.closeQuietly(output);
+            IOUtils.closeQuietly(in);
+        }
+        return encodeHexString(digest.digest());
+    }
+
+    private static NodeStore initNodeStore(final Optional<Blob> blobProp1,
+        final Optional<Blob> blobProp2,
+        final Optional<String> stringProp,
+        final Optional<Integer> intProp,
+        final Optional<List<Blob>> blobPropList)
+        throws CommitFailedException {
+        final NodeStore nodeStore = new MemoryNodeStore();
+        NodeBuilder rootBuilder = nodeStore.getRoot().builder();
+        NodeBuilder builder = initNodeBuilder(rootBuilder);
+
+        if (blobProp1.isPresent()) {
+            builder.setProperty("blobProp1", blobProp1.get());
+        }
+        if (blobProp2.isPresent()) {
+            builder.setProperty("blobProp2", blobProp2.get());
+        }
+        if (stringProp.isPresent()) {
+            builder.setProperty("stringProp", stringProp.get());
+        }
+        if (intProp.isPresent()) {
+            builder.setProperty("intProp", intProp.get());
+        }
+        if (blobPropList.isPresent()) {
+            builder.setProperty(MultiBinaryPropertyState
+                .binaryPropertyFromBlob("blobPropList", blobPropList.get()));
+        }
+
+        nodeStore.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        return nodeStore;
+    }
+
+    private static NodeBuilder initNodeBuilder(final NodeBuilder rootBuilder) {
+        NodeBuilder builder = rootBuilder;
+        for (final String nodeName : PathUtils.elements(testNodePathName)) {
+            builder = builder.child(nodeName);
+        }
+        return builder;
+    }
+
+    @Test
+    public void noPath()
+        throws Exception {
+        assertFalse(stats.isFileSynced(testNodePathName));
+    }
+
+    @Test
+    public void nullString()
+        throws Exception {
+        assertFalse(stats.isFileSynced(null));
+    }
+
+    @Test
+    public void emptyString()
+        throws Exception {
+        assertFalse(stats.isFileSynced(""));
+    }
+
+    @Test
+    public void differentPaths() throws Exception {
+        init(4);
+        final NodeStore nodeStore = new MemoryNodeStore();
+        stats.nodeStore = nodeStore;
+
+        final String path1 = "path/to/node/1";
+        final String path2 = "path/to/node/2";
+        final String path3 = "shortpath";
+        final String path4 = "a/very/very/long/path/leads/to/node/4";
+        final List<String> paths = Lists.newArrayList(path1, path2, path3, path4);
+        final String leadingSlashPath = "/" + path1;
+
+        final List<String> blobContents = Lists.newArrayList("1", "2", "3", "4");
+        final List<Blob> blobs = Lists.newArrayList(
+            mock(Blob.class),
+            mock(Blob.class),
+            mock(Blob.class),
+            mock(Blob.class)
+        );
+        final List<String> blobIds = Lists.newArrayList(
+            getIdForInputStream(getStream(blobContents.get(0))),
+            getIdForInputStream(getStream(blobContents.get(1))),
+            getIdForInputStream(getStream(blobContents.get(2))),
+            getIdForInputStream(getStream(blobContents.get(3)))
+        );
+        when(blobs.get(0).getContentIdentity()).thenReturn(blobIds.get(0));
+        when(blobs.get(1).getContentIdentity()).thenReturn(blobIds.get(1));
+        when(blobs.get(2).getContentIdentity()).thenReturn(blobIds.get(2));
+        when(blobs.get(3).getContentIdentity()).thenReturn(blobIds.get(3));
+
+        final NodeBuilder rootBuilder = nodeStore.getRoot().builder();
+
+        final List<NodeBuilder> builders = Lists.newArrayList();
+        for (final String path : paths) {
+            NodeBuilder builder = rootBuilder;
+            for (final String nodeName : PathUtils.elements(path)) {
+                builder = builder.child(nodeName);
+            }
+            builders.add(builder);
+        }
+        builders.get(0).setProperty("blob1", blobs.get(0));
+        builders.get(1).setProperty("blob2", blobs.get(1));
+        builders.get(2).setProperty("blob3", blobs.get(2));
+        builders.get(3).setProperty("blob4", blobs.get(3));
+
+        nodeStore.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        final List<DataRecord> records = Lists.newArrayList();
+        try {
+            for (final String s : blobContents) {
+                records.add(dataStore.addRecord(getStream(s)));
+            }
+
+            taskLatch.countDown();
+            callbackLatch.countDown();
+            waitFinish();
+
+            for (final String path : Lists
+                .newArrayList(path1, path2, path3, path4, leadingSlashPath)) {
+                assertTrue(stats.isFileSynced(path));
+            }
+
+            for (final String invalidPath : Lists
+                .newArrayList(path1 + "/", "/" + path1 + "/", "/path//to/node///1")) {
+                try {
+                    stats.isFileSynced(invalidPath);
+                    assertFalse(false); // shouldn't get here on an invalid path
+                } catch (AssertionError e) {
+                    // expected
+                }
+            }
+        }
+        finally {
+            delete(dataStore, records);
+        }
+    }
+
+    @Test
+    public void multiplePropertiesAndBinarySynced() throws Exception {
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.<Blob>absent(),
+            Optional.of("abc"),
+            Optional.of(123),
+            Optional.<List<Blob>>absent());
+
+        assertSyncedTrue(stats, dataStore, new FileInputStream(testFile));
+    }
+
+    @Test
+    public void multipleBinaryPropsAllSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.of(mockBlob2),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.<List<Blob>>absent());
+
+        assertSyncedTrue(stats, dataStore, new FileInputStream(testFile),
+            getStream("testContents2"));
+    }
+
+    @Test
+    public void multipleBinaryPropsNotAllSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.of(mockBlob2),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.<List<Blob>>absent());
+
+        assertSyncedFalse(stats, dataStore, new FileInputStream(testFile));
+    }
+
+    @Test
+    public void binaryPropSingle() throws Exception {
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob);
+        NodeStore nodeStore = initNodeStore(Optional.<Blob>absent(),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+
+        assertSyncedTrue(stats, dataStore, new FileInputStream(testFile));
+    }
+
+    @Test
+    public void binariesPropertyMultiple() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob, mockBlob2);
+        NodeStore nodeStore = initNodeStore(Optional.<Blob>absent(),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+
+        assertSyncedTrue(stats, dataStore, new FileInputStream(testFile),
+            getStream("testContents2"));
+    }
+
+    @Test
+    public void binariesPropertyNotAllSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob, mockBlob2);
+        NodeStore nodeStore = initNodeStore(Optional.<Blob>absent(),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+
+        assertSyncedFalse(stats, dataStore, new FileInputStream(testFile));
+    }
+
+    @Test
+    public void binarySyncedAndBinariesNotSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        Blob mockBlob3 = mock(Blob.class);
+        final String id3 = getIdForInputStream(getStream("testContents3"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id3);
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob2, mockBlob3);
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+
+        assertSyncedFalse(stats, dataStore, new FileInputStream(testFile),
+            getStream("testContents2"));
+    }
+
+    @Test
+    public void binaryNotSyncedAndBinariesSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        Blob mockBlob3 = mock(Blob.class);
+        final String id3 = getIdForInputStream(getStream("testContents3"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id3);
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob2, mockBlob3);
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+        assertSyncedFalse(stats, dataStore, getStream("testContents2"),
+            getStream("testContents3"));
+    }
+
+    @Test
+    public void binaryAndBinariesSynced() throws Exception {
+        Blob mockBlob2 = mock(Blob.class);
+        final String id2 = getIdForInputStream(getStream("testContents2"));
+        when(mockBlob2.getContentIdentity()).thenReturn(id2);
+        Blob mockBlob3 = mock(Blob.class);
+        final String id3 = getIdForInputStream(getStream("testContents3"));
+        when(mockBlob3.getContentIdentity()).thenReturn(id3);
+        List<Blob> blobPropList = Lists.newArrayList(mockBlob2, mockBlob3);
+        NodeStore nodeStore = initNodeStore(Optional.of(mockBlob),
+            Optional.<Blob>absent(),
+            Optional.<String>absent(),
+            Optional.<Integer>absent(),
+            Optional.of(blobPropList));
+
+        assertSyncedFalse(stats, dataStore, new FileInputStream(testFile),
+            getStream("testContents2"), getStream("testContents3"));
+    }
+
+    private static void delete(AbstractSharedCachingDataStore s3ds, List<DataRecord> recs)
+        throws DataStoreException {
+        for (DataRecord rec : recs) {
+            if (null != rec) {
+                s3ds.deleteRecord(rec.getIdentifier());
+            }
+        }
+    }
+
+    private void assertSyncedFalse(ConsolidatedDataStoreCacheStats mBean,
+        AbstractSharedCachingDataStore s3ds, InputStream... streams) throws DataStoreException {
+
+        List<DataRecord> recs = Lists.newArrayList();
+        try {
+            for (InputStream is : streams) {
+                recs.add(s3ds.addRecord(is));
+                IOUtils.closeQuietly(is);
+            }
+            assertFalse(mBean.isFileSynced(testNodePathName));
+            taskLatch.countDown();
+            callbackLatch.countDown();
+            waitFinish();
+        } finally {
+            delete(s3ds, recs);
+        }
+    }
+
+    private void assertSyncedTrue(ConsolidatedDataStoreCacheStats mBean,
+        AbstractSharedCachingDataStore s3ds, InputStream... streams) throws DataStoreException {
+        taskLatch.countDown();
+        callbackLatch.countDown();
+
+        List<DataRecord> recs = Lists.newArrayList();
+        try {
+            for (InputStream is : streams) {
+                recs.add(s3ds.addRecord(is));
+                IOUtils.closeQuietly(is);
+            }
+            waitFinish();
+            assertTrue(mBean.isFileSynced(testNodePathName));
+        } finally {
+            delete(s3ds, recs);
+        }
+    }
+
+    private void waitFinish() {
+        try {
+            // wait for upload finish
+            afterExecuteLatch.await();
+            // Force execute removal from staging cache
+            ScheduledFuture<?> scheduledFuture = scheduledExecutor
+                .schedule(dataStore.getCache().getStagingCache().new RemoveJob(), 0,
+                    TimeUnit.MILLISECONDS);
+            scheduledFuture.get();
+            LOG.info("After jobs completed");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static InputStream getStream(String str) {
+        return new ByteArrayInputStream(str.getBytes(Charsets.UTF_8));
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/blob/ConsolidatedDataStoreStatsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java?rev=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java (added)
+++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,134 @@
+/*
+ * 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.plugins.document;
+
+import java.util.Map;
+
+import org.apache.jackrabbit.oak.api.jmx.ConsolidatedDataStoreCacheStatsMBean;
+import org.apache.jackrabbit.oak.plugins.blob.AbstractSharedCachingDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.ConsolidatedDataStoreCacheStats;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.sling.testing.mock.osgi.ReferenceViolationException;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.osgi.framework.ServiceRegistration;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.sling.testing.mock.osgi.MockOsgi.deactivate;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests the registration of the {@link ConsolidatedDataStoreCacheStatsMBean}.
+ */
+public class DocumentCachingDataStoreStatsTest {
+
+    @Rule
+    public OsgiContext context = new OsgiContext();
+
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
+
+    @BeforeClass
+    public static void checkMongoDbAvailable() {
+        Assume.assumeTrue(MongoUtils.isAvailable());
+    }
+
+    @Before
+    public void setUp() {
+        context.registerService(StatisticsProvider.class, StatisticsProvider.NOOP);
+    }
+
+    @Test
+    public void testUseCachingBlobStore() {
+        ServiceRegistration delegateReg =
+            context.bundleContext().registerService(AbstractSharedCachingDataStore.class.getName(),
+                mock(AbstractSharedCachingDataStore.class), null);
+        assertNotNull(context.getService(AbstractSharedCachingDataStore.class));
+        registerBlobStore();
+
+        registerDocumentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNotNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        deactivate(dataStoreStats);
+        unregisterDocumentNodeStoreService();
+        unregisterBlobStore();
+        delegateReg.unregister();
+    }
+
+    @Test
+    public void testNoCachingBlobStore() {
+        expectedEx.expect(ReferenceViolationException.class);
+
+        registerBlobStore();
+
+        registerDocumentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        unregisterDocumentNodeStoreService();
+        unregisterBlobStore();
+    }
+
+    private DocumentNodeStoreService documentNodeStoreService;
+
+    private void registerDocumentNodeStoreService(boolean customBlobStore) {
+        Map<String, Object> properties = newHashMap();
+
+        properties.put("mongouri", MongoUtils.URL);
+        properties.put("db", MongoUtils.DB);
+        properties.put(DocumentNodeStoreService.CUSTOM_BLOB_STORE, customBlobStore);
+        documentNodeStoreService =
+            context.registerInjectActivateService(new DocumentNodeStoreService(), properties);
+    }
+
+    private void unregisterDocumentNodeStoreService() {
+        deactivate(documentNodeStoreService);
+    }
+
+    private ServiceRegistration blobStore;
+
+    private void registerBlobStore() {
+        blobStore = context.bundleContext()
+            .registerService(BlobStore.class.getName(), mock(BlobStore.class), null);
+    }
+
+    private void unregisterBlobStore() {
+        blobStore.unregister();
+    }
+
+    private void assertServiceActivated() {
+        assertNotNull(context.getService(NodeStore.class));
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentCachingDataStoreStatsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java?rev=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java (added)
+++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,133 @@
+/*
+ * 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.plugins.segment;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.jackrabbit.oak.api.jmx.ConsolidatedDataStoreCacheStatsMBean;
+import org.apache.jackrabbit.oak.plugins.blob.AbstractSharedCachingDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.ConsolidatedDataStoreCacheStats;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.sling.testing.mock.osgi.ReferenceViolationException;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.osgi.framework.ServiceRegistration;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService.CUSTOM_BLOB_STORE;
+import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.DIRECTORY;
+import static org.apache.sling.testing.mock.osgi.MockOsgi.deactivate;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests the registration of the {@link ConsolidatedDataStoreCacheStatsMBean}.
+ */
+public class SegmentCachingDataStoreStatsTest {
+
+    @Rule
+    public OsgiContext context = new OsgiContext();
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        context.registerService(StatisticsProvider.class, StatisticsProvider.NOOP);
+    }
+
+    @Test
+    public void testUseCachingBlobStore() {
+        ServiceRegistration delegateReg =
+            context.bundleContext().registerService(AbstractSharedCachingDataStore.class.getName(),
+                mock(AbstractSharedCachingDataStore.class), null);
+        assertNotNull(context.getService(AbstractSharedCachingDataStore.class));
+        registerBlobStore();
+
+        registerSegmentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNotNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        deactivate(dataStoreStats);
+        unregisterSegmentNodeStoreService();
+        unregisterBlobStore();
+        delegateReg.unregister();
+    }
+
+    @Test
+    public void testNoCachingBlobStore() {
+        expectedEx.expect(ReferenceViolationException.class);
+
+        registerBlobStore();
+
+        registerSegmentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        unregisterSegmentNodeStoreService();
+        unregisterBlobStore();
+    }
+
+    private SegmentNodeStoreService segmentNodeStoreService;
+
+    private void registerSegmentNodeStoreService(boolean customBlobStore) {
+        Map<String, Object> properties = newHashMap();
+
+        properties.put(CUSTOM_BLOB_STORE, customBlobStore);
+        properties.put(DIRECTORY, folder.getRoot().getAbsolutePath());
+
+        segmentNodeStoreService = context.registerInjectActivateService(new SegmentNodeStoreService(), properties);
+    }
+
+    private void unregisterSegmentNodeStoreService() {
+        deactivate(segmentNodeStoreService);
+    }
+
+    private ServiceRegistration blobStore;
+
+    private void registerBlobStore() {
+        blobStore = context.bundleContext().registerService(BlobStore.class.getName(), mock(BlobStore.class), null);
+    }
+
+    private void unregisterBlobStore() {
+        blobStore.unregister();
+    }
+
+    private void assertServiceActivated() {
+        assertNotNull(context.getService(NodeStore.class));
+        assertNotNull(context.getService(SegmentStoreProvider.class));
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/segment/SegmentCachingDataStoreStatsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java?rev=1766339&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java (added)
+++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java Mon Oct 24 04:51:47 2016
@@ -0,0 +1,134 @@
+/*
+ * 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.segment;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.jackrabbit.oak.api.jmx.ConsolidatedDataStoreCacheStatsMBean;
+import org.apache.jackrabbit.oak.plugins.blob.AbstractSharedCachingDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.ConsolidatedDataStoreCacheStats;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.sling.testing.mock.osgi.ReferenceViolationException;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.osgi.framework.ServiceRegistration;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.CUSTOM_BLOB_STORE;
+import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.DIRECTORY;
+import static org.apache.sling.testing.mock.osgi.MockOsgi.deactivate;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests the registration of the {@link ConsolidatedDataStoreCacheStatsMBean}.
+ */
+public class SegmentCachingDataStoreStatsTest {
+
+    @Rule
+    public OsgiContext context = new OsgiContext();
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+
+    @Rule
+    public ExpectedException expectedEx = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        context.registerService(StatisticsProvider.class, StatisticsProvider.NOOP);
+    }
+
+    @Test
+    public void testUseCachingBlobStore() {
+        ServiceRegistration delegateReg =
+            context.bundleContext().registerService(AbstractSharedCachingDataStore.class.getName(),
+                mock(AbstractSharedCachingDataStore.class), null);
+        assertNotNull(context.getService(AbstractSharedCachingDataStore.class));
+        registerBlobStore();
+
+        registerSegmentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNotNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        deactivate(dataStoreStats);
+        unregisterSegmentNodeStoreService();
+        unregisterBlobStore();
+        delegateReg.unregister();
+    }
+
+    @Test
+    public void testNoCachingBlobStore() {
+        expectedEx.expect(ReferenceViolationException.class);
+
+        registerBlobStore();
+
+        registerSegmentNodeStoreService(true);
+        assertServiceActivated();
+
+        ConsolidatedDataStoreCacheStats dataStoreStats =
+            context.registerInjectActivateService(new ConsolidatedDataStoreCacheStats(), null);
+        assertNull(context.getService(ConsolidatedDataStoreCacheStatsMBean.class));
+
+        unregisterSegmentNodeStoreService();
+        unregisterBlobStore();
+    }
+
+    private SegmentNodeStoreService segmentNodeStoreService;
+
+    private void registerSegmentNodeStoreService(boolean customBlobStore) {
+        Map<String, Object> properties = newHashMap();
+
+        properties.put(CUSTOM_BLOB_STORE, customBlobStore);
+        properties.put(DIRECTORY, folder.getRoot().getAbsolutePath());
+
+        segmentNodeStoreService =
+            context.registerInjectActivateService(new SegmentNodeStoreService(), properties);
+    }
+
+    private void unregisterSegmentNodeStoreService() {
+        deactivate(segmentNodeStoreService);
+    }
+
+    private ServiceRegistration blobStore;
+
+    private void registerBlobStore() {
+        blobStore = context.bundleContext().registerService(BlobStore.class.getName(), mock(BlobStore.class), null);
+    }
+
+    private void unregisterBlobStore() {
+        blobStore.unregister();
+    }
+
+    private void assertServiceActivated() {
+        assertNotNull(context.getService(NodeStore.class));
+        assertNotNull(context.getService(SegmentStoreProvider.class));
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native