You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2010/10/27 14:36:03 UTC
svn commit: r1027938 - in /jackrabbit/trunk/jackrabbit-core/src:
main/java/org/apache/jackrabbit/core/state/
test/java/org/apache/jackrabbit/core/state/
test/resources/org/apache/jackrabbit/core/nodetype/xml/
Author: angela
Date: Wed Oct 27 12:36:03 2010
New Revision: 1027938
URL: http://svn.apache.org/viewvc?rev=1027938&view=rev
Log:
JCR-2780 - Best effort merge if concurrent modifications include changes to mixin types
Added:
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java (with props)
jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd (with props)
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/TestAll.java
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java?rev=1027938&r1=1027937&r2=1027938&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java Wed Oct 27 12:36:03 2010
@@ -17,12 +17,22 @@
package org.apache.jackrabbit.core.state;
import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.id.PropertyId;
import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
import org.apache.jackrabbit.spi.Name;
-
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QNodeTypeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Set;
/**
* Internal utility class used for merging concurrent changes that occurred
@@ -86,11 +96,7 @@ class NodeStateMerger {
}
// mixin types
- if (!state.getMixinTypeNames().equals(overlayedState.getMixinTypeNames())) {
- // the mixins have been modified but by just looking at the diff we
- // can't determine where the change happened since the diffs of either
- // removing a mixin from the overlayed or adding a mixin to the
- // transient state would look identical...
+ if (!mergeMixinTypes(state, overlayedState, context)) {
return false;
}
@@ -189,6 +195,175 @@ class NodeStateMerger {
}
}
+ /**
+ *
+ * @param state
+ * @param overlayedState
+ * @return true if the mixin type names are the same in both node states or
+ * if the mixin modifications do not conflict (and could be merged); false
+ * otherwise.
+ */
+ private static boolean mergeMixinTypes(NodeState state, NodeState overlayedState, MergeContext ctx) {
+ Set<Name> mixins = new HashSet<Name>(state.getMixinTypeNames());
+ Set<Name> overlayedMixins = new HashSet<Name>(overlayedState.getMixinTypeNames());
+ if (mixins.equals(overlayedMixins)) {
+ // no net effect modifications at all -> merge child items defined
+ // by the mixins according to the general rule.
+ return true;
+ }
+
+ PropertyId mixinPropId = new PropertyId(state.getNodeId(), NameConstants.JCR_MIXINTYPES);
+
+ boolean mergeDone;
+ if (ctx.isAdded(mixinPropId)) {
+ // jcr:mixinTypes property was created for 'state'.
+ // changes is safe (without need to merge),
+ // - overlayed state doesn't have any mixins OR
+ // - overlayed state got the same (or a subset) added
+ // and non of the items defined by the new mixin(s) collides with
+ // existing items on the overlayed state
+ if (overlayedMixins.isEmpty() || mixins.containsAll(overlayedMixins)) {
+ mixins.removeAll(overlayedMixins);
+ mergeDone = !conflicts(state, mixins, ctx, true);
+ } else {
+ // different mixins added in overlayedState and state
+ // -> don't merge
+ mergeDone = false;
+ }
+ } else if (ctx.isDeleted(mixinPropId)) {
+ // jcr:mixinTypes property was removed in 'state'.
+ // we can't determine if there was any change to mixin types in the
+ // overlayed state.
+ // -> don't merge.
+ mergeDone = false;
+ } else if (ctx.isModified(mixinPropId)) {
+ /* jcr:mixinTypes property was modified in 'state'.
+ NOTE: if the mixins of the overlayed state was modified as well
+ the property (jcr:mixinTypes) cannot not be persisted (stale).
+
+ since there is not way to determine if the overlayed mixins have
+ been modified just check for conflicts related to a net mixin
+ addition.
+ */
+ if (mixins.containsAll(overlayedMixins)) {
+ // net result of modifications is only addition.
+ // -> so far the changes are save if there are no conflicts
+ // caused by mixins modification in 'state'.
+ // NOTE: the save may still fail if the mixin property has
+ // been modified in the overlayed state as well.
+ mixins.removeAll(overlayedMixins);
+ mergeDone = !conflicts(state, mixins, ctx, true);
+ } else {
+ // net result is either a removal in 'state' or modifications
+ // in both node states.
+ // -> don't merge.
+ mergeDone = false;
+ }
+ } else {
+ // jcr:mixinTypes property was added or modified in the overlayed
+ // state but neither added nor modified in 'state'.
+ if (overlayedMixins.containsAll(mixins)) {
+ // the modification in the overlayed state only includes the
+ // addition of mixin node types, but no removal.
+ // -> need to check if any added items from state would
+ // collide with the items defined by the new mixin on the
+ // overlayed state.
+ overlayedMixins.removeAll(mixins);
+ if (!conflicts(state, overlayedMixins, ctx, false)) {
+ // update the mixin names in 'state'. the child items defined
+ // by the new mixins will be added later on during merge of
+ // child nodes and properties.
+ state.setMixinTypeNames(overlayedMixins);
+ mergeDone = true;
+ } else {
+ mergeDone = false;
+ }
+ } else {
+ // either remove-mixin(s) or both add and removal of mixin in
+ // the overlayed state.
+ // -> we cannot merge easily
+ mergeDone = false;
+ }
+ }
+
+ return mergeDone;
+ }
+
+ /**
+ *
+ * @param state The state of the node to be saved.
+ * @param addedMixins The added mixins to be used for testing
+ * @param ctx
+ * @param compareToOverlayed
+ * @return true if a conflict can be determined, false otherwise.
+ */
+ private static boolean conflicts(NodeState state,
+ Set<Name> addedMixins,
+ MergeContext ctx, boolean compareToOverlayed) {
+ try {
+ // check for all added mixin types in one state if there are colliding
+ // child items in the other state.
+ // this is currently a simple check for named item definitions;
+ // if the mixin defines residual item definitions -> return false.
+ for (Name mixinName : addedMixins) {
+ EffectiveNodeType ent = ctx.getEffectiveNodeType(mixinName);
+
+ if (ent.getUnnamedItemDefs().length > 0) {
+ // the mixin defines residual child definitions -> cannot
+ // easily determine conflicts
+ return false;
+ }
+
+ NodeState overlayed = (NodeState) state.getOverlayedState();
+ for (ChildNodeEntry cne : state.getChildNodeEntries()) {
+ if (ent.getNamedNodeDefs(cne.getName()).length > 0) {
+ if (ctx.isAdded(cne.getId()) || isAutoCreated(cne, ent)) {
+ if (!compareToOverlayed || overlayed.hasChildNodeEntry(cne.getName())) {
+ return true;
+ }
+ } // else: neither added nor autocreated in 'state' .
+
+ } // else: child node not defined by the added mixin type
+ }
+
+ for (Name propName : state.getPropertyNames()) {
+ if (ent.getNamedPropDefs(propName).length > 0) {
+ PropertyId pid = new PropertyId(state.getNodeId(), propName);
+ if (ctx.isAdded(pid) || isAutoCreated(propName, ent)) {
+ if (!compareToOverlayed || overlayed.hasPropertyName(propName)) {
+ return true;
+ }
+ } // else: neither added nor autocreated in 'state'
+ } // else: property not defined by added mixin
+ }
+ }
+ } catch (NoSuchNodeTypeException e) {
+ // unable to determine collision
+ return true;
+ }
+
+ // no conflict detected
+ return false;
+ }
+
+ private static boolean isAutoCreated(ChildNodeEntry cne, EffectiveNodeType ent) {
+ for (QNodeDefinition def : ent.getAutoCreateNodeDefs()) {
+ if (def.getName().equals(cne.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isAutoCreated(Name propertyName, EffectiveNodeType ent) {
+ for (QPropertyDefinition def : ent.getAutoCreatePropDefs()) {
+ if (def.getName().equals(propertyName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
//-----------------------------------------------------< inner interfaces >
/**
@@ -199,5 +374,6 @@ class NodeStateMerger {
boolean isDeleted(ItemId id);
boolean isModified(ItemId id);
boolean allowsSameNameSiblings(NodeId id);
+ EffectiveNodeType getEffectiveNodeType(Name ntName) throws NoSuchNodeTypeException;
}
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=1027938&r1=1027937&r2=1027938&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Wed Oct 27 12:36:03 2010
@@ -17,6 +17,7 @@
package org.apache.jackrabbit.core.state;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -24,6 +25,7 @@ import javax.jcr.PropertyType;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeType;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
@@ -246,9 +248,9 @@ public class SharedItemStateManager
public ItemState getItemState(ItemId id)
throws NoSuchItemStateException, ItemStateException {
// check the virtual root ids (needed for overlay)
- for (int i = 0; i < virtualProviders.length; i++) {
- if (virtualProviders[i].isVirtualRoot(id)) {
- return virtualProviders[i].getItemState(id);
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ if (virtualProvider.isVirtualRoot(id)) {
+ return virtualProvider.getItemState(id);
}
}
@@ -266,9 +268,9 @@ public class SharedItemStateManager
}
// check if there is a virtual state for the specified item
- for (int i = 0; i < virtualProviders.length; i++) {
- if (virtualProviders[i].hasItemState(id)) {
- return virtualProviders[i].getItemState(id);
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ if (virtualProvider.hasItemState(id)) {
+ return virtualProvider.getItemState(id);
}
}
@@ -280,8 +282,8 @@ public class SharedItemStateManager
*/
public boolean hasItemState(ItemId id) {
// check the virtual root ids (needed for overlay)
- for (int i = 0; i < virtualProviders.length; i++) {
- if (virtualProviders[i].isVirtualRoot(id)) {
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ if (virtualProvider.isVirtualRoot(id)) {
return true;
}
}
@@ -307,8 +309,8 @@ public class SharedItemStateManager
}
// otherwise check virtual ones
- for (int i = 0; i < virtualProviders.length; i++) {
- if (virtualProviders[i].hasItemState(id)) {
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ if (virtualProvider.hasItemState(id)) {
return true;
}
}
@@ -334,9 +336,9 @@ public class SharedItemStateManager
}
// check virtual providers
- for (int i = 0; i < virtualProviders.length; i++) {
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
try {
- return virtualProviders[i].getNodeReferences(id);
+ return virtualProvider.getNodeReferences(id);
} catch (NoSuchItemStateException e) {
// ignore
}
@@ -370,8 +372,8 @@ public class SharedItemStateManager
}
// check virtual providers
- for (int i = 0; i < virtualProviders.length; i++) {
- if (virtualProviders[i].hasNodeReferences(id)) {
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ if (virtualProvider.hasNodeReferences(id)) {
return true;
}
}
@@ -449,8 +451,8 @@ public class SharedItemStateManager
*/
public void dispose() {
// remove virtual item state providers (see JCR-2023)
- for (int i = 0; i < virtualProviders.length; i++) {
- virtualProviders[i].removeListener(this);
+ for (VirtualItemStateProvider virtualProvider : virtualProviders) {
+ virtualProvider.removeListener(this);
}
virtualProviders = new VirtualItemStateProvider[0];
@@ -638,6 +640,10 @@ public class SharedItemStateManager
}
}
+ public EffectiveNodeType getEffectiveNodeType(Name ntName) throws NoSuchNodeTypeException {
+ return ntReg.getEffectiveNodeType(ntName);
+ }
+
protected NodeState getNodeState(NodeId id)
throws ItemStateException {
if (local.has(id)) {
@@ -1422,7 +1428,7 @@ public class SharedItemStateManager
private boolean isShareable(NodeState state) throws RepositoryException {
// shortcut: check some wellknown built-in types first
Name primary = state.getNodeTypeName();
- Set mixins = state.getMixinTypeNames();
+ Set<Name> mixins = state.getMixinTypeNames();
if (mixins.contains(NameConstants.MIX_SHAREABLE)) {
return true;
}
Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java?rev=1027938&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java Wed Oct 27 12:36:03 2010
@@ -0,0 +1,978 @@
+/*
+ * 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.core.state;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeIterator;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <code>NodeStateMergerTest</code>...
+ */
+public class NodeStateMergerTest extends AbstractJCRTest {
+
+ /** Name of the cnd nodetype file for import and namespace registration. */
+ private static final String TEST_NODETYPES = "org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd";
+
+ private List<String> testMixins = new ArrayList<String>();
+
+ private Node testNode;
+
+ private Session sessionB;
+ private Node testNodeB;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Reader cnd = new InputStreamReader(getClass().getClassLoader().getResourceAsStream(TEST_NODETYPES));
+ CndImporter.registerNodeTypes(cnd, superuser);
+ cnd.close();
+
+ NodeTypeIterator it = superuser.getWorkspace().getNodeTypeManager().getMixinNodeTypes();
+ while (it.hasNext()) {
+ NodeType nt = it.nextNodeType();
+ if (nt.getName().startsWith("test:")) {
+ testMixins.add(nt.getName());
+ }
+ }
+
+ testNode = testRootNode.addNode(nodeName1, "nt:unstructured");
+ superuser.save();
+
+ sessionB = getHelper().getSuperuserSession();
+ testNodeB = sessionB.getNode(testNode.getPath());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (sessionB != null) {
+ sessionB.logout();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Add the same property with both sessions but with different values.
+ *
+ * @throws RepositoryException
+ */
+ public void testAddSamePropertiesWithDifferentValues() throws RepositoryException {
+ assertFalse(testNode.hasProperty(propertyName2));
+ assertFalse(testNodeB.hasProperty(propertyName2));
+
+ testNode.setProperty(propertyName2, "value");
+
+ testNodeB.setProperty(propertyName2, "otherValue");
+ sessionB.save();
+
+ superuser.save();
+
+ assertEquals("value", testNode.getProperty(propertyName2).getString());
+ testNodeB.refresh(false);
+ assertEquals("value", testNodeB.getProperty(propertyName2).getString());
+ }
+
+ /**
+ * Modify the same property with both sessions but with different values.
+ *
+ * @throws RepositoryException
+ */
+ public void testModifySamePropertiesWithDifferentValues() throws RepositoryException {
+ testNode.setProperty(propertyName2, "test");
+ superuser.save();
+ testNodeB.refresh(false);
+
+ assertTrue(testNodeB.hasProperty(propertyName2));
+
+ try {
+ testNode.setProperty(propertyName2, "value");
+
+ testNodeB.setProperty(propertyName2, "otherValue");
+ sessionB.save();
+
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //------------------ Tests adding jcr:mixinType property in the session1 ---
+ /**
+ * Both node don't have any mixins assigned yet. Test if adding the same
+ * mixin node with both sessions works.
+ *
+ * @throws RepositoryException
+ */
+ public void testAddSameMixinToSessionsAB() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin(mixReferenceable);
+
+ testNodeB.addMixin(mixReferenceable);
+ sessionB.save();
+
+ superuser.save();
+ }
+
+ /**
+ * Same as {@link #testAddSameMixinToSessionsAB} but in addition
+ * adding non-conflicting properties defined by this mixin. The properties
+ * must be merged silently.
+ *
+ * @throws RepositoryException
+ */
+ public void testAddSameMixinToSessionsAB2() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_1");
+ testNode.setProperty("test:prop_double", 124);
+
+ testNodeB.addMixin("test:mixinProp_1");
+ testNodeB.setProperty("test:prop_string", "abc");
+ sessionB.save();
+
+ superuser.save();
+
+ assertEquals("abc", testNode.getProperty("test:prop_string").getString());
+ testNodeB.refresh(false);
+ assertEquals(124, testNodeB.getProperty("test:prop_double").getLong());
+ }
+
+ /**
+ * Same as {@link #testAddSameMixinToSessionsAB} but in addition
+ * a property defined by this mixin with different value. Value of overlayed
+ * property (from sessionB) must be merged into sessionA.
+ *
+ * @throws RepositoryException
+ */
+ public void testAddSameMixinToSessionsAB3() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_1");
+ testNode.setProperty("test:prop_double", 124);
+
+ testNodeB.addMixin("test:mixinProp_1");
+ testNodeB.setProperty("test:prop_double", 134);
+ sessionB.save();
+
+ superuser.save();
+
+ assertEquals(124, testNode.getProperty("test:prop_double").getLong());
+ testNodeB.refresh(false);
+ assertEquals(124, testNodeB.getProperty("test:prop_double").getLong());
+ }
+
+ /**
+ * Same as {@link #testAddSameMixinToSessionsAB3} having additional
+ * modifications in the overlayed state (modifications by session B).
+ *
+ * @throws RepositoryException
+ */
+ public void testAddSameMixinToSessionsAB4() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_1");
+ testNode.setProperty("test:prop_double", 124);
+
+ testNodeB.addMixin("test:mixinProp_1");
+ testNodeB.setProperty("test:prop_double", 134);
+ testNodeB.setProperty("more", "yes");
+ sessionB.save();
+
+ superuser.save();
+
+ assertEquals(124, testNode.getProperty("test:prop_double").getLong());
+ testNodeB.refresh(false);
+ assertEquals(124, testNodeB.getProperty("test:prop_double").getLong());
+ }
+
+ /**
+ * Test adding mixin(s) to sessionA while the overlayed
+ * (attached to testNode2, sessionB) doesn't have any mixins defined.
+ * The changes should be merged as there is no conflict.
+ *
+ * @throws RepositoryException
+ */
+ public void testMixinAddedInSessionA() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ for (String mixin : testMixins) {
+ testNode.addMixin(mixin);
+ }
+
+ testNodeB.addNode(nodeName1, "nt:unstructured");
+ testNodeB.setProperty(propertyName1, "anyValue");
+ sessionB.save();
+
+ superuser.save();
+
+ assertTrue(testNode.hasNode(nodeName1));
+ assertTrue(testNode.hasProperty(propertyName1));
+
+ testNodeB.refresh(false);
+ for (String mixin : testMixins) {
+ assertTrue(testNodeB.isNodeType(mixin));
+ }
+ }
+
+ /**
+ * Test adding mixin(s) to sessionA, while the other sessionB
+ * doesn't have any mixins defined. The changes should be merged as there
+ * is no conflict.
+ *
+ * @throws RepositoryException
+ */
+ public void testMixinAddedInSessionA2() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.setProperty("test:prop_double", sessionB.getValueFactory().createValue(false));
+ sessionB.save();
+
+ superuser.save();
+ assertTrue(testNode.isNodeType("test:mixinProp_1"));
+ assertEquals(PropertyType.BOOLEAN, testNode.getProperty("test:prop_double").getType());
+
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.isNodeType("test:mixinProp_1"));
+ assertEquals(PropertyType.BOOLEAN, testNodeB.getProperty("test:prop_double").getType());
+ assertEquals(testNode.getProperty("test:prop_double").getString(), testNodeB.getProperty("test:prop_double").getString());
+ assertEquals("nt:unstructured", testNode.getProperty("test:prop_double").getDefinition().getDeclaringNodeType().getName());
+ }
+
+ /**
+ * Test adding mixin(s) to sessionA while sessionB doesn't add new mixins
+ * but has new child items that collide with the items defined by the new mixin.
+ * The merge should in this case fail.
+ *
+ * @throws RepositoryException
+ */
+ public void testAddedInSessionAConflictsWithChildItemsInSessionB() throws RepositoryException {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_5"); // has an autocreated property
+
+ testNodeB.setProperty("test:prop_long_p", "conflict");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ assertFalse(testNodeB.isNodeType("test:mixinProp_5"));
+ testNodeB.refresh(false);
+ assertEquals("conflict", testNodeB.getProperty("test:prop_long_p").getString());
+ }
+ }
+
+ /**
+ * Similar to {@link #testAddedInSessionAConflictsWithChildItemsInSessionB}
+ * but adding mix:referenceable in the SessionA while SessionB
+ * gets a property jcr:uuid (with different type).
+ *
+ * @throws RepositoryException
+ */
+// TODO: uncomment once JCR-2779 is fixed
+// public void testAddedReferenceableSessionAConflictsWithPropInSessionB() throws RepositoryException {
+// assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+//
+// testNode.addMixin(mixReferenceable);
+//
+// Property p = testNode2.setProperty("jcr:uuid", session2.getValueFactory().createValue(false));
+// assertTrue(testNode2.hasProperty("jcr:uuid"));
+// assertTrue(p.isNew());
+// session2.save();
+//
+// assertTrue(testNode2.hasProperty("jcr:uuid"));
+// assertTrue(p.isNew());
+// try {
+// superuser.save();
+// fail();
+// } catch (InvalidItemStateException e) {
+// assertTrue(testNode.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.STRING, testNode.getProperty("jcr:uuid").getType());
+// }
+// }
+
+ /**
+ * Same as {@link #testAddedInSessionAConflictsWithChildItemsInSessionB}
+ * but in addition the overlayed state gets an addition child node.
+ *
+ * @throws RepositoryException
+ */
+// TODO: uncomment once JCR-2779 is fixed
+// public void testAddedReferenceableSessionAConflictsWithPropInSessionB2() throws RepositoryException {
+// assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+//
+// testNode.addMixin(mixReferenceable);
+//
+// testNode2.setProperty("jcr:uuid", session2.getValueFactory().createValue(false));
+// testNode2.addNode("test");
+// session2.save();
+//
+// assertTrue(testNode2.hasProperty("jcr:uuid"));
+// assertTrue(testNode2.getProperty("jcr:uuid").isNew());
+// try {
+// superuser.save();
+// fail();
+// } catch (InvalidItemStateException e) {
+// assertTrue(testNode.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.STRING, testNode.getProperty("jcr:uuid").getType());
+// }
+// }
+
+ /**
+ * Adding different node types in the SessionA and SessionB:
+ * Not handled and thus the merge must fail.
+ *
+ * @throws RepositoryException
+ */
+ public void testDifferentMixinAddedInSessionAB() throws Exception {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.addMixin("test:mixinProp_3");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail("Different mixin types added both in SessionA and SessionB. InvalidItemStateException expected.");
+ } catch (InvalidItemStateException e) {
+ // expected
+ assertFalse(testNode.isNodeType("test:mixinProp_3"));
+ }
+ }
+
+ //---------------- Tests removing jcr:mixinType property in the SessionA ---
+ /**
+ * Remove the jcr:mixinType property by removing all mixin types. Merge
+ * to changes made in the overlayed state should fail.
+ * @throws Exception
+ */
+ public void testMixinRemovedInSessionA() throws Exception {
+ for (int i = 1; i<=5; i++) {
+ testNode.addMixin("test:mixinProp_" + i);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+
+ // remove all mixin types
+ for (NodeType mixin : testNode.getMixinNodeTypes()) {
+ testNode.removeMixin(mixin.getName());
+ }
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNodeB.addNode(nodeName1, "nt:unstructured");
+ testNodeB.setProperty(propertyName1, "anyValue");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinRemovedInSessionA} with different mixin types.
+ * @throws Exception
+ */
+ public void testMixinRemovedInSessionA2() throws Exception {
+ for (int i = 1; i<=4; i++) {
+ testNode.addMixin("test:mixinNode_" + i);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+
+ // remove all mixin types
+ for (NodeType mixin : testNode.getMixinNodeTypes()) {
+ testNode.removeMixin(mixin.getName());
+ }
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNodeB.addNode(nodeName1, "nt:unstructured");
+ testNodeB.setProperty(propertyName1, "anyValue");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //--------------- Tests modifying jcr:mixinType property in the SessionA ---
+ /**
+ * Modify the existing mixin property in the SessionA change without adding
+ * conflicting modifications to SessionB.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedInSessionA() throws Exception {
+ testNode.addMixin("test:mixinProp_5");
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modify the mixin types
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.addNode(nodeName1, "nt:unstructured");
+ testNodeB.setProperty(propertyName1, "anyValue");
+ sessionB.save();
+
+ superuser.save();
+
+ assertTrue(testNode.hasProperty(propertyName1));
+ assertTrue(testNode.hasNode(nodeName1));
+
+ assertTrue(testNodeB.isNodeType("test:mixinProp_1"));
+ assertTrue(Arrays.asList(testNodeB.getMixinNodeTypes()).contains(sessionB.getWorkspace().getNodeTypeManager().getNodeType("test:mixinProp_1")));
+ }
+
+ public void testMixinModifiedInSessionAB() throws Exception {
+ testNode.addMixin("test:mixinProp_5");
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modify the mixin types
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.addMixin("test:mixinProp_1");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ public void testMixinModifiedInSessionAB2() throws Exception {
+ testNode.addMixin("test:mixinProp_5");
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modify the mixin types
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.removeMixin("test:mixinProp_5");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ assertTrue(testNode.hasProperty("jcr:mixinTypes"));
+ assertTrue(testNode.isNodeType("test:mixinProp_1"));
+ assertTrue(testNode.isNodeType("test:mixinProp_5"));
+ assertEquals(2, testNode.getMixinNodeTypes().length);
+ }
+ }
+
+ public void testMixinModifiedInSessionAB3() throws Exception {
+ testNode.addMixin("test:mixinProp_5");
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modify the mixin types
+ testNode.addMixin("test:mixinProp_1");
+ testNode.setProperty(propertyName1, "value");
+
+ testNodeB.removeMixin("test:mixinProp_5");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ assertTrue(testNode.hasProperty("jcr:mixinTypes"));
+ assertTrue(testNode.isNodeType("test:mixinProp_1"));
+ assertTrue(testNode.isNodeType("test:mixinProp_5"));
+ assertEquals(2, testNode.getMixinNodeTypes().length);
+ }
+ }
+
+ public void testMixinModifiedInSessionAB4() throws Exception {
+ testNode.addMixin("test:mixinProp_5");
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modify the mixin types
+ testNode.addMixin("test:mixinProp_1");
+
+ testNodeB.addMixin("test:mixinProp_2");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //-------------------------- Tests adding jcr:mixinType only in SessionB ---
+ /**
+ * No jcr:mixinTypes property present in the SessionA but was added
+ * to the overlayed state while other changes were made to the SessionA.
+ * @throws Exception
+ */
+ public void testMixinAddedInSessionB() throws Exception {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addNode(nodeName2);
+
+ testNodeB.addMixin("test:mixinProp_1");
+ sessionB.save();
+
+ superuser.save();
+
+ assertTrue(testNode.isNodeType("test:mixinProp_1"));
+ assertTrue(testNode.hasProperty("jcr:mixinTypes"));
+
+ assertTrue(testNodeB.hasNode(nodeName2));
+ }
+
+ /**
+ * Same as {@link #testMixinAddedInSessionB} but having 2 SessionA modifications.
+ * @throws Exception
+ */
+ public void testMixinAddedInSessionB2() throws Exception {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addNode(nodeName2);
+ testNode.setProperty(propertyName1, "value");
+
+ testNodeB.addMixin("test:mixinProp_1");
+ sessionB.save();
+
+ superuser.save();
+
+ assertTrue(testNode.isNodeType("test:mixinProp_1"));
+ assertTrue(testNode.hasProperty("jcr:mixinTypes"));
+
+ assertTrue(testNodeB.hasNode(nodeName2));
+ }
+
+ /**
+ * Add the mixin property in SessionB by adding a single mixin
+ * and create a conflicting item in the SessionA -> merge must fail.
+ * @throws Exception
+ */
+ public void testMixinAddedInSessionBWithConflictingChanges() throws Exception {
+ assertFalse(testNode.hasProperty("jcr:mixinTypes"));
+
+ testNode.addNode(nodeName2);
+ testNode.setProperty("test:prop_long_p", "value");
+
+ testNodeB.addMixin("test:mixinProp_5");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //----------------------- Test jcr:mixinTypes removed in SessionB ----------
+ /**
+ * Test if removing the (existing) jcr:mixinTypes property in the overlayed
+ * state (by removing all mixins) is not merged to SessionA changes.
+ * @throws Exception
+ */
+ public void testMixinRemovedInSessionB() throws Exception {
+ for (int i = 1; i<=5; i++) {
+ testNode.addMixin("test:mixinProp_" + i);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+
+ testNode.addNode(nodeName1, "nt:unstructured");
+ testNode.setProperty(propertyName1, "anyValue");
+
+ // remove all mixin types
+ for (NodeType mixin : testNodeB.getMixinNodeTypes()) {
+ testNodeB.removeMixin(mixin.getName());
+ }
+ assertFalse(testNodeB.hasProperty("jcr:mixinTypes"));
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //----------------------- Test mixin modification (add-only) in SessionB ---
+ /**
+ * Existing mixins are modified in SessionB but not in the
+ * SessionA, where the net effect is 'add-only' in the SessionA.
+ * Changes should be merge if there are no conflicting SessionA
+ * modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionB() throws Exception {
+ for (int i = 1; i<=5; i++) {
+ testNode.addMixin("test:mixinProp_" + i);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // non-conflicting modification in SessionA
+ testNode.setProperty(propertyName1, "value");
+
+ testNodeB.addMixin("test:mixinNode_1");
+ sessionB.save();
+
+ superuser.save();
+
+ assertTrue(testNode.isNodeType("test:mixinNode_1"));
+ List<NodeType> mx = Arrays.asList(testNode.getMixinNodeTypes());
+ assertTrue(mx.contains(superuser.getWorkspace().getNodeTypeManager().getNodeType("test:mixinNode_1")));
+
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty(propertyName1));
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionB} with different
+ * SessionA modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionB2() throws Exception {
+ for (int i = 1; i<=5; i++) {
+ testNode.addMixin("test:mixinProp_" + i);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // non-conflicting modification in SessionA
+ testNode.setProperty(propertyName1, "value");
+ testNode.addNode(nodeName2);
+
+ testNodeB.addMixin("test:mixinNode_1");
+ sessionB.save();
+
+ superuser.save();
+ assertTrue(testNode.isNodeType("test:mixinNode_1"));
+ List<NodeType> mx = Arrays.asList(testNode.getMixinNodeTypes());
+ assertTrue(mx.contains(superuser.getWorkspace().getNodeTypeManager().getNodeType("test:mixinNode_1")));
+
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty(propertyName1));
+ assertTrue(testNodeB.hasNode(nodeName2));
+ }
+
+ /**
+ * Test if the merge of add-only mixin modification in the overlayed stated
+ * is aborted if there are conflicting SessionA changes present.
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionBWithConflictingChanges() throws Exception {
+ testNode.addMixin(mixReferenceable);
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // conflicting modification in SessionA
+ testNode.setProperty(propertyName1, "value");
+ testNode.addNode("test:child_1");
+
+ testNodeB.addMixin("test:mixinNode_1");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionBWithConflictingChanges}
+ * with different SessionA modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionBWithConflictingChanges2() throws Exception {
+ testNode.addMixin(mixReferenceable);
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // conflicting modification in SessionA
+ testNode.addNode("test:child_1");
+
+ testNodeB.addMixin("test:mixinNode_1");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionBWithConflictingChanges}
+ * with different mixin and SessionA modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionBWithConflictingChanges3() throws Exception {
+ testNode.addMixin(mixReferenceable);
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // conflicting modification in SessionA
+ testNode.setProperty("test:prop_long_p", "non-long-value");
+
+ testNodeB.addMixin("test:mixinProp_5");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionBWithConflictingChanges}
+ * with different mixin and other kind of modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedAddInSessionBWithConflictingChanges4() throws Exception {
+ testNode.addMixin(mixReferenceable);
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // conflicting modification in session1
+ testNode.setProperty("test:prop_long_p", "non-long-value");
+ testNode.addNode("test:child_1");
+
+ testNodeB.addMixin("test:mixinProp_5");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionBWithConflictingChanges}
+ * but using mix:referencable as mixin to force the modification in the
+ * overlayed state.
+ *
+ * @throws Exception
+ */
+// TODO: uncomment once JCR-2779 is fixed
+// public void testMixinModifiedReferenceableInSessionBConflicting() throws RepositoryException {
+// testNode.addMixin("test:mixinProp_1");
+// superuser.save();
+// assertTrue(testNode2.hasProperty("jcr:mixinTypes"));
+//
+// testNode.setProperty("jcr:uuid", superuser.getValueFactory().createValue(false));
+//
+// testNode2.addMixin(mixReferenceable);
+// session2.save();
+//
+// assertTrue(testNode.hasProperty("jcr:uuid"));
+// assertTrue(testNode.getProperty("jcr:uuid").isNew());
+// try {
+// superuser.save();
+// fail();
+// } catch (InvalidItemStateException e) {
+// assertFalse(testNode.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.BOOLEAN, testNode.getProperty("jcr:uuid").getType());
+//
+// assertTrue(testNode2.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.STRING, testNode2.getProperty("jcr:uuid").getType());
+// }
+// }
+
+ /**
+ * Same as {@link #testMixinModifiedAddInSessionBWithConflictingChanges}
+ * but using mix:referencable as mixin to force the modification in the
+ * overlayed state.
+ *
+ * @throws Exception
+ */
+// TODO: uncomment once JCR-2779 is fixed
+// public void testMixinModifiedReferenceableInSessionBConflicting2() throws RepositoryException {
+// testNode.addMixin("test:mixinProp_1");
+// superuser.save();
+// assertTrue(testNode2.hasProperty("jcr:mixinTypes"));
+//
+// testNode.setProperty("jcr:uuid", superuser.getValueFactory().createValue(false));
+// testNode.addNode(nodeName2);
+//
+// testNode2.addMixin(mixReferenceable);
+// session2.save();
+//
+// assertTrue(testNode.hasProperty("jcr:uuid"));
+// assertTrue(testNode.getProperty("jcr:uuid").isNew());
+// try {
+// superuser.save();
+// fail();
+// } catch (InvalidItemStateException e) {
+// assertFalse(testNode.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.BOOLEAN, testNode.getProperty("jcr:uuid").getType());
+//
+// assertTrue(testNode2.isNodeType(mixReferenceable));
+// assertEquals(PropertyType.STRING, testNode2.getProperty("jcr:uuid").getType());
+// }
+// }
+
+ //-------------------- Test mixin modification (remove-only) in SessionB ---
+ /**
+ * Test if merge fails if some mixin removal occurred in the SessionB.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedRemovedInSessionB() throws Exception {
+ for (String mixin : testMixins) {
+ testNode.addMixin(mixin);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+
+ // modification in session1
+ testNode.setProperty(propertyName1, "value");
+
+ // mixin-removal in the session2
+ testNodeB.removeMixin("test:mixinProp_1");
+ testNodeB.removeMixin("test:mixinProp_2");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Same as {@link #testMixinModifiedRemovedInSessionB} but with different
+ * SessionA modifications.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedRemovedInSessionB2() throws Exception {
+ for (String mixin : testMixins) {
+ testNode.addMixin(mixin);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+
+ // modification in SessionA
+ testNode.setProperty(propertyName1, "value");
+ testNode.addNode(nodeName2);
+
+ // mixin-removal in SessionB
+ testNodeB.removeMixin("test:mixinProp_1");
+ testNodeB.removeMixin("test:mixinProp_2");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+
+ //---------------------------- Test other mixin modification in SessionB ---
+ /**
+ * Test if merge fails if the mixins of the overlayed state (sessionB) were
+ * modified in a combination of add and removal of mixin.
+ *
+ * @throws Exception
+ */
+ public void testMixinModifiedInSessionB() throws Exception {
+ for (String mixin : testMixins) {
+ testNode.addMixin(mixin);
+ }
+ superuser.save();
+ testNodeB.refresh(false);
+ assertTrue(testNodeB.hasProperty("jcr:mixinTypes"));
+
+ // modification in SessionA
+ testNode.setProperty(propertyName1, "value");
+
+ // mixin-removal in the SessionB
+ testNodeB.addMixin(mixReferenceable);
+ testNodeB.removeMixin("test:mixinProp_2");
+ sessionB.save();
+
+ try {
+ superuser.save();
+ fail();
+ } catch (InvalidItemStateException e) {
+ // expected
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/NodeStateMergerTest.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision Rev URL
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/TestAll.java?rev=1027938&r1=1027937&r2=1027938&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/TestAll.java Wed Oct 27 12:36:03 2010
@@ -40,6 +40,7 @@ public class TestAll extends TestCase {
suite.addTestSuite(DefaultISMLockingDeadlockTest.class);
suite.addTestSuite(FineGrainedISMLockingTest.class);
suite.addTestSuite(NameSetTest.class);
+ suite.addTestSuite(NodeStateMergerTest.class);
return suite;
}
Added: jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd?rev=1027938&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd Wed Oct 27 12:36:03 2010
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+<test = "http://www.apache.org/jackrabbit/test">
+
+[test:mixinProp_1] mixin
+ - test:prop_double (double)
+ - test:prop_string (string)
+
+[test:mixinProp_2] mixin
+ - test:prop_name_p (name) protected
+
+[test:mixinProp_3] mixin
+ - test:prop_boolean_p (boolean) = 'true' protected autocreated
+
+[test:mixinProp_4] mixin
+ - * (string)
+
+[test:mixinProp_5] mixin
+ - test:prop_long_p (long) = '123' autocreated
+
+
+
+[test:mixinNode_1] mixin
+ + test:child_1 (nt:unstructured) = nt:unstructured
+
+[test:mixinNode_2] mixin
+ + test:child_2 (nt:unstructured) = nt:unstructured protected
+
+[test:mixinNode_3] mixin
+ + test:child_3 (nt:unstructured) = nt:unstructured protected autocreated
+
+[test:mixinNode_4] mixin
+ + * (nt:unstructured) = nt:unstructured
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_nodestatemerger_nodetypes.cnd
------------------------------------------------------------------------------
svn:eol-style = native