You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by md...@apache.org on 2009/02/24 12:26:41 UTC

svn commit: r747347 - in /jackrabbit/trunk/jackrabbit-spi-commons/src: main/java/org/apache/jackrabbit/spi/commons/batch/ test/java/org/apache/jackrabbit/spi/commons/batch/

Author: mduerig
Date: Tue Feb 24 11:26:41 2009
New Revision: 747347

URL: http://svn.apache.org/viewvc?rev=747347&view=rev
Log:
JCR-1983: Provide change log consolidator

Added:
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java   (with props)

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,40 @@
+/*
+ * 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.spi.commons.batch;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Batch;
+
+
+/**
+ * A <code>ChangeLog</code> is a specialized {@link Batch} which
+ * keeps a list of {@link Operation}s. The {@link #apply(Batch)} method
+ * applies these operations to another batch.
+ */
+public interface ChangeLog extends Batch {
+
+    /**
+     * Applies the {@link Operation}s contained in this change log to
+     * the passed <code>batch</code>.
+     * @param batch
+     * @return  The <code>batch</code> passed in as argument with the
+     *   operations from this change log applied.
+     * @throws RepositoryException
+     */
+    public Batch apply(Batch batch) throws RepositoryException;
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLog.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,143 @@
+/*
+ * 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.spi.commons.batch;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Batch;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.QValue;
+
+/**
+ * This {@link ChangeLog} implementation simply keeps back all calls to its {@link Batch} methods as
+ * a list of {@link #operations} (with item of type {@link Operation}). When {@link #apply(Batch)
+ * applied} to a batch, all operations in the list are {@link Operation#apply(Batch) applied} to that
+ * batch.
+ */
+public class ChangeLogImpl implements ChangeLog {
+
+    /**
+     * {@link Operation}s kept in this change log.
+     */
+    protected final List operations = new LinkedList();
+
+    public void addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid)
+            throws RepositoryException {
+
+        addOperation(Operations.addNode(parentId, nodeName, nodetypeName, uuid));
+    }
+
+    public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
+        addOperation(Operations.addProperty(parentId, propertyName, value));
+    }
+
+    public void addProperty(NodeId parentId, Name propertyName, QValue[] values)
+            throws RepositoryException {
+
+        addOperation(Operations.addProperty(parentId, propertyName, values));
+    }
+
+    public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
+        addOperation(Operations.move(srcNodeId, destParentNodeId, destName));
+    }
+
+    public void remove(ItemId itemId) throws RepositoryException {
+        addOperation(Operations.remove(itemId));
+    }
+
+    public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId)
+            throws RepositoryException {
+
+        addOperation(Operations.reorderNodes(parentId, srcNodeId, beforeNodeId));
+    }
+
+    public void setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) throws RepositoryException {
+        addOperation(Operations.setMixins(nodeId, mixinNodeTypeNames));
+    }
+
+    public void setValue(PropertyId propertyId, QValue value) throws RepositoryException {
+        addOperation(Operations.setValue(propertyId, value));
+    }
+
+    public void setValue(PropertyId propertyId, QValue[] values) throws RepositoryException {
+        addOperation(Operations.setValue(propertyId, values));
+    }
+
+    public Batch apply(Batch batch) throws RepositoryException {
+        if (batch == null) {
+            throw new IllegalArgumentException("Batch must not be null");
+        }
+        for (Iterator it = operations.iterator(); it.hasNext(); ) {
+            Operation op = (Operation) it.next();
+            op.apply(batch);
+        }
+        return batch;
+    }
+
+    /**
+     * This method is called when an operation is added to the list of {@link #operations}
+     * kept by this change log.
+     * @param op  {@link Operation} to add
+     * @throws RepositoryException
+     */
+    protected void addOperation(Operation op) throws RepositoryException {
+        operations.add(op);
+    }
+
+    // -----------------------------------------------------< Object >---
+
+    public String toString() {
+        StringBuffer b = new StringBuffer();
+        for (Iterator it = operations.iterator(); it.hasNext(); ) {
+            b.append(it.next());
+            if (it.hasNext()) {
+                b.append(", ");
+            }
+        }
+        return b.toString();
+    }
+
+    public boolean equals(Object other) {
+        if (null == other) {
+            return false;
+        }
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof ChangeLogImpl) {
+            return equals((ChangeLogImpl) other);
+        }
+        return false;
+    }
+
+    public boolean equals(ChangeLogImpl other) {
+        return operations.equals(other.operations);
+    }
+
+    public int hashCode() {
+        throw new IllegalArgumentException("Not hashable");
+    }
+
+}
+

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,688 @@
+/*
+ * 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.spi.commons.batch;
+
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Batch;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+
+/**
+ * A {@link ChangeLog} implementation which does basic consolidation on its
+ * {@link org.apache.jackrabbit.spi.commons.batch.Operation Operation}s. That is, cancelling
+ * operations are removed if possible. In general this is not possible across
+ * {@link org.apache.jackrabbit.spi.commons.batch.Operations.Move move} operations. The individual
+ * {@link CancelableOperation CancelableOperation} implementations document their behavior
+ * concerning cancellation.
+ */
+public class ConsolidatingChangeLog extends ChangeLogImpl {
+    private static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance();
+
+    /**
+     * Create a new instance of a consolidating change log.
+     */
+    public ConsolidatingChangeLog() {
+        super();
+    }
+
+    /**
+     * Create a {@link Path} from the {@link NodeId} of a parent and the {@link Name} of a
+     * child.
+     * @param parentId  node id of the parent
+     * @param name  name of the child
+     * @return  the path of the item <code>name</code>
+     * @throws RepositoryException
+     */
+    protected static Path getPath(NodeId parentId, Name name) throws RepositoryException {
+        Path parent = parentId.getPath();
+        if (!parent.isAbsolute()) {
+            throw new IllegalArgumentException("Path not absoulte: " + parent);
+        }
+
+        return PATH_FACTORY.create(parent, name, true);
+    }
+
+    /**
+     * Determine the {@link Path} from an {@link ItemId}.
+     * @param itemId
+     * @return  path of the item <code>itemId</code>
+     */
+    protected static Path getPath(ItemId itemId) {
+        Path path = itemId.getPath();
+        if (!path.isAbsolute()) {
+            throw new IllegalArgumentException("Path not absoulte: " + path);
+        }
+        return path;
+    }
+
+    // -----------------------------------------------------< ChangeLog >---
+
+    public void addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid)
+            throws RepositoryException {
+
+        addOperation(CancelableOperations.addNode(parentId, nodeName, nodetypeName, uuid));
+    }
+
+    public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
+        addOperation(CancelableOperations.addProperty(parentId, propertyName, value));
+    }
+
+    public void addProperty(NodeId parentId, Name propertyName, QValue[] values) throws RepositoryException {
+        addOperation(CancelableOperations.addProperty(parentId, propertyName, values));
+    }
+
+    public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
+        addOperation(CancelableOperations.move(srcNodeId, destParentNodeId, destName));
+    }
+
+    public void remove(ItemId itemId) throws RepositoryException {
+        addOperation(CancelableOperations.remove(itemId));
+    }
+
+    public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId)
+            throws RepositoryException {
+
+        addOperation(CancelableOperations.reorderNodes(parentId, srcNodeId, beforeNodeId));
+    }
+
+    public void setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) throws RepositoryException {
+        addOperation(CancelableOperations.setMixins(nodeId, mixinNodeTypeNames));
+    }
+
+    public void setValue(PropertyId propertyId, QValue value) throws RepositoryException {
+        addOperation(CancelableOperations.setValue(propertyId, value));
+    }
+
+    public void setValue(PropertyId propertyId, QValue[] values) throws RepositoryException {
+        addOperation(CancelableOperations.setValue(propertyId, values));
+    }
+
+    /**
+     * Determines the cancellation behavior from the list of {@link ChangeLogImpl#operations operations}
+     * and the current operation <code>op</code>:
+     * <ul>
+     * <li>When the current operation is cancelled by the last operation, the list of operations
+     *   is not modified.</li>
+     * <li>When the current operation and the last operation cancel each other, the last operation is
+     *   removed from the list of operations.</li>
+     * <li>When the last operation is cancelled by this operation, the last operation is removed from
+     *   the list of operations and determination of cancellation starts from scratch.</li>
+     * <li>Otherwise add the current operation to the list of operations.</li>
+     * </ul>
+     */
+    protected void addOperation(Operation op) throws RepositoryException {
+        if (!(op instanceof CancelableOperation)) {
+            throw new IllegalArgumentException("Operation not instance of "
+                    + CancelableOperation.class.getName());
+        }
+
+        CancelableOperation otherOp = (CancelableOperation) op;
+        for (Iterator it = new OperationsBackwardWithSentinel(); it.hasNext(); ) {
+            CancelableOperation thisOp = (CancelableOperation) it.next();
+            switch (thisOp.cancel(otherOp)) {
+                case CancelableOperation.CANCEL_THIS:
+                    it.remove();
+                    continue;
+                case CancelableOperation.CANCEL_OTHER:
+                    return;
+                case CancelableOperation.CANCEL_BOTH:
+                    it.remove();
+                    return;
+                case CancelableOperation.CANCEL_NONE:
+                    super.addOperation(otherOp);
+                    return;
+                default:
+                    assert false : "Invalid case in switch";
+            }
+        }
+    }
+
+    // -----------------------------------------------------< private >---
+
+    private class OperationsBackwardWithSentinel implements Iterator {
+        private final ListIterator it = operations.listIterator(operations.size());
+        private boolean last = !it.hasPrevious();
+        private boolean done;
+
+        public boolean hasNext() {
+            return it.hasPrevious() || last;
+        }
+
+        public Object next() {
+            if (last) {
+                done = true;
+                return CancelableOperations.empty();
+            }
+            else {
+                Object o = it.previous();
+                last = !it.hasPrevious();
+                return o;
+            }
+        }
+
+        public void remove() {
+            if (done) {
+                throw new IllegalStateException("Cannot remove last element");
+            }
+            else {
+                it.remove();
+            }
+        }
+    }
+
+    // -----------------------------------------------------< CancelableOperations >---
+
+    /**
+     * This class represent an {@link Operation} which can be cancelled by another operation
+     * or which cancels another operation.
+     */
+    protected interface CancelableOperation extends Operation {
+
+        /**
+         * The other operation cancels this operations
+         */
+        public static final int CANCEL_THIS = 0;
+
+        /**
+         * This operation cancels the other operation
+         */
+        public static final int CANCEL_OTHER = 1;
+
+        /**
+         * This operation and the other operation cancel each other mutually
+         */
+        public static final int CANCEL_BOTH = 2;
+
+        /**
+         * No cancellation
+         */
+        public static final int CANCEL_NONE = 3;
+
+        /**
+         * Determines the cancellation behavior of the <code>other</code> operation
+         * on this operation.
+         * @param other
+         * @return  Either {@link #CANCEL_THIS}, {@link #CANCEL_OTHER}, {@link #CANCEL_OTHER}
+         *   or {@link #CANCEL_NONE}
+         * @throws RepositoryException
+         */
+        public int cancel(CancelableOperation other) throws RepositoryException;
+    }
+
+    /**
+     * Factory for creating {@link ConsolidatingChangeLog.CancelableOperation CancelableOperation}s.
+     * The inner classes of this class all implement the <code>CancelableOperation</code> interface.
+     *
+     * @see Operation
+     */
+    protected static final class CancelableOperations {
+        private CancelableOperations() {
+            super();
+        }
+
+        // -----------------------------------------------------< Empty >---
+
+        /**
+         * An <code>Empty</code> operation never cancels another operation and is never
+         * cancelled by any other operation.
+         */
+        public static class Empty extends Operations.Empty implements CancelableOperation {
+
+            /**
+             * @return {@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE}
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating an {@link Empty Empty} operation.
+         * @return
+         */
+        public static CancelableOperation empty() {
+            return new Empty();
+        }
+
+        // -----------------------------------------------------< AddNode >---
+
+        /**
+         * An <code>AddNode</code> operation is is cancelled by a
+         * {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} operation higher up the tree.
+         * The remove operation is also cancelled if it is targeted at the same node than this add
+         * operation.
+         */
+        public static class AddNode extends Operations.AddNode implements CancelableOperation {
+
+            public AddNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid) {
+                super(parentId, nodeName, nodetypeName, uuid);
+            }
+
+            /**
+             * @return
+             * <ul>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_BOTH CANCEL_BOTH} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has this node
+             *   as target.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_THIS CANCEL_THIS} if
+             *  <code>other</code> is an instance of
+             *  {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has an node higher up
+             *  the hierarchy as target.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE} otherwise.</li>
+             * </ul>
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(parentId, nodeName);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    if (thisPath.equals(otherPath)) {
+                        return CANCEL_BOTH;
+                    }
+                    return (thisPath.isDescendantOf(otherPath))
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating an {@link AddNode AddNode} operation.
+         * @see Batch#addNode(NodeId, Name, Name, String)
+         *
+         * @param parentId
+         * @param nodeName
+         * @param nodetypeName
+         * @param uuid
+         * @return
+         */
+        public static CancelableOperation addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid) {
+            return new AddNode(parentId, nodeName, nodetypeName, uuid);
+        }
+
+        // -----------------------------------------------------< AddProperty >---
+
+        /**
+         * <code>AddProperty</code> operations might cancel with
+         * {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and
+         * {@link ConsolidatingChangeLog.CancelableOperations.SetValue SetValue} operations.
+         */
+        public static class AddProperty extends Operations.AddProperty implements CancelableOperation {
+
+            public AddProperty(NodeId parentId, Name propertyName, QValue value) {
+                super(parentId, propertyName, value);
+            }
+
+            public AddProperty(NodeId parentId, Name propertyName, QValue[] values) {
+                super(parentId, propertyName, values);
+            }
+
+            /**
+             * @return
+             * <ul>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_BOTH CANCEL_BOTH} if
+             *  <code>other</code> is an instance of
+             *  {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has this property as
+             *  target or if <code>other</code> is an instance of
+             *  {@link ConsolidatingChangeLog.CancelableOperations.SetValue SetValue} for a value of
+             *  <code>null</code> and has this property as target.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_THIS CANCEL_THIS} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has a node higher up
+             *   the hierarchy as target.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_OTHER CANCEL_OTHER} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.SetValue SetValue} and has this
+             *   property as target.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE} otherwise.</li>
+             * <ul>
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(parentId, propertyName);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    if (thisPath.equals(otherPath)) {
+                        return CANCEL_BOTH;
+                    }
+                    return (thisPath.isDescendantOf(otherPath))
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                if (other instanceof SetValue) {
+                    SetValue setValue = (SetValue) other;
+                    Path thisPath = ConsolidatingChangeLog.getPath(parentId, propertyName);
+                    Path otherPath = ConsolidatingChangeLog.getPath(setValue.propertyId);
+                    if (thisPath.equals(otherPath)) {
+                        if (!isMultivalued && setValue.values[0] == null) {
+                            return CANCEL_BOTH;
+                        }
+                        else if (values.length == setValue.values.length) {
+                            for (int k = 0; k < values.length; k++) {
+                                if (!values[k].equals(setValue.values[k])) {
+                                    return CANCEL_NONE;
+                                }
+                            }
+                            return CANCEL_OTHER;
+                        }
+                    }
+                }
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating an {@link AddProperty AddProperty} operation.
+         *
+         * @see Batch#addProperty(NodeId, Name, QValue)
+         * @param parentId
+         * @param propertyName
+         * @param value
+         * @return
+         */
+        public static CancelableOperation addProperty(NodeId parentId, Name propertyName, QValue value) {
+            return new AddProperty(parentId, propertyName, value);
+        }
+
+        /**
+         * Factory method for creating an {@link AddProperty AddProperty} operation.
+         *
+         * @see Batch#addProperty(NodeId, Name, QValue[])
+         * @param parentId
+         * @param propertyName
+         * @param values
+         * @return
+         */
+        public static CancelableOperation addProperty(NodeId parentId, Name propertyName, QValue[] values) {
+            return new AddProperty(parentId, propertyName, values);
+        }
+
+        // -----------------------------------------------------< Move >---
+
+        /**
+         * An <code>Move</code> operation never cancels another operation and is never
+         * cancelled by any other operation.
+         */
+        public static class Move extends Operations.Move implements CancelableOperation {
+
+            public Move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) {
+                super(srcNodeId, destParentNodeId, destName);
+            }
+
+            /**
+             * @return {@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE}
+             */
+            public int cancel(CancelableOperation other) {
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating a {@link Move Move} operation.
+         *
+         * @see Batch#move(NodeId, NodeId, Name)
+         * @param srcNodeId
+         * @param destParentNodeId
+         * @param destName
+         * @return
+         */
+        public static CancelableOperation move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) {
+            return new Move(srcNodeId, destParentNodeId, destName);
+        }
+
+        // -----------------------------------------------------< Remove >---
+
+        /**
+         * An <code>Remove</code> operation never cancels another operation and is never
+         * cancelled by any other operation.
+         */
+        public static class Remove extends Operations.Remove implements CancelableOperation {
+
+            public Remove(ItemId itemId) {
+                super(itemId);
+            }
+
+            /**
+             * @return {@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE}
+             */
+            public int cancel(CancelableOperation other) {
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating a {@link Remove Remove} operation.
+         *
+         * @see Batch#move(NodeId, NodeId, Name)
+         * @param itemId
+         * @return
+         */
+        public static CancelableOperation remove(ItemId itemId) {
+            return new Remove(itemId);
+        }
+
+        // -----------------------------------------------------< Reorder Nodes >---
+
+        /**
+         * A <code>ReorderNodes</code> operation might cancel with
+         * {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and
+         * {@link ConsolidatingChangeLog.CancelableOperations.ReorderNodes ReorderNodes} operations.
+         */
+        public static class ReorderNodes extends Operations.ReorderNodes implements CancelableOperation {
+
+            public ReorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) {
+                super(parentId, srcNodeId, beforeNodeId);
+            }
+
+            /**
+             * @return
+             * <ul>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_THIS CANCEL_THIS} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has an node higher up
+             *   the hierarchy or this node as target. Or if <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.ReorderNodes ReorderNodes} which
+             *   has this node as target and neither <code>srcNodeId</code> nor <code>beforeNodeId</code>
+             *   has same name siblings.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE} otherwise.</li>
+             * </ul>
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(srcNodeId);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    return thisPath.isDescendantOf(otherPath) || thisPath.equals(otherPath)
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                if (other instanceof ReorderNodes) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(parentId);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((ReorderNodes) other).parentId);
+                    return thisPath.equals(otherPath) && !hasSNS(srcNodeId) && !hasSNS(beforeNodeId)
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                return CANCEL_NONE;
+            }
+
+            private boolean hasSNS(NodeId nodeId) {
+                Path p = ConsolidatingChangeLog.getPath(nodeId);
+                return p.getNameElement().getIndex() > 1;
+            }
+        }
+
+        /**
+         * Factory method for creating a {@link ReorderNodes ReorderNodes} operation.
+         *
+         * @see Batch#reorderNodes(NodeId, NodeId, NodeId)
+         * @param parentId
+         * @param srcNodeId
+         * @param beforeNodeId
+         * @return
+         */
+        public static CancelableOperation reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) {
+            return new ReorderNodes(parentId, srcNodeId, beforeNodeId);
+        }
+
+        // -----------------------------------------------------< SetMixins >---
+
+        /**
+         * A <code>SetMixins</code> operation might cancel with
+         * {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and
+         * {@link ConsolidatingChangeLog.CancelableOperations.SetMixins SetMixins} operations.
+         */
+        public static class SetMixins extends Operations.SetMixins implements CancelableOperation {
+
+            public SetMixins(NodeId nodeId, Name[] mixinNodeTypeNames) {
+                super(nodeId, mixinNodeTypeNames);
+            }
+
+            /**
+             * @return
+             * <ul>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_THIS CANCEL_THIS} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has an node higher up
+             *   the hierarchy or this node as target. Or if <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.SetMixins SetMixins} which has this node
+             *   as target and has the same <code>mixinNodeTypeNames</code>.</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE} otherwise.</li>
+             * </ul>
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(nodeId);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    return thisPath.isDescendantOf(otherPath) || thisPath.equals(otherPath)
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                if (other instanceof SetMixins) {
+                    SetMixins setMixin = (SetMixins) other;
+                    if (mixinNodeTypeNames.length == setMixin.mixinNodeTypeNames.length) {
+                        Path thisPath = ConsolidatingChangeLog.getPath(nodeId);
+                        Path otherPath = ConsolidatingChangeLog.getPath(setMixin.nodeId);
+                        if (thisPath.equals(otherPath)) {
+                            for (int k = 0; k < mixinNodeTypeNames.length; k++) {
+                                if (!mixinNodeTypeNames[k].equals(setMixin.mixinNodeTypeNames[k])) {
+                                    return CANCEL_NONE;
+                                }
+                            }
+                            return CANCEL_THIS;
+                        }
+                    }
+                }
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating a {@link SetMixins SetMixins} operation.
+         *
+         * @see Batch#setMixins(NodeId, Name[])
+         * @param nodeId
+         * @param mixinNodeTypeNames
+         * @return
+         */
+        public static CancelableOperation setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) {
+            return new SetMixins(nodeId, mixinNodeTypeNames);
+        }
+
+        // -----------------------------------------------------< SetValue >---
+
+        /**
+         * A <code>SetValue</code> operation might cancel with
+         * {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and
+         * {@link ConsolidatingChangeLog.CancelableOperations.SetValue SetValue} operations.
+         */
+        public static class SetValue extends Operations.SetValue implements CancelableOperation {
+            public SetValue(PropertyId propertyId, QValue value) {
+                super(propertyId, value);
+            }
+
+            public SetValue(PropertyId propertyId, QValue[] values) {
+                super(propertyId, values);
+            }
+
+            /**
+             * @return
+             * <ul>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_THIS CANCEL_THIS} if
+             *   <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.Remove Remove} and has an node higher up
+             *   the hierarchy or this node as target. Or if <code>other</code> is an instance of
+             *   {@link ConsolidatingChangeLog.CancelableOperations.SetValue SetValue} which has this
+             *   property as target</li>
+             * <li>{@link ConsolidatingChangeLog.CancelableOperation#CANCEL_NONE CANCEL_NONE} otherwise.</li>
+             * </ul>
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(propertyId);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    return thisPath.isDescendantOf(otherPath) || thisPath.equals(otherPath)
+                        ? CANCEL_THIS
+                        : CANCEL_NONE;
+                }
+                if (other instanceof SetValue) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(propertyId);
+                    Path otherPath = ConsolidatingChangeLog.getPath(((SetValue) other).propertyId);
+                    if (thisPath.equals(otherPath)) {
+                        return CANCEL_THIS;
+                    }
+                }
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating a {@link SetValue SetValue} operation.
+         *
+         * @see Batch#setValue(PropertyId, QValue)
+         * @param propertyId
+         * @param value
+         * @return
+         */
+        public static CancelableOperation setValue(PropertyId propertyId, QValue value) {
+            return new SetValue(propertyId, value);
+        }
+
+        /**
+         * Factory method for creating a {@link SetValue SetValue} operation.
+         *
+         * @see Batch#setValue(PropertyId, QValue[])
+         * @param propertyId
+         * @param values
+         * @return
+         */
+        public static CancelableOperation setValue(PropertyId propertyId, QValue[] values) {
+            return new SetValue(propertyId, values);
+        }
+
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,34 @@
+/*
+ * 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.spi.commons.batch;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Batch;
+
+/**
+ * An <code>Operation</code> represents a method call on a {@link Batch}.
+ */
+public interface Operation {
+
+    /**
+     * Apply this operation to the given {@link Batch}
+     * @param batch
+     * @throws RepositoryException
+     */
+    public void apply(Batch batch) throws RepositoryException;
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,698 @@
+/*
+ * 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.spi.commons.batch;
+
+import java.util.Arrays;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Batch;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.QValue;
+
+/**
+ * Factory for creating {@link Operation}s. The inner classes of this class
+ * all implement the <code>Operation</code> interface. They are representatives
+ * for the method calls on a {@link Batch}. In addition {@link Empty} represents
+ * the empty operation which does nothing.
+ */
+public final class Operations {
+    private Operations() {
+        super();
+    }
+
+    // -----------------------------------------------------< Empty >---
+
+    /**
+     * Representative of the empty {@link Operation} which does nothing when
+     * applied to a {@link Batch}.
+     */
+    public static class Empty implements Operation {
+        private static final Empty INSTANCE = new Empty();
+
+        protected Empty() {
+            super();
+        }
+
+        /**
+         * This method has no effect.
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException { /* nothing to do */ }
+
+        public String toString() {
+            return "Empty[]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            return other instanceof Empty;
+        }
+
+        public int hashCode() {
+            return Empty.class.hashCode();
+        }
+    }
+
+    /**
+     * Factory method for creating an {@link Empty} operation.
+     * @return
+     */
+    public static Operation empty() {
+        return Empty.INSTANCE;
+    }
+
+    // -----------------------------------------------------< AddNode >---
+
+    /**
+     * Representative of an add-node {@link Operation} which calls
+     * {@link Batch#addNode(NodeId, Name, Name, String)} when applied to a {@link Batch}.
+     */
+    public static class AddNode implements Operation {
+        protected final NodeId parentId;
+        protected final Name nodeName;
+        protected final Name nodetypeName;
+        protected final String uuid;
+
+        /**
+         * Create a new add-node {@link Operation} for the given arguments.
+         * @see Batch#addNode(NodeId, Name, Name, String)
+         *
+         * @param parentId
+         * @param nodeName
+         * @param nodetypeName
+         * @param uuid
+         */
+        public AddNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid) {
+            super();
+            this.parentId = parentId;
+            this.nodeName = nodeName;
+            this.nodetypeName = nodetypeName;
+            this.uuid = uuid;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.addNode(parentId, nodeName, nodetypeName, uuid);
+        }
+
+        public String toString() {
+            return "AddNode[" + parentId + ", " + nodeName + ", " + nodetypeName + ", " + uuid + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof AddNode) {
+                return equals((AddNode) other);
+            }
+            return false;
+        }
+
+        public boolean equals(AddNode other) {
+            return Operations.equals(parentId, other.parentId)
+                && Operations.equals(nodeName, other.nodeName)
+                && Operations.equals(nodetypeName, other.nodetypeName)
+                && Operations.equals(uuid, other.uuid);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                      41 * (
+                          41 * (
+                              41 + Operations.hashCode(parentId))
+                          + Operations.hashCode(nodeName))
+                      + Operations.hashCode(nodetypeName))
+                  + Operations.hashCode(uuid);
+        }
+
+    }
+
+    /**
+     * Factory method for creating an {@link AddNode} operation.
+     * @see Batch#addNode(NodeId, Name, Name, String)
+     *
+     * @param parentId
+     * @param nodeName
+     * @param nodetypeName
+     * @param uuid
+     * @return
+     */
+    public static Operation addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid) {
+        return new AddNode(parentId, nodeName, nodetypeName, uuid);
+    }
+
+    // -----------------------------------------------------< AddProperty >---
+
+    /**
+     * Representative of an add-property {@link Operation} which calls
+     * {@link Batch#addProperty(NodeId, Name, QValue)} or {@link Batch#addProperty(NodeId, Name, QValue[])}
+     * depending on whether the property is multi valued or not when applied to a {@link Batch}.
+     */
+    public static class AddProperty implements Operation {
+        protected final NodeId parentId;
+        protected final Name propertyName;
+        protected final QValue[] values;
+        protected final boolean isMultivalued;
+
+        private AddProperty(NodeId parentId, Name propertyName, QValue[] values, boolean isMultivalued) {
+            super();
+            this.parentId = parentId;
+            this.propertyName = propertyName;
+            this.values = values;
+            this.isMultivalued = isMultivalued;
+        }
+
+        /**
+         * Create a new add-property {@link Operation} for the given arguments.
+         * @see Batch#addProperty(NodeId, Name, QValue)
+         *
+         * @param parentId
+         * @param propertyName
+         * @param value
+         */
+        public AddProperty(NodeId parentId, Name propertyName, QValue value) {
+            this(parentId, propertyName, new QValue[] { value }, false);
+        }
+
+        /**
+         * Create a new add-property {@link Operation} for the given arguments.
+         * @see Batch#addProperty(NodeId, Name, QValue[])
+         *
+         * @param parentId
+         * @param propertyName
+         * @param values
+         */
+        public AddProperty(NodeId parentId, Name propertyName, QValue[] values) {
+            this(parentId, propertyName, values, true);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            if (isMultivalued) {
+                batch.addProperty(parentId, propertyName, values);
+            }
+            else {
+                batch.addProperty(parentId, propertyName, values[0]);
+            }
+        }
+
+        public String toString() {
+            return "AddProperty[" + parentId + ", " + propertyName + ", " + values + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof AddProperty) {
+                return equals((AddProperty) other);
+            }
+            return false;
+        }
+
+        public boolean equals(AddProperty other) {
+            return Operations.equals(parentId, other.parentId)
+                && Operations.equals(propertyName, other.propertyName)
+                && isMultivalued == other.isMultivalued
+                && Arrays.equals(values, other.values);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                       41 * (
+                           41 + Operations.hashCode(parentId))
+                       + Operations.hashCode(propertyName))
+                   + Operations.hashCode(values);
+        }
+    }
+
+    /**
+     * Factory method for creating an {@link AddProperty} operation.
+     *
+     * @see Batch#addProperty(NodeId, Name, QValue)
+     * @param parentId
+     * @param propertyName
+     * @param value
+     * @return
+     */
+    public static Operation addProperty(NodeId parentId, Name propertyName, QValue value) {
+        return new AddProperty(parentId, propertyName, value);
+    }
+
+    /**
+     * Factory method for creating an {@link AddProperty} operation.
+     *
+     * @see Batch#addProperty(NodeId, Name, QValue[])
+     * @param parentId
+     * @param propertyName
+     * @param values
+     * @return
+     */
+    public static Operation addProperty(NodeId parentId, Name propertyName, QValue[] values) {
+        return new AddProperty(parentId, propertyName, values);
+    }
+
+    // -----------------------------------------------------< Move >---
+
+    /**
+     * Representative of a move {@link Operation} which calls
+     * {@link Batch#move(NodeId, NodeId, Name)} when applied to a {@link Batch}.
+     */
+    public static class Move implements Operation {
+        protected final NodeId srcNodeId;
+        protected final NodeId destParentNodeId;
+        protected final Name destName;
+
+        /**
+         * Create a new move {@link Operation} for the given arguments.
+         *
+         * @see Batch#move(NodeId, NodeId, Name)
+         * @param srcNodeId
+         * @param destParentNodeId
+         * @param destName
+         */
+        public Move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) {
+            super();
+            this.srcNodeId = srcNodeId;
+            this.destParentNodeId = destParentNodeId;
+            this.destName = destName;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.move(srcNodeId, destParentNodeId, destName);
+        }
+
+        public String toString() {
+            return "Move[" + srcNodeId + ", " + destParentNodeId + ", " + destName + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof Move) {
+                return equals((Move) other);
+            }
+            return false;
+        }
+
+        public boolean equals(Move other) {
+            return Operations.equals(srcNodeId, other.srcNodeId)
+                && Operations.equals(destParentNodeId, other.destParentNodeId)
+                && Operations.equals(destName, other.destName);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                        41 * (
+                            41 + Operations.hashCode(srcNodeId))
+                        + Operations.hashCode(destParentNodeId))
+                    + Operations.hashCode(destName);
+        }
+    }
+
+    /**
+     * Factory method for creating a {@link Move} operation.
+     *
+     * @see Batch#move(NodeId, NodeId, Name)
+     * @param srcNodeId
+     * @param destParentNodeId
+     * @param destName
+     * @return
+     */
+    public static Operation move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) {
+        return new Move(srcNodeId, destParentNodeId, destName);
+    }
+
+    // -----------------------------------------------------< Remove >---
+
+    /**
+     * Representative of a remove {@link Operation} which calls {@link Batch#remove(ItemId)} when
+     * applied to a {@link Batch}.
+     */
+    public static class Remove implements Operation {
+        protected final ItemId itemId;
+
+        /**
+         * Create a new remove {@link Operation} for the given arguments.
+         *
+         * @see Batch#move(NodeId, NodeId, Name)
+         * @param itemId
+         */
+        public Remove(ItemId itemId) {
+            super();
+            this.itemId = itemId;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.remove(itemId);
+        }
+
+        public String toString() {
+            return "Remove[" + itemId + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof Remove) {
+                return equals((Remove) other);
+            }
+            return false;
+        }
+
+        public boolean equals(Remove other) {
+            return Operations.equals(itemId, other.itemId);
+        }
+
+        public int hashCode() {
+            return 41 + Operations.hashCode(itemId);
+        }
+    }
+
+    /**
+     * Factory method for creating a {@link Remove} operation.
+     *
+     * @see Batch#move(NodeId, NodeId, Name)
+     * @param itemId
+     * @return
+     */
+    public static Operation remove(ItemId itemId) {
+        return new Remove(itemId);
+    }
+
+    // -----------------------------------------------------< ReorderNodes >---
+
+    /**
+     * Representative of a reorder-nodes {@link Operation} which calls
+     * {@link Batch#reorderNodes(NodeId, NodeId, NodeId)} when applied to a {@link Batch}.
+     */
+    public static class ReorderNodes implements Operation {
+        protected final NodeId parentId;
+        protected final NodeId srcNodeId;
+        protected final NodeId beforeNodeId;
+
+        /**
+         * Create a new reorder-nodes {@link Operation} for the given arguments.
+         *
+         * @see Batch#reorderNodes(NodeId, NodeId, NodeId)
+         * @param parentId
+         * @param srcNodeId
+         * @param beforeNodeId
+         */
+        public ReorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) {
+            super();
+            this.parentId = parentId;
+            this.srcNodeId = srcNodeId;
+            this.beforeNodeId = beforeNodeId;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.reorderNodes(parentId, srcNodeId, beforeNodeId);
+        }
+
+        public String toString() {
+            return "ReorderNodes[" + parentId + ", " + srcNodeId + ", " + beforeNodeId + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof ReorderNodes) {
+                return equals((ReorderNodes) other);
+            }
+            return false;
+        }
+
+        public boolean equals(ReorderNodes other) {
+            return Operations.equals(parentId, other.parentId)
+                && Operations.equals(srcNodeId, other.srcNodeId)
+                && Operations.equals(beforeNodeId, other.beforeNodeId);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                        41 * (
+                            41 + Operations.hashCode(parentId))
+                        + Operations.hashCode(srcNodeId))
+                    + Operations.hashCode(beforeNodeId);
+        }
+    }
+
+    /**
+     * Factory method for creating a reorder-nodes {@link Operation} for the given arguments.
+     *
+     * @see Batch#reorderNodes(NodeId, NodeId, NodeId)
+     * @param parentId
+     * @param srcNodeId
+     * @param beforeNodeId
+     * @return
+     */
+    public static Operation reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) {
+        return new ReorderNodes(parentId, srcNodeId, beforeNodeId);
+    }
+
+    // -----------------------------------------------------< SetMixins >---
+
+    /**
+     * Representative of a set-mixin {@link Operation} which calls
+     * {@link Batch#setMixins(NodeId, Name[])} when applied to a {@link Batch}.
+     */
+    public static class SetMixins implements Operation {
+        protected final NodeId nodeId;
+        protected final Name[] mixinNodeTypeNames;
+
+        /**
+         * Create a new set-mixin {@link Operation} for the given arguments.
+         *
+         * @see Batch#setMixins(NodeId, Name[])
+         * @param nodeId
+         * @param mixinNodeTypeNames
+         */
+        public SetMixins(NodeId nodeId, Name[] mixinNodeTypeNames) {
+            super();
+            this.nodeId = nodeId;
+            this.mixinNodeTypeNames = mixinNodeTypeNames;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.setMixins(nodeId, mixinNodeTypeNames);
+        }
+
+        public String toString() {
+            return "SetMixins[" + nodeId + ", " + mixinNodeTypeNames + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof SetMixins) {
+                return equals((SetMixins) other);
+            }
+            return false;
+        }
+
+        public boolean equals(SetMixins other) {
+            return Operations.equals(nodeId, other.nodeId)
+                && Arrays.equals(mixinNodeTypeNames, other.mixinNodeTypeNames);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                        41 + Operations.hashCode(nodeId))
+                    + Operations.hashCode(mixinNodeTypeNames);
+        }
+    }
+
+    /**
+     * Factory method for creating a set-mixin {@link Operation} for the given arguments.
+     *
+     * @see Batch#setMixins(NodeId, Name[])
+     * @param nodeId
+     * @param mixinNodeTypeNames
+     * @return
+     */
+    public static Operation setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) {
+        return new SetMixins(nodeId, mixinNodeTypeNames);
+    }
+
+    // -----------------------------------------------------< SetValue >---
+
+    /**
+     * Representative of a set-value {@link Operation} which calls
+     * {@link Batch#setValue(PropertyId, QValue)} or {@link Batch#setValue(PropertyId, QValue[])}
+     * depending on whether the property is multi valued or not when applied to a {@link Batch}.
+     */
+    public static class SetValue implements Operation {
+        protected final PropertyId propertyId;
+        protected final QValue[] values;
+        protected final boolean isMultivalued;
+
+        private SetValue(PropertyId propertyId, QValue[] values, boolean isMultivalued) {
+            super();
+            this.propertyId = propertyId;
+            this.values = values;
+            this.isMultivalued = isMultivalued;
+        }
+
+        /**
+         * Create a new set-value {@link Operation} for the given arguments.
+         *
+         * @see Batch#setValue(PropertyId, QValue)
+         * @param propertyId
+         * @param value
+         */
+        public SetValue(PropertyId propertyId, QValue value) {
+            this(propertyId, new QValue[]{ value }, false);
+        }
+
+        /**
+         * Create a new set-value {@link Operation} for the given arguments.
+         *
+         * @see Batch#setValue(PropertyId, QValue[])
+         * @param propertyId
+         * @param values
+         */
+        public SetValue(PropertyId propertyId, QValue[] values) {
+            this(propertyId, values, true);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            if (isMultivalued) {
+                batch.setValue(propertyId, values);
+            }
+            else {
+                batch.setValue(propertyId, values[0]);
+            }
+        }
+
+        public String toString() {
+            return "SetValue[" + propertyId + ", " + values + "]";
+        }
+
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof SetValue) {
+                return equals((SetValue) other);
+            }
+            return false;
+        }
+
+        public boolean equals(SetValue other) {
+            return Operations.equals(propertyId, other.propertyId)
+                && isMultivalued == other.isMultivalued
+                && Arrays.equals(values, other.values);
+        }
+
+        public int hashCode() {
+            return 41 * (
+                        41 + Operations.hashCode(propertyId))
+                   + Operations.hashCode(values);
+        }
+    }
+
+    /**
+     * Factory method for creating set-value {@link Operation} for the given arguments.
+     *
+     * @see Batch#setValue(PropertyId, QValue)
+     * @param propertyId
+     * @param value
+     * @return
+     */
+    public static Operation setValue(PropertyId propertyId, QValue value) {
+        return new SetValue(propertyId, value);
+    }
+
+    /**
+     * Factory method for creating a set-value {@link Operation} for the given arguments.
+     *
+     * @see Batch#setValue(PropertyId, QValue[])
+     * @param propertyId
+     * @param values
+     * @return
+     */
+    public static Operation setValue(final PropertyId propertyId, final QValue[] values) {
+        return new SetValue(propertyId, values);
+    }
+
+    // -----------------------------------------------------< private >---
+
+    protected static boolean equals(Object o1, Object o2) {
+        return o1 == null
+            ? o2 == null
+            : o1.equals(o2);
+    }
+
+    protected static int hashCode(Object o) {
+        return o == null
+            ? 0
+            : o.hashCode();
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java?rev=747347&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java Tue Feb 24 11:26:41 2009
@@ -0,0 +1,447 @@
+/*
+ * 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.spi.commons.batch;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import junit.framework.TestCase;
+
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.Path.Element;
+import org.apache.jackrabbit.spi.commons.identifier.IdFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
+
+public class ConsolidatedBatchTest extends TestCase {
+    private final IdFactory idFactory = IdFactoryImpl.getInstance();
+    private final PathFactory pathFactory = PathFactoryImpl.getInstance();
+    private final NameFactory nameFactory = NameFactoryImpl.getInstance();
+    private final QValueFactory valueFactory = QValueFactoryImpl.getInstance();
+
+    private final ChangeLog[][] changeLogs;
+
+    public ConsolidatedBatchTest() throws RepositoryException {
+        changeLogs = new TestChangeLog[][] {
+            { new TestChangeLog() // 1
+                .addNode("/my/path/MyNode")
+                .addNode("/my/path/MyNode2")
+             ,new TestChangeLog()
+                .addNode("/my/path/MyNode")
+                .addNode("/my/path/MyNode2")
+            }
+            ,
+            { new TestChangeLog() // 2
+                .addNode("/my/path/MyNode")
+                .delItem("/my/path/MyNode")
+             ,new TestChangeLog()
+            }
+            ,
+            { new TestChangeLog() // 3
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path/MyNode")
+                .addNode("/my/path/MyNode2")
+                .addNode("/my/path2")
+                .addNode("/my/path2/MyNode")
+                .delItem("/my")
+             ,new TestChangeLog()
+            }
+            ,
+            { new TestChangeLog() // 4
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .delItem("/my/path2")
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+            }
+            ,
+            { new TestChangeLog() // 5
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .delItem("/my/path")
+                .delItem("/my/path2")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .delItem("/my/path")
+                .delItem("/my/path2")
+            }
+            ,
+            { new TestChangeLog() // 6
+                .addNode("/my")
+                .addNode("/my/path")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+            }
+            ,
+            { new TestChangeLog() // 7
+                .addNode("/my")
+                .addNode("/my/path")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .delItem("/my/path/Prop")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addNode("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 8
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .movItem("/my/path", "my/path2")
+                .delItem("/my")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .movItem("/my/path", "my/path2")
+                .delItem("/my")
+            }
+            ,
+            { new TestChangeLog() // 9
+                .addNode("/my")
+                .addNode("/my/path")
+                .addNode("/my/path2")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .delItem("/my")
+                .movItem("/my/path", "my/path2")
+             ,new TestChangeLog()
+                .movItem("/my/path", "my/path2")
+            }
+            ,
+            { new TestChangeLog() // 10
+                .ordNode("/my/path")
+                .ordNode("/my/path")
+                .ordNode("/my/path")
+             ,new TestChangeLog()
+                .ordNode("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 11
+                .ordNode("/my/path")
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .delItem("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 12
+                .addNode("/my")
+                .ordNode("/my/path")
+                .delItem("/my")
+             ,new TestChangeLog()
+            }
+            ,
+            { new TestChangeLog() // 13
+                .addNode("/my")
+                .mixNode("/my", "MyMixin")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .mixNode("/my", "MyMixin")
+            }
+            ,
+            { new TestChangeLog() // 14
+                .addNode("/my")
+                .mixNode("/my", "MyMixin")
+                .mixNode("/my", "MyMixin")
+                .mixNode("/my", "MyMixin")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .mixNode("/my", "MyMixin")
+            }
+            ,
+            { new TestChangeLog() // 15
+                .addNode("/my")
+                .mixNode("/my", "MyMixin")
+                .mixNode("/my", "MyMixin")
+                .mixNode("/my", "MyMixin")
+                .delItem("/my")
+             ,new TestChangeLog()
+            }
+            ,
+            { new TestChangeLog() // 16
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello2", PropertyType.STRING)
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello2", PropertyType.STRING)
+            }
+            ,
+            { new TestChangeLog() // 17
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello", PropertyType.STRING)
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+            }
+            ,
+            { new TestChangeLog() // 18
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello2", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello3", PropertyType.STRING)
+             ,new TestChangeLog()
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello3", PropertyType.STRING)
+            }
+            ,
+            { new TestChangeLog() // 19
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello2", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello3", PropertyType.STRING)
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .delItem("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 20
+                .addNode("/my")
+                .addProp("/my/path/Prop", "hello", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello2", PropertyType.STRING)
+                .setValu("/my/path/Prop", "hello3", PropertyType.STRING)
+                .delItem("/my")
+             ,new TestChangeLog()
+            }
+            ,
+            { new TestChangeLog() // 21
+                .addNode("/my")
+                .ordNode("/my")
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .ordNode("/my")
+                .delItem("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 22
+                .addNode("/my")
+                .ordNode("/my")
+                .ordNode("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .ordNode("/my")
+                .ordNode("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 23
+                .addNode("/my")
+                .ordNode("/my")
+                .addNode("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .ordNode("/my")
+                .addNode("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 24
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .delItem("/my/path")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .delItem("/my/path")
+            }
+            ,
+            { new TestChangeLog() // 25
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .mixNode("/my", "mix2")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .mixNode("/my", "mix2")
+            }
+            ,
+            { new TestChangeLog() // 26
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .addNode("/my2")
+             ,new TestChangeLog()
+                .addNode("/my")
+                .mixNode("/my", "mix")
+                .addNode("/my2")
+            }
+            ,
+            { new TestChangeLog() // 27
+                .setValu("/my/Prop", "value", PropertyType.STRING)
+                .delItem("/my2")
+             ,new TestChangeLog()
+                .setValu("/my/Prop", "value", PropertyType.STRING)
+                .delItem("/my2")
+            }
+            ,
+            { new TestChangeLog() // 28
+                .setValu("/my/Prop", "value", PropertyType.STRING)
+                .addNode("/my2")
+             ,new TestChangeLog()
+                .setValu("/my/Prop", "value", PropertyType.STRING)
+                .addNode("/my2")
+            }
+            ,
+            { new TestChangeLog() // 29
+                .addProp("/my/Prop", "value", PropertyType.STRING)
+                .delItem("/my2")
+             ,new TestChangeLog()
+                .addProp("/my/Prop", "value", PropertyType.STRING)
+                .delItem("/my2")
+            }
+            ,
+            { new TestChangeLog() // 30
+                .addProp("/my/Prop", "value", PropertyType.STRING)
+                .setValu("/my/Prop", null, PropertyType.STRING)
+             ,new TestChangeLog()
+            }
+            ,
+        };
+    }
+
+    public void testChangeLogConsolidation() throws RepositoryException {
+        for (int k = 0; k < changeLogs.length; k++) {
+            ChangeLog changeLog = changeLogs[k][0];
+            ChangeLog expected = changeLogs[k][1];
+            assertEquals("Test no " + (k + 1), expected, changeLog.apply(new ConsolidatingChangeLog()));
+        }
+    }
+
+    // -----------------------------------------------------< private >---
+
+    private String nsPrefix(String name) {
+        return name.startsWith("{")
+            ? name
+            : "{}" + name;
+    }
+
+    private Name createName(String name) {
+        return nameFactory.create(nsPrefix(name));
+    }
+
+    private Path createPath(String path) {
+        String[] names = path.split("/");
+        Element[] elements = new Element[names.length];
+        for (int k = 0; k < names.length; k++) {
+            if ("".equals(names[k])) {
+                elements[k] = pathFactory.getRootElement();
+            }
+            else {
+                elements[k] = pathFactory.createElement(nameFactory.create(nsPrefix(names[k])));
+            }
+        }
+        if (elements.length == 0) {
+            return pathFactory.getRootPath();
+        }
+        return pathFactory.create(elements);
+    }
+
+    private NodeId createNodeId(Path path) {
+        return idFactory.createNodeId((String) null, path);
+    }
+
+    public NodeId createNodeId(String nodeId) {
+        return createNodeId(createPath(nodeId));
+    }
+
+    public PropertyId createPropertyId(String propertyId) throws PathNotFoundException {
+        Path path = createPath(propertyId);
+        return idFactory.createPropertyId(createNodeId(path.getAncestor(1)), path.getNameElement().getName());
+    }
+
+    private QValue createValue(String value, int type) throws RepositoryException {
+        return value == null
+            ? null
+            : valueFactory.create(value, type);
+    }
+
+    // -----------------------------------------------------< ChangeLog >---
+
+    private class TestChangeLog extends ChangeLogImpl {
+
+        public TestChangeLog addNode(String nodeId) throws RepositoryException {
+            Path path = createPath(nodeId);
+            addNode(createNodeId(path.getAncestor(1)), path.getNameElement().getName(),
+                    createName("anyType"), null);
+
+            return this;
+        }
+
+        public TestChangeLog addProp(String propertyId, String value, int type) throws RepositoryException {
+            Path path = createPath(propertyId);
+            addProperty(createNodeId(path.getAncestor(1)), path.getNameElement().getName(), createValue(value, type));
+            return this;
+        }
+
+        public TestChangeLog movItem(String srcNodeId, String destNodeId) throws RepositoryException {
+            Path path = createPath(destNodeId);
+            move(createNodeId(srcNodeId), createNodeId(path.getAncestor(1)), path.getNameElement().getName());
+            return this;
+        }
+
+        public TestChangeLog delItem(String nodeId) throws RepositoryException {
+            remove(createNodeId(nodeId));
+            return this;
+        }
+
+        public ChangeLog removeProperty(String propertyId) throws RepositoryException {
+            remove(createPropertyId(propertyId));
+            return this;
+        }
+
+        public TestChangeLog ordNode(String nodeId) throws RepositoryException {
+            NodeId srcNodeId = createNodeId(nodeId);
+            NodeId parentId = createNodeId(srcNodeId.getPath());
+            reorderNodes(parentId, srcNodeId, createNodeId("/any/path"));
+            return this;
+        }
+
+        public TestChangeLog mixNode(String nodeId, String mixinName) throws RepositoryException {
+            setMixins(createNodeId(nodeId), new Name[] { createName(mixinName) });
+            return this;
+        }
+
+        public TestChangeLog setValu(String propertyId, String value, int type) throws RepositoryException {
+            setValue(createPropertyId(propertyId), createValue(value, type));
+            return this;
+        }
+
+    }
+
+}
+

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java
------------------------------------------------------------------------------
    svn:eol-style = native