jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r795452 [2/3] - in /jackrabbit/sandbox/tripod-JCR-2209: ./ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/
Date Sun, 19 Jul 2009 00:09:41 GMT
Modified: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java?rev=795452&r1=795451&r2=795452&view=diff
==============================================================================
--- jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java (original)
+++ jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java Sun Jul 19 00:09:40 2009
@@ -16,30 +16,29 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import org.apache.jackrabbit.core.NodeImpl;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.VersionException;
+
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.state.ChildNodeEntry;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.state.ChildNodeEntry;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.PropertyType;
-import javax.jcr.ReferentialIntegrityException;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.version.VersionException;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 /**
  * Implements a <code>InternalVersionHistory</code>
  */
@@ -400,7 +399,7 @@
             // Check if there is only root version and version labels nodes
             if (childNodes.length == 2) {
                 log.debug("Removing orphan version history as it contains only two children");
-                NodeStateEx parentNode = vMgr.getNodeStateEx(node.getParentId());
+                NodeStateEx parentNode = node.getParent();
                 // Remove version history node
                 parentNode.removeNode(node.getName());
                 // store changes for this node and his children
@@ -489,22 +488,21 @@
      * @return the newly created version
      * @throws RepositoryException if an error occurs
      */
-    InternalVersionImpl checkin(Name name, NodeImpl src)
+    InternalVersionImpl checkin(Name name, NodeStateEx src)
             throws RepositoryException {
 
         // copy predecessors from src node
         InternalValue[] predecessors;
         if (src.hasProperty(NameConstants.JCR_PREDECESSORS)) {
-            Value[] preds = src.getProperty(NameConstants.JCR_PREDECESSORS).getValues();
-            predecessors = new InternalValue[preds.length];
-            for (int i = 0; i < preds.length; i++) {
-                NodeId predId = new NodeId(preds[i].getString());
+            predecessors = src.getPropertyValues(NameConstants.JCR_PREDECESSORS);
+            // check all predecessors
+            for (InternalValue pred: predecessors) {
+                NodeId predId = pred.getNodeId();
                 // check if version exist
                 if (!nameCache.containsValue(predId)) {
                     throw new RepositoryException(
                             "Invalid predecessor in source node: " + predId);
                 }
-                predecessors[i] = InternalValue.create(predId);
             }
         } else {
             // with simple versioning, the node does not contain a predecessors
@@ -526,7 +524,7 @@
 
         // check for jcr:activity
         if (src.hasProperty(NameConstants.JCR_ACTIVITY)) {
-            InternalValue act = src.getProperty(NameConstants.JCR_ACTIVITY).internalGetValue();
+            InternalValue act = src.getPropertyValue(NameConstants.JCR_ACTIVITY);
             vNode.setPropertyValue(NameConstants.JCR_ACTIVITY, act);
         }
 
@@ -608,7 +606,7 @@
             InternalValue[] ivalues = new InternalValue[mixins.size()];
             Iterator<Name> iter = mixins.iterator();
             for (int i = 0; i < mixins.size(); i++) {
-                ivalues[i] = InternalValue.create((Name) iter.next());
+                ivalues[i] = InternalValue.create(iter.next());
             }
             node.setPropertyValues(NameConstants.JCR_FROZENMIXINTYPES, PropertyType.NAME, ivalues);
         }

Modified: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java?rev=795452&r1=795451&r2=795452&view=diff
==============================================================================
--- jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java (original)
+++ jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java Sun Jul 19 00:09:40 2009
@@ -16,35 +16,53 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.ArrayList;
 
-import javax.jcr.ItemNotFoundException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
 import javax.jcr.version.VersionHistory;
 
-import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.ItemValidator;
 import org.apache.jackrabbit.core.LazyItemIterator;
-import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.WorkspaceImpl;
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
-import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the {@link javax.jcr.version.VersionManager}.
  * <p/>
+ * This class implements the JCR Version Manager interface but most of the
+ * operations are performed in the super classes. this is only cosmetic to
+ * avoid huge source files.
+ * <p/>
  * Note: For a cleaner architecture, we should probably rename the existing classes
  * that implement the internal version manager, and name this VersionManagerImpl.
  */
-public class JcrVersionManagerImpl implements javax.jcr.version.VersionManager {
+public class JcrVersionManagerImpl extends JcrVersionManagerImplMerge
+        implements javax.jcr.version.VersionManager {
 
     /**
      * default logger
@@ -52,48 +70,48 @@
     private static final Logger log = LoggerFactory.getLogger(JcrVersionManagerImpl.class);
 
     /**
-     * workspace session
-     */
-    private final SessionImpl session;
-
-    /**
-     * the node id of the current activity
-     */
-    private NodeId currentActivity;
-
-
-    /**
      * Creates a new version manager for the given session
      * @param session workspace sesion
+     * @param stateMgr the underlying state manager
+     * @param hierMgr local hierarchy manager
      */
-    public JcrVersionManagerImpl(SessionImpl session) {
-        this.session = session;
+    public JcrVersionManagerImpl(SessionImpl session,
+                                 UpdatableItemStateManager stateMgr,
+                                 HierarchyManager hierMgr) {
+        super(session, stateMgr, hierMgr);
     }
 
     /**
      * {@inheritDoc}
      */
     public Version checkin(String absPath) throws RepositoryException {
-        return session.getNode(absPath).checkin();
+        // check lock status, holds and permissions
+        int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD |
+                ItemValidator.CHECK_PENDING_CHANGES_ON_NODE;
+        NodeStateEx state = getNodeState(absPath, options, Permission.VERSION_MNGMT);
+        NodeId baseId = checkoutCheckin(state, true, false);
+        return (VersionImpl) session.getNodeById(baseId);
     }
 
     /**
      * {@inheritDoc}
      */
     public void checkout(String absPath) throws RepositoryException {
-        session.getNode(absPath).checkout();
+        // check lock status, holds and permissions
+        int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD;
+        NodeStateEx state = getNodeState(absPath, options, Permission.VERSION_MNGMT);
+        checkoutCheckin(state, false, true);
     }
 
     /**
      * {@inheritDoc}
      */
     public Version checkpoint(String absPath) throws RepositoryException {
-        // this is not quite correct, since the entire checkpoint operation
-        // should be atomic
-        Node node = session.getNode(absPath);
-        Version v = node.checkin();
-        node.checkout();
-        return v;
+        int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD |
+                ItemValidator.CHECK_PENDING_CHANGES_ON_NODE;
+        NodeStateEx state = getNodeState(absPath, options, Permission.VERSION_MNGMT);
+        NodeId baseId = checkoutCheckin(state, true, true);
+        return (VersionImpl) session.getNodeById(baseId);
     }
 
     /**
@@ -108,7 +126,9 @@
      */
     public VersionHistory getVersionHistory(String absPath)
             throws RepositoryException {
-        return session.getNode(absPath).getVersionHistory();
+        NodeStateEx state = getNodeState(absPath);
+        InternalVersionHistory vh = getVersionHistory(state);
+        return (VersionHistory) session.getNodeById(vh.getId());
     }
 
     /**
@@ -116,40 +136,79 @@
      */
     public Version getBaseVersion(String absPath)
             throws RepositoryException {
-        return session.getNode(absPath).getBaseVersion();
+        NodeStateEx state = getNodeState(absPath);
+        InternalVersion v = getBaseVersion(state);
+        return (Version) session.getNodeById(v.getId());
     }
 
     /**
      * {@inheritDoc}
      */
-    public void restore(Version[] versions, boolean removeExisting)
+    public void restore(Version version, boolean removeExisting)
             throws RepositoryException {
-        session.getWorkspace().restore(versions, removeExisting);
+        restore(new Version[]{version}, removeExisting);
     }
 
     /**
      * {@inheritDoc}
      */
-    public void restore(String absPath, String versionName,
-                        boolean removeExisting)
+    public void restore(Version[] versions, boolean removeExisting)
             throws RepositoryException {
-        session.getNode(absPath).restore(versionName, removeExisting);
+        // check for pending changes
+        if (session.hasPendingChanges()) {
+            String msg = "Unable to restore version. Session has pending changes.";
+            log.debug(msg);
+            throw new InvalidItemStateException(msg);
+        }
+        // add all versions to map of versions to restore
+        final Map<NodeId, InternalVersion> toRestore = new HashMap<NodeId, InternalVersion>();
+        for (Version version : versions) {
+            InternalVersion v = vMgr.getVersion(((VersionImpl) version).getNodeId());
+            NodeId historyId = v.getVersionHistory().getId();
+            // check for collision
+            if (toRestore.containsKey(historyId)) {
+                throw new VersionException("Unable to restore. Two or more versions have same version history.");
+            }
+            toRestore.put(historyId, v);
+        }
+        restore(toRestore, removeExisting);
     }
 
     /**
      * {@inheritDoc}
      */
-    public void restore(Version version, boolean removeExisting)
+    public void restore(String absPath, String versionName,
+                        boolean removeExisting)
             throws RepositoryException {
-        session.getWorkspace().restore(new Version[]{version}, removeExisting);
+        int options = ItemValidator.CHECK_PENDING_CHANGES | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD;
+        NodeStateEx state = getNodeState(absPath, options, Permission.NONE);
+        restore(state, session.getQName(versionName), removeExisting);
     }
 
+
     /**
      * {@inheritDoc}
      */
     public void restore(String absPath, Version version, boolean removeExisting)
             throws RepositoryException {
-        session.getNode(absPath).restore(version, removeExisting);
+
+        // first check if node exists
+        if (session.nodeExists(absPath)) {
+            // normal restore
+            int options = ItemValidator.CHECK_PENDING_CHANGES | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD;
+            NodeStateEx state = getNodeState(absPath, options, Permission.NONE);
+            restore(state, version, removeExisting);
+        } else {
+            // parent has to exist
+            Path path = session.getQPath(absPath);
+            Path parentPath = path.getAncestor(1);
+            Name name = path.getNameElement().getName();
+            NodeImpl parent = session.getItemManager().getNode(parentPath);
+
+            int options = ItemValidator.CHECK_PENDING_CHANGES | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD;
+            NodeStateEx state = getNodeState(parent, options, Permission.NONE);
+            restore(state, name, version, removeExisting);
+        }
     }
 
     /**
@@ -158,7 +217,23 @@
     public void restoreByLabel(String absPath, String versionLabel,
                                boolean removeExisting)
             throws RepositoryException {
-        session.getNode(absPath).restoreByLabel(versionLabel, removeExisting);
+        int options = ItemValidator.CHECK_PENDING_CHANGES | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD;
+        NodeStateEx state = getNodeState(absPath, options, Permission.NONE);
+        restoreByLabel(state, session.getQName(versionLabel), removeExisting);
+    }
+
+    /**
+     * Does an update.
+     * @see javax.jcr.Node#update(String)
+     *
+     * @param node the node to update
+     * @param srcWorkspaceName the source workspace name
+     * @throws RepositoryException if an error occurs
+     */
+    public void update(NodeImpl node, String srcWorkspaceName)
+            throws RepositoryException {
+        NodeStateEx state = getNodeState(node, ItemValidator.CHECK_PENDING_CHANGES, Permission.VERSION_MNGMT);
+        mergeOrUpdate(state, srcWorkspaceName, null, false, false);
     }
 
     /**
@@ -167,18 +242,64 @@
     public NodeIterator merge(String absPath, String srcWorkspace,
                               boolean bestEffort)
             throws RepositoryException {
-        return ((NodeImpl) session.getNode(absPath))
-                .merge(srcWorkspace, bestEffort, false);
+        return merge(absPath, srcWorkspace, bestEffort, false);
     }
 
     /**
      * {@inheritDoc}
      */
-    public NodeIterator merge(String absPath, String srcWorkspace,
+    public NodeIterator merge(String absPath, String srcWorkspaceName,
                               boolean bestEffort, boolean isShallow)
             throws RepositoryException {
-        return ((NodeImpl) session.getNode(absPath))
-                .merge(srcWorkspace, bestEffort, isShallow);
+        NodeStateEx state = getNodeState(absPath, ItemValidator.CHECK_PENDING_CHANGES, Permission.VERSION_MNGMT);
+        List<ItemId> failedIds = new LinkedList<ItemId>();
+        mergeOrUpdate(state, srcWorkspaceName, failedIds, bestEffort, isShallow);
+        return new LazyItemIterator(session.getItemManager(), failedIds);
+    }
+
+    /**
+     * Combines merge and update method
+     * @param state the state to merge or update
+     * @param srcWorkspaceName source workspace name
+     * @param failedIds list that will contain the failed ids.
+     *        if <code>null</code> and update will be performed.
+     * @param bestEffort best effort flag
+     * @param isShallow is shallow flag
+     * @throws RepositoryException if an error occurs
+     */
+    private void mergeOrUpdate(NodeStateEx state, String srcWorkspaceName,
+                               List<ItemId> failedIds, boolean bestEffort,
+                               boolean isShallow)
+            throws RepositoryException {
+        // if same workspace, ignore
+        if (!srcWorkspaceName.equals(session.getWorkspace().getName())) {
+            // check authorization for specified workspace
+            if (!session.getAccessManager().canAccess(srcWorkspaceName)) {
+                throw new AccessDeniedException("not authorized to access " + srcWorkspaceName);
+            }
+            // get root node of src workspace
+            SessionImpl srcSession = null;
+            try {
+                // create session on other workspace for current subject
+                // (may throw NoSuchWorkspaceException and AccessDeniedException)
+                srcSession = ((RepositoryImpl) session.getRepository())
+                        .createSession(session.getSubject(), srcWorkspaceName);
+                WorkspaceImpl srcWsp = (WorkspaceImpl) srcSession.getWorkspace();
+                NodeId rootNodeId = ((NodeImpl) srcSession.getRootNode()).getNodeId();
+                NodeStateEx srcRoot = new NodeStateEx(
+                        srcWsp.getItemStateManager(),
+                        ntReg,
+                        rootNodeId);
+                merge(state, srcRoot, failedIds, bestEffort, isShallow);
+            } catch (ItemStateException e) {
+                throw new RepositoryException(e);
+            } finally {
+                if (srcSession != null) {
+                    // we don't need the other session anymore, logout
+                    srcSession.logout();
+                }
+            }
+        }
     }
 
     /**
@@ -186,7 +307,9 @@
      */
     public void doneMerge(String absPath, Version version)
             throws RepositoryException {
-        session.getNode(absPath).doneMerge(version);
+        int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_VERSIONING | ItemValidator.CHECK_PENDING_CHANGES_ON_NODE | ItemValidator.CHECK_HOLD;
+        NodeStateEx state = getNodeState(absPath, options, Permission.VERSION_MNGMT);
+        finishMerge(state, version, false);
     }
 
     /**
@@ -194,7 +317,9 @@
      */
     public void cancelMerge(String absPath, Version version)
             throws RepositoryException {
-        session.getNode(absPath).cancelMerge(version);
+        int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_VERSIONING | ItemValidator.CHECK_PENDING_CHANGES_ON_NODE | ItemValidator.CHECK_HOLD;
+        NodeStateEx state = getNodeState(absPath, options, Permission.VERSION_MNGMT);
+        finishMerge(state, version, true);
     }
 
     /**
@@ -268,83 +393,55 @@
         if (activity == null) {
             throw new UnsupportedRepositoryOperationException("Given activity not found.");
         }
-        boolean success = false;
-        try {
-            NodeIterator ret = internalMerge(activity);
-            session.save();
-            success = true;
-            return ret;
-        } finally {
-            if (!success) {
-                // revert session
-                try {
-                    log.debug("reverting changes applied during merge...");
-                    session.refresh(false);
-                } catch (RepositoryException e) {
-                    log.error("Error while reverting changes applied merge restore.", e);
-                }
-            }
-        }
+        List<ItemId> failedIds = new ArrayList<ItemId>();
+        merge(activity, failedIds);
+        return new LazyItemIterator(session.getItemManager(), failedIds);
     }
 
     /**
-     * Internally does the merge without saving the changes.
-     * @param activity internal activity
+     * returns the node state for the given path
+     * @param path path of the node
      * @throws RepositoryException if an error occurs
-     * @return a node iterator of all failed nodes
+     * @return the node state
      */
-    private NodeIterator internalMerge(InternalActivity activity)
-            throws RepositoryException {
-        List<ItemId> failedIds = new ArrayList<ItemId>();
-        Map<NodeId, InternalVersion> changeSet = activity.getChangeSet();
-        ChangeSetVersionSelector vsel = new ChangeSetVersionSelector(changeSet);
-        Iterator<NodeId> iter = changeSet.keySet().iterator();
-        while (iter.hasNext()) {
-            InternalVersion v = changeSet.remove(iter.next());
-            try {
-                NodeImpl node = session.getNodeById(
-                        v.getVersionHistory().getVersionableId());
-                InternalVersion base = ((VersionImpl) node.getBaseVersion()).getInternalVersion();
-                VersionImpl version = (VersionImpl) session.getNodeById(v.getId());
-                // if base version is newer than version, add to failed list
-                // but merge it anyways
-                if (base.isMoreRecent(version.getInternalVersion())) {
-                    failedIds.add(node.getNodeId());
-                    // should we add it to the jcr:mergeFailed property ?
-                } else {
-                    Version[] vs = node.internalRestore(version, vsel, true);
-                    for (Version restored: vs) {
-                        changeSet.remove(((VersionImpl) restored).getNodeId());
-                    }
-                }
-            } catch (ItemNotFoundException e) {
-                // ignore nodes not present in this workspace (not best practice)
-            }
-
-            // reset iterator
-            iter = changeSet.keySet().iterator();
-        }
-        return new LazyItemIterator(session.getItemManager(), failedIds);
+    private NodeStateEx getNodeState(String path) throws RepositoryException {
+        return getNodeState(path, 0, 0);
     }
 
     /**
-     * Internal version selector that selects the version in the changeset.
+     * checks the permissions for the given path and returns the node state
+     * @param path path of the node
+     * @param options options to check
+     * @param permissions permissions to check
+     * @throws RepositoryException if an error occurs
+     * @return the node state
      */
-    private class ChangeSetVersionSelector implements VersionSelector {
-
-        private final Map<NodeId, InternalVersion> changeSet;
-
-        private ChangeSetVersionSelector(Map<NodeId, InternalVersion> changeSet) {
-            this.changeSet = changeSet;
-        }
+    private NodeStateEx getNodeState(String path, int options, int permissions)
+            throws RepositoryException {
+        return getNodeState((NodeImpl) session.getNode(path), options, permissions);
+    }
 
-        public Version select(VersionHistory vh) throws RepositoryException {
-            InternalVersion v = changeSet.get(((VersionHistoryImpl) vh).getNodeId());
-            if (v != null) {
-                return (Version) session.getNodeById(v.getId());
-            } else {
-                return null;
+    /**
+     * checks the permissions for the given path and returns the node state
+     * @param node the node
+     * @param options options to check
+     * @param permissions permissions to check
+     * @throws RepositoryException if an error occurs
+     * @return the node state
+     */
+    private NodeStateEx getNodeState(NodeImpl node, int options, int permissions)
+            throws RepositoryException {
+        try {
+            if (options > 0 || permissions > 0) {
+                session.getValidator().checkModify(node, options, permissions);
             }
+            return new NodeStateEx(
+                    stateMgr,
+                    ntReg,
+                    (NodeState) stateMgr.getItemState(node.getNodeId()),
+                    node.getQName());
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
         }
     }
 }
\ No newline at end of file

Copied: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplBase.java (from r795442, jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java)
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplBase.java?p2=jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplBase.java&p1=jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java&r1=795442&r2=795452&rev=795452&view=diff
==============================================================================
--- jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImpl.java (original)
+++ jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplBase.java Sun Jul 19 00:09:40 2009
@@ -16,335 +16,462 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
 import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
 
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.LazyItemIterator;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.HierarchyManager;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.state.DefaultISMLocking;
+import org.apache.jackrabbit.core.state.ISMLocking;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.LocalItemStateManager;
+import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.Path;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
 /**
- * Implementation of the {@link javax.jcr.version.VersionManager}.
- * <p/>
- * Note: For a cleaner architecture, we should probably rename the existing classes
- * that implement the internal version manager, and name this VersionManagerImpl.
+ * The JCR Version Manager impementation is split in several classes in order to
+ * group related methods together.
+ * </p>
+ * this class provides basic routines for all operations and the methods related
+ * to checkin and checkout.
  */
-public class JcrVersionManagerImpl implements javax.jcr.version.VersionManager {
+abstract public class JcrVersionManagerImplBase {
 
     /**
      * default logger
      */
-    private static final Logger log = LoggerFactory.getLogger(JcrVersionManagerImpl.class);
+    private static final Logger log = LoggerFactory.getLogger(JcrVersionManagerImplBase.class);
 
     /**
      * workspace session
      */
-    private final SessionImpl session;
+    protected final SessionImpl session;
 
     /**
-     * the node id of the current activity
+     * item state manager for all workspace operations
      */
-    private NodeId currentActivity;
-
+    protected final UpdatableItemStateManager stateMgr;
 
     /**
-     * Creates a new version manager for the given session
-     * @param session workspace sesion
+     * hierarch manager that operates on the localte state manager
      */
-    public JcrVersionManagerImpl(SessionImpl session) {
-        this.session = session;
-    }
+    protected final HierarchyManager hierMgr;
 
     /**
-     * {@inheritDoc}
+     * node type registry
      */
-    public Version checkin(String absPath) throws RepositoryException {
-        return session.getNode(absPath).checkin();
-    }
+    protected final NodeTypeRegistry ntReg;
 
     /**
-     * {@inheritDoc}
+     * the session version manager.
      */
-    public void checkout(String absPath) throws RepositoryException {
-        session.getNode(absPath).checkout();
-    }
+    protected final VersionManager vMgr;
 
     /**
-     * {@inheritDoc}
+     * the lock on this version manager
      */
-    public Version checkpoint(String absPath) throws RepositoryException {
-        // this is not quite correct, since the entire checkpoint operation
-        // should be atomic
-        Node node = session.getNode(absPath);
-        Version v = node.checkin();
-        node.checkout();
-        return v;
-    }
+    private final DefaultISMLocking rwLock = new DefaultISMLocking();
 
     /**
-     * {@inheritDoc}
+     * the node id of the current activity
      */
-    public boolean isCheckedOut(String absPath) throws RepositoryException {
-        return session.getNode(absPath).isCheckedOut();
-    }
+    protected NodeId currentActivity;
 
     /**
-     * {@inheritDoc}
+     * Creates a new version manager base for the given session
+     * @param session workspace sesion
+     * @param stateMgr the underlying state manager
+     * @param hierMgr local hierarchy manager
      */
-    public VersionHistory getVersionHistory(String absPath)
-            throws RepositoryException {
-        return session.getNode(absPath).getVersionHistory();
+    protected JcrVersionManagerImplBase(SessionImpl session,
+                                        UpdatableItemStateManager stateMgr,
+                                        HierarchyManager hierMgr) {
+        this.session = session;
+        this.stateMgr = stateMgr;
+        this.hierMgr = hierMgr;
+        this.ntReg = session.getNodeTypeManager().getNodeTypeRegistry();
+        this.vMgr = session.getVersionManager();
     }
 
     /**
-     * {@inheritDoc}
+     * Performs a checkin or checkout operation. if <code>checkin</code> is
+     * <code>true</code> the node is checked in. if <code>checkout</code> is
+     * <code>true</code> the node is checked out. if both flags are <code>true</code>
+     * the checkin is performed prior to the checkout and the operation is
+     * equivalent to a checkpoint operation.
+     *
+     * @param state node state
+     * @param checkin if <code>true</code> the node is checked in.
+     * @param checkout if <code>true</code> the node is checked out.
+     * @return the node id of the base version or <code>null</code> for a pure
+     *         checkout.
+     * @throws RepositoryException if an error occurs
      */
-    public Version getBaseVersion(String absPath)
+    protected NodeId checkoutCheckin(NodeStateEx state, boolean checkin, boolean checkout)
             throws RepositoryException {
-        return session.getNode(absPath).getBaseVersion();
-    }
+        assert(checkin || checkout);
 
-    /**
-     * {@inheritDoc}
-     */
-    public void restore(Version[] versions, boolean removeExisting)
-            throws RepositoryException {
-        session.getWorkspace().restore(versions, removeExisting);
-    }
+        // check if versionable
+        boolean isFull = checkVersionable(state);
 
-    /**
-     * {@inheritDoc}
-     */
-    public void restore(String absPath, String versionName,
-                        boolean removeExisting)
-            throws RepositoryException {
-        session.getNode(absPath).restore(versionName, removeExisting);
-    }
+        // check flags
+        if (isCheckedOut(state)) {
+            if (checkout && !checkin) {
+                // pure checkout
+                String msg = safeGetJCRPath(state) + ": Node is already checked-out. ignoring.";
+                log.debug(msg);
+                return null;
+            }
+        } else {
+            if (!checkout) {
+                // pure checkin
+                String msg = safeGetJCRPath(state) + ": Node is already checked-in. ignoring.";
+                log.debug(msg);
+                if (isFull) {
+                    return getBaseVersionId(state);
+                } else {
+                    // get base version from version history
+                    return vMgr.getHeadVersionOfNode(state.getNodeId()).getId();
+                }
+            }
+            checkin = false;
+        }
 
-    /**
-     * {@inheritDoc}
-     */
-    public void restore(Version version, boolean removeExisting)
-            throws RepositoryException {
-        session.getWorkspace().restore(new Version[]{version}, removeExisting);
-    }
+        NodeId baseId = isFull && checkout
+                ? vMgr.canCheckout(state, currentActivity)
+                : null;
 
-    /**
-     * {@inheritDoc}
-     */
-    public void restore(String absPath, Version version, boolean removeExisting)
-            throws RepositoryException {
-        session.getNode(absPath).restore(version, removeExisting);
+        // perform operation
+        JcrVersionManagerImpl.WriteOperation ops = startWriteOperation();
+        try {
+            // the 2 cases could be consolidated but is clearer this way
+            if (checkin) {
+                InternalVersion v = vMgr.checkin(session, state);
+                baseId = v.getId();
+                if (isFull) {
+                    state.setPropertyValue(
+                            NameConstants.JCR_BASEVERSION,
+                            InternalValue.create(baseId));
+                    state.setPropertyValues(NameConstants.JCR_PREDECESSORS, PropertyType.REFERENCE, InternalValue.EMPTY_ARRAY);
+                    state.removeProperty(NameConstants.JCR_ACTIVITY);
+                }
+            }
+            if (checkout) {
+                if (isFull) {
+                    state.setPropertyValues(
+                            NameConstants.JCR_PREDECESSORS,
+                            PropertyType.REFERENCE,
+                            new InternalValue[]{InternalValue.create(baseId)}
+                    );
+                    if (currentActivity != null) {
+                        state.setPropertyValue(
+                                NameConstants.JCR_ACTIVITY,
+                                InternalValue.create(currentActivity)
+                        );
+                    }
+                }
+            }
+            state.setPropertyValue(NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(checkout));
+            state.store();
+            ops.save();
+            return baseId;
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
+        } finally {
+            ops.close();
+        }
     }
 
+
     /**
-     * {@inheritDoc}
-     */
-    public void restoreByLabel(String absPath, String versionLabel,
-                               boolean removeExisting)
-            throws RepositoryException {
-        session.getNode(absPath).restoreByLabel(versionLabel, removeExisting);
+     * Checks if the underlying node is versionable, i.e. has 'mix:versionable' or a
+     * 'mix:simpleVersionable'.
+     * @param state node state
+     * @return <code>true</code> if this node is full versionable, i.e. is
+     *         of nodetype mix:versionable
+     * @throws UnsupportedRepositoryOperationException if this node is not versionable at all
+     */
+    protected boolean checkVersionable(NodeStateEx state)
+            throws UnsupportedRepositoryOperationException, RepositoryException {
+        if (state.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE)) {
+            return true;
+        } else if (state.getEffectiveNodeType().includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) {
+            return false;
+        } else {
+            String msg = "Unable to perform a versioning operation on a " +
+                         "non versionable node: " + safeGetJCRPath(state);
+            log.debug(msg);
+            throw new UnsupportedRepositoryOperationException(msg);
+        }
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the JCR path for the given node state without throwing an exception.
+     * @param state node state
+     * @return a JCR path string
      */
-    public NodeIterator merge(String absPath, String srcWorkspace,
-                              boolean bestEffort)
-            throws RepositoryException {
-        return ((NodeImpl) session.getNode(absPath))
-                .merge(srcWorkspace, bestEffort, false);
+    protected String safeGetJCRPath(NodeStateEx state) {
+        Path path;
+        try {
+            path = hierMgr.getPath(state.getNodeId());
+        } catch (RepositoryException e) {
+            log.warn("unable to calculate path for {}", state.getNodeId());
+            return state.getNodeId().toString();
+        }
+        try {
+            return session.getJCRPath(path);
+        } catch (NamespaceException e) {
+            log.warn("unable to calculate path for {}", path);
+            return path.toString();
+        }
     }
 
     /**
-     * {@inheritDoc}
+     * Determines the checked-out status of the given node state.
+     * <p/>
+     * A node is considered <i>checked-out</i> if it is versionable and
+     * checked-out, or is non-versionable but its nearest versionable ancestor
+     * is checked-out, or is non-versionable and there are no versionable
+     * ancestors.
+     *
+     * @param state node state
+     * @return a boolean
+     * @see javax.jcr.version.VersionManager#isCheckedOut(String)
+     * @see Node#isCheckedOut()
+     * @throws RepositoryException if an error occurs
      */
-    public NodeIterator merge(String absPath, String srcWorkspace,
-                              boolean bestEffort, boolean isShallow)
-            throws RepositoryException {
-        return ((NodeImpl) session.getNode(absPath))
-                .merge(srcWorkspace, bestEffort, isShallow);
+    protected boolean isCheckedOut(NodeStateEx state) throws RepositoryException {
+        return state.getPropertyValue(NameConstants.JCR_ISCHECKEDOUT).getBoolean();
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the node id of the base version, retrieved from the node state
+     * @param state node state
+     * @return the node id of the base version or <code>null</code> if not defined
      */
-    public void doneMerge(String absPath, Version version)
-            throws RepositoryException {
-        session.getNode(absPath).doneMerge(version);
+    protected NodeId getBaseVersionId(NodeStateEx state) {
+        InternalValue value = state.getPropertyValue(NameConstants.JCR_BASEVERSION);
+        return value == null ? null : value.getNodeId();
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the internal version history for the underlying node.
+     * @param state node state
+     * @return internal version history
+     * @throws RepositoryException if an error occurs
      */
-    public void cancelMerge(String absPath, Version version)
+    protected InternalVersionHistory getVersionHistory(NodeStateEx state)
             throws RepositoryException {
-        session.getNode(absPath).cancelMerge(version);
+        boolean isFull = checkVersionable(state);
+        if (isFull) {
+            NodeId id = state.getPropertyValue(NameConstants.JCR_VERSIONHISTORY).getNodeId();
+            return vMgr.getVersionHistory(id);
+        } else {
+            return vMgr.getVersionHistoryOfNode(state.getNodeId());
+        }
     }
 
     /**
-     * {@inheritDoc}
+     * helper class that returns the internal version for a JCR one.
+     * @param v the jcr version
+     * @return the internal version
+     * @throws RepositoryException if an error occurs
      */
-    public Node createConfiguration(String absPath, Version baseline)
-            throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("comming soon...");
+    protected InternalVersion getVersion(Version v) throws RepositoryException {
+        return vMgr.getVersion(((VersionImpl) v).getNodeId());
     }
 
     /**
-     * {@inheritDoc}
+     * Returns the internal base version for the underlying node.
+     * @param state node state
+     * @return internal base version
+     * @throws RepositoryException if an error occurs
      */
-    public Node setActivity(Node activity) throws RepositoryException {
-        Node oldActivity = getActivity();
-        if (activity == null) {
-            currentActivity = null;
+    protected InternalVersion getBaseVersion(NodeStateEx state) throws RepositoryException {
+        boolean isFull = checkVersionable(state);
+        if (isFull) {
+            NodeId id = getBaseVersionId(state);
+            return vMgr.getVersion(id);
         } else {
-            NodeImpl actNode = (NodeImpl) activity;
-            if (!actNode.isNodeType(NameConstants.NT_ACTIVITY)) {
-                throw new UnsupportedRepositoryOperationException("Given node is not an activity.");
-            }
-            currentActivity = actNode.getNodeId();
+            // note, that the method currently only works for linear version
+            // graphs (i.e. simple versioning)
+            return vMgr.getHeadVersionOfNode(state.getNodeId());
         }
-        return oldActivity;
     }
 
     /**
-     * {@inheritDoc}
+     * returns the node state for the given node id
+     * @param nodeId the node id
+     * @throws RepositoryException if an error occurs
+     * @return the node state or null if not found
      */
-    public Node getActivity() throws RepositoryException {
-        if (currentActivity == null) {
+    protected NodeStateEx getNodeStateEx(NodeId nodeId) throws RepositoryException {
+        if (!stateMgr.hasItemState(nodeId)) {
             return null;
-        } else {
-            return session.getNodeById(currentActivity);
+        }
+        try {
+            return new NodeStateEx(
+                    stateMgr,
+                    ntReg,
+                    (NodeState) stateMgr.getItemState(nodeId),
+                    null);
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
         }
     }
 
     /**
-     * {@inheritDoc}
+     * Checks modify and permissions
+     * @param state state to check
+     * @param options options to check
+     * @param permissions permissions to check
+     * @throws RepositoryException if an error occurs
      */
-    public Node createActivity(String title) throws RepositoryException {
-        NodeId id = session.getVersionManager().createActivity(session, title);
-        return session.getNodeById(id);
+    protected void checkModify(NodeStateEx state, int options, int permissions)
+            throws RepositoryException {
+        NodeImpl node;
+        try {
+            node = session.getNodeById(state.getNodeId());
+        } catch (RepositoryException e) {
+            // ignore
+            return;
+        }
+        session.getValidator().checkModify(node, options, permissions);
     }
 
     /**
-     * {@inheritDoc}
+     * Checks modify and permissions
+     * @param node node to check
+     * @param options options to check
+     * @param permissions permissions to check
+     * @throws RepositoryException if an error occurs
      */
-    public void removeActivity(Node node) throws RepositoryException {
-        NodeImpl actNode = (NodeImpl) node;
-        if (!actNode.isNodeType(NameConstants.NT_ACTIVITY)) {
-            throw new UnsupportedRepositoryOperationException("Given node is not an activity.");
-        }
-        NodeId actId = actNode.getNodeId();
-        session.getVersionManager().removeActivity(session, actId);
-        if (actId.equals(currentActivity)) {
-            currentActivity = null;
-        }
+    protected void checkModify(NodeImpl node, int options, int permissions)
+            throws RepositoryException {
+        session.getValidator().checkModify(node, options, permissions);
     }
 
     /**
-     * {@inheritDoc}
+     * Helper for managing write operations.
      */
-    public NodeIterator merge(Node activityNode) throws RepositoryException {
-        NodeImpl actNode = (NodeImpl) activityNode;
-        if (!actNode.isNodeType(NameConstants.NT_ACTIVITY)) {
-            throw new UnsupportedRepositoryOperationException("Given node is not an activity.");
-        }
-        InternalActivity activity = session.getVersionManager().getActivity(actNode.getNodeId());
-        if (activity == null) {
-            throw new UnsupportedRepositoryOperationException("Given activity not found.");
+    public class WriteOperation {
+
+        /**
+         * Flag for successful completion of the write operation.
+         */
+        private boolean success = false;
+
+        private final ISMLocking.WriteLock lock;
+
+        public WriteOperation(ISMLocking.WriteLock lock) {
+            this.lock = lock;
         }
-        boolean success = false;
-        try {
-            NodeIterator ret = internalMerge(activity);
-            session.save();
+
+        /**
+         * Saves the pending operations in the {@link LocalItemStateManager}.
+         *
+         * @throws ItemStateException if the pending state is invalid
+         * @throws RepositoryException if the pending state could not be persisted
+         */
+        public void save() throws ItemStateException, RepositoryException {
+            stateMgr.update();
             success = true;
-            return ret;
-        } finally {
-            if (!success) {
-                // revert session
-                try {
-                    log.debug("reverting changes applied during merge...");
-                    session.refresh(false);
-                } catch (RepositoryException e) {
-                    log.error("Error while reverting changes applied merge restore.", e);
+        }
+
+        /**
+         * Closes the write operation. The pending operations are cancelled
+         * if they could not be properly saved. Finally the write lock is
+         * released.
+         */
+        public void close() {
+            try {
+                if (!success) {
+                    // update operation failed, cancel all modifications
+                    stateMgr.cancel();
                 }
+            } finally {
+                lock.release();
             }
         }
     }
 
     /**
-     * Internally does the merge without saving the changes.
-     * @param activity internal activity
-     * @throws RepositoryException if an error occurs
-     * @return a node iterator of all failed nodes
+     * Acquires the write lock on this version manager.
+     * @return returns the write lock
      */
-    private NodeIterator internalMerge(InternalActivity activity)
-            throws RepositoryException {
-        List<ItemId> failedIds = new ArrayList<ItemId>();
-        Map<NodeId, InternalVersion> changeSet = activity.getChangeSet();
-        ChangeSetVersionSelector vsel = new ChangeSetVersionSelector(changeSet);
-        Iterator<NodeId> iter = changeSet.keySet().iterator();
-        while (iter.hasNext()) {
-            InternalVersion v = changeSet.remove(iter.next());
+    protected ISMLocking.WriteLock acquireWriteLock() {
+        while (true) {
             try {
-                NodeImpl node = session.getNodeById(
-                        v.getVersionHistory().getVersionableId());
-                InternalVersion base = ((VersionImpl) node.getBaseVersion()).getInternalVersion();
-                VersionImpl version = (VersionImpl) session.getNodeById(v.getId());
-                // if base version is newer than version, add to failed list
-                // but merge it anyways
-                if (base.isMoreRecent(version.getInternalVersion())) {
-                    failedIds.add(node.getNodeId());
-                    // should we add it to the jcr:mergeFailed property ?
-                } else {
-                    Version[] vs = node.internalRestore(version, vsel, true);
-                    for (Version restored: vs) {
-                        changeSet.remove(((VersionImpl) restored).getNodeId());
-                    }
-                }
-            } catch (ItemNotFoundException e) {
-                // ignore nodes not present in this workspace (not best practice)
+                return rwLock.acquireWriteLock(null);
+            } catch (InterruptedException e) {
+                // ignore
             }
-
-            // reset iterator
-            iter = changeSet.keySet().iterator();
         }
-        return new LazyItemIterator(session.getItemManager(), failedIds);
     }
 
     /**
-     * Internal version selector that selects the version in the changeset.
+     * acquires the read lock on this version manager.
+     * @return returns the read lock
      */
-    private class ChangeSetVersionSelector implements VersionSelector {
-
-        private final Map<NodeId, InternalVersion> changeSet;
-
-        private ChangeSetVersionSelector(Map<NodeId, InternalVersion> changeSet) {
-            this.changeSet = changeSet;
+    protected ISMLocking.ReadLock acquireReadLock() {
+        while (true) {
+            try {
+                return rwLock.acquireReadLock(null);
+            } catch (InterruptedException e) {
+                // ignore
+            }
         }
+    }
 
-        public Version select(VersionHistory vh) throws RepositoryException {
-            InternalVersion v = changeSet.get(((VersionHistoryImpl) vh).getNodeId());
-            if (v != null) {
-                return (Version) session.getNodeById(v.getId());
-            } else {
-                return null;
+
+    /**
+     * Starts a write operation by acquiring the write lock and setting the
+     * item state manager to the "edit" state. If something goes wrong, the
+     * write lock is released and an exception is thrown.
+     * <p>
+     * The pattern for using this method and the returned helper instance is:
+     * <pre>
+     *     WriteOperation operation = startWriteOperation();
+     *     try {
+     *         ...
+     *         operation.save(); // if everything is OK
+     *         ...
+     *     } catch (...) {
+     *         ...
+     *     } finally {
+     *         operation.close();
+     *     }
+     * </pre>
+     *
+     * @return write operation helper
+     * @throws RepositoryException if the write operation could not be started
+     */
+    public WriteOperation startWriteOperation() throws RepositoryException {
+        boolean success = false;
+        ISMLocking.WriteLock lock = acquireWriteLock();
+        try {
+            stateMgr.edit();
+            success = true;
+            return new WriteOperation(lock);
+        } catch (IllegalStateException e) {
+            throw new RepositoryException("Unable to start edit operation.", e);
+        } finally {
+            if (!success) {
+                lock.release();
             }
         }
     }
+
 }
\ No newline at end of file

Added: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java?rev=795452&view=auto
==============================================================================
--- jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java (added)
+++ jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java Sun Jul 19 00:09:40 2009
@@ -0,0 +1,522 @@
+/*
+ * 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.version;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.MergeException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.ItemValidator;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.core.state.ChildNodeEntry;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The JCR Version Manager impementation is split in several classes in order to
+ * group related methods together.
+ * </p>
+ * this class provides methods for the merge operations.
+ */
+abstract public class JcrVersionManagerImplMerge extends JcrVersionManagerImplRestore {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcrVersionManagerImplMerge.class);
+
+    /**
+     * Creates a new version manager for the given session
+     * @param session workspace sesion
+     * @param stateMgr the underlying state manager
+     * @param hierMgr local hierarchy manager
+     */
+    protected JcrVersionManagerImplMerge(SessionImpl session,
+                                        UpdatableItemStateManager stateMgr,
+                                        HierarchyManager hierMgr) {
+        super(session, stateMgr, hierMgr);
+    }
+
+    /**
+     * Merges/Updates this node with its corresponding ones
+     *
+     * @param state state to merge or update
+     * @param srcRoot src workspace root node
+     * @param failedIds list of failed ids
+     * @param bestEffort best effor flag
+     * @param shallow is shallow flag
+     * @throws RepositoryException if an error occurs
+     * @throws ItemStateException if an error occurs
+     */
+    protected void merge(NodeStateEx state, NodeStateEx srcRoot,
+                       List<ItemId> failedIds,
+                       boolean bestEffort, boolean shallow)
+            throws RepositoryException, ItemStateException {
+
+        if (shallow) {
+            // If <code>isShallow</code> is <code>true</code> and this node is not
+            // versionable, then this method returns and no changes are made.
+            if (!state.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE)) {
+                return;
+            }
+        }
+        NodeStateEx srcNode = getCorrespondingNode(state, srcRoot);
+        if (srcNode == null) {
+            // If this node (the one on which merge is called) does not have a corresponding
+            // node in the indicated workspace, then the merge method returns quietly and no
+            // changes are made.
+            return;
+        }
+        JcrVersionManagerImpl.WriteOperation ops = startWriteOperation();
+        try {
+            internalMerge(state, srcRoot, failedIds, bestEffort, shallow);
+            state.store();
+            ops.save();
+        } finally {
+            ops.close();
+        }
+    }
+
+    /**
+     * Merges/Updates this node with its corresponding ones
+     *
+     * @param state state to merge or update
+     * @param srcRoot src workspace root node
+     * @param failedIds list of failed ids
+     * @param bestEffort best effor flag
+     * @param shallow is shallow flag
+     * @throws RepositoryException if an error occurs
+     * @throws ItemStateException if an error occurs
+     */
+    private void internalMerge(NodeStateEx state, NodeStateEx srcRoot,
+                       List<ItemId> failedIds,
+                       boolean bestEffort, boolean shallow)
+            throws RepositoryException, ItemStateException {
+
+        NodeStateEx srcNode = doMergeTest(state, srcRoot, failedIds, bestEffort);
+        if (srcNode == null) {
+            if (!shallow) {
+                // leave, iterate over children, but ignore non-versionable child
+                // nodes (see JCR-1046)
+                for (NodeStateEx n: state.getChildNodes()) {
+                    if (n.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE)) {
+                        internalMerge(n, srcRoot, failedIds, bestEffort, shallow);
+                    }
+                }
+            }
+            return;
+        }
+
+        // check lock and hold status if node exists
+        checkModify(state, ItemValidator.CHECK_LOCK | ItemValidator.CHECK_HOLD, Permission.NONE);
+
+        // remove all properties that are not present in srcNode
+        for (PropertyState prop: state.getProperties()) {
+            if (!srcNode.hasProperty(prop.getName())) {
+                state.removeProperty(prop.getName());
+            }
+        }
+
+        // update all properties from the src node
+        for (PropertyState prop: srcNode.getProperties()) {
+            Name propName = prop.getName();
+            // ignore system types
+            if (propName.equals(NameConstants.JCR_PRIMARYTYPE)
+                    || propName.equals(NameConstants.JCR_MIXINTYPES)
+                    || propName.equals(NameConstants.JCR_UUID)) {
+                continue;
+            }
+            state.copyFrom(prop);
+        }
+
+        // update the mixin types
+        state.setMixins(srcNode.getState().getMixinTypeNames());
+
+        // remove the child nodes in N but not in N'
+        LinkedList<ChildNodeEntry> toDelete = new LinkedList<ChildNodeEntry>();
+        for (ChildNodeEntry entry: state.getState().getChildNodeEntries()) {
+            if (!srcNode.getState().hasChildNodeEntry(entry.getName(), entry.getIndex())) {
+                toDelete.add(entry);
+            }
+        }
+        for (ChildNodeEntry entry: toDelete) {
+            state.removeNode(entry.getName(), entry.getIndex());
+        }
+        state.store();
+
+        // add source ones
+        for (ChildNodeEntry entry: srcNode.getState().getChildNodeEntries()) {
+            NodeStateEx child = state.getNode(entry.getName(), entry.getIndex());
+            if (child == null) {
+                // if destination workspace already has such an node, remove it
+                if (state.hasNode(entry.getId())) {
+                    child = state.getNode(entry.getId());
+                    NodeStateEx parent = child.getParent();
+                    parent.removeNode(child);
+                    parent.store();
+                }
+                // create new child
+                NodeStateEx srcChild = srcNode.getNode(entry.getId());
+                child = state.addNode(entry.getName(), srcChild.getState().getNodeTypeName(), srcChild.getNodeId());
+                child.setMixins(srcChild.getState().getMixinTypeNames());
+                // copy src child
+                state.store();
+                internalMerge(child, srcRoot, null, bestEffort, false);
+            } else if (!shallow) {
+                // recursively merge
+                internalMerge(child, srcRoot, failedIds, bestEffort, false);
+
+            }
+        }
+    }
+
+    /**
+     * Returns the corresponding node in the workspace of the given session.
+     * <p/>
+     * Given a node N1 in workspace W1, its corresponding node N2 in workspace
+     * W2 is defined as follows:
+     * <ul>
+     * <li>If N1 is the root node of W1 then N2 is the root node of W2.
+     * <li>If N1 is referenceable (has a UUID) then N2 is the node in W2 with
+     * the same UUID.
+     * <li>If N1 is not referenceable (does not have a UUID) then there is some
+     * node M1 which is either the nearest ancestor of N1 that is
+     * referenceable, or is the root node of W1. If the corresponding node
+     * of M1 is M2 in W2, then N2 is the node with the same relative path
+     * from M2 as N1 has from M1.
+     * </ul>
+     *
+     * @param state N1
+     * @param srcRoot the root node state of W2
+     * @return the corresponding node or <code>null</code> if no corresponding
+     *         node exists.
+     * @throws RepositoryException If another error occurs.
+     */
+    private NodeStateEx getCorrespondingNode(NodeStateEx state, NodeStateEx srcRoot)
+            throws RepositoryException {
+
+        // search nearest ancestor that is referenceable
+        NodeStateEx m1 = state;
+        LinkedList<ChildNodeEntry> elements = new LinkedList<ChildNodeEntry>();
+        while (m1.getParentId() != null &&
+                !m1.getEffectiveNodeType().includesNodeType(NameConstants.MIX_REFERENCEABLE)) {
+            NodeStateEx parent = m1.getParent();
+            elements.addFirst(parent.getState().getChildNodeEntry(m1.getNodeId()));
+            m1 = parent;
+        }
+
+        // check if corresponding ancestor exists
+        if (srcRoot.hasNode(m1.getNodeId())) {
+            NodeStateEx m2 = srcRoot.getNode(m1.getNodeId());
+            Iterator<ChildNodeEntry> iter = elements.iterator();
+            while (iter.hasNext() && m2 != null) {
+                ChildNodeEntry e = iter.next();
+                m2 = m2.getNode(e.getName(), e.getIndex());
+            }
+            return m2;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Performs the merge test. If the result is 'update', then the corresponding
+     * source node is returned. if the result is 'leave' or 'besteffort-fail'
+     * then <code>null</code> is returned. If the result of the merge test is
+     * 'fail' with bestEffort set to <code>false</code> a MergeException is
+     * thrown.
+     * <p/>
+     * jsr170 - 8.2.10 Merge:
+     * [...]
+     * <ul>
+     * <li>If N is currently checked-in then:</li>
+     * <ul>
+     * <li>If V' is a successor (to any degree) of V, then the merge result
+     *     for N is update.
+     * </li>
+     * <li>If V' is a predecessor (to any degree) of V or if V and
+     *     V' are identical (i.e., are actually the same version),
+     *     then the merge result for N is leave.
+     * </li>
+     * <li>If V is neither a successor of, predecessor of, nor
+     *     identical with V', then the merge result for N is failed.
+     *     This is the case where N and N' represent divergent
+     *     branches of the version graph, thus determining the
+     *     result of a merge is non-trivial.
+     * </li>
+     * </ul>
+     * <li>If N is currently checked-out then:</li>
+     * <ul>
+     * <li>If V' is a predecessor (to any degree) of V or if V and
+     *     V' are identical (i.e., are actually the same version),
+     *     then the merge result for N is leave.
+     * </li>
+     * <li>If any other relationship holds between V and V',
+     *     then the merge result for N is fail.
+     * </li>
+     * </ul>
+     * </ul>
+     *
+     * @param state state to test
+     * @param srcRoot root node state of the source workspace
+     * @param failedIds the list to store the failed node ids.
+     * @param bestEffort the best effort flag
+     * @return the corresponding source node or <code>null</code>
+     * @throws RepositoryException if an error occurs.
+     * @throws AccessDeniedException if access is denied
+     */
+    private NodeStateEx doMergeTest(NodeStateEx state, NodeStateEx srcRoot, List<ItemId> failedIds, boolean bestEffort)
+            throws RepositoryException, AccessDeniedException {
+
+        // If N does not have a corresponding node then the merge result for N is leave.
+        NodeStateEx srcNode = getCorrespondingNode(state, srcRoot);
+        if (srcNode == null) {
+            return null;
+        }
+
+        // if not versionable, update
+        if (!state.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE) || failedIds == null) {
+            return srcNode;
+        }
+        // if source node is not versionable, leave
+        if (!srcNode.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE)) {
+            return null;
+        }
+        // test versions
+        InternalVersion v = getBaseVersion(state);
+        InternalVersion vp = getBaseVersion(srcNode);
+        if (vp.isMoreRecent(v) && !isCheckedOut(state)) {
+            // I f V' is a successor (to any degree) of V, then the merge result for
+            // N is update. This case can be thought of as the case where N' is
+            // "newer" than N and therefore N should be updated to reflect N'.
+            return srcNode;
+        } else if (v.equals(vp) || v.isMoreRecent(vp)) {
+            // If V' is a predecessor (to any degree) of V or if V and V' are
+            // identical (i.e., are actually the same version), then the merge
+            // result for N is leave. This case can be thought of as the case where
+            // N' is "older" or the "same age" as N and therefore N should be left alone.
+            return null;
+        } else {
+            // If V is neither a successor of, predecessor of, nor identical
+            // with V', then the merge result for N is failed. This is the case
+            // where N and N' represent divergent branches of the version graph,
+            // thus determining the result of a merge is non-trivial.
+            if (bestEffort) {
+                // add 'offending' version to jcr:mergeFailed property
+                Set<NodeId> set = getMergeFailed(state);
+                set.add(vp.getId());
+                setMergeFailed(state, set);
+                failedIds.add(state.getNodeId());
+                state.store();
+                return null;
+            } else {
+                String msg =
+                    "Unable to merge nodes. Violating versions. " + safeGetJCRPath(state);
+                log.debug(msg);
+                throw new MergeException(msg);
+            }
+        }
+    }
+
+    /**
+     * Perform {@link Node#cancelMerge(Version)} or {@link Node#doneMerge(Version)}
+     * depending on the value of <code>cancel</code>.
+     * @param state state to finish
+     * @param version version
+     * @param cancel flag inidicates if this is a cancel operation
+     * @throws RepositoryException if an error occurs
+     */
+    protected void finishMerge(NodeStateEx state, Version version, boolean cancel)
+            throws RepositoryException {
+        // check versionable
+        if (!checkVersionable(state)) {
+            throw new UnsupportedRepositoryOperationException("Node not full versionable: " + safeGetJCRPath(state));
+        }
+
+        // check if version is in mergeFailed list
+        Set<NodeId> failed = getMergeFailed(state);
+        NodeId versionId = ((VersionImpl) version).getNodeId();
+        if (!failed.remove(versionId)) {
+            String msg =
+                "Unable to finish merge. Specified version is not in"
+                + " jcr:mergeFailed property: " + safeGetJCRPath(state);
+            log.error(msg);
+            throw new VersionException(msg);
+        }
+
+        JcrVersionManagerImpl.WriteOperation ops = startWriteOperation();
+        try {
+            // remove version from mergeFailed list
+            setMergeFailed(state, failed);
+
+            if (!cancel) {
+                // add version to jcr:predecessors list
+                InternalValue[] vals = state.getPropertyValues(NameConstants.JCR_PREDECESSORS);
+                InternalValue[] v = new InternalValue[vals.length + 1];
+                for (int i = 0; i < vals.length; i++) {
+                    v[i] = InternalValue.create(vals[i].getNodeId());
+                }
+                v[vals.length] = InternalValue.create(versionId);
+                state.setPropertyValues(NameConstants.JCR_PREDECESSORS, PropertyType.REFERENCE, v, true);
+            }
+            state.store();
+            ops.save();
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
+        } finally {
+            ops.close();
+        }
+    }
+
+    /**
+     * Returns a set of nodeid from the jcr:mergeFailed property of the given state
+     * @param state the state
+     * @return set of node ids
+     * @throws RepositoryException if an error occurs
+     */
+    private Set<NodeId> getMergeFailed(NodeStateEx state)
+            throws RepositoryException {
+        Set<NodeId> set = new HashSet<NodeId>();
+        if (state.hasProperty(NameConstants.JCR_MERGEFAILED)) {
+            InternalValue[] vals = state.getPropertyValues(NameConstants.JCR_MERGEFAILED);
+            for (InternalValue val : vals) {
+                set.add(val.getNodeId());
+            }
+        }
+        return set;
+    }
+
+    /**
+     * Updates the merge failed property of the given state/
+     * @param state the state to update
+     * @param set the set of ids
+     * @throws RepositoryException if an error occurs
+     */
+    private void setMergeFailed(NodeStateEx state, Set<NodeId> set)
+            throws RepositoryException {
+        if (set.isEmpty()) {
+            state.removeProperty(NameConstants.JCR_MERGEFAILED);
+        } else {
+            InternalValue[] vals = new InternalValue[set.size()];
+            Iterator<NodeId> iter = set.iterator();
+            int i = 0;
+            while (iter.hasNext()) {
+                NodeId id = iter.next();
+                vals[i++] = InternalValue.create(id);
+            }
+            state.setPropertyValues(NameConstants.JCR_MERGEFAILED, PropertyType.REFERENCE, vals, true);
+        }
+    }
+
+    /**
+     * Merge the given activity to this workspace
+     *
+     * @param activity internal activity
+     * @param failedIds list of failed ids
+     * @throws RepositoryException if an error occurs
+     */
+    protected void merge(InternalActivity activity, List<ItemId> failedIds)
+            throws RepositoryException {
+
+        Map<NodeId, InternalVersion> changeSet = activity.getChangeSet();
+        ChangeSetVersionSelector vsel = new ChangeSetVersionSelector(changeSet);
+        WriteOperation ops = startWriteOperation();
+        try {
+            Iterator<NodeId> iter = changeSet.keySet().iterator();
+            while (iter.hasNext()) {
+                InternalVersion v = changeSet.remove(iter.next());
+                NodeStateEx state = getNodeStateEx(v.getFrozenNode().getFrozenId());
+                if (state != null) {
+                    InternalVersion base = getBaseVersion(state);
+                    // if base version is newer than version, add to failed list
+                    // but merge it anyways
+                    if (base.isMoreRecent(v)) {
+                        failedIds.add(state.getNodeId());
+                        // add it to the jcr:mergeFailed property
+                        Set<NodeId> set = getMergeFailed(state);
+                        set.add(base.getId());
+                        setMergeFailed(state, set);
+                        state.store();
+                    } else {
+                        for (InternalVersion restored: internalRestore(state, v, vsel, true)) {
+                            changeSet.remove(restored.getVersionHistory().getId());
+                        }
+                    }
+                }
+
+                // reset iterator
+                iter = changeSet.keySet().iterator();
+            }
+            ops.save();
+        } catch (ItemStateException e) {
+            throw new RepositoryException(e);
+        } finally {
+            ops.close();
+        }
+    }
+
+    /**
+     * Internal version selector that selects the version in the changeset.
+     */
+    private static class ChangeSetVersionSelector implements VersionSelector {
+
+        /**
+         * the change set.
+         */
+        private final Map<NodeId, InternalVersion> changeSet;
+
+        /**
+         * creates a changeset version selector
+         * @param changeSet the changeset map from history id -> version
+         */
+        private ChangeSetVersionSelector(Map<NodeId, InternalVersion> changeSet) {
+            this.changeSet = changeSet;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * Selects the version in the changeset
+         */
+        public InternalVersion select(InternalVersionHistory vh) throws RepositoryException {
+            return changeSet.get(vh.getId());
+        }
+    }
+
+}
\ No newline at end of file

Propchange: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/tripod-JCR-2209/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/JcrVersionManagerImplMerge.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url



Mime
View raw message