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 st...@apache.org on 2016/10/13 11:24:24 UTC
svn commit: r1764657 - in /jackrabbit/oak/trunk/oak-core/src:
main/java/org/apache/jackrabbit/oak/core/
main/java/org/apache/jackrabbit/oak/plugins/observation/
test/java/org/apache/jackrabbit/oak/plugins/observation/
Author: stefanegli
Date: Thu Oct 13 11:24:24 2016
New Revision: 1764657
URL: http://svn.apache.org/viewvc?rev=1764657&view=rev
Log:
OAK-4907 : introducing the ChangeCollectorProvider, a ValidatorProvider that composes a ChangeSet and sets it on the CommitContext for downstream users - which typically are Observers - to enjoy
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java (with props)
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java (with props)
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java (with props)
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/SimpleCommitContext.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/SimpleCommitContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/SimpleCommitContext.java?rev=1764657&r1=1764656&r2=1764657&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/SimpleCommitContext.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/SimpleCommitContext.java Thu Oct 13 11:24:24 2016
@@ -30,6 +30,11 @@ public class SimpleCommitContext impleme
private final Map<String, Object> attrs = Maps.newHashMap();
@Override
+ public String toString() {
+ return "CommitContext[attrs="+attrs+"]";
+ }
+
+ @Override
public void set(String name, Object value) {
attrs.put(checkNotNull(name), value);
}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java?rev=1764657&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java Thu Oct 13 11:24:24 2016
@@ -0,0 +1,301 @@
+/*
+ * 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.observation;
+
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toInteger;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.commit.CommitContext;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * A ChangeCollectorProvider can be hooked into Oak thus enabling the collection
+ * of ChangeSets of changed items of a commit, which downstream Observers can
+ * then use at their convenience.
+ * <p>
+ *
+ * @see ChangeSet for details on what is tracked and how that data should be
+ * interpreted
+ */
+@Component(immediate = true)
+@Property(name = "type", value = "changeCollectorProvider", propertyPrivate = true)
+@Service(ValidatorProvider.class)
+public class ChangeCollectorProvider extends ValidatorProvider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ChangeCollectorProvider.class);
+
+ public static final String COMMIT_CONTEXT_OBSERVATION_CHANGESET = "oak.observation.changeSet";
+
+ private static final int DEFAULT_MAX_ITEMS = 50;
+ @Property(longValue = DEFAULT_MAX_ITEMS, label = "Maximum Number of Collected Items (per type)", description = "Integer value indicating maximum number of individual items of changes - "
+ + "such as property, nodeType, node name, path - to be collected. If there are "
+ + "more changes, the collection is considered failed and marked as such. " + "Default is "
+ + DEFAULT_MAX_ITEMS)
+ private static final String PROP_MAX_ITEMS = "maxItems";
+
+ private static final int DEFAULT_MAX_PATH_DEPTH = 9;
+ @Property(longValue = DEFAULT_MAX_PATH_DEPTH, label = "Maximum depth of paths to collect", description = "Integer value indicating maximum depth of paths to collect. "
+ + "Paths deeper than this will not be individually reported, and instead "
+ + "a path at this max depth will be added. Note that this doesn't affect "
+ + "any other collected item such as property, nodeType - ie those will "
+ + "all be collected irrespective of this config param." + "Default is " + DEFAULT_MAX_PATH_DEPTH)
+ private static final String PROP_MAX_PATH_DEPTH = "maxPathDepth";
+
+ /**
+ * There is one CollectorSupport per validation process - it is shared
+ * between multiple instances of ChangeCollector (Validator) - however it
+ * can remain unsynchronized as validators are executed single-threaded.
+ */
+ private static class CollectorSupport {
+ private final CommitInfo info;
+ private final int maxPathDepth;
+ private final ChangeSetBuilder changeSetBuilder;
+
+ private CollectorSupport(@Nonnull CommitInfo info, @Nonnull ChangeSetBuilder changeSetBuilder,
+ int maxPathDepth) {
+ this.info = info;
+ this.changeSetBuilder = changeSetBuilder;
+ this.maxPathDepth = maxPathDepth;
+ }
+
+ @Override
+ public String toString() {
+ return "CollectorSupport with " + changeSetBuilder;
+ }
+
+ private CommitInfo getInfo() {
+ return info;
+ }
+
+ private int getMaxPathDepth() {
+ return maxPathDepth;
+ }
+
+ private ChangeSetBuilder getChangeSetBuilder() {
+ return changeSetBuilder;
+ }
+
+ private Set<String> getParentPaths() {
+ return changeSetBuilder.getParentPaths();
+ }
+
+ private Set<String> getParentNodeNames() {
+ return changeSetBuilder.getParentNodeNames();
+ }
+
+ private Set<String> getParentNodeTypes() {
+ return changeSetBuilder.getParentNodeTypes();
+ }
+
+ private Set<String> getPropertyNames() {
+ return changeSetBuilder.getPropertyNames();
+ }
+ }
+
+ /**
+ * ChangeCollectors are the actual working-horse Validators that are created
+ * for each level thus as a whole propage through the entire change.
+ * <p>
+ * The actual data is collected via a per-commit CollectorSupport and its
+ * underlying ChangeSet (the latter is where the actual changes end up in).
+ * <p>
+ * When finished - ie in the last==root leave() - the resulting ChangeSet is
+ * marked immutable and set in the CommitContext.
+ */
+ private static class ChangeCollector implements Validator {
+
+ private final CollectorSupport support;
+
+ private final boolean isRoot;
+ private final NodeState parentNodeOrNull;
+ private final String path;
+ private final String childName;
+ private final int level;
+
+ private boolean changed;
+
+ private static ChangeCollector newRootCollector(@Nonnull CommitInfo info, int maxItems, int maxPathDepth) {
+ ChangeSetBuilder changeSetBuilder = new ChangeSetBuilder(maxItems, maxPathDepth);
+ CollectorSupport support = new CollectorSupport(info, changeSetBuilder, maxPathDepth);
+ return new ChangeCollector(support, true, null, "/", null, 0);
+ }
+
+ private ChangeCollector newChildCollector(@Nonnull NodeState parentNode, @Nonnull String childName) {
+ return new ChangeCollector(support, false, parentNode, concat(path, childName), childName, level + 1);
+ }
+
+ private ChangeCollector(@Nonnull CollectorSupport support, boolean isRoot, @Nullable NodeState parentNodeOrNull,
+ @Nonnull String path, @Nullable String childNameOrNull, int level) {
+ this.support = support;
+ this.isRoot = isRoot;
+ this.parentNodeOrNull = parentNodeOrNull;
+ this.path = path;
+ this.childName = childNameOrNull;
+ this.level = level;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeCollector[path=" + path + "]";
+ }
+
+ @Override
+ public void enter(NodeState before, NodeState after) throws CommitFailedException {
+ // nothing to be done here
+ }
+
+ @Override
+ public void leave(NodeState before, NodeState after) throws CommitFailedException {
+ // first check if we have to add anything to paths and/or nodeNames
+ if (changed && level <= support.getMaxPathDepth()) {
+ support.getParentPaths().add(path);
+ }
+ if (changed && childName != null) {
+ support.getParentNodeNames().add(childName);
+ }
+ if (changed && parentNodeOrNull != null) {
+ String primaryType = parentNodeOrNull.getName(JcrConstants.JCR_PRIMARYTYPE);
+ if (primaryType != null) {
+ support.getParentNodeTypes().add(primaryType);
+ }
+ Iterables.addAll(support.getParentNodeTypes(), parentNodeOrNull.getNames(JcrConstants.JCR_MIXINTYPES));
+ }
+
+ // then if we're not at the root, we're done
+ if (!isRoot) {
+ return;
+ }
+
+ // but if we're at the root, then we add the ChangeSet to the
+ // CommitContext of the CommitInfo
+ CommitContext commitContext = (CommitContext) support.getInfo().getInfo().get(CommitContext.NAME);
+ commitContext.set(COMMIT_CONTEXT_OBSERVATION_CHANGESET, support.getChangeSetBuilder().build());
+ }
+
+ @Override
+ public void propertyAdded(PropertyState after) throws CommitFailedException {
+ changed = true;
+ support.getPropertyNames().add(after.getName());
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
+ changed = true;
+ support.getPropertyNames().add(before.getName());
+ }
+
+ @Override
+ public void propertyDeleted(PropertyState before) throws CommitFailedException {
+ changed = true;
+ support.getPropertyNames().add(before.getName());
+ }
+
+ @Override
+ public Validator childNodeAdded(String childName, NodeState after) throws CommitFailedException {
+ changed = true;
+ return newChildCollector(after, childName);
+ }
+
+ @Override
+ public Validator childNodeChanged(String childName, NodeState before, NodeState after)
+ throws CommitFailedException {
+ if (level == support.getMaxPathDepth()) {
+ // then we'll cut off further paths below.
+ // to compensate, add the current path at this level
+ support.getParentPaths().add(path);
+
+ // however, continue normally to handle names/types/properties
+ // below
+ }
+
+ return newChildCollector(after, childName);
+ }
+
+ @Override
+ public Validator childNodeDeleted(String childName, NodeState before) throws CommitFailedException {
+ changed = true;
+ return newChildCollector(before, childName);
+ }
+
+ }
+
+ private int maxItems = DEFAULT_MAX_ITEMS;
+
+ private int maxPathDepth = DEFAULT_MAX_PATH_DEPTH;
+
+ @Activate
+ protected void activate(ComponentContext context, Map<String, ?> config) {
+ maxItems = toInteger(config.get(PROP_MAX_ITEMS), DEFAULT_MAX_ITEMS);
+ maxPathDepth = toInteger(config.get(PROP_MAX_PATH_DEPTH), DEFAULT_MAX_PATH_DEPTH);
+ LOG.info("activate: maxItems=" + maxItems + ", maxPathDepth=" + maxPathDepth);
+ }
+
+ /** FOR TESTING-ONLY **/
+ protected void setMaxPathDepth(int maxPathDepth) {
+ this.maxPathDepth = maxPathDepth;
+ }
+
+ /** FOR TESTING-ONLY **/
+ protected int getMaxPathDepth() {
+ return this.maxPathDepth;
+ }
+
+ /** FOR TESTING-ONLY **/
+ protected void setMaxItems(int maxItems) {
+ this.maxItems = maxItems;
+ }
+
+ /** FOR TESTING-ONLY **/
+ protected int getMaxItems() {
+ return this.maxItems;
+ }
+
+ @Override
+ protected Validator getRootValidator(NodeState before, NodeState after, CommitInfo info) {
+ if (info == null || !info.getInfo().containsKey(CommitContext.NAME)) {
+ // then we cannot do change-collecting, as we can't store
+ // it in the info
+ return null;
+ }
+
+ return ChangeCollector.newRootCollector(info, maxItems, maxPathDepth);
+ }
+
+}
Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java?rev=1764657&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java Thu Oct 13 11:24:24 2016
@@ -0,0 +1,103 @@
+/*
+ * 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.observation;
+
+import java.util.Set;
+
+import javax.annotation.CheckForNull;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * A ChangeSet is a collection of items that have been changed as part of a
+ * commit. A ChangeSet is immutable and built by a ChangeSetBuilder.
+ * <p>
+ * Those items are parent paths, parent node names, parent node types and
+ * (child) properties. 'Changed' refers to any of add, remove, change (where
+ * applicable).
+ * <p>
+ * A ChangeSet is piggybacked on a CommitInfo in the CommitContext and can be
+ * used by (downstream) Observers for their convenience.
+ * <p>
+ * To limit memory usage, the ChangeSet has a limit on the number of items,
+ * each, that it collects. If one of those items reach the limit this is called
+ * an 'overflow' and the corresponding item type is marked as having
+ * 'overflown'. Downstream Observers should thus check if a particular item has
+ * overflown or not - this is indicated with null as the return value of the
+ * corresponding getters (while empty means: not overflown but nothing changed
+ * of that type).
+ * <p>
+ * Also, the ChangeSet carries a 'maxPathDepth' which is the depth of the path
+ * up until which paths have been collected. Thus any path that is longer than
+ * this 'maxPathDepth' will be cut off and only reported up to that max depth.
+ * Downstream Observers should thus inspect the 'maxPathDepth' and compare
+ * actual path depths with it in order to find out if any child paths have been
+ * cut off.
+ * <p>
+ * Naming: note that path, node name and node types all refer to the *parent* of
+ * a change. While properties naturally are leafs.
+ */
+public class ChangeSet {
+
+ private final int maxPathDepth;
+ private final Set<String> parentPaths;
+ private final Set<String> parentNodeNames;
+ private final Set<String> parentNodeTypes;
+ private final Set<String> propertyNames;
+
+ ChangeSet(int maxPathDepth, Set<String> parentPaths, Set<String> parentNodeNames, Set<String> parentNodeTypes,
+ Set<String> propertyNames) {
+ this.maxPathDepth = maxPathDepth;
+ this.parentPaths = parentPaths == null ? null : ImmutableSet.copyOf(parentPaths);
+ this.parentNodeNames = parentNodeNames == null ? null : ImmutableSet.copyOf(parentNodeNames);
+ this.parentNodeTypes = parentNodeTypes == null ? null : ImmutableSet.copyOf(parentNodeTypes);
+ this.propertyNames = propertyNames == null ? null : ImmutableSet.copyOf(propertyNames);
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeSet{paths[maxDepth:" + maxPathDepth + "]=" + parentPaths + ", propertyNames=" + propertyNames
+ + ", nodeNames=" + parentNodeNames + ", nodeTypes=" + parentNodeTypes + "}";
+ }
+
+ @CheckForNull
+ public Set<String> getParentPaths() {
+ return parentPaths;
+ }
+
+ @CheckForNull
+ public Set<String> getParentNodeNames() {
+ return parentNodeNames;
+ }
+
+ @CheckForNull
+ public Set<String> getParentNodeTypes() {
+ return parentNodeTypes;
+ }
+
+ @CheckForNull
+ public Set<String> getPropertyNames() {
+ return propertyNames;
+ }
+
+ public int getMaxPrefilterPathDepth() {
+ return maxPathDepth;
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSet.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java?rev=1764657&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java Thu Oct 13 11:24:24 2016
@@ -0,0 +1,669 @@
+/*
+ * 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.observation;
+
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.core.SimpleCommitContext;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
+import org.apache.jackrabbit.oak.spi.commit.CommitContext;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
+public class ChangeCollectorProviderTest {
+
+ ChangeCollectorProvider collectorProvider;
+ private ContentRepository contentRepository;
+ private ContentSession session;
+ private Recorder recorder;
+ private SecurityProviderImpl securityProvider;
+
+ class ContentChange {
+ final NodeState root;
+ final CommitInfo info;
+
+ ContentChange(NodeState root, CommitInfo info) {
+ this.root = root;
+ this.info = info;
+ }
+ }
+
+ class Recorder implements Observer {
+ List<ContentChange> changes = new LinkedList<ContentChange>();
+
+ @Override
+ public void contentChanged(NodeState root, CommitInfo info) {
+ changes.add(new ContentChange(root, info));
+ }
+
+ }
+
+ protected SecurityProvider getSecurityProvider() {
+ if (securityProvider == null) {
+ securityProvider = new SecurityProviderImpl(ConfigurationParameters.EMPTY);
+ }
+ return securityProvider;
+ }
+
+ /**
+ * Checks that the actual string set provided matches the expected one. A
+ * match is when all elements occur, irrespective of the order.
+ */
+ private void assertMatches(String msg, Set<String> actuals, String... expected) {
+ if ((actuals == null || actuals.size() == 0) && expected.length != 0) {
+ fail("assertion failed for '" + msg + "': expected length " + expected.length + " != actual 0."
+ + " Expected: '" + Arrays.toString(expected) + "', got: '" + actuals + "'");
+ } else if (expected.length == 0 && actuals != null && actuals.size() != 0) {
+ fail("assertion failed for '" + msg + "': expected length == 0, actual " + actuals.size() + "."
+ + " Expected: '" + Arrays.toString(expected) + "', got: '" + actuals + "'");
+ } else if (expected.length != actuals.size()) {
+ fail("assertion failed for '" + msg + "': expected length (" + expected.length + ") != actual ("
+ + actuals.size() + ")." + " Expected: '" + Arrays.toString(expected) + "', got: '" + actuals + "'");
+ }
+ for (String anExpected : expected) {
+ if (!actuals.contains(anExpected)) {
+ fail("assertion failed for '" + msg + "': expected '" + anExpected + "' not found. Got: '" + actuals
+ + "'");
+ }
+ }
+ }
+
+ /**
+ * Assumes that the recorder got 1 call, and extracts the ChangeSet from
+ * that call
+ */
+ private ChangeSet getSingleChangeSet() {
+ assertEquals(recorder.changes.size(), 1);
+ CommitContext commitContext = (CommitContext) recorder.changes.get(0).info.getInfo().get(CommitContext.NAME);
+ assertNotNull(commitContext);
+ ChangeSet changeSet = (ChangeSet) commitContext
+ .get(ChangeCollectorProvider.COMMIT_CONTEXT_OBSERVATION_CHANGESET);
+ assertNotNull(changeSet);
+ return changeSet;
+ }
+
+ @Before
+ public void setup() throws PrivilegedActionException, CommitFailedException {
+ collectorProvider = new ChangeCollectorProvider();
+ recorder = new Recorder();
+ Oak oak = new Oak().with(new InitialContent()).with(collectorProvider).with(recorder)
+ .with(getSecurityProvider());
+ contentRepository = oak.createContentRepository();
+
+ session = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() {
+ @Override
+ public ContentSession run() throws LoginException, NoSuchWorkspaceException {
+ return contentRepository.login(null, null);
+ }
+ });
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/").addChild("test");
+ rootTree.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:parentType", Type.NAME);
+ Tree child1 = rootTree.addChild("child1");
+ child1.setProperty("child1Prop", 1);
+ child1.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:childType", Type.NAME);
+ Tree grandChild1 = child1.addChild("grandChild1");
+ grandChild1.setProperty("grandChild1Prop", 1);
+ grandChild1.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:grandChildType", Type.NAME);
+ Tree greatGrandChild1 = grandChild1.addChild("greatGrandChild1");
+ greatGrandChild1.setProperty("greatGrandChild1Prop", 1);
+ greatGrandChild1.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:greatGrandChildType", Type.NAME);
+ Tree child2 = rootTree.addChild("child2");
+ child2.setProperty("child2Prop", 1);
+ child2.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:childType", Type.NAME);
+ Tree grandChild2 = child2.addChild("grandChild2");
+ grandChild2.setProperty("grandChild2Prop", 1);
+ grandChild2.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:grandChildType", Type.NAME);
+ recorder.changes.clear();
+ root.commit();
+
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child2", "/test/child1",
+ "/test/child1/grandChild1/greatGrandChild1", "/", "/test", "/test/child1/grandChild1",
+ "/test/child2/grandChild2");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "child2", "child1", "greatGrandChild1", "test",
+ "grandChild1", "grandChild2");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "test:childType",
+ "test:grandChildType", "test:greatGrandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "child1Prop",
+ "child2Prop", "grandChild1Prop", "grandChild2Prop", "greatGrandChild1Prop");
+
+ // clear the recorder so that we start off empty
+ recorder.changes.clear();
+ }
+
+ private static CommitInfo newCommitInfoWithCommitContext(String sessionId, String userId) {
+ return new CommitInfo(sessionId, userId,
+ ImmutableMap.<String, Object> builder().put(CommitContext.NAME, new SimpleCommitContext()).build());
+ }
+
+ @Test
+ public void testNull() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setChildNode("test");
+ builder.setChildNode("a1").setChildNode("b1").setProperty("p1", 1);
+ NodeState before = builder.getNodeState();
+
+ builder = before.builder();
+ builder.setChildNode("a2").setChildNode("b12").setProperty("p12", "12");
+ NodeState after = builder.getNodeState();
+
+ assertNull(collectorProvider.getRootValidator(before, after, null));
+ assertNull(collectorProvider.getRootValidator(before, after, CommitInfo.EMPTY));
+ assertNotNull(collectorProvider.getRootValidator(before, after,
+ newCommitInfoWithCommitContext(CommitInfo.OAK_UNKNOWN, CommitInfo.OAK_UNKNOWN)));
+ }
+
+ @Test
+ public void testRemoveChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ assertTrue(rootTree.getChild("child1").remove());
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/child1", "/test/child1/grandChild1",
+ "/test/child1/grandChild1/greatGrandChild1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "child1", "grandChild1",
+ "greatGrandChild1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "test:childType",
+ "test:grandChildType", "test:greatGrandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "child1Prop",
+ "grandChild1Prop", "greatGrandChild1Prop");
+ }
+
+ @Test
+ public void testRemoveGreatGrandChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ assertTrue(rootTree.getChild("child1").getChild("grandChild1").getChild("greatGrandChild1").remove());
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child1/grandChild1/greatGrandChild1",
+ "/test/child1/grandChild1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "grandChild1", "greatGrandChild1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:greatGrandChildType",
+ "test:grandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE,
+ "greatGrandChild1Prop");
+ }
+
+ @Test
+ public void testChangeGreatGrandChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ rootTree.getChild("child1").getChild("grandChild1").getChild("greatGrandChild1")
+ .setProperty("greatGrandChild1Prop", 2);
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child1/grandChild1/greatGrandChild1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "greatGrandChild1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:greatGrandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), "greatGrandChild1Prop");
+ }
+
+ @Test
+ public void testChangeGreatAndGrandChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ rootTree.getChild("child1").getChild("grandChild1").setProperty("grandChild1Prop", 2);
+ rootTree.getChild("child1").getChild("grandChild1").getChild("greatGrandChild1")
+ .setProperty("greatGrandChild1Prop", 2);
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child1/grandChild1",
+ "/test/child1/grandChild1/greatGrandChild1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "grandChild1", "greatGrandChild1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:grandChildType",
+ "test:greatGrandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), "grandChild1Prop", "greatGrandChild1Prop");
+ }
+
+ @Test
+ public void testAddEmptyChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ rootTree.addChild("child");
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType");
+ assertMatches("propertyNames", changeSet.getPropertyNames());
+ }
+
+ @Test
+ public void testAddEmptyGrandChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree child = rootTree.addChild("child");
+ child.addChild("grandChild");
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/child");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "child");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType");
+ assertMatches("propertyNames", changeSet.getPropertyNames());
+ }
+
+ @Test
+ public void testAddNonEmptyGrandChild() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree child = rootTree.addChild("child");
+ child.setProperty("childProperty", 1);
+ Tree grandChild = child.addChild("grandChild");
+ grandChild.setProperty("grandChildProperty", 2);
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/child", "/test/child/grandChild");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "child", "grandChild");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), "childProperty", "grandChildProperty");
+ }
+
+ @Test
+ public void testAddSomeChildren() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ for (int i = 0; i < 10; i++) {
+ Tree child = rootTree.addChild("x" + i);
+ child.setProperty(JcrConstants.JCR_PRIMARYTYPE, "test:type" + i, Type.NAME);
+ child.setProperty("foo" + i, "bar");
+ }
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/x0", "/test/x1", "/test/x2",
+ "/test/x3", "/test/x4", "/test/x5", "/test/x6", "/test/x7", "/test/x8", "/test/x9");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "x0", "x1", "x2", "x3", "x4", "x5",
+ "x6", "x7", "x8", "x9");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "test:type0", "test:type1",
+ "test:type2", "test:type3", "test:type4", "test:type5", "test:type6", "test:type7", "test:type8",
+ "test:type9");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "foo0", "foo1",
+ "foo2", "foo3", "foo4", "foo5", "foo6", "foo7", "foo8", "foo9");
+ }
+
+ @Test
+ public void testAddEmptyRemoveChildren() throws CommitFailedException {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree child = rootTree.addChild("child");
+ child.addChild("grandChild");
+ assertTrue(rootTree.getChild("child2").remove());
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/child", "/test/child2",
+ "/test/child2/grandChild2");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "child", "child2", "grandChild2");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "test:childType",
+ "test:grandChildType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "child2Prop",
+ "grandChild2Prop");
+ }
+
+ @Test
+ public void testAddMaxPathDepthAll() throws CommitFailedException, PrivilegedActionException {
+ for (int i = 0; i < 16; i++) {
+ setup();
+ doAddMaxPathDepth(i);
+ }
+ }
+
+ private void doAddMaxPathDepth(int maxPathDepth) throws CommitFailedException {
+ collectorProvider.setMaxPathDepth(maxPathDepth);
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree next = rootTree;
+ for (int i = 0; i < 16; i++) {
+ next = next.addChild("n" + i);
+ if (i % 3 != 0) {
+ next.setProperty("nextProp" + i, i);
+ next.setProperty(JcrConstants.JCR_PRIMARYTYPE, i % 2 == 0 ? "test:even" : "test:odd", Type.NAME);
+ }
+ }
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ List<String> expectedParentPaths = new LinkedList<String>();
+ if (maxPathDepth == 0) {
+ expectedParentPaths.add("/");
+ } else {
+ expectedParentPaths.add("/test");
+ }
+ for (int i = 0; i < maxPathDepth - 1; i++) {
+ StringBuffer path = new StringBuffer("/test");
+ for (int j = 0; j < i; j++) {
+ path.append("/n" + j);
+ }
+ expectedParentPaths.add(path.toString());
+ }
+ assertMatches("parentPaths-" + maxPathDepth, changeSet.getParentPaths(),
+ expectedParentPaths.toArray(new String[0]));
+ assertMatches("parentNodeNames-" + maxPathDepth, changeSet.getParentNodeNames(), "test", "n0", "n1", "n2", "n3",
+ "n4", "n5", "n6", "n7", "n8", "n9", "n10", "n11", "n12", "n13",
+ "n14"/* , "n15" */);
+ assertMatches("parentNodeTypes-" + maxPathDepth, changeSet.getParentNodeTypes(), "test:parentType", "test:even",
+ "test:odd");
+ assertMatches("propertyNames-" + maxPathDepth, changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE,
+ /* "nextProp0", */"nextProp1", "nextProp2", /* "nextProp3", */ "nextProp4",
+ "nextProp5"/* , "nextProp6" */
+ , "nextProp7", "nextProp8", /* "nextProp9", */"nextProp10", "nextProp11",
+ /* "nextProp12", */ "nextProp13",
+ "nextProp14"/* , "nextProp15" */);
+ }
+
+ @Test
+ public void testAddMixin() throws Exception {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ rootTree.addChild("child").setProperty(JcrConstants.JCR_MIXINTYPES, Arrays.asList("aMixin1", "aMixin2"),
+ Type.NAMES);
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/child");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "child");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "aMixin1", "aMixin2");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_MIXINTYPES);
+ }
+
+ @Test
+ public void testAddNodeWithProperties() throws Exception {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree aChild = rootTree.addChild("newchild");
+ aChild.setProperty("aProp", "aValue", Type.NAME);
+ aChild.setProperty(JcrConstants.JCR_PRIMARYTYPE, "aPrimaryType", Type.NAME);
+
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test", "/test/newchild");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "test", "newchild");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType", "aPrimaryType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "aProp");
+ }
+
+ @Test
+ public void testPathNotOverflown() throws Exception {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Set<String> expectedParentPaths = Sets.newHashSet();
+ expectedParentPaths.add("/test");
+ Set<String> expectedParentNodeNames = Sets.newHashSet();
+ expectedParentNodeNames.add("test");
+ Set<String> expectedParentNodeTypes = Sets.newHashSet();
+ expectedParentNodeTypes.add("test:parentType");
+ // do maxItems-1 iterations only, as the above already adds 1 item - to
+ // avoid overflowing
+ for (int i = 0; i < collectorProvider.getMaxItems() - 1; i++) {
+ Tree aChild = rootTree.addChild("manychildren" + i);
+ aChild.setProperty("aProperty", "foo");
+ aChild.setProperty(JcrConstants.JCR_PRIMARYTYPE, "aChildPrimaryType" + i, Type.NAME);
+ expectedParentPaths.add("/test/manychildren" + i);
+ expectedParentNodeNames.add("manychildren" + i);
+ expectedParentNodeTypes.add("aChildPrimaryType" + i);
+ }
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), expectedParentPaths.toArray(new String[0]));
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(),
+ expectedParentNodeNames.toArray(new String[0]));
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(),
+ expectedParentNodeTypes.toArray(new String[0]));
+ assertMatches("propertyNames", changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE, "aProperty");
+ }
+
+ @Test
+ public void testPathOverflown() throws Exception {
+ doTestPathOverflown(0);
+ for (int overflowCnt = 1; overflowCnt <= 64 * 1024; overflowCnt += overflowCnt) {
+ doTestPathOverflown(overflowCnt);
+ }
+ }
+
+ private void doTestPathOverflown(int overflowCnt) throws CommitFailedException, PrivilegedActionException {
+ setup();
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ for (int i = 0; i < collectorProvider.getMaxItems() + overflowCnt; i++) {
+ rootTree.addChild("manychildren" + i).setProperty("aProperty", "foo");
+ ;
+ }
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertEquals("parentPaths", null, changeSet.getParentPaths());
+ assertEquals("parentNodeNames", null, changeSet.getParentNodeNames());
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:parentType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), "aProperty");
+ }
+
+ @Test
+ public void testPropertyNotOverflown() throws Exception {
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree child1 = rootTree.getChild("child1");
+
+ Set<String> expectedPropertyNames = Sets.newHashSet();
+ for (int i = 0; i < collectorProvider.getMaxItems(); i++) {
+ child1.setProperty("aProperty" + i, "foo");
+ expectedPropertyNames.add("aProperty" + i);
+ }
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "child1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:childType");
+ assertMatches("propertyNames", changeSet.getPropertyNames(), expectedPropertyNames.toArray(new String[0]));
+ }
+
+ @Test
+ public void testPropertyOverflown() throws Exception {
+ for (int overflowCnt = 1; overflowCnt <= 64 * 1024; overflowCnt += overflowCnt) {
+ doTestPropertyOverflown(overflowCnt);
+ }
+ }
+
+ private void doTestPropertyOverflown(int overflowCnt) throws CommitFailedException, PrivilegedActionException {
+ setup();
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree child1 = rootTree.getChild("child1");
+
+ Set<String> expectedPropertyNames = Sets.newHashSet();
+ for (int i = 0; i < collectorProvider.getMaxItems() + overflowCnt; i++) {
+ child1.setProperty("aProperty" + i, "foo");
+ expectedPropertyNames.add("aProperty" + i);
+ }
+ root.commit();
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths", changeSet.getParentPaths(), "/test/child1");
+ assertMatches("parentNodeNames", changeSet.getParentNodeNames(), "child1");
+ assertMatches("parentNodeTypes", changeSet.getParentNodeTypes(), "test:childType");
+ assertEquals("propertyNames", null, changeSet.getPropertyNames());
+ }
+
+ @Test
+ public void testRemoveMaxPathDepthAll() throws CommitFailedException, PrivilegedActionException {
+ for (int i = 0; i < 16; i++) {
+ setup();
+ doRemoveMaxPathDepth(i);
+ }
+ }
+
+ private void doRemoveMaxPathDepth(int maxPathDepth) throws CommitFailedException {
+ collectorProvider.setMaxPathDepth(maxPathDepth);
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree next = rootTree;
+ for (int i = 0; i < 16; i++) {
+ next = next.addChild("n" + i);
+ if (i % 3 != 0) {
+ next.setProperty("nextProp" + i, i);
+ next.setProperty(JcrConstants.JCR_PRIMARYTYPE, i % 2 == 0 ? "test:even" : "test:odd", Type.NAME);
+ }
+ }
+ root.commit();
+
+ // now do the delete
+ recorder.changes.clear();
+ root = session.getLatestRoot();
+ rootTree = root.getTree("/test");
+ next = rootTree;
+ for (int i = 0; i < 15; i++) {
+ next = next.getChild("n" + i);
+ if (i == 14) {
+ next.remove();
+ }
+ }
+ root.commit();
+
+ ChangeSet changeSet = getSingleChangeSet();
+ Set<String> expectedParentPaths = new HashSet<String>();
+ String path = "/";
+ if (maxPathDepth == 1) {
+ path = "/test";
+ } else if (maxPathDepth > 1) {
+ path = "/test";
+ for (int i = 0; i < maxPathDepth - 1; i++) {
+ path = concat(path, "n" + i);
+ }
+ }
+ expectedParentPaths.add(path);
+ assertMatches("parentPaths-" + maxPathDepth, changeSet.getParentPaths(),
+ expectedParentPaths.toArray(new String[0]));
+ assertMatches("parentNodeNames-" + maxPathDepth, changeSet.getParentNodeNames(), "n13", "n14");
+ assertMatches("parentNodeTypes-" + maxPathDepth, changeSet.getParentNodeTypes(), "test:even", "test:odd");
+ assertMatches("propertyNames-" + maxPathDepth, changeSet.getPropertyNames(), JcrConstants.JCR_PRIMARYTYPE,
+ "nextProp14");
+ }
+
+ @Test
+ public void testChangeMaxPathDepthAll() throws CommitFailedException, PrivilegedActionException {
+ for (int maxPathDepth = 0; maxPathDepth < 16; maxPathDepth++) {
+ for (int changeAt = 0; changeAt < 16; changeAt++) {
+ setup();
+ doChangeMaxPathDepth(changeAt, maxPathDepth);
+ }
+ }
+ }
+
+ private void doChangeMaxPathDepth(int changeAt, int maxPathDepth) throws CommitFailedException {
+ collectorProvider.setMaxPathDepth(maxPathDepth);
+ Root root = session.getLatestRoot();
+ Tree rootTree = root.getTree("/test");
+ Tree next = rootTree;
+ for (int i = 0; i < 16; i++) {
+ next = next.addChild("n" + i);
+ if (i % 3 != 0) {
+ next.setProperty("nextProp" + i, i);
+ next.setProperty(JcrConstants.JCR_PRIMARYTYPE, i % 2 == 0 ? "test:even" : "test:odd", Type.NAME);
+ }
+ }
+ root.commit();
+ recorder.changes.clear();
+
+ // now do the change
+ root = session.getLatestRoot();
+ rootTree = root.getTree("/test");
+ next = rootTree;
+ List<String> expectedParentPaths = new LinkedList<String>();
+ List<String> expectedParentNodeNames = new LinkedList<String>();
+ List<String> expectedParentNodeTypes = new LinkedList<String>();
+ List<String> expectedPropertyNames = new LinkedList<String>();
+ expectedPropertyNames.add(JcrConstants.JCR_PRIMARYTYPE);
+ String parent = "/";
+ if (maxPathDepth > 0) {
+ parent = "/test";
+ }
+ for (int i = 0; i <= changeAt; i++) {
+ String childName = "n" + i;
+ next = next.getChild(childName);
+ if (i < maxPathDepth - 1) {
+ parent = concat(parent, childName);
+ }
+ if (i == changeAt) {
+ expectedParentNodeNames.add(next.getName());
+ String propertyName = "nextProp" + i;
+ next.setProperty(propertyName, i + 1);
+ expectedPropertyNames.add(propertyName);
+ final String nodeTypeName = i % 2 == 0 ? "test:evenChanged" : "test:oddChanged";
+ expectedParentNodeTypes.add(nodeTypeName);
+ next.setProperty(JcrConstants.JCR_PRIMARYTYPE, nodeTypeName, Type.NAME);
+ }
+ }
+ expectedParentPaths.add(parent);
+ root.commit();
+
+ ChangeSet changeSet = getSingleChangeSet();
+ assertMatches("parentPaths-" + changeAt + "-" + maxPathDepth, changeSet.getParentPaths(),
+ expectedParentPaths.toArray(new String[0]));
+ assertMatches("parentNodeNames-" + changeAt + "-" + maxPathDepth, changeSet.getParentNodeNames(),
+ expectedParentNodeNames.toArray(new String[0]));
+ assertMatches("parentNodeTypes-" + changeAt + "-" + maxPathDepth, changeSet.getParentNodeTypes(),
+ expectedParentNodeTypes.toArray(new String[0]));
+ assertMatches("propertyNames-" + changeAt + "-" + maxPathDepth, changeSet.getPropertyNames(),
+ expectedPropertyNames.toArray(new String[0]));
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeCollectorProviderTest.java
------------------------------------------------------------------------------
svn:eol-style = native