Index: /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/xml/GenericImporter.java
===================================================================
--- /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/xml/GenericImporter.java	(revision 0)
+++ /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/xml/GenericImporter.java	(revision 0)
@@ -0,0 +1,489 @@
+/*
+ * 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.core.xml;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.core.BatchedItemOperations;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.core.nodetype.NodeDef;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.uuid.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * GenericImporter. It imports an XML document managed by the right ContentHandler into 
+ * the specified BatchedItemOperations
+ *
+ */
+public class GenericImporter {
+    
+    private static Logger log = LoggerFactory.getLogger(SysViewImporter.class);
+    
+    private final NodeState importTarget;
+    private final NodeTypeRegistry ntReg;
+    private final HierarchyManager hierMgr;
+    private final BatchedItemOperations itemOps;
+    
+    private final int uuidBehavior;
+    
+    private boolean aborted = false;
+    private Stack parents;
+    
+    /**
+     * helper object that keeps track of remapped uuid's and imported reference
+     * properties that might need correcting depending on the uuid mappings
+     */
+    private final ReferenceChangeTracker refTracker;
+    
+    private boolean raw;
+    
+    private boolean skip = false;
+    
+    //Used to find when stopping skipping
+    private NodeInfo skipNode;
+    
+    private NodeState existing = null;
+    
+    /**
+     * Creates a new <code>sysViewImporter</code> instance.
+     *
+     * @param parentPath   target path where to add the imported subtree
+     * @param hierMgr the HierarchyManager of the Repository
+     * @param ntReg the NodeTypeRegistry of the repository
+     * @param itemOps BatchedItemOperations to use
+     * @param uuidBehavior flag that governs how incoming UUIDs are handled
+     * @param raw boolean: if true we will not initialize anything (ie versioning) 
+     * and will not check anything before importing the data
+     * @throws PathNotFoundException        if no node exists at
+     *                                      <code>parentPath</code> or if the
+     *                                      current session is not granted read
+     *                                      access.
+     * @throws ConstraintViolationException if the node at
+     *                                      <code>parentPath</code> is protected
+     * @throws VersionException             if the node at
+     *                                      <code>parentPath</code> is not
+     *                                      checked-out
+     * @throws LockException                if a lock prevents the addition of
+     *                                      the subtree
+     * @throws RepositoryException          if another error occurs
+     */
+    public GenericImporter(Path parentPath,
+            HierarchyManager hierMgr,
+            NodeTypeRegistry ntReg,
+            BatchedItemOperations itemOps,
+            int uuidBehavior,
+            boolean raw)
+    throws PathNotFoundException, ConstraintViolationException,
+    VersionException, LockException, RepositoryException {
+        
+        this.hierMgr = hierMgr;
+        this.itemOps = itemOps;
+        this.raw = raw;
+        
+        //Perform preliminary checks
+        itemOps.verifyCanWrite(parentPath);
+        importTarget = itemOps.getNodeState(parentPath);
+        this.ntReg = ntReg;
+        this.uuidBehavior = uuidBehavior;
+        aborted = false;
+        refTracker = new ReferenceChangeTracker();
+        parents = new Stack();
+        parents.push(importTarget);
+    }
+    
+    /**
+     * Can we add this node
+     *
+     * @param parent
+     * @param nodeInfo
+     * @return
+     * @throws RepositoryException 
+     * @throws ItemExistsException 
+     * @throws ItemNotFoundException 
+     * @throws LockException 
+     * @throws VersionException 
+     * @throws AccessDeniedException 
+     * @throws ConstraintViolationException 
+     */
+    private boolean checkNode(NodeState parent, NodeInfo nodeInfo, List propInfo) throws ConstraintViolationException, AccessDeniedException, VersionException, LockException, ItemNotFoundException, ItemExistsException, RepositoryException {
+        
+        itemOps.checkAddNode(parent, nodeInfo.getName(), nodeInfo.getNodeTypeName(),
+                BatchedItemOperations.CHECK_ACCESS
+                | BatchedItemOperations.CHECK_CONSTRAINTS
+                | BatchedItemOperations.CHECK_LOCK
+                | BatchedItemOperations.CHECK_VERSIONING);
+        
+        QName nodeName = nodeInfo.getName();
+        QName ntName = nodeInfo.getNodeTypeName();
+        
+        if (parent.hasChildNodeEntry(nodeName)) {
+            // a node with that name already exists...
+            //No need to check for more than one, since if it
+            //is the case we can import it.
+            NodeState.ChildNodeEntry entry =
+                parent.getChildNodeEntry(nodeName, 1);
+            NodeId idExisting = entry.getId();
+            NodeState existing = (NodeState) itemOps.getItemState(idExisting);
+            NodeDef def = ntReg.getNodeDef(existing.getDefinitionId());
+            if (!def.allowsSameNameSiblings()) {
+                // existing doesn't allow same-name siblings,
+                // check for potential conflicts
+                EffectiveNodeType entExisting =
+                    itemOps.getEffectiveNodeType(existing);
+                if (!raw && def.isProtected() && entExisting.includesNodeType(ntName)) {
+                    return false;
+                }
+                
+                if (def.isAutoCreated() && entExisting.includesNodeType(ntName)) {
+                    // this node has already been auto-created,
+                    // no need to create it
+                    this.existing = existing;
+                } else {
+                    throw new ItemExistsException(itemOps.safeGetJCRPath(existing.getNodeId()));
+                }
+            }
+        }
+        
+        if (parent.hasPropertyName(nodeName)) {
+            /**
+             * a property with the same name already exists; if this property
+             * has been imported as well (e.g. through document view import
+             * where an element can have the same name as one of the attributes
+             * of its parent element) we have to rename the onflicting property;
+             *
+             * see http://issues.apache.org/jira/browse/JCR-61
+             */
+            PropertyId propId = new PropertyId(parent.getNodeId(), nodeName);
+            PropertyState conflicting = itemOps.getPropertyState(propId);
+            if (conflicting.getStatus() == ItemState.STATUS_NEW) {
+                // assume this property has been imported as well;
+                // rename conflicting property
+                // @todo use better reversible escaping scheme to create unique name
+                QName newName = new QName(nodeName.getNamespaceURI(), nodeName.getLocalName() + "_");
+                if (parent.hasPropertyName(newName)) {
+                    newName = new QName(newName.getNamespaceURI(), newName.getLocalName() + "_");
+                }
+                PropertyState newProp =
+                    itemOps.createPropertyState(parent, newName,
+                            conflicting.getType(), conflicting.getValues().length);
+                newProp.setValues(conflicting.getValues());
+                parent.removePropertyName(nodeName);
+                itemOps.store(parent);
+                itemOps.destroy(conflicting);
+            }
+        }
+        
+        return true;
+    }
+    
+    private void createProperties(NodeState myNode, List propInfos) throws ItemNotFoundException, ItemExistsException, ConstraintViolationException, ValueFormatException, RepositoryException {
+        // process properties
+
+        Iterator iter = propInfos.iterator();
+        while (iter.hasNext()) {
+            PropInfo pi = (PropInfo) iter.next();
+            pi.apply(myNode, itemOps, ntReg, refTracker);
+        }
+    }
+    
+    private NodeState createNode(NodeState parent, NodeInfo nodeInfo) throws ConstraintViolationException, RepositoryException {
+        
+        NodeDef def =
+            itemOps.findApplicableNodeDefinition(nodeInfo.getName(), nodeInfo.getNodeTypeName(), parent);
+        
+        // potential uuid conflict
+        NodeState conflicting;
+        NodeState node;
+        
+        try {
+            conflicting = itemOps.getNodeState(nodeInfo.getId());
+        } catch (ItemNotFoundException infe) {
+            conflicting = null;
+        }
+        if (conflicting != null) {
+            // resolve uuid conflict
+            node = resolveUUIDConflict(parent, conflicting, nodeInfo);
+        }
+        else {
+            // do create new node
+            node = itemOps.createNodeState(parent, nodeInfo.getName(), nodeInfo.getNodeTypeName(), nodeInfo.getMixinNames(), null, def);
+        }
+        return node;
+    }
+    
+    private NodeState resolveUUIDConflict(NodeState parent, NodeState conflicting, NodeInfo nodeInfo) throws ItemExistsException, ConstraintViolationException, IllegalStateException, RepositoryException {
+        NodeState node = null;
+        switch (uuidBehavior) {
+        
+        case ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING:
+            NodeId parentId = conflicting.getParentId();
+            if (parentId == null) {
+                String msg = "root node cannot be replaced";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+            // 'replace' current parent with parent of conflicting
+            try {
+                parent = itemOps.getNodeState(parentId);
+            } catch (ItemNotFoundException infe) {
+                // should never get here...
+                String msg = "internal error: failed to retrieve parent state";
+                log.error(msg, infe);
+                throw new RepositoryException(msg, infe);
+            }
+            // remove conflicting:
+            // check if conflicting can be removed
+            // (access rights, node type constraints, locking & versioning status)
+            itemOps.checkRemoveNode(conflicting,
+                    BatchedItemOperations.CHECK_ACCESS
+                    | BatchedItemOperations.CHECK_LOCK
+                    | BatchedItemOperations.CHECK_VERSIONING
+                    | BatchedItemOperations.CHECK_CONSTRAINTS);
+            // do remove conflicting (recursive)
+            itemOps.removeNodeState(conflicting);
+            // do create new node
+            node = itemOps.createNodeState(parent, nodeInfo.getName(),
+                    nodeInfo.getNodeTypeName(), nodeInfo.getMixinNames(),
+                    nodeInfo.getId());
+            break;
+            
+        case ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING: 
+            // make sure conflicting node is not importTarget or an ancestor thereof
+            Path p0 = hierMgr.getPath(importTarget.getNodeId());
+            Path p1 = hierMgr.getPath(conflicting.getNodeId());
+            try {
+                if (p1.equals(p0) || p1.isAncestorOf(p0)) {
+                    String msg = "cannot remove ancestor node";
+                    log.debug(msg);
+                    throw new ConstraintViolationException(msg);
+                }
+            } catch (MalformedPathException mpe) {
+                // should never get here...
+                String msg = "internal error: failed to determine degree of relationship";
+                log.error(msg, mpe);
+                throw new RepositoryException(msg, mpe);
+            }
+            // remove conflicting:
+            // check if conflicting can be removed
+            // (access rights, node type constraints, locking & versioning status)
+            itemOps.checkRemoveNode(conflicting,
+                    BatchedItemOperations.CHECK_ACCESS
+                    | BatchedItemOperations.CHECK_LOCK
+                    | BatchedItemOperations.CHECK_VERSIONING
+                    | BatchedItemOperations.CHECK_CONSTRAINTS);
+            // do remove conflicting (recursive)
+            itemOps.removeNodeState(conflicting);
+
+            // create new with given uuid:
+            // do create new node
+            node = itemOps.createNodeState(parent, nodeInfo.getName(),
+                    nodeInfo.getNodeTypeName(), nodeInfo.getMixinNames(),
+                    nodeInfo.getId());
+            break;
+
+        case ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW:
+            String msg = "a node with uuid " + nodeInfo.getId()
+            + " already exists!";
+            log.debug(msg);
+            throw new ItemExistsException(msg);
+            
+        case ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW: 
+            // create new with new uuid:
+            // check if new node can be added (check access rights &
+            // node type constraints only, assume locking & versioning status
+            // has already been checked on ancestor)
+            node = itemOps.createNodeState(parent, nodeInfo.getName(),
+                    nodeInfo.getNodeTypeName(), nodeInfo.getMixinNames(), null);
+            // remember uuid mapping
+            EffectiveNodeType ent = itemOps.getEffectiveNodeType(node);
+            if (ent.includesNodeType(QName.MIX_REFERENCEABLE)) {
+                refTracker.mappedUUID(nodeInfo.getId().getUUID(), node.getNodeId().getUUID());
+            }
+            break;
+        } 
+        return node;
+    }
+    
+    protected boolean isSkipped(NodeInfo nodeInfo) {
+        return skip;
+    }
+    
+    //-------------------------------------------------------------< Importer >
+    /**
+     * {@inheritDoc}
+     */
+    public void start() throws RepositoryException {
+        try {
+            // start update operation
+            itemOps.edit();
+        } catch (IllegalStateException ise) {
+            aborted = true;
+            String msg = "internal error: failed to start update operation";
+            log.debug(msg);
+            throw new RepositoryException(msg, ise);
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void startNode(NodeInfo nodeInfo, List propInfos)
+    throws RepositoryException {
+        if (aborted) {
+            return;
+        }
+        
+        NodeState parent = (NodeState) parents.peek();
+        
+        if (raw && !checkNode(parent, nodeInfo, propInfos)) {
+            skip = true;
+        }
+        
+        if (skip) {
+            return;
+        }
+        //Should this node and children be skipped?
+        else if (isSkipped(nodeInfo)) {
+            skip  = true;
+            skipNode = nodeInfo;
+            return;
+        }
+        NodeState myNode;
+        if (existing == null) {
+            myNode = createNode(parent, nodeInfo);
+        }
+        else {
+            myNode = existing;
+            existing = null;
+        }
+        createProperties(myNode, propInfos);
+        parents.push(myNode);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void endNode(NodeInfo nodeInfo) throws RepositoryException {
+        //End of skip mode
+        if (skipNode.equals(nodeInfo)) {
+            skip = false;
+            skipNode = null;
+            return;
+        }
+        
+        if (aborted || skip) {
+            return;
+        }
+        
+        try {
+            NodeState node = (NodeState) parents.pop();
+            
+            if (!raw) {
+                postProcess(node);
+            }
+            itemOps.store(node);
+        } catch (IllegalStateException e) {
+            itemOps.cancel();
+            aborted = true;
+        }
+    }
+    
+    private void postProcess(NodeState node) {
+        // TODO Auto-generated method stub
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void end() throws RepositoryException {
+        if (aborted) {
+            itemOps.cancel();
+            return;
+        }
+        
+        /**
+         * adjust references that refer to uuid's which have been mapped to
+         * newly gererated uuid's on import
+         */
+        Iterator iter = refTracker.getProcessedReferences();
+        while (iter.hasNext()) {
+            PropertyState prop = (PropertyState) iter.next();
+            // being paranoid...
+            if (prop.getType() != PropertyType.REFERENCE) {
+                continue;
+            }
+            boolean modified = false;
+            InternalValue[] values = prop.getValues();
+            InternalValue[] newVals = new InternalValue[values.length];
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                UUID original = (UUID) val.internalValue();
+                UUID adjusted = refTracker.getMappedUUID(original);
+                if (adjusted != null) {
+                    newVals[i] = InternalValue.create(adjusted);
+                    modified = true;
+                } else {
+                    // reference doesn't need adjusting, just copy old value
+                    newVals[i] = val;
+                }
+            }
+            if (modified) {
+                prop.setValues(newVals);
+                itemOps.store(prop);
+            }
+        }
+        refTracker.clear();
+
+        // make sure import target is valid according to its definition
+        itemOps.validate(importTarget);
+        
+        // finally store the state of the import target
+        // (the parent of the imported subtree)
+        itemOps.store(importTarget);
+        
+        // finish update
+        itemOps.update();
+    }
+}
+
