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 al...@apache.org on 2013/11/08 17:21:12 UTC

svn commit: r1540107 - in /jackrabbit/oak/trunk: oak-core/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ oak-jcr/ oak-jcr/src/main/java/org/apache/jackrabb...

Author: alexparvulescu
Date: Fri Nov  8 16:21:12 2013
New Revision: 1540107

URL: http://svn.apache.org/r1540107
Log:
OAK-685 Enforce referential integrity for referenceable nodes
 - initial patch


Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/pom.xml
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java
    jackrabbit/oak/trunk/oak-jcr/pom.xml
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java

Modified: jackrabbit/oak/trunk/oak-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/pom.xml?rev=1540107&r1=1540106&r2=1540107&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-core/pom.xml Fri Nov  8 16:21:12 2013
@@ -54,6 +54,7 @@
               org.apache.jackrabbit.oak.plugins.index.aggregate,
               org.apache.jackrabbit.oak.plugins.index.nodetype,
               org.apache.jackrabbit.oak.plugins.index.property,
+              org.apache.jackrabbit.oak.plugins.index.reference,
               org.apache.jackrabbit.oak.plugins.memory,
               org.apache.jackrabbit.oak.plugins.name,
               org.apache.jackrabbit.oak.plugins.nodetype,

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java?rev=1540107&r1=1540106&r2=1540107&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java Fri Nov  8 16:21:12 2013
@@ -255,7 +255,7 @@ public class IdentifierManager {
     }
 
     @CheckForNull
-    private String resolveUUID(String uuid) {
+    public String resolveUUID(String uuid) {
         return resolveUUID(StringPropertyState.stringProperty("", uuid));
     }
 

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java?rev=1540107&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java Fri Nov  8 16:21:12 2013
@@ -0,0 +1,374 @@
+/*
+ * 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.index.reference;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.oak.api.Type.REFERENCE;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static org.apache.jackrabbit.oak.api.Type.WEAKREFERENCE;
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
+import static org.apache.jackrabbit.oak.api.CommitFailedException.INTEGRITY;
+import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.core.ImmutableRoot;
+import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
+import org.apache.jackrabbit.oak.plugins.version.VersionConstants;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Index editor for keeping a references to a node up to date.
+ * 
+ */
+class ReferenceEditor implements Editor {
+
+    // TODO
+    // - look into using a storage strategy (trees) -> OAK-1134
+    // - what happens when you move a node? who updates the backlinks?
+    // - clearer error messages
+
+    private final static String REF_NAME = ":references";
+    private final static String WEAK_REF_NAME = ":weakreferences";
+
+    /** Parent editor, or {@code null} if this is the root editor. */
+    private final ReferenceEditor parent;
+
+    /** Name of this node, or {@code null} for the root node. */
+    private final String name;
+
+    /** Path of this editor, built lazily in {@link #getPath()}. */
+    private String path;
+
+    /** The Id Manager, built lazily in {@link #getIdManager()}. */
+    private IdentifierManager idManager;
+
+    private final NodeBuilder builder;
+
+    /**
+     * <UUID, Set<paths-pointing-to-the-uuid>>
+     */
+    private final Map<String, Set<String>> newRefs;
+
+    /**
+     * <UUID, Set<paths-pointing-to-the-uuid>>
+     */
+    private final Map<String, Set<String>> rmRefs;
+
+    /**
+     * <UUID, Set<paths-pointing-to-the-uuid>>
+     */
+    private final Map<String, Set<String>> newWeakRefs;
+
+    /**
+     * <UUID, Set<paths-pointing-to-the-uuid>>
+     */
+    private final Map<String, Set<String>> rmWeakRefs;
+
+    /**
+     * set of legally removed Ids
+     */
+    private final Map<String, String> rmIds;
+
+    public ReferenceEditor(NodeBuilder builder) {
+        this.parent = null;
+        this.name = null;
+        this.path = "/";
+        this.builder = builder;
+        this.newRefs = newHashMap();
+        this.rmRefs = newHashMap();
+        this.newWeakRefs = newHashMap();
+        this.rmWeakRefs = newHashMap();
+        this.rmIds = newHashMap();
+    }
+
+    private ReferenceEditor(ReferenceEditor parent, String name) {
+        this.parent = parent;
+        this.name = name;
+        this.path = null;
+        this.builder = parent.builder;
+        this.newRefs = parent.newRefs;
+        this.rmRefs = parent.rmRefs;
+        this.newWeakRefs = parent.newWeakRefs;
+        this.rmWeakRefs = parent.rmWeakRefs;
+        this.rmIds = parent.rmIds;
+    }
+
+    /**
+     * Returns the path of this node, building it lazily when first requested.
+     */
+    private String getPath() {
+        if (path == null) {
+            path = concat(parent.getPath(), name);
+        }
+        return path;
+    }
+
+    /**
+     * Returns the id manager, building it lazily when first requested.
+     */
+    private IdentifierManager getIdManager() {
+        if (idManager == null) {
+            if (parent != null) {
+                return parent.getIdManager();
+            }
+            this.idManager = new IdentifierManager(new ImmutableRoot(
+                    this.builder.getNodeState()));
+        }
+        return idManager;
+    }
+
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
+    }
+
+    @Override
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (parent == null) {
+            Set<String> offending = newHashSet(rmIds.keySet());
+            offending.removeAll(rmRefs.keySet());
+            if (!offending.isEmpty()) {
+                throw new CommitFailedException(INTEGRITY, 1,
+                        "Unable to delete referenced node");
+            }
+
+            // local uuid-> nodebuilder cache
+            Map<String, NodeBuilder> builders = newHashMap();
+            for (Entry<String, Set<String>> ref : rmRefs.entrySet()) {
+                String uuid = ref.getKey();
+                if (rmIds.containsKey(uuid)) {
+                    continue;
+                }
+                NodeBuilder child = resolveUUID(uuid, builders);
+                if (child == null) {
+                    throw new CommitFailedException(INTEGRITY, 2,
+                            "Unable to resolve UUID " + uuid);
+                }
+                Set<String> rm = ref.getValue();
+                Set<String> add = newHashSet();
+                if (newRefs.containsKey(uuid)) {
+                    add = newRefs.remove(uuid);
+                }
+                set(child, REF_NAME, add, rm);
+            }
+            for (Entry<String, Set<String>> ref : newRefs.entrySet()) {
+                String uuid = ref.getKey();
+                if (rmIds.containsKey(uuid)) {
+                    continue;
+                }
+                NodeBuilder child = resolveUUID(uuid, builders);
+                if (child == null) {
+                    throw new CommitFailedException(INTEGRITY, 3,
+                            "Unable to resolve UUID " + uuid);
+                }
+                Set<String> add = ref.getValue();
+                Set<String> rm = newHashSet();
+                set(child, REF_NAME, add, rm);
+            }
+            for (Entry<String, Set<String>> ref : rmWeakRefs.entrySet()) {
+                String uuid = ref.getKey();
+                if (rmIds.containsKey(uuid)) {
+                    continue;
+                }
+                NodeBuilder child = resolveUUID(uuid, builders);
+                if (child == null) {
+                    // TODO log warning?
+                    continue;
+                }
+                Set<String> rm = ref.getValue();
+                Set<String> add = newHashSet();
+                if (newWeakRefs.containsKey(uuid)) {
+                    add = newWeakRefs.remove(uuid);
+                }
+                set(child, WEAK_REF_NAME, add, rm);
+            }
+            for (Entry<String, Set<String>> ref : newWeakRefs.entrySet()) {
+                String uuid = ref.getKey();
+                if (rmIds.containsKey(uuid)) {
+                    continue;
+                }
+                NodeBuilder child = resolveUUID(uuid, builders);
+                if (child == null) {
+                    // TODO log warning?
+                    continue;
+                }
+                Set<String> add = ref.getValue();
+                Set<String> rm = newHashSet();
+                set(child, WEAK_REF_NAME, add, rm);
+            }
+        }
+    }
+
+    private NodeBuilder resolveUUID(String uuid,
+            Map<String, NodeBuilder> builders) {
+        checkState(parent == null);
+
+        if (builders.containsKey(uuid)) {
+            return builders.get(uuid);
+        }
+        String path = getIdManager().resolveUUID(uuid);
+
+        if (path == null) {
+            return null;
+        }
+        NodeBuilder child = getChild(builder, path);
+        if (child != null) {
+            builders.put(uuid, child);
+        }
+        return child;
+    }
+
+    private static NodeBuilder getChild(NodeBuilder root, String path) {
+        NodeBuilder child = root;
+        for (String p : elements(path)) {
+            child = child.child(p);
+        }
+        return child;
+    }
+
+    private static void set(NodeBuilder child, String name, Set<String> add,
+            Set<String> rm) {
+        // TODO should we optimize for the remove/add case? intersect the
+        // sets, work on the diffs?
+
+        Set<String> vals;
+        PropertyState ref = child.getProperty(name);
+        if (ref != null) {
+            vals = newHashSet(ref.getValue(STRINGS));
+        } else {
+            vals = newHashSet();
+        }
+        vals.addAll(add);
+        vals.removeAll(rm);
+        if (!vals.isEmpty()) {
+            child.setProperty(name, vals, STRINGS);
+        } else {
+            child.removeProperty(name);
+        }
+    }
+
+    @Override
+    public void propertyAdded(PropertyState after) {
+        propertyChanged(null, after);
+    }
+
+    @Override
+    public void propertyChanged(PropertyState before, PropertyState after) {
+        if (before != null) {
+            if (before.getType() == REFERENCE) {
+                put(rmRefs, before.getValue(STRING), getPath());
+            }
+            if (before.getType() == WEAKREFERENCE) {
+                put(rmWeakRefs, before.getValue(STRING), getPath());
+            }
+        }
+        if (after != null) {
+            if (after.getType() == REFERENCE) {
+                put(newRefs, after.getValue(STRING), getPath());
+            }
+            if (after.getType() == WEAKREFERENCE) {
+                put(newWeakRefs, after.getValue(STRING), getPath());
+            }
+        }
+    }
+
+    private static void put(Map<String, Set<String>> map, String key,
+            String value) {
+        Set<String> values = map.get(key);
+        if (values == null) {
+            values = newHashSet();
+        }
+        values.add(value);
+        map.put(key, values);
+    }
+
+    @Override
+    public void propertyDeleted(PropertyState before) {
+        propertyChanged(before, null);
+    }
+
+    @Override
+    public Editor childNodeAdded(String name, NodeState after) {
+        String path = concat(getPath(), name);
+        if (isVersionStorePath(path)) {
+            return null;
+        }
+        return new ReferenceEditor(this, name);
+    }
+
+    @Override
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) {
+        String path = concat(getPath(), name);
+        if (isVersionStorePath(path)) {
+            return null;
+        }
+        return new ReferenceEditor(this, name);
+    }
+
+    @Override
+    public Editor childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        String path = concat(getPath(), name);
+        if (isVersionStorePath(path)) {
+            return null;
+        }
+
+        for (PropertyState ps : before.getProperties()) {
+            if (ps.getType() == REFERENCE) {
+                put(rmRefs, ps.getValue(STRING), path);
+            }
+            if (ps.getType() == WEAKREFERENCE) {
+                put(rmWeakRefs, ps.getValue(STRING), path);
+            }
+        }
+        if (before.hasProperty(REF_NAME)) {
+            String uuid = before.getString(JCR_UUID);
+            if (uuid != null) {
+                rmIds.put(uuid, path);
+            }
+        }
+        return new ReferenceEditor(this, name);
+    }
+
+    private static boolean isVersionStorePath(@Nonnull String oakPath) {
+        if (oakPath.indexOf(JcrConstants.JCR_SYSTEM) == 1) {
+            for (String p : VersionConstants.SYSTEM_PATHS) {
+                if (oakPath.startsWith(p)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}

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

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java?rev=1540107&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java Fri Nov  8 16:21:12 2013
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.reference;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.VisibleEditor;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+@Component
+@Service(EditorProvider.class)
+public class ReferenceEditorProvider implements EditorProvider {
+
+    @Override
+    public Editor getRootEditor(NodeState before, NodeState after,
+            NodeBuilder builder) throws CommitFailedException {
+        return new VisibleEditor(new ReferenceEditor(builder));
+    }
+
+}

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

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-jcr/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/pom.xml?rev=1540107&r1=1540106&r2=1540107&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-jcr/pom.xml Fri Nov  8 16:21:12 2013
@@ -37,9 +37,7 @@
       org.apache.jackrabbit.test.api.SessionTest#testMoveConstraintViolationExceptionSrc               <!-- OAK-132 -->
       org.apache.jackrabbit.test.api.SessionTest#testMoveConstraintViolationExceptionDest              <!-- OAK-132 -->
       org.apache.jackrabbit.test.api.SessionTest#testMoveLockException
-      org.apache.jackrabbit.test.api.SessionUUIDTest#testSaveReferentialIntegrityException             <!-- OAK-66 -->
       org.apache.jackrabbit.test.api.NodeTest#testRemoveNodeParentLocked
-      org.apache.jackrabbit.test.api.NodeUUIDTest#testSaveReferentialIntegrityException                <!-- OAK-66 -->
       org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testLocked
       org.apache.jackrabbit.test.api.WorkspaceCopyVersionableTest#testCopyNodesVersionableAndCheckedIn <!-- OAK-118 -->
       org.apache.jackrabbit.test.api.WorkspaceMoveVersionableTest#testMoveNodesVersionableAndCheckedIn <!-- OAK-118 -->

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java?rev=1540107&r1=1540106&r2=1540107&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java Fri Nov  8 16:21:12 2013
@@ -32,6 +32,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider;
 import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider;
 import org.apache.jackrabbit.oak.plugins.name.NameValidatorProvider;
 import org.apache.jackrabbit.oak.plugins.name.NamespaceValidatorProvider;
 import org.apache.jackrabbit.oak.plugins.nodetype.RegistrationEditorProvider;
@@ -70,6 +71,7 @@ public class Jcr {
         with(new TypeEditorProvider());
         with(new RegistrationEditorProvider());
         with(new ConflictValidatorProvider());
+        with(new ReferenceEditorProvider());
 
         with(new PropertyIndexEditorProvider());