You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2004/09/20 18:11:26 UTC

svn commit: rev 46939 - incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core

Author: tripod
Date: Mon Sep 20 09:11:24 2004
New Revision: 46939

Modified:
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/ItemImpl.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/NodeImpl.java
Log:
Adjusted NodeImpl.update() and NodeImpl.merge() closer to spec.

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/ItemImpl.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/ItemImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/ItemImpl.java	Mon Sep 20 09:11:24 2004
@@ -64,6 +64,9 @@
     // jcr:lastModified
     public static final QName PROPNAME_LAST_MODIFIED =
 	    new QName(NamespaceRegistryImpl.NS_JCR_URI, "lastModified");
+    // jcr:mergeFailed 
+    public static final QName PROPNAME_MERGE_FAILED =
+	    new QName(NamespaceRegistryImpl.NS_JCR_URI, "mergeFailed");
 
     protected static final int STATUS_NORMAL = 0;
     protected static final int STATUS_MODIFIED = 1;

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/NodeImpl.java
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/NodeImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/jcr/core/NodeImpl.java	Mon Sep 20 09:11:24 2004
@@ -653,6 +653,11 @@
 	}
     }
 
+    /**
+     * Checks if this node is the root node.
+     * todo: is this the root node of this workspace?
+     * @return
+     */
     protected boolean isRepositoryRoot() {
 	return ((NodeState) state).getUUID().equals(rep.getRootNodeUUID());
     }
@@ -2157,20 +2162,79 @@
 	    throws NoSuchWorkspaceException, AccessDeniedException,
 	    RepositoryException {
 
-	// @todo Node.update has changed semantics; check with current spec...
+	NodeImpl srcNode = getCorrespondingNode(srcWorkspaceName);
+	if (srcNode==null) {
+	    throw new ItemNotFoundException("No corresponding node for " + safeGetJCRPath());
+	}
+	// not sure, if clone overrides 'this' node.
+	session.getWorkspace().clone(srcWorkspaceName, srcNode.getPath(), getPath());
+    }
+
+    /**
+     * Returns the corresponding node in the <code>scrWorkspaceName</code> of
+     * this node.
+     * <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 srcWorkspaceName
+     * @return the corresponding node or <code>null</code> if no corresponding
+     *         node exists.
+     * @throws NoSuchWorkspaceException If <code>srcWorkspace</code> does not exist.
+     * @throws AccessDeniedException If the current session does not have sufficient rights to perform the operation.
+     * @throws RepositoryException If another error occurs.
+     */
+    private NodeImpl getCorrespondingNode(String srcWorkspaceName)
+	    throws NoSuchWorkspaceException, AccessDeniedException,
+	    RepositoryException {
 
 	SessionImpl srcSession = rep.getSystemSession(srcWorkspaceName);
-	// get src node either by uuid or path. since all nodes in the RI do
-	// have UUIDs, should we always take the internal uuid?
-	NodeImpl srcNode;
+	Node root = session.getRootNode();
+	// if (isRepositoryRoot()) [don't know, if this works correctly with workspaces]
+	if (isSame(root)) {
+	    return (NodeImpl) srcSession.getRootNode();
+	}
+
+	// if this node is referenceable, return the corresponding one
 	if (isNodeType(NodeTypeRegistry.MIX_REFERENCEABLE)) {
-	    srcNode = (NodeImpl) srcSession.getNodeByUUID(getUUID());
-	} else {
-	    srcNode = (NodeImpl) srcSession.getItem(getPath());
+	    try {
+		return (NodeImpl) srcSession.getNodeByUUID(getUUID());
+	    } catch (ItemNotFoundException e) {
+		return null;
+	    }
 	}
 
-	internalUpdate(srcNode, false, false);
-	save();
+	// search nearest ancestor that is referenceable
+	NodeImpl m1 = this;
+	while (!m1.isSame(root) && !m1.isNodeType(NodeTypeRegistry.MIX_REFERENCEABLE)) {
+	    m1 = (NodeImpl) m1.getParent();
+	}
+	// special treatment for root
+	if (m1.isSame(root)) {
+	    return (NodeImpl) srcSession.getItem(getPath());
+	}
+
+	// calculate relative path. please note, that this cannot be done
+	// iteratively in the 'while' loop above, since getName() does not
+	// return the relative path, but just the name (without path indices)
+	// n1.getPath() = /foo/bar/something[1]
+	// m1.getPath() = /foo
+	//      relpath = bar/something[1]
+	String relPath = getPath().substring(m1.getPath().length()+1);
+	try {
+	    return (NodeImpl) srcSession.getNodeByUUID(m1.getUUID()).getNode(relPath);
+	} catch (ItemNotFoundException e) {
+	    return null;
+	}
     }
 
     /**
@@ -2180,113 +2244,126 @@
 	    throws UnsupportedRepositoryOperationException, NoSuchWorkspaceException,
 	    AccessDeniedException, MergeException, RepositoryException {
 
-	checkVersionable();
-	SessionImpl srcSession = rep.getSystemSession(srcWorkspace);
-	NodeImpl srcNode = (NodeImpl) srcSession.getNodeByUUID(getUUID());
-	srcNode.checkVersionable();
+	NodeImpl srcNode = doMergeTest(srcWorkspace, bestEffort);
+	if (srcNode!=null) {
+	    // remove properties
+	    PropertyIterator pi = getProperties();
+	    while (pi.hasNext()) {
+		Property p = pi.nextProperty();
+		if (!srcNode.hasProperty(p.getName())) {
+		    p.setValue((Value) null);
+		}
+	    }
+	    // copy properties
+	    pi = srcNode.getProperties();
+	    while (pi.hasNext()) {
+		PropertyImpl p = (PropertyImpl) pi.nextProperty();
+		internalCopyPropertyFrom(p);
+	    }
+
+	    // remove subnodes
+	    NodeIterator ni = getNodes();
+	    while (ni.hasNext()) {
+		// if the subnode does not exist in the src, and this is update,
+		// so delete here aswell?
+		Node n = ni.nextNode();
+		if (!srcNode.hasNode(n.getName())) {
+		    // todo: how does this work for same name siblings?
+		    remove(n.getName());
+		}
+	    }
+	    // 'clone' nodes that do not exist
+	    ni = srcNode.getNodes();
+	    while (ni.hasNext()) {
+		Node n = ni.nextNode();
+		if (!hasNode(n.getName())) {
+		    // todo: probably need some internal stuff
+		    // todo: how does this work for same name siblings?
+		    // todo: since clone is a ws operation, 'save' does not work later
+		    session.getWorkspace().clone(srcWorkspace, n.getPath(), getPath() + "/" + n.getName());
+		} else {
+		    // do recursive merge
+		    n.merge(srcWorkspace, bestEffort);
+		}
+	    }
+	} else {
+	    // do not change this node, but recuse merge
+	    NodeIterator ni = srcNode.getNodes();
+	    while (ni.hasNext()) {
+		ni.nextNode().merge(srcWorkspace, bestEffort);
+	    }
+	}
 
-	internalUpdate(srcNode, bestEffort, true);
 	save();
     }
 
     /**
-     * Internal helper that combines the functionalities of update and merge.
+     * 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.
      *
-     * @see Node#update(String)
-     * @see Node#merge(String, boolean)
+     * @param srcWorkspace
+     * @param bestEffort
+     * @return
+     * @throws RepositoryException
+     * @throws AccessDeniedException
      */
-    private void internalUpdate(NodeImpl srcNode, boolean bestEffort, boolean merge)
-	    throws RepositoryException {
+    private NodeImpl doMergeTest(String srcWorkspace, boolean bestEffort)
+	    throws RepositoryException, AccessDeniedException {
 
-	boolean ignore = false;
-	if (merge) {
-	    if (!getVersionHistory().isSame(srcNode.getVersionHistory())) {
-		String msg = "Unable to merge nodes. They have differen version histories " + safeGetJCRPath();
-		log.debug(msg);
-		throw new MergeException(msg);
-	    }
-	    VersionImpl v = (VersionImpl) getBaseVersion();
-	    VersionImpl srcV = (VersionImpl) srcNode.getBaseVersion();
-	    // check if version in src is newer
-	    if (srcV.isMoreRecent(v)) {
-		// src version is newer than this version
-		ignore = false;
-	    } else if (v.isMoreRecent(srcV)) {
-		// this version is newer than src version, ignore
-		ignore = true;
-	    } else {
-		// versions are same. according to spec -> merge exception
-		// but 'ignore' seems to be better
-		ignore = true;
-	    }
-	}
-	if (!ignore) {
-	    // copy the proerties
-	    PropertyIterator piter = srcNode.getProperties();
-	    while (piter.hasNext()) {
-		PropertyImpl prop = (PropertyImpl) piter.nextProperty();
-		switch (prop.getDefinition().getOnParentVersion()) {
-		    case OnParentVersionAction.ABORT:
-			throw new RepositoryException("Update aborted due to OPV in " + prop.safeGetJCRPath());
-		    case OnParentVersionAction.COMPUTE:
-		    case OnParentVersionAction.IGNORE:
-		    case OnParentVersionAction.INITIALIZE:
-			break;
-		    case OnParentVersionAction.VERSION:
-		    case OnParentVersionAction.COPY:
-			Value[] values = prop.getValues();
-			InternalValue[] ivalues = new InternalValue[values.length];
-			for (int i = 0; i < values.length; i++) {
-			    ivalues[i] = InternalValue.create(values[i], session.getNamespaceResolver());
-			}
-			internalSetProperty(prop.getQName(), ivalues);
-			break;
-		}
-	    }
+	// If N does not have a corresponding node then the merge result for N is leave.
+	NodeImpl srcNode = getCorrespondingNode(srcWorkspace);
+	if (srcNode==null) {
+	    return null;
 	}
 
-	// copy the childnodes
-	NodeIterator niter = srcNode.getNodes();
-	while (niter.hasNext()) {
-	    NodeImpl srcChild = (NodeImpl) niter.nextNode();
-	    NodeImpl ownChild = null;
-	    if (srcChild.isNodeType(NodeTypeRegistry.MIX_REFERENCEABLE)) {
-		ownChild = (NodeImpl) srcChild.session.getNodeByUUID(srcChild.getUUID());
-		if (!ownChild.getParent().isSame(this)) {
-		    // source child is not at same location?
-		    ownChild = null;
+	// if not versionable, update
+	if (!isNodeType(NodeTypeRegistry.MIX_VERSIONABLE)) {
+	    return srcNode;
+	}
+	// if source node is not versionable, leave
+	if (!srcNode.isNodeType(NodeTypeRegistry.MIX_VERSIONABLE)) {
+	    return null;
+	}
+	// test versions
+	VersionImpl v = (VersionImpl) getBaseVersion();
+	VersionImpl vp = (VersionImpl) srcNode.getBaseVersion();
+	if (vp.isMoreRecent(v) && !isCheckedOut()) {
+	    // 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.isSame(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
+		if (hasProperty(ItemImpl.PROPNAME_MERGE_FAILED)) {
+		    Value[] values = getProperty(ItemImpl.PROPNAME_MERGE_FAILED).getValues();
+		    Value[] newValues = new Value[values.length+1];
+		    System.arraycopy(values, 0, newValues, 0, values.length);
+		    newValues[values.length] = new ReferenceValue(vp);
+		    setProperty(ItemImpl.PROPNAME_MERGE_FAILED, newValues);
+		} else {
+		    Value[] newValues = new Value[1];
+		    newValues[0] = new ReferenceValue(vp);
+		    setProperty(ItemImpl.PROPNAME_MERGE_FAILED, newValues);
 		}
+		return null;
 	    } else {
-		try {
-		    ownChild = (NodeImpl) srcChild.session.getItem(srcChild.getPath());
-		} catch (PathNotFoundException e) {
-		    // ignore
-		}
-	    }
-
-	    if (!ignore) {
-		switch (srcChild.getDefinition().getOnParentVersion()) {
-		    case OnParentVersionAction.ABORT:
-			throw new RepositoryException("Update aborted due to OPV in " + srcChild.safeGetJCRPath());
-		    case OnParentVersionAction.COMPUTE:
-		    case OnParentVersionAction.IGNORE:
-		    case OnParentVersionAction.INITIALIZE:
-			break;
-		    case OnParentVersionAction.VERSION:
-			// todo: implement
-			break;
-		    case OnParentVersionAction.COPY:
-			// todo: implement
-			break;
-		}
-	    }
-
-	    // If isDeep is set to true then every node with a UUID in the
-	    // subtree rooted at this node is updated.
-
-	    // @todo Node.merge has changed semantics; check with current spec...
-	    if (bestEffort && ownChild != null && srcChild.isNodeType(NodeTypeRegistry.MIX_REFERENCEABLE)) {
-		ownChild.internalUpdate(srcChild, true, merge);
+		String msg = "Unable to merge nodes. Violating versions. " + safeGetJCRPath();
+		log.debug(msg);
+		throw new MergeException(msg);
 	    }
 	}
     }