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
|