diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java index b4a513b..0ba3bb0 100644 --- oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java @@ -90,23 +90,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; class JackrabbitNodeState extends AbstractNodeState { private static final Logger log = LoggerFactory.getLogger(JackrabbitNodeState.class); - private static long count = 0; - - private static void logNewNode(JackrabbitNodeState state) { - count++; - if (count % 10000 == 0) { - log.info("Migrating node #" + count + ": " + state.getPath()); - } - } - private JackrabbitNodeState parent; private final String name; private String path; /** @@ -206,15 +197,14 @@ class JackrabbitNodeState extends AbstractNodeState { this.properties = createProperties(bundle); this.nodes = createNodes(bundle); this.skipOnError = parent.skipOnError; this.mountPoints = parent.mountPoints; this.nodeStateCache = parent.nodeStateCache; setChildOrder(); fixFrozenUuid(); - logNewNode(this); } JackrabbitNodeState( PersistenceManager source, NodeState root, Map uriToPrefix, NodeId id, String path, String workspaceName, Map mountPoints, @@ -244,15 +234,14 @@ class JackrabbitNodeState extends AbstractNodeState { NodePropBundle bundle = loader.loadBundle(id); this.properties = createProperties(bundle); this.nodes = createNodes(bundle); setChildOrder(); } catch (ItemStateException e) { throw new IllegalStateException("Unable to access node " + id, e); } - logNewNode(this); } @Override public String toString() { return getPath(); } diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositorySidegrade.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositorySidegrade.java index 361208a..c1cbcf5 100755 --- oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositorySidegrade.java +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositorySidegrade.java @@ -32,30 +32,36 @@ import org.apache.jackrabbit.oak.spi.commit.CommitInfo; import org.apache.jackrabbit.oak.spi.commit.EditorHook; import org.apache.jackrabbit.oak.spi.commit.EmptyHook; import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade.LoggingCompositeHook; +import org.apache.jackrabbit.oak.upgrade.nodestate.report.LoggingReporter; +import org.apache.jackrabbit.oak.upgrade.nodestate.report.ReportingNodeState; import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier; import org.apache.jackrabbit.oak.upgrade.version.VersionCopyConfiguration; import org.apache.jackrabbit.oak.upgrade.version.VersionableEditor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableSet.copyOf; import static com.google.common.collect.ImmutableSet.of; import static com.google.common.collect.Sets.union; import static org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade.DEFAULT_EXCLUDE_PATHS; import static org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade.DEFAULT_INCLUDE_PATHS; import static org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade.DEFAULT_MERGE_PATHS; import static org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade.calculateEffectiveIncludePaths; import static org.apache.jackrabbit.oak.upgrade.version.VersionCopier.copyVersionStorage; public class RepositorySidegrade { + private static final Logger LOG = LoggerFactory.getLogger(RepositorySidegrade.class); + /** * Target node store. */ private final NodeStore target; private final NodeStore source; @@ -203,15 +209,16 @@ public class RepositorySidegrade { NodeState root = source.getRoot(); NodeBuilder builder = target.getRoot().builder(); new InitialContent().initialize(builder); if (initializer != null) { initializer.initialize(builder); } - copyState(builder, root); + + copyState(builder, ReportingNodeState.wrap(root, new LoggingReporter(LOG, "Copying", 10000, -1))); cleanCheckpoints(builder); } catch (Exception e) { throw new RepositoryException("Failed to copy content", e); } } diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java index 93b5133..9505c23 100644 --- oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java @@ -104,14 +104,16 @@ import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration; import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits; import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConfiguration; import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.upgrade.nodestate.report.LoggingReporter; +import org.apache.jackrabbit.oak.upgrade.nodestate.report.ReportingNodeState; import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier; import org.apache.jackrabbit.oak.upgrade.security.GroupEditorProvider; import org.apache.jackrabbit.oak.upgrade.security.RestrictionEditorProvider; import org.apache.jackrabbit.oak.upgrade.version.VersionCopyConfiguration; import org.apache.jackrabbit.oak.upgrade.version.VersionableEditor; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QNodeDefinition; @@ -404,16 +406,19 @@ public class RepositoryUpgrade { // Triggers compilation of type information, which we need for // the type predicates used by the bulk copy operations below. new TypeEditorProvider(false).getRootEditor( base, builder.getNodeState(), builder, null); NodeState root = builder.getNodeState(); - final NodeState sourceState = JackrabbitNodeState.createRootNodeState( - source, workspaceName, root, uriToPrefix, copyBinariesByReference, skipOnError); + final NodeState sourceState = ReportingNodeState.wrap( + JackrabbitNodeState.createRootNodeState( + source, workspaceName, root, uriToPrefix, copyBinariesByReference, skipOnError), + new LoggingReporter(logger, "Migrating", 10000, -1) + ); final Stopwatch watch = Stopwatch.createStarted(); logger.info("Copying workspace content"); copyWorkspace(sourceState, builder, workspaceName); builder.getNodeState(); // on TarMK this does call triggers the actual copy logger.info("Upgrading workspace content completed in {}s ({})", watch.elapsed(TimeUnit.SECONDS), watch); diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java new file mode 100644 index 0000000..75d123c --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java @@ -0,0 +1,55 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.slf4j.Logger; + +/** + * A Reporter implementation that logs every nth node + * and/or any nth property to the given logger on {@code info} + * level. + */ +public class LoggingReporter extends PeriodicReporter { + + private final Logger logger; + + private final String verb; + + /** + * Constructor that allows setting the intervals to log node and property + * accesses to a given logger. + * + * @param logger The logger to log the progress to. + * @param nodeLogInterval Every how many nodes a log message should be written. + * @param propertyLogInterval Every how many properties a log message should be written. + */ + public LoggingReporter(final Logger logger, final int nodeLogInterval, final int propertyLogInterval) { + this(logger, "Loading", nodeLogInterval, propertyLogInterval); + } + + /** + * Like {@link #LoggingReporter(Logger, int, int)}, however this constructor allow + * to customize the verb of the log message. + *
+ * The messages are of the format: "{verb} node #100: /path/to/the/node + * + * @param logger The logger to log the progress to. + * @param verb The verb to use for logging. + * @param nodeLogInterval Every how many nodes a log message should be written. + * @param propertyLogInterval Every how many properties a log message should be written. + */ + public LoggingReporter(final Logger logger, final String verb, final int nodeLogInterval, final int propertyLogInterval) { + super(nodeLogInterval, propertyLogInterval); + this.logger = logger; + this.verb = verb; + } + + @Override + protected void reportPeriodicNode(final long count, final ReportingNodeState nodeState) { + logger.info("{} node #{}: {}", verb, count, nodeState.getPath()); + } + + @Override + protected void reportPeriodicProperty(final long count, final ReportingNodeState parent, final String propertyName) { + logger.info("{} properties #{}: {}", verb, count, PathUtils.concat(parent.getPath(), propertyName)); + } +} diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java new file mode 100644 index 0000000..304fb09 --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java @@ -0,0 +1,66 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Abstract class that simplifies development of a Reporter + * that should only report every nth event (node or property seen). + */ +public abstract class PeriodicReporter implements Reporter { + + private final int nodeLogInterval; + + private final int propertyLogInterval; + + private AtomicLong nodes = new AtomicLong(0); + + private AtomicLong properties = new AtomicLong(0); + + protected PeriodicReporter(final int nodeLogInterval, final int propertyLogInterval) { + this.nodeLogInterval = nodeLogInterval; + this.propertyLogInterval = propertyLogInterval; + } + + /** + * Callback called every nth time a node is accessed. + * + * @param count The count of reported nodes. + * @param nodeState The node that was reported. + */ + protected abstract void reportPeriodicNode( + final long count, final ReportingNodeState nodeState); + + /** + * Callback called every nth time a property is accessed. + * + * @param count The count of reported properties. + * @param parent The parent node of the reported property. + * @param propertyName The name of the reported property. + */ + protected abstract void reportPeriodicProperty( + final long count, final ReportingNodeState parent, final String propertyName); + + @Override + public final void reportNode(final ReportingNodeState nodeState) { + if (nodeLogInterval == -1) { + return; + } + + final long count = nodes.incrementAndGet(); + if (count % nodeLogInterval == 0) { + reportPeriodicNode(count, nodeState); + } + } + + @Override + public final void reportProperty(final ReportingNodeState parent, final String propertyName) { + if (propertyLogInterval == -1) { + return; + } + + final long count = properties.incrementAndGet(); + if (count % propertyLogInterval == 0) { + reportPeriodicProperty(count, parent, propertyName); + } + } +} diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java new file mode 100644 index 0000000..aba9237 --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java @@ -0,0 +1,25 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +/** + * A {@code Reporter} receives callbacks for every NodeState + * and PropertyState that was accessed via a {ReportingNodeState} + * instance. + */ +public interface Reporter { + + /** + * Callback reporting that the given {@code nodeState} was accessed. + * + * @param nodeState The accessed {@code ReportingNodeState} instance. + */ + void reportNode(final ReportingNodeState nodeState); + + /** + * Callback reporting that the property named {@code propertyName} + * was accessed on the {@code parent} node. + * + * @param parent The parent node state of the reported property. + * @param propertyName The name of the reported property. + */ + void reportProperty(final ReportingNodeState parent, final String propertyName); +} diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java new file mode 100644 index 0000000..00d193d --- /dev/null +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java @@ -0,0 +1,251 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; +import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A decoration layer for NodeState instances that intercepts + * all accesses to NodeStates and PropertyStates (getters) and + * informs a {@link Reporter} via its callbacks that the respective + * NodeStates or PropertyStates have been accessed. + *
+ * The decoration is deep, i.e. any child NodeStates will be + * decorated as well and will report to the same {@code Reporter} + * instance. + *
+ * For convenience, a {@link PeriodicReporter} abstract class exists. + * This simplifies reporting every nth node/property only. + *
+ * Note: Multiple accesses to the same node or property are each + * reported. Therefore if exactly counting unique accesses is a + * requirement, the reporter needs to take care of de-duplication. + * + * @see Reporter + * @see PeriodicReporter + * @see LoggingReporter + */ +public class ReportingNodeState implements NodeState { + + private final ReportingNodeState parent; + private final NodeState delegate; + private final String name; + private final Reporter reporter; + + public static NodeState wrap(NodeState nodeState, Reporter reporter) { + return wrapAndReport(null, "/", nodeState, reporter); + } + + private static NodeState wrapAndReport(@Nullable ReportingNodeState parent, @Nonnull String name, + @Nonnull NodeState delegate, @Nonnull Reporter reporter) { + final ReportingNodeState nodeState = new ReportingNodeState(parent, delegate, name, reporter); + reporter.reportNode(nodeState); + return nodeState; + } + + protected NodeState wrapChild(String name, NodeState delegate) { + return wrapAndReport(this, name, delegate, this.reporter); + } + + private ReportingNodeState(ReportingNodeState parent, NodeState delegate, String name, Reporter reporter) { + this.parent = parent; + this.delegate = delegate; + this.name = name; + this.reporter = reporter; + } + + /** + * ReportingNodeState instances provide access to their path via their + * parent hierarchy. Note that calculating the path on every access may + * incur a significant performance penalty. + * + * @return The path of the ReportingNodeState instance, assuming that + * the first wrapped instance is the root node. + */ + public String getPath() { + if (parent == null) { + return name; + } + return PathUtils.concat(parent.getPath(), name); + } + + @Override + @Nonnull + public NodeBuilder builder() { + return new ReadOnlyBuilder(this); + } + + @Override + public boolean compareAgainstBaseState(final NodeState base, final NodeStateDiff diff) { + return delegate.compareAgainstBaseState(base, new ReportingDiff(diff, this)); + } + + @Override + public boolean exists() { + return delegate.exists(); + } + + @Override + public boolean hasChildNode(@Nonnull final String name) { + return delegate.hasChildNode(name); + } + + @Override + @Nonnull + public NodeState getChildNode(@Nonnull final String name) throws IllegalArgumentException { + return wrapChild(name, delegate.getChildNode(name)); + } + + @Override + public long getChildNodeCount(final long max) { + return delegate.getChildNodeCount(max); + } + + @Override + public Iterable getChildNodeNames() { + return delegate.getChildNodeNames(); + } + + @Override + @Nonnull + public Iterable getChildNodeEntries() { + return Iterables.transform( + delegate.getChildNodeEntries(), + new Function() { + @Override + @Nonnull + public ChildNodeEntry apply(final ChildNodeEntry childNodeEntry) { + final String name = childNodeEntry.getName(); + return new MemoryChildNodeEntry(name, wrapChild(name, childNodeEntry.getNodeState())); + } + } + ); + } + + @Override + public boolean hasProperty(@Nonnull final String name) { + return delegate.hasProperty(name); + } + + @Override + public boolean getBoolean(@Nonnull final String name) { + reporter.reportProperty(this, name); + return delegate.getBoolean(name); + } + + @Override + public long getLong(final String name) { + reporter.reportProperty(this, name); + return delegate.getLong(name); + } + + @Override + @CheckForNull + public String getName(@Nonnull final String name) { + reporter.reportProperty(this, name); + return delegate.getName(name); + } + + @Override + @Nonnull + public Iterable getNames(@Nonnull final String name) { + reporter.reportProperty(this, name); + return delegate.getNames(name); + } + + + @Override + @CheckForNull + public String getString(final String name) { + reporter.reportProperty(this, name); + return delegate.getString(name); + } + + @Override + @Nonnull + public Iterable getStrings(@Nonnull final String name) { + reporter.reportProperty(this, name); + return delegate.getStrings(name); + } + + @Override + @CheckForNull + public PropertyState getProperty(@Nonnull final String name) { + reporter.reportProperty(this, name); + return delegate.getProperty(name); + } + + @Override + public long getPropertyCount() { + return delegate.getPropertyCount(); + } + + @Override + @Nonnull + public Iterable getProperties() { + return Iterables.transform( + delegate.getProperties(), + new Function() { + @Override + @Nonnull + public PropertyState apply(final PropertyState propertyState) { + reporter.reportProperty(ReportingNodeState.this, name); + return propertyState; + } + } + ); + } + + private static class ReportingDiff implements NodeStateDiff { + + private final NodeStateDiff diff; + private ReportingNodeState parent; + + public ReportingDiff(NodeStateDiff diff, ReportingNodeState parent) { + this.diff = diff; + this.parent = parent; + } + + @Override + public boolean childNodeAdded(final String name, final NodeState after) { + return diff.childNodeAdded(name, parent.wrapChild(name, after)); + } + + @Override + public boolean childNodeChanged(final String name, final NodeState before, final NodeState after) { + return diff.childNodeChanged(name, before, parent.wrapChild(name, after)); + } + + @Override + public boolean childNodeDeleted(final String name, final NodeState before) { + return diff.childNodeDeleted(name, before); + } + + @Override + public boolean propertyAdded(final PropertyState after) { + return diff.propertyAdded(after); + } + + @Override + public boolean propertyChanged(final PropertyState before, final PropertyState after) { + return diff.propertyChanged(before, after); + } + + @Override + public boolean propertyDeleted(final PropertyState before) { + return diff.propertyDeleted(before); + } + } +} diff --git oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/AssertingPeriodicReporter.java oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/AssertingPeriodicReporter.java new file mode 100644 index 0000000..da2f211 --- /dev/null +++ oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/AssertingPeriodicReporter.java @@ -0,0 +1,57 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +class AssertingPeriodicReporter extends PeriodicReporter { + + private List expectedNodeCounts; + + private List expectedPropertyCounts; + + private List reportedNodeCounts; + + private List reportedPropertyCounts; + + public AssertingPeriodicReporter(final int nodeLogInterval, final int propertyLogInterval) { + super(nodeLogInterval, propertyLogInterval); + reportedNodeCounts = Lists.newArrayList(); + reportedPropertyCounts = Lists.newArrayList(); + } + + public void verify() { + if (expectedNodeCounts != null) { + assertThat("Expected node counts", reportedNodeCounts, equalTo(expectedNodeCounts)); + } + + if (expectedPropertyCounts != null) { + assertThat("Expected property counts", reportedPropertyCounts, equalTo(expectedPropertyCounts)); + } + } + + @Override + protected void reportPeriodicNode(final long count, final ReportingNodeState nodeState) { + reportedNodeCounts.add(count); + } + + @Override + protected void reportPeriodicProperty(final long count, final ReportingNodeState parent, final String propertyName) { + reportedPropertyCounts.add(count); + } + + public AssertingPeriodicReporter expectedNodeCounts(final Long... counts) { + expectedNodeCounts = Lists.newArrayList(counts); + return this; + } + + public AssertingPeriodicReporter expectedPropertyCounts(final Long... counts) { + expectedPropertyCounts = Lists.newArrayList(counts); + return this; + } +} diff --git oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporterTest.java oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporterTest.java new file mode 100644 index 0000000..0c2f78d --- /dev/null +++ oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporterTest.java @@ -0,0 +1,33 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import org.junit.Test; + +import static org.junit.Assert.assertThat; + +public class PeriodicReporterTest { + + @Test + public void callbackEveryTenNodes() { + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(10, -1); + reporter.expectedNodeCounts(10L, 20L, 30L); + + for (int i = 1; i < 40; i++) { + reporter.reportNode(null); + } + + reporter.verify(); + } + + @Test + public void callbackEveryTenProperties() { + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(-1, 10); + reporter.expectedPropertyCounts(10L, 20L, 30L); + + for (int i = 1; i < 40; i++) { + reporter.reportProperty(null, null); + } + + reporter.verify(); + } + +} diff --git oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeStateTest.java oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeStateTest.java new file mode 100644 index 0000000..a0f4d05 --- /dev/null +++ oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeStateTest.java @@ -0,0 +1,90 @@ +package org.apache.jackrabbit.oak.upgrade.nodestate.report; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Test; + +public class ReportingNodeStateTest { + + @Test + public void getChildNodeReportsNode() { + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(10, 10); + reporter.expectedNodeCounts(10L, 20L); + reporter.expectedPropertyCounts(); + + final NodeState nodeState = ReportingNodeState.wrap(EmptyNodeState.EMPTY_NODE, reporter); + for (int i = 0; i < 20; i++) { + nodeState.getChildNode("a" + i); + } + + reporter.verify(); + } + + @Test + public void getChildNodeEntriesReportsNode() { + final NodeBuilder builder = EmptyNodeState.EMPTY_NODE.builder(); + for (int i = 0; i < 20; i++) { + builder.child("a" + i); + } + + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(10, 10); + reporter.expectedNodeCounts(10L); + reporter.expectedPropertyCounts(); + + final NodeState nodeState = ReportingNodeState.wrap(builder.getNodeState(), reporter); + int counter = 0; + for (final ChildNodeEntry entry : nodeState.getChildNodeEntries()) { + if (++counter == 10) { + break; + } + } + + reporter.verify(); + } + + @Test + public void getPropertyReportsProperty() { + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(10, 10); + reporter.expectedNodeCounts(); + reporter.expectedPropertyCounts(10L, 20L); + + final NodeState nodeState = ReportingNodeState.wrap(EmptyNodeState.EMPTY_NODE, reporter); + final String name = "a"; + for (int i = 0; i < 3; i++) { + // 3 * 7 property requests = 21 + nodeState.getProperty(name); + nodeState.getBoolean(name); + nodeState.getLong(name); + nodeState.getString(name); + nodeState.getStrings(name); + nodeState.getName(name); + nodeState.getNames(name); + } + reporter.verify(); + } + + @Test + public void getPropertiesReportsProperty() { + final NodeBuilder builder = EmptyNodeState.EMPTY_NODE.builder(); + for (int i = 0; i < 20; i++) { + builder.setProperty("a" + i, "foo"); + } + + final AssertingPeriodicReporter reporter = new AssertingPeriodicReporter(10, 10); + reporter.expectedNodeCounts(); + reporter.expectedPropertyCounts(10L); + + final NodeState nodeState = ReportingNodeState.wrap(builder.getNodeState(), reporter); + int counter = 0; + for (final PropertyState property : nodeState.getProperties()) { + if (++counter == 10) { + break; + } + } + + reporter.verify(); + } +}