Author: angela
Date: Wed Apr 1 10:36:00 2015
New Revision: 1670603
URL: http://svn.apache.org/r1670603
Log:
OAK-2246 : UUID collision check is not does not work in transient space
Modified:
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImporterImpl.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/xml/ImportTest.java
Modified:
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImporterImpl.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImporterImpl.java?rev=1670603&r1=1670602&r2=1670603&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImporterImpl.java
(original)
+++
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImporterImpl.java
Wed Apr 1 10:36:00 2015
@@ -47,6 +47,7 @@ import com.google.common.base.Predicates
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
@@ -82,26 +83,10 @@ public class ImporterImpl implements Imp
private final String userID;
private final AccessManager accessManager;
- /**
- * There are two IdentifierManagers used.
- *
- * 1) currentStateIdManager - Associated with current root on which all
import
- * operations are being performed
- *
- * 2) baseStateIdManager - Associated with the initial root on which
- * no modifications are performed
- */
- private final IdentifierManager currentStateIdManager;
- private final IdentifierManager baseStateIdManager;
-
private final EffectiveNodeTypeProvider effectiveNodeTypeProvider;
private final DefinitionProvider definitionProvider;
- /**
- * Set of newly created uuid from nodes which are
- * created in this import
- */
- private final Set<String> uuids = new HashSet<String>();
+ private final IdResolver idLookup;
private final Stack<Tree> parents;
@@ -171,8 +156,7 @@ public class ImporterImpl implements Imp
accessManager = sessionContext.getAccessManager();
- currentStateIdManager = new IdentifierManager(root);
- baseStateIdManager = new
IdentifierManager(sd.getContentSession().getLatestRoot());
+ idLookup = new IdResolver(root, sd.getContentSession());
refTracker = new ReferenceChangeTracker();
@@ -465,24 +449,7 @@ public class ImporterImpl implements Imp
}
} else {
- //1. First check from base state that tree corresponding to
- //this id exist
- Tree conflicting = baseStateIdManager.getTree(id);
-
- if (conflicting == null) {
- //1.a. Check if id is found in newly created nodes
- if (uuids.contains(id)) {
- conflicting = currentStateIdManager.getTree(id);
- }
- } else {
- //1.b Re obtain the conflicting tree from Id Manager
- //associated with current root. Such that any operation
- //on it gets reflected in later operations
- //In case a tree with same id was removed earlier then it
- //would return null
- conflicting = currentStateIdManager.getTree(id);
- }
-
+ Tree conflicting = idLookup.getConflictingTree(id);
if (conflicting != null && conflicting.exists()) {
// resolve uuid conflict
tree = resolveUUIDConflict(parent, conflicting, id,
nodeInfo);
@@ -522,22 +489,7 @@ public class ImporterImpl implements Imp
}
}
- collectUUIDs(parent);
- }
-
- private void collectUUIDs(Tree tree) {
- if (tree == null) {
- return;
- }
-
- String uuid = TreeUtil.getString(tree, JcrConstants.JCR_UUID);
- if (uuid != null) {
- uuids.add(uuid);
- }
-
- for (Tree child : tree.getChildren()) {
- collectUUIDs(child);
- }
+ idLookup.rememberImportedUUIDs(parent);
}
@Override
@@ -621,4 +573,78 @@ public class ImporterImpl implements Imp
tree.setProperty(prop);
}
}
+
+ /**
+ * Resolves 'uuid' property values to {@code Tree} objects and optionally
+ * keeps track of newly imported UUIDs.
+ */
+ private static final class IdResolver {
+ /**
+ * There are two IdentifierManagers used.
+ *
+ * 1) currentStateIdManager - Associated with current root on which
all import
+ * operations are being performed
+ *
+ * 2) baseStateIdManager - Associated with the initial root on which
+ * no modifications are performed
+ */
+ private final IdentifierManager currentStateIdManager;
+ private final IdentifierManager baseStateIdManager;
+
+ /**
+ * Set of newly created uuid from nodes which are
+ * created in this import, which are only remembered if the editing
+ * session doesn't have any pending transient changes preventing this
+ * performance optimisation from working properly (see OAK-2246).
+ */
+ private final Set<String> importedUUIDs;
+
+ private IdResolver(@Nonnull Root root, @Nonnull ContentSession
contentSession) {
+ currentStateIdManager = new IdentifierManager(root);
+ baseStateIdManager = new
IdentifierManager(contentSession.getLatestRoot());
+
+ if (!root.hasPendingChanges()) {
+ importedUUIDs = new HashSet<String>();
+ } else {
+ importedUUIDs = null;
+ }
+ }
+
+
+ @CheckForNull
+ private Tree getConflictingTree(@Nonnull String id) {
+ //1. First check from base state that tree corresponding to
+ //this id exist
+ Tree conflicting = baseStateIdManager.getTree(id);
+ if (conflicting == null && importedUUIDs != null) {
+ //1.a. Check if id is found in newly created nodes
+ if (importedUUIDs.contains(id)) {
+ conflicting = currentStateIdManager.getTree(id);
+ }
+ } else {
+ //1.b Re obtain the conflicting tree from Id Manager
+ //associated with current root. Such that any operation
+ //on it gets reflected in later operations
+ //In case a tree with same id was removed earlier then it
+ //would return null
+ conflicting = currentStateIdManager.getTree(id);
+ }
+ return conflicting;
+ }
+
+ private void rememberImportedUUIDs(@CheckForNull Tree tree) {
+ if (tree == null || importedUUIDs == null) {
+ return;
+ }
+
+ String uuid = TreeUtil.getString(tree, JcrConstants.JCR_UUID);
+ if (uuid != null) {
+ importedUUIDs.add(uuid);
+ }
+
+ for (Tree child : tree.getChildren()) {
+ rememberImportedUUIDs(child);
+ }
+ }
+ }
}
Modified:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/xml/ImportTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/xml/ImportTest.java?rev=1670603&r1=1670602&r2=1670603&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/xml/ImportTest.java
(original)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/xml/ImportTest.java
Wed Apr 1 10:36:00 2015
@@ -18,45 +18,212 @@ package org.apache.jackrabbit.oak.jcr.xm
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
import org.apache.jackrabbit.test.AbstractJCRTest;
public class ImportTest extends AbstractJCRTest {
- public void testReplaceUUID() throws Exception {
+ private String uuid;
+ private String path;
+ private String siblingPath;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
Node node = testRootNode.addNode(nodeName1);
node.addMixin(mixReferenceable);
- superuser.save();
+ Node sibling = testRootNode.addNode(nodeName2);
+
+ uuid = node.getIdentifier();
+ path = node.getPath();
+ siblingPath = sibling.getPath();
+ }
+
+ private InputStream getImportStream() throws RepositoryException,
IOException {
OutputStream out = new ByteArrayOutputStream();
- superuser.exportSystemView(node.getPath(), out, true, false);
+ superuser.exportSystemView(path, out, true, false);
+ return new ByteArrayInputStream(out.toString().getBytes());
+ }
+
+ public void testReplaceUUID() throws Exception {
+ superuser.save();
+
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ superuser.save();
+
+ // original node must have been replaced (but no child node added)
+ assertTrue(testRootNode.hasNode(nodeName1));
+ Node n2 = testRootNode.getNode(nodeName1);
+ assertTrue(n2.isNodeType(mixReferenceable));
+ assertEquals(uuid, n2.getIdentifier());
+
+ Node sibling = superuser.getNode(siblingPath);
+ assertFalse(sibling.hasNode(nodeName1));
+ }
+
+ /**
+ * @see <a
href="https://issues.apache.org/jira/browse/OAK-2246">OAK-2246</a>
+ */
+ public void testTransientReplaceUUID() throws Exception {
+ superuser.importXML(path, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ superuser.save();
- superuser.importXML(node.getPath(), new
ByteArrayInputStream(out.toString().getBytes()),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ // original node must have been replaced (but no child node added)
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
superuser.save();
+ // original node must have been replaced (but no child node added)
assertTrue(testRootNode.hasNode(nodeName1));
Node n2 = testRootNode.getNode(nodeName1);
assertTrue(n2.isNodeType(mixReferenceable));
+ assertEquals(uuid, n2.getIdentifier());
+
+ Node sibling = superuser.getNode(siblingPath);
+ assertFalse(sibling.hasNode(nodeName1));
+ }
+
+ public void testReplaceUUIDSameTree() throws Exception {
+ superuser.save();
+
+ superuser.importXML(path, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ superuser.save();
+
+ // original node must have been replaced (but no child node added)
+ assertTrue(testRootNode.hasNode(nodeName1));
+ Node n2 = testRootNode.getNode(nodeName1);
+ assertTrue(n2.isNodeType(mixReferenceable));
+ assertEquals(uuid, n2.getIdentifier());
assertFalse(n2.hasNode(nodeName1));
}
-// TODO : FIXME (OAK-2246)
-// public void testTransientReplaceUUID() throws Exception {
-// Node node = testRootNode.addNode(nodeName1);
-// node.addMixin(mixReferenceable);
-//
-// OutputStream out = new ByteArrayOutputStream();
-// superuser.exportSystemView(node.getPath(), out, true, false);
-//
-// superuser.importXML(node.getPath(), new
ByteArrayInputStream(out.toString().getBytes()),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-// superuser.save();
-//
-// assertTrue(testRootNode.hasNode(nodeName1));
-// Node n2 = testRootNode.getNode(nodeName1);
-// assertTrue(n2.isNodeType(mixReferenceable));
-// assertFalse(n2.hasNode(nodeName1));
-// }
+ /**
+ * @see <a
href="https://issues.apache.org/jira/browse/OAK-2246">OAK-2246</a>
+ */
+ public void testTransientReplaceUUIDSameTree() throws Exception {
+ superuser.importXML(path, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ superuser.save();
+
+ // original node must have been replaced (but no child node added)
+ assertTrue(testRootNode.hasNode(nodeName1));
+ Node n2 = testRootNode.getNode(nodeName1);
+ assertTrue(n2.isNodeType(mixReferenceable));
+ assertEquals(uuid, n2.getIdentifier());
+ assertFalse(n2.hasNode(nodeName1));
+ }
+
+ public void testRemoveUUID() throws Exception {
+ superuser.save();
+
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+ superuser.save();
+
+ // original node must have been removed
+ assertFalse(testRootNode.hasNode(nodeName1));
+
+ Node sibling = superuser.getNode(siblingPath);
+ assertTrue(sibling.hasNode(nodeName1));
+
+ Node imported = sibling.getNode(nodeName1);
+ assertTrue(imported.isNodeType(mixReferenceable));
+ assertEquals(uuid, imported.getIdentifier());
+ }
+
+ public void testTransientRemoveUUID() throws Exception {
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+ superuser.save();
+
+ // original node must have been removed
+ assertFalse(testRootNode.hasNode(nodeName1));
+
+ Node sibling = superuser.getNode(siblingPath);
+ assertTrue(sibling.hasNode(nodeName1));
+
+ Node imported = sibling.getNode(nodeName1);
+ assertTrue(imported.isNodeType(mixReferenceable));
+ assertEquals(uuid, imported.getIdentifier());
+ }
+
+ public void testRemoveUUIDSameTree() throws Exception {
+ superuser.save();
+
+ try {
+ superuser.importXML(path, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+ fail("ConstraintViolationException expected");
+ } catch (ConstraintViolationException e) {
+ // success
+ }
+ }
+
+ public void testTransientRemoveUUIDSameTree() throws Exception {
+ try {
+ superuser.importXML(path, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+ fail("ConstraintViolationException expected");
+ } catch (ConstraintViolationException e) {
+ // success
+ }
+ }
+
+ public void testCreateNewUUID() throws Exception {
+ superuser.save();
+
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+ superuser.save();
+
+ // original node must still exist
+ assertTrue(testRootNode.hasNode(nodeName1));
+
+ // verify the import produced the expected new node
+ Node sibling = superuser.getNode(siblingPath);
+ assertTrue(sibling.hasNode(nodeName1));
+
+ Node imported = sibling.getNode(nodeName1);
+ assertTrue(imported.isNodeType(mixReferenceable));
+ assertFalse(uuid.equals(imported.getIdentifier()));
+ }
+
+ public void testTransientCreateNewUUID() throws Exception {
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+ superuser.save();
+
+ // original node must still exist
+ assertTrue(testRootNode.hasNode(nodeName1));
+
+ // verify the import produced the expected new node
+ Node sibling = superuser.getNode(siblingPath);
+ assertTrue(sibling.hasNode(nodeName1));
+
+ Node imported = sibling.getNode(nodeName1);
+ assertTrue(imported.isNodeType(mixReferenceable));
+ assertFalse(uuid.equals(imported.getIdentifier()));
+ }
+
+ public void testThrow() throws Exception {
+ superuser.save();
+
+ try {
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+ fail("ItemExistsException expected");
+ } catch (ItemExistsException e) {
+ // success
+ }
+ }
+
+ public void testTransientThrow() throws Exception {
+ try {
+ superuser.importXML(siblingPath, getImportStream(),
ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+ fail("ItemExistsException expected");
+ } catch (ItemExistsException e) {
+ // success
+ }
+ }
}
\ No newline at end of file