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());