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 ju...@apache.org on 2012/06/01 10:13:31 UTC

svn commit: r1345006 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/name/ main/java/org/apache/jackrabbit/oak/plugins/type/ main/java/org/apache/jackrabbit/oak/spi/commit/ test/java/org/apache/jackrabbit/oak/spi/co...

Author: jukka
Date: Fri Jun  1 08:13:30 2012
New Revision: 1345006

URL: http://svn.apache.org/viewvc?rev=1345006&view=rev
Log:
OAK-125: Improved namespace registry

Add a validator for the /jcr:system/jcr:namespaces tree.
Restore the Validator return value in childNodeDeleted as it makes NamespaceValidator easier to write.
Added some utility classes and tests for the validator infrastructure.

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidatorProvider.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/DefaultValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/FailingValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidatorTest.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NameValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/ValidatingCommitHook.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/Validator.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NameValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NameValidator.java?rev=1345006&r1=1345005&r2=1345006&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NameValidator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NameValidator.java Fri Jun  1 08:13:30 2012
@@ -102,8 +102,8 @@ class NameValidator implements Validator
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
-        // do nothing
+    public Validator childNodeDeleted(String name, NodeState before) {
+        return null;
     }
 
 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidator.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidator.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidator.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,72 @@
+/*
+ * 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.name;
+
+import java.util.Map;
+
+import javax.jcr.PropertyType;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
+
+class NamespaceValidator extends DefaultValidator {
+
+    private final Map<String, String> map;
+
+    public NamespaceValidator(Map<String, String> map) {
+        this.map = map;
+    }
+
+    //-----------------------------------------------------< NodeValidator >--
+
+    @Override
+    public void propertyAdded(PropertyState after)
+            throws CommitFailedException {
+        String prefix = after.getName();
+        if (map.containsKey(prefix)) {
+            throw new CommitFailedException(
+                    "Namespace mapping already registered: " + prefix);
+        } else if (Namespaces.isValidPrefix(prefix)) {
+            if (after.isArray()
+                    || after.getValue().getType() != PropertyType.STRING) {
+                throw new CommitFailedException(
+                        "Invalid namespace mapping: " + prefix);
+            }
+            
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyState before, PropertyState after)
+            throws CommitFailedException {
+        if (map.containsKey(after.getName())) {
+            throw new CommitFailedException(
+                    "Namespace modification not allowed: " + after.getName());
+        }
+    }
+
+    @Override
+    public void propertyDeleted(PropertyState before)
+            throws CommitFailedException {
+        if (map.containsKey(before.getName())) {
+            throw new CommitFailedException(
+                    "Namespace removal not allowed: " + before.getName());
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidatorProvider.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidatorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceValidatorProvider.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,45 @@
+/*
+ * 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.name;
+
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.spi.commit.SubtreeValidator;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Validator service that checks that all node and property names as well
+ * as any name values are syntactically valid and that any namespace prefixes
+ * are properly registered.
+ */
+@Component
+@Service(ValidatorProvider.class)
+public class NamespaceValidatorProvider implements ValidatorProvider {
+
+    @Override
+    public Validator getRootValidator(NodeState before, NodeState after) {
+        Map<String, String> map = Namespaces.getNamespaceMap(before);
+        Validator validator =
+                new NamespaceValidator(map);
+        return new SubtreeValidator(validator, "jcr:system", "jcr:namespaces");
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidator.java?rev=1345006&r1=1345005&r2=1345006&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/type/TypeValidator.java Fri Jun  1 08:13:30 2012
@@ -91,8 +91,9 @@ class TypeValidator implements Validator
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
+    public Validator childNodeDeleted(String name, NodeState before) {
         // TODO: validate removed child node
+        return null;
     }
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeValidator.java?rev=1345006&r1=1345005&r2=1345006&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeValidator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeValidator.java Fri Jun  1 08:13:30 2012
@@ -44,7 +44,6 @@ public class CompositeValidator implemen
     @Override
     public void propertyChanged(PropertyState before, PropertyState after)
             throws CommitFailedException {
-
         for (Validator validator : validators) {
             validator.propertyChanged(before, after);
         }
@@ -61,26 +60,48 @@ public class CompositeValidator implemen
     public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
         List<Validator> childValidators = new ArrayList<Validator>(validators.size());
         for (Validator validator : validators) {
-            childValidators.add(
-                    validator.childNodeAdded(name, after));
+            Validator child = validator.childNodeAdded(name, after);
+            if (child != null) {
+                childValidators.add(child);
+            }
+        }
+        if (!childValidators.isEmpty()) {
+            return new CompositeValidator(childValidators);
+        } else {
+            return null;
         }
-        return new CompositeValidator(childValidators);
     }
 
     @Override
     public Validator childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
         List<Validator> childValidators = new ArrayList<Validator>(validators.size());
         for (Validator validator : validators) {
-            childValidators.add(
-                    validator.childNodeChanged(name, before, after));
+            Validator child = validator.childNodeChanged(name, before, after);
+            if (child != null) {
+                childValidators.add(child);
+            }
+        }
+        if (!childValidators.isEmpty()) {
+            return new CompositeValidator(childValidators);
+        } else {
+            return null;
         }
-        return new CompositeValidator(childValidators);
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+    public Validator childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+        List<Validator> childValidators = new ArrayList<Validator>(validators.size());
         for (Validator validator : validators) {
-            validator.childNodeDeleted(name, before);
+            Validator child = validator.childNodeDeleted(name, before);
+            if (child != null) {
+                childValidators.add(child);
+            }
+        }
+        if (!childValidators.isEmpty()) {
+            return new CompositeValidator(childValidators);
+        } else {
+            return null;
         }
     }
+
 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/DefaultValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/DefaultValidator.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/DefaultValidator.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/DefaultValidator.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,68 @@
+/*
+ * 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.spi.commit;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Validator that does nothing by default and doesn't recurse into subtrees.
+ * Useful as a sentinel or as a base class for more complex validators.
+ *
+ * @since Oak 0.3
+ */
+public class DefaultValidator implements Validator {
+
+    @Override
+    public void propertyAdded(PropertyState after)
+            throws CommitFailedException {
+        // do nothing
+    }
+
+    @Override
+    public void propertyChanged(PropertyState before, PropertyState after)
+            throws CommitFailedException {
+        // do nothing
+    }
+
+    @Override
+    public void propertyDeleted(PropertyState before)
+            throws CommitFailedException {
+        // do nothing
+    }
+
+    @Override
+    public Validator childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
+        return null;
+    }
+
+    @Override
+    public Validator childNodeChanged(
+            String name, NodeState before, NodeState after)
+            throws CommitFailedException {
+        return null;
+    }
+
+    @Override
+    public Validator childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        return null;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/FailingValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/FailingValidator.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/FailingValidator.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/FailingValidator.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,78 @@
+/*
+ * 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.spi.commit;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Validator that rejects all changes. Useful as a sentinel or as
+ * a tool for testing composite validators.
+ *
+ * @since Oak 0.3
+ */
+public class FailingValidator implements Validator {
+
+    private final String message;
+
+    public FailingValidator() {
+        this("All changes are rejected");
+    }
+
+    public FailingValidator(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public void propertyAdded(PropertyState after)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+    @Override
+    public void propertyChanged(PropertyState before, PropertyState after)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+    @Override
+    public void propertyDeleted(PropertyState before)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+    @Override
+    public Validator childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+    @Override
+    public Validator childNodeChanged(
+            String name, NodeState before, NodeState after)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+    @Override
+    public Validator childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        throw new CommitFailedException(message);
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,77 @@
+/*
+ * 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.spi.commit;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Validator that detects changes to a specified subtree and delegates the
+ * validation of such changes to another given validator.
+ *
+ * @since Oak 0.3
+ */
+public class SubtreeValidator extends DefaultValidator {
+
+    private final Validator validator;
+
+    private final String head;
+
+    private final List<String> tail;
+
+    public SubtreeValidator(Validator validator, String... path) {
+        this(validator, Arrays.asList(path));
+    }
+
+    private SubtreeValidator(Validator validator, List<String> path) {
+        assert validator != null;
+        assert path != null && !path.isEmpty();
+        this.validator = validator;
+        this.head = path.get(0);
+        this.tail = path.subList(1, path.size());
+    }
+
+    @Override
+    public Validator childNodeAdded(String name, NodeState after) {
+        return descend(name);
+    }
+
+    @Override
+    public Validator childNodeChanged(
+            String name, NodeState before, NodeState after) {
+        return descend(name);
+    }
+
+    @Override
+    public Validator childNodeDeleted(String name, NodeState before) {
+        return descend(name);
+    }
+
+    private Validator descend(String name) {
+        if (!head.equals(name)) {
+            return null;
+        } else if (tail.isEmpty()) {
+            return validator;
+        } else {
+            return new SubtreeValidator(validator, tail);
+        }
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/ValidatingCommitHook.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/ValidatingCommitHook.java?rev=1345006&r1=1345005&r2=1345006&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/ValidatingCommitHook.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/ValidatingCommitHook.java Fri Jun  1 08:13:30 2012
@@ -16,9 +16,10 @@
  */
 package org.apache.jackrabbit.oak.spi.commit;
 
+import static org.apache.jackrabbit.oak.plugins.memory.MemoryNodeState.EMPTY_NODE;
+
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
@@ -29,6 +30,7 @@ import org.apache.jackrabbit.oak.spi.sta
  * passed to the class' constructor.
  */
 public class ValidatingCommitHook implements CommitHook {
+
     private final ValidatorProvider validatorProvider;
 
     /**
@@ -41,11 +43,11 @@ public class ValidatingCommitHook implem
     }
 
     @Override
-    public NodeState beforeCommit(NodeStore store, NodeState before, NodeState after)
+    public NodeState beforeCommit(
+            NodeStore store, NodeState before, NodeState after)
             throws CommitFailedException {
-
-        Validator rootValidator = validatorProvider.getRootValidator(before, after);
-        validate(store, before, after, rootValidator);
+        Validator validator = validatorProvider.getRootValidator(before, after);
+        new ValidatorDiff(validator, store).validate(validator, before, after);
         return after;
     }
 
@@ -56,112 +58,121 @@ public class ValidatingCommitHook implem
 
     //------------------------------------------------------------< private >---
 
-    private static void validate(final NodeStore store, NodeState before, NodeState after,
-            final Validator validator) throws CommitFailedException {
+    private static class ValidatorDiff implements NodeStateDiff {
+
+        private final Validator validator;
+
+        private final NodeStore store;
 
-        /*
+        /**
          * Checked exceptions don't compose. So we need to hack around.
          * See http://markmail.org/message/ak67n5k7mr3vqylm and
          * http://markmail.org/message/bhocbruikljpuhu6
          */
-        final CommitFailedException[] exception = {null};
+        private CommitFailedException exception = null;
 
-        store.compare(before, after, new NodeStateDiff() {
-            @Override
-            public void propertyAdded(PropertyState after) {
-                if (exception[0] == null) {
-                    try {
-                        validator.propertyAdded(after);
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
-                    }
-                }
+        private ValidatorDiff(Validator validator, NodeStore store) {
+            this.validator = validator;
+            this.store = store;
+        }
+
+        /**
+         * Validates the given subtree by diffing and recursing through it.
+         *
+         * @param validator validator for the root of the subtree
+         * @param before state of the original subtree
+         * @param after state of the modified subtree
+         * @throws CommitFailedException if validation failed
+         */
+        public void validate(
+                Validator validator, NodeState before, NodeState after)
+                throws CommitFailedException {
+            ValidatorDiff diff = new ValidatorDiff(validator, store);
+            store.compare(before, after, diff);
+            if (exception != null) {
+                throw exception;
             }
+        }
 
-            @Override
-            public void propertyChanged(PropertyState before, PropertyState after) {
-                if (exception[0] == null) {
-                    try {
-                        validator.propertyChanged(before, after);
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
-                    }
+        //-------------------------------------------------< NodeStateDiff >--
+
+        @Override
+        public void propertyAdded(PropertyState after) {
+            if (exception == null) {
+                try {
+                    validator.propertyAdded(after);
+                } catch (CommitFailedException e) {
+                    exception = e;
                 }
             }
+        }
 
-            @Override
-            public void propertyDeleted(PropertyState before) {
-                if (exception[0] == null) {
-                    try {
-                        validator.propertyDeleted(before);
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
-                    }
+        @Override
+        public void propertyChanged(PropertyState before, PropertyState after) {
+            if (exception == null) {
+                try {
+                    validator.propertyChanged(before, after);
+                } catch (CommitFailedException e) {
+                    exception = e;
                 }
             }
+        }
 
-            @Override
-            public void childNodeAdded(String name, NodeState after) {
-                if (exception[0] == null) {
-                    try {
-                        Validator childValidator = validator.childNodeAdded(name, after);
-                        if (childValidator != null) {
-                            validate(after, validator);
-                        }
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
-                    }
+        @Override
+        public void propertyDeleted(PropertyState before) {
+            if (exception == null) {
+                try {
+                    validator.propertyDeleted(before);
+                } catch (CommitFailedException e) {
+                    exception = e;
                 }
             }
+        }
 
-            @Override
-            public void childNodeChanged(String name, NodeState before, NodeState after) {
-                if (exception[0] == null) {
-                    try {
-                        Validator childValidator = validator.childNodeChanged(name, before, after);
-                        if (childValidator != null) {
-                            validate(store, before, after, childValidator);
-                        }
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
+        @Override
+        public void childNodeAdded(String name, NodeState after) {
+            if (exception == null) {
+                try {
+                    Validator v = validator.childNodeAdded(name, after);
+                    if (v != null) {
+                        validate(v, EMPTY_NODE, after);
                     }
+                } catch (CommitFailedException e) {
+                    exception = e;
                 }
             }
+        }
 
-            @Override
-            public void childNodeDeleted(String name, NodeState before) {
-                if (exception[0] == null) {
-                    try {
-                        validator.childNodeDeleted(name, before);
-                    }
-                    catch (CommitFailedException e) {
-                        exception[0] = e;
+        @Override
+        public void childNodeChanged(
+                String name, NodeState before, NodeState after) {
+            if (exception == null) {
+                try {
+                    Validator v =
+                            validator.childNodeChanged(name, before, after);
+                    if (v != null) {
+                        validate(v, before, after);
                     }
+                } catch (CommitFailedException e) {
+                    exception = e;
                 }
             }
-        });
-
-        if (exception[0] != null) {
-            throw new CommitFailedException(exception[0]);
         }
-    }
-
-    private static void validate(NodeState nodeState, Validator validator)
-            throws CommitFailedException {
 
-        for (PropertyState property : nodeState.getProperties()) {
-            validator.propertyAdded(property);
+        @Override
+        public void childNodeDeleted(String name, NodeState before) {
+            if (exception == null) {
+                try {
+                    Validator v = validator.childNodeDeleted(name, before);
+                    if (v != null) {
+                        validate(v, before, EMPTY_NODE);
+                    }
+                } catch (CommitFailedException e) {
+                    exception = e;
+                }
+            }
         }
 
-        for (ChildNodeEntry child : nodeState.getChildNodeEntries()) {
-            Validator childValidator = validator.childNodeAdded(
-                    child.getName(), child.getNodeState());
-            validate(child.getNodeState(), childValidator);
-        }
     }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/Validator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/Validator.java?rev=1345006&r1=1345005&r2=1345006&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/Validator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/Validator.java Fri Jun  1 08:13:30 2012
@@ -82,9 +82,12 @@ public interface Validator {
     /**
      * Validate a deleted node
      * @param before the original node
+     * @return a {@code Validator} for the removed subtree or
+     * {@code null} if validation should not decent into the subtree
      * @throws CommitFailedException  if validation fails.
      */
-    void childNodeDeleted(String name, NodeState before)
+    @CheckForNull
+    Validator childNodeDeleted(String name, NodeState before)
             throws CommitFailedException;
 
 }

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidatorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidatorTest.java?rev=1345006&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidatorTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidatorTest.java Fri Jun  1 08:13:30 2012
@@ -0,0 +1,54 @@
+/*
+ * 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.spi.commit;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static org.apache.jackrabbit.oak.plugins.memory.MemoryNodeState.EMPTY_NODE;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.junit.Test;
+
+public class SubtreeValidatorTest {
+
+
+    @Test
+    public void testSubtreeValidator() throws CommitFailedException {
+        Validator delegate = new FailingValidator();
+        Validator validator = new SubtreeValidator(delegate, "one", "two");
+
+        assertNull(validator.childNodeAdded("zero", EMPTY_NODE));
+        assertNull(validator.childNodeChanged("two", EMPTY_NODE, EMPTY_NODE));
+        assertNull(validator.childNodeDeleted("foo", EMPTY_NODE));
+
+        assertNotNull(validator.childNodeAdded("one", EMPTY_NODE));
+        assertNotNull(validator.childNodeChanged("one", EMPTY_NODE, EMPTY_NODE));
+        assertNotNull(validator.childNodeDeleted("one", EMPTY_NODE));
+
+        // Descend to the subtree
+        validator = validator.childNodeChanged("one", EMPTY_NODE, EMPTY_NODE);
+        assertNull(validator.childNodeAdded("zero", EMPTY_NODE));
+        assertNull(validator.childNodeChanged("one", EMPTY_NODE, EMPTY_NODE));
+        assertNull(validator.childNodeDeleted("foo", EMPTY_NODE));
+
+        assertEquals(delegate, validator.childNodeAdded("two", EMPTY_NODE));
+        assertEquals(delegate, validator.childNodeChanged("two", EMPTY_NODE, EMPTY_NODE));
+        assertEquals(delegate, validator.childNodeDeleted("two", EMPTY_NODE));
+    }
+
+}