Author: rhauch
Date: 2009-03-20 14:22:45 -0400 (Fri, 20 Mar 2009)
New Revision: 784
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/Children.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java
Log:
DNA-194 Implement update JCR capability
Added most of the infrastructure to allow updates to the SessionCache. The cached
representations of the nodes are changed and corresponding requests are accumulated in a
Graph.Batch that are sent to the repository store when SessionCache.save() is called.
This implementation hooks some of the infrastructure up to the JcrAbstractNode
implementation, although more tests need to be written to verify the behavior.
Currently, only deleting nodes has been tested and is thought to be complete. Changing
properties, creating children, and moving nodes are partially implemented. (Some changes
to the node type manager are required, but there is currently an ongoing refactoring of
JcrNodeTypeManager. This is the primary reason for committing this partially-complete
work.)
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-03-18 17:05:16 UTC
(rev 783)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-03-20 18:22:45 UTC
(rev 784)
@@ -54,6 +54,7 @@
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathNotFoundException;
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.PropertyFactory;
import org.jboss.dna.graph.property.Reference;
@@ -66,6 +67,7 @@
import org.jboss.dna.graph.request.CreateWorkspaceRequest;
import org.jboss.dna.graph.request.DeleteBranchRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
+import org.jboss.dna.graph.request.InvalidRequestException;
import org.jboss.dna.graph.request.InvalidWorkspaceException;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
@@ -77,6 +79,7 @@
import org.jboss.dna.graph.request.ReadPropertyRequest;
import org.jboss.dna.graph.request.RemovePropertiesRequest;
import org.jboss.dna.graph.request.Request;
+import org.jboss.dna.graph.request.UnsupportedRequestException;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior;
@@ -217,6 +220,12 @@
* </p>
*
* @param request the request to be executed (may be a {@link CompositeRequest}.
+ * @throws PathNotFoundException if the request used a node that did not exist
+ * @throws InvalidRequestException if the request was not valid
+ * @throws InvalidWorkspaceException if the workspace used in the request was not
valid
+ * @throws UnsupportedRequestException if the request was not supported by the
source
+ * @throws RepositorySourceException if an error occurs during execution
+ * @throws RuntimeException if a runtime error occurs during execution
*/
protected void execute( Request request ) {
RepositoryConnection connection =
Graph.this.getConnectionFactory().createConnection(getSourceName());
@@ -1948,6 +1957,24 @@
}
/**
+ * Return whether this batch has been {@link #execute() executed}.
+ *
+ * @return true if this batch has already been executed, or false otherwise
+ */
+ public boolean hasExecuted() {
+ return executed;
+ }
+
+ /**
+ * Determine whether this batch needs to be executed (there are requests and the
batch has not been executed yet).
+ *
+ * @return true if there are some requests in this batch that need to be
executed, or false execution is not required
+ */
+ public boolean isExecuteRequired() {
+ return !executed || requestQueue.size() != 0;
+ }
+
+ /**
* Obtain the graph that this batch uses.
*
* @return the graph; never null
@@ -3318,6 +3345,11 @@
};
}
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.Graph.Executable#execute()
+ */
public Results execute() {
return this.requestQueue.execute();
}
@@ -3406,6 +3438,12 @@
* Stop accumulating the requests, submit them to the repository source, and
return the results.
*
* @return the results containing the requested information from the repository.
+ * @throws PathNotFoundException if a request used a node that did not exist
+ * @throws InvalidRequestException if a request was not valid
+ * @throws InvalidWorkspaceException if the workspace used in a request was not
valid
+ * @throws UnsupportedRequestException if a request was not supported by the
source
+ * @throws RepositorySourceException if an error occurs during execution
+ * @throws RuntimeException if a runtime error occurs during execution
*/
Results execute();
}
@@ -4539,7 +4577,15 @@
* @param nodeName the name of the new node
* @return the interface used to complete the request
*/
- CreateAction<Next> nodeNamed( String nodeName );
+ Create<Next> nodeNamed( String nodeName );
+
+ /**
+ * Specify the name of the node that is to be created.
+ *
+ * @param nodeName the name of the new node
+ * @return the interface used to complete the request
+ */
+ Create<Next> nodeNamed( Name nodeName );
}
/**
@@ -4653,6 +4699,7 @@
void submit( Request request );
void submit( List<Request> requests );
+
}
/**
@@ -4700,6 +4747,15 @@
return this.requests;
}
+ /**
+ * Determine the number of requests that are currently in the queue.
+ *
+ * @return the number of currently-enqueued requests
+ */
+ public int size() {
+ return requests.size();
+ }
+
public void submit( Request request ) {
if (!requests.isEmpty() && request instanceof
UpdatePropertiesRequest) {
// If the previous request was also an update, then maybe they can be
merged ...
@@ -5593,6 +5649,10 @@
Name nameObj = factory.create(name);
return new CreateAction<T>(afterConjunction(), queue(), parent,
workspaceName, nameObj);
}
+
+ public CreateAction<T> nodeNamed( Name name ) {
+ return new CreateAction<T>(afterConjunction(), queue(), parent,
workspaceName, name);
+ }
}
@Immutable
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java 2009-03-18 17:05:16
UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java 2009-03-20 18:22:45
UTC (rev 784)
@@ -166,33 +166,4 @@
return path().size();
}
- /**
- * {@inheritDoc}
- *
- * @throws UnsupportedOperationException always
- * @see javax.jcr.Item#refresh(boolean)
- */
- public void refresh( boolean keepChanges ) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws UnsupportedOperationException always
- * @see javax.jcr.Item#remove()
- */
- public void remove() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws UnsupportedOperationException always
- * @see javax.jcr.Item#save()
- */
- public void save() {
- throw new UnsupportedOperationException();
- }
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-03-18 17:05:16
UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-03-20 18:22:45
UTC (rev 784)
@@ -990,4 +990,24 @@
}
return patterns;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UnsupportedOperationException always
+ * @see javax.jcr.Item#refresh(boolean)
+ */
+ public void refresh( boolean keepChanges ) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UnsupportedOperationException always
+ * @see javax.jcr.Item#save()
+ */
+ public void save() {
+ throw new UnsupportedOperationException();
+ }
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -264,4 +264,34 @@
public final void setValue( Node value ) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UnsupportedOperationException always
+ * @see javax.jcr.Item#refresh(boolean)
+ */
+ public void refresh( boolean keepChanges ) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UnsupportedOperationException always
+ * @see javax.jcr.Item#remove()
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws UnsupportedOperationException always
+ * @see javax.jcr.Item#save()
+ */
+ public void save() {
+ throw new UnsupportedOperationException();
+ }
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-03-18 17:05:16 UTC
(rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-03-20 18:22:45 UTC
(rev 784)
@@ -76,6 +76,10 @@
public static I18n nodeDefinitionCouldNotBeDeterminedForNode;
public static I18n missingNodeTypeForExistingNode;
+ public static I18n unableToRemoveRootNode;
+ public static I18n unableToMoveNodeToBeChildOfDecendent;
+ public static I18n nodeHasAlreadyBeenRemovedFromThisSession;
+
public static I18n typeNotFound;
public static I18n supertypeNotFound;
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java 2009-03-18 17:05:16 UTC
(rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java 2009-03-20 18:22:45 UTC
(rev 784)
@@ -24,6 +24,7 @@
package org.jboss.dna.jcr;
import java.util.UUID;
+import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
@@ -85,4 +86,23 @@
public String getPath() throws RepositoryException {
return cache.getPathFor(nodeUuid).getString(namespaces());
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Item#remove()
+ */
+ public void remove() throws RepositoryException {
+ try {
+ SessionCache.NodeEditor editor = cache.getEditorFor(nodeInfo().getParent());
+ editor.destroyChild(nodeUuid);
+ } catch (ItemNotFoundException err) {
+ String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeUuid,
cache.workspaceName());
+ throw new RepositoryException(msg);
+ } catch (InvalidItemStateException err) {
+ String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeUuid,
cache.workspaceName());
+ throw new RepositoryException(msg);
+ }
+ }
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java 2009-03-18 17:05:16 UTC
(rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java 2009-03-20 18:22:45 UTC
(rev 784)
@@ -28,6 +28,7 @@
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
import net.jcip.annotations.NotThreadSafe;
/**
@@ -115,4 +116,15 @@
}
throw new ItemNotFoundException(JcrI18n.tooDeep.text(depth));
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see javax.jcr.Item#remove()
+ */
+ public void remove() throws ConstraintViolationException {
+ String msg = JcrI18n.unableToRemoveRootNode.text(cache.workspaceName());
+ throw new ConstraintViolationException(msg);
+ }
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-03-18 17:05:16 UTC
(rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-03-20 18:22:45 UTC
(rev 784)
@@ -633,10 +633,9 @@
/**
* {@inheritDoc}
*
- * @throws UnsupportedOperationException always
* @see javax.jcr.Session#save()
*/
- public void save() {
- throw new UnsupportedOperationException();
+ public void save() throws RepositoryException {
+ cache.save();
}
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java 2009-03-18 17:05:16
UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java 2009-03-20 18:22:45
UTC (rev 784)
@@ -27,12 +27,15 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Set;
import java.util.UUID;
+import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
@@ -40,6 +43,7 @@
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
@@ -60,6 +64,7 @@
import org.jboss.dna.graph.property.ValueFactories;
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.property.ValueFormatException;
+import org.jboss.dna.jcr.cache.ChangedNodeInfo;
import org.jboss.dna.jcr.cache.ChildNode;
import org.jboss.dna.jcr.cache.Children;
import org.jboss.dna.jcr.cache.EmptyChildren;
@@ -119,22 +124,26 @@
private final String workspaceName;
protected final ExecutionContext context;
protected final PathFactory pathFactory;
- private final NameFactory nameFactory;
- private final ValueFactory<String> stringFactory;
- private final NamespaceRegistry namespaces;
- private final PropertyFactory propertyFactory;
+ protected final NameFactory nameFactory;
+ protected final ValueFactory<String> stringFactory;
+ protected final NamespaceRegistry namespaces;
+ protected final PropertyFactory propertyFactory;
private final Graph store;
private final Name defaultPrimaryTypeName;
private final Property defaultPrimaryTypeProperty;
- private final Path rootPath;
+ protected final Path rootPath;
+ protected final Name residualName;
private UUID root;
private final ReferenceMap<UUID, AbstractJcrNode> jcrNodes;
private final ReferenceMap<PropertyId, AbstractJcrProperty> jcrProperties;
- private final HashMap<UUID, ImmutableNodeInfo> cachedNodes;
- private final HashMap<UUID, NodeInfo> changedNodes;
+ protected final HashMap<UUID, ImmutableNodeInfo> cachedNodes;
+ protected final HashMap<UUID, ChangedNodeInfo> changedNodes;
+ protected final HashMap<UUID, NodeInfo> deletedNodes;
+ private Graph.Batch operations;
+
public SessionCache( JcrSession session,
String workspaceName,
ExecutionContext context,
@@ -156,12 +165,17 @@
this.defaultPrimaryTypeName = JcrNtLexicon.UNSTRUCTURED;
this.defaultPrimaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE,
this.defaultPrimaryTypeName);
this.rootPath = pathFactory.createRootPath();
+ this.residualName = nameFactory.create(JcrNodeType.RESIDUAL_ITEM_NAME);
this.jcrNodes = new ReferenceMap<UUID,
AbstractJcrNode>(ReferenceType.STRONG, ReferenceType.SOFT);
this.jcrProperties = new ReferenceMap<PropertyId,
AbstractJcrProperty>(ReferenceType.STRONG, ReferenceType.SOFT);
this.cachedNodes = new HashMap<UUID, ImmutableNodeInfo>();
- this.changedNodes = new HashMap<UUID, NodeInfo>();
+ this.changedNodes = new HashMap<UUID, ChangedNodeInfo>();
+ this.deletedNodes = new HashMap<UUID, NodeInfo>();
+
+ // Create the batch operations ...
+ this.operations = this.store.batch();
}
JcrSession session() {
@@ -180,6 +194,39 @@
return session.nodeTypeManager();
}
+ final Graph.Batch operations() {
+ return operations;
+ }
+
+ /**
+ * Save any changes that have been accumulated by this session.
+ *
+ * @throws RepositoryException if any error resulting while saving the changes to the
repository
+ */
+ public void save() throws RepositoryException {
+ if (operations.isExecuteRequired()) {
+ // Execute the batched operations ...
+ try {
+ operations.execute();
+ } catch (RuntimeException e) {
+ throw new RepositoryException(e);
+ }
+
+ // Create a new batch for future operations ...
+ operations = store.batch();
+ // Remove all the cached items that have been changed or deleted ...
+ for (UUID changedUuid : changedNodes.keySet()) {
+ cachedNodes.remove(changedUuid);
+ }
+ for (UUID changedUuid : deletedNodes.keySet()) {
+ cachedNodes.remove(changedUuid);
+ }
+ // Remove all the changed and deleted infos ...
+ changedNodes.clear();
+ deletedNodes.clear();
+ }
+ }
+
public JcrRootNode findJcrRootNode() throws RepositoryException {
return (JcrRootNode)findJcrNode(findNodeInfoForRoot().getUuid());
}
@@ -215,11 +262,13 @@
* @return the information for the referenced node; never null
* @throws ItemNotFoundException if the reference node with the supplied UUID does
not exist
* @throws PathNotFoundException if the node given by the relative path does not
exist
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
* @throws RepositoryException if any other error occurs while reading information
from the repository
* @see #findNodeInfoForRoot()
*/
public AbstractJcrNode findJcrNode( UUID uuidOfReferenceNode,
- Path relativePath ) throws PathNotFoundException,
RepositoryException {
+ Path relativePath )
+ throws PathNotFoundException, InvalidItemStateException, RepositoryException {
// An existing JCR Node object was not found, so we'll have to create it by
finding the underlying
// NodeInfo for the node (from the changed state or the cache) ...
NodeInfo info = findNodeInfo(uuidOfReferenceNode, relativePath);
@@ -268,11 +317,13 @@
* @return the information for the referenced item; never null
* @throws ItemNotFoundException if the reference node with the supplied UUID does
not exist, or if an item given by the
* supplied relative path does not exist
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
* @throws RepositoryException if any other error occurs while reading information
from the repository
* @see #findNodeInfoForRoot()
*/
public AbstractJcrItem findJcrItem( UUID uuidOfReferenceNode,
- Path relativePath ) throws ItemNotFoundException,
RepositoryException {
+ Path relativePath )
+ throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
// A pathological case is an empty relative path ...
if (relativePath.size() == 0) {
return findJcrNode(uuidOfReferenceNode);
@@ -332,6 +383,412 @@
}
/**
+ * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties
or children on the node identified by the
+ * supplied UUID. The node must exist prior to this call, either as a node that
exists in the workspace or as a node that was
+ * created within this session but not yet persited to the workspace.
+ *
+ * @param uuid the UUID of the node that is to be changed; may not be null and must
represent an <i>existing</i> node
+ * @return the editor; never null
+ * @throws ItemNotFoundException if no such node could be found in the session or
workspace
+ * @throws InvalidItemStateException if the item has been marked for deletion within
this session
+ * @throws RepositoryException if any other error occurs while reading information
from the repository
+ */
+ public NodeEditor getEditorFor( UUID uuid ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
+ // See if we already have something in the changed nodes ...
+ ChangedNodeInfo info = changedNodes.get(uuid);
+ Location currentLocation = null;
+ if (info == null) {
+ // Or in the cache ...
+ NodeInfo cached = cachedNodes.get(uuid);
+ if (cached == null) {
+ cached = loadFromGraph(uuid, null);
+ }
+ // Now put into the changed nodes ...
+ info = new ChangedNodeInfo(cached);
+ changedNodes.put(uuid, info);
+ currentLocation = info.getOriginalLocation();
+ } else {
+ // compute the current location ...
+ currentLocation = Location.create(getPathFor(info), uuid);
+ }
+ return new NodeEditor(info, currentLocation);
+ }
+
+ /**
+ * An interface used to manipulate a node's properties and children.
+ */
+ public final class NodeEditor {
+ private final ChangedNodeInfo node;
+ private final Location currentLocation;
+
+ protected NodeEditor( ChangedNodeInfo node,
+ Location currentLocation ) {
+ this.node = node;
+ this.currentLocation = currentLocation;
+ }
+
+ /**
+ * Set the value for the property. If the property does not exist, it will be
added. If the property does exist, the
+ * existing values will be replaced with the supplied value.
+ *
+ * @param name the property name; may not be null
+ * @param value the new property values, which may be converted to the
appropriate {@link PropertyType type}
+ * @throws ConstraintViolationException if the property could not be set because
of a node type constraint or property
+ * definition constraint
+ */
+ public void setProperty( Name name,
+ Object value ) throws ConstraintViolationException {
+ Property dnaProp = propertyFactory.create(name, value);
+ setProperty(name, dnaProp);
+ }
+
+ /**
+ * Set the values for the property. If the property does not exist, it will be
added. If the property does exist, the
+ * existing values will be replaced with those that are supplied.
+ *
+ * @param name the property name; may not be null
+ * @param value the new property values, which may be converted to the
appropriate {@link PropertyType type}
+ * @throws ConstraintViolationException if the property could not be set because
of a node type constraint or property
+ * definition constraint
+ */
+ public void setProperty( Name name,
+ Object[] value ) throws ConstraintViolationException {
+ Property dnaProp = propertyFactory.create(name, value);
+ setProperty(name, dnaProp);
+ }
+
+ protected final void setProperty( Name name,
+ Property dnaProp ) throws
ConstraintViolationException {
+ PropertyInfo existing = node.getProperty(name);
+ PropertyInfo newProperty = null;
+ if (existing != null) {
+ // We're replacing an existing property, but we still need to check
that the property definition
+ // defines a type. So, find the property definition for the existing
property ...
+ JcrPropertyDefinition definition =
nodeTypes().getPropertyDefinition(existing.getDefinitionId(),
+
existing.isMultiValued());
+
+ // Look at the required property type ...
+ int propertyType = definition.getRequiredType();
+ if (propertyType == PropertyType.UNDEFINED) {
+ // We need set the new type to that defined by the values ...
+ propertyType = JcrSession.jcrPropertyTypeFor(dnaProp);
+ }
+
+ // Csreate the property info ...
+ newProperty = new PropertyInfo(existing.getPropertyId(),
existing.getDefinitionId(), propertyType, dnaProp,
+ existing.isMultiValued());
+ } else {
+ // It's a new property ...
+ PropertyId id = new PropertyId(node.getUuid(), name);
+ // Look find the property definition to use ...
+ JcrPropertyDefinition definition = findBestPropertyDefintion(dnaProp);
+
+ // Figure out the property type ...
+ int propertyType = definition.getRequiredType();
+ if (propertyType == PropertyType.UNDEFINED) {
+ propertyType = JcrSession.jcrPropertyTypeFor(dnaProp);
+ }
+ // Create the property info ...
+ newProperty = new PropertyInfo(id, definition.getId(), propertyType,
dnaProp, definition.isMultiple());
+ }
+ node.setProperty(newProperty, context().getValueFactories());
+ operations().set(dnaProp).on(currentLocation);
+ }
+
+ /**
+ * Remove the existing property with the supplied name.
+ *
+ * @param name the property name; may not be null
+ * @return true if there was a property with the supplied name, or false if no
such property existed
+ */
+ public boolean removeProperty( Name name ) {
+ if (node.removeProperty(name) != null) {
+ operations().remove(name).on(currentLocation);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Move the child specified by the supplied UUID to be a child of this node,
appending the child to the end of the current
+ * list of children. This method automatically disconnects the node from its
current parent.
+ *
+ * @param nodeUuid the UUID of the existing node; may not be null
+ * @return the representation of the newly-added child, which includes the {@link
ChildNode#getSnsIndex()
+ * same-name-sibling index}
+ * @throws ItemNotFoundException if the specified child node could be found in
the session or workspace
+ * @throws InvalidItemStateException if the specified child has been marked for
deletion within this session
+ * @throws ConstraintViolationException if moving the node into this node
violates this node's definition
+ * @throws RepositoryException if any other error occurs while reading
information from the repository
+ */
+ public ChildNode moveToBeChild( UUID nodeUuid )
+ throws ItemNotFoundException, InvalidItemStateException,
ConstraintViolationException, RepositoryException {
+
+ if (nodeUuid.equals(node.getUuid()) || isAncestor(nodeUuid)) {
+ Path pathOfNode = getPathFor(nodeUuid);
+ Path thisPath = currentLocation.getPath();
+ String msg =
JcrI18n.unableToMoveNodeToBeChildOfDecendent.text(pathOfNode, thisPath, workspaceName());
+ throw new RepositoryException(msg);
+ }
+
+ // Is the node already a child?
+ ChildNode child = node.getChildren().getChild(nodeUuid);
+ if (child != null) return child;
+
+ // Get an editor for the child (in its current location) and one for its
parent ...
+ NodeEditor existingNodeEditor = getEditorFor(nodeUuid);
+ ChangedNodeInfo existingNodeInfo = existingNodeEditor.node;
+ UUID existingParent = existingNodeInfo.getParent();
+ NodeEditor existingParentEditor = getEditorFor(existingParent);
+ ChangedNodeInfo existingParentInfo = existingParentEditor.node;
+
+ // Verify that this node's definition allows the specified child ...
+ Name childName =
existingParentInfo.getChildren().getChild(nodeUuid).getName();
+ int numSns =
node.getChildren().getCountOfSameNameSiblingsWithName(childName);
+ JcrNodeDefinition definition = findBestChildNodeDefinition(childName,
numSns);
+ if (!definition.getId().equals(node.getDefinitionId())) {
+ // The node definition changed, so try to set the property ...
+ try {
+ setProperty(DnaLexicon.NODE_DEFINITON,
definition.getId().getString());
+ } catch (ConstraintViolationException e) {
+ // We can't set this property on the node (according to the node
definition).
+ // But we still want the node info to have the correct node
definition.
+ // When it is reloaded into a cache (after being persisted), the
correct node definition
+ // will be computed again ...
+ node.setDefinitionId(definition.getId());
+
+ // And remove the property from the info ...
+ existingNodeEditor.removeProperty(DnaLexicon.NODE_DEFINITON);
+ }
+ }
+
+ // Remove the node from the current parent and add it to this ...
+ child = existingParentInfo.removeChild(nodeUuid, pathFactory);
+ ChildNode newChild = node.addChild(child.getName(), child.getUuid(),
pathFactory);
+
+ // Set the child's changed representation to point to this node as its
parent ...
+ existingNodeInfo.setParent(node.getUuid());
+
+ // Now, record the operation to do this ...
+ operations().move(existingNodeEditor.currentLocation).into(currentLocation);
+
+ return newChild;
+ }
+
+ /**
+ * Create a new node as a child of this node, using the supplied name and
(optionally) the supplied UUID.
+ *
+ * @param name the name for the new child; may not be null
+ * @param desiredUuid the desired UUID, or null if the UUID for the child should
be generated automatically
+ * @param primaryTypeName the name of the primary type for the new node
+ * @param nodeDefinitionId
+ * @return the representation of the newly-created child, which includes the
{@link ChildNode#getSnsIndex()
+ * same-name-sibling index}
+ * @throws InvalidItemStateException if the specified child has been marked for
deletion within this session
+ * @throws ConstraintViolationException if moving the node into this node
violates this node's definition
+ * @throws RepositoryException if any other error occurs while reading
information from the repository
+ */
+ public ChildNode createChild( Name name,
+ UUID desiredUuid,
+ Name primaryTypeName,
+ NodeDefinitionId nodeDefinitionId )
+ throws InvalidItemStateException, ConstraintViolationException,
RepositoryException {
+ if (desiredUuid == null) desiredUuid = UUID.randomUUID();
+
+ // Verify that this node accepts a child of the supplied name (given any
existing SNS nodes) ...
+ int numSns = node.getChildren().getCountOfSameNameSiblingsWithName(name);
+ JcrNodeDefinition definition = findBestChildNodeDefinition(name, numSns);
+
+ ChildNode result = node.addChild(name, desiredUuid, pathFactory);
+
+ // ---------------------------------------------------------
+ // Now create the child node representation in the cache ...
+ // ---------------------------------------------------------
+ Path newPath = pathFactory.create(currentLocation.getPath(),
result.getSegment());
+ Location location = Location.create(newPath, desiredUuid);
+
+ // Create the properties ...
+ Map<Name, PropertyInfo> properties = new HashMap<Name,
PropertyInfo>();
+ Property primaryTypeProp = propertyFactory.create(JcrLexicon.PRIMARY_TYPE,
primaryTypeName);
+ Property nodeDefinitionProp =
propertyFactory.create(DnaLexicon.NODE_DEFINITON, nodeDefinitionId.getString());
+
+ // Create the property info for the "jcr:primaryType" child
property ...
+ JcrPropertyDefinition primaryTypeDefn =
findBestPropertyDefintion(primaryTypeProp, primaryTypeName);
+ PropertyDefinitionId primaryTypeDefinitionId = primaryTypeDefn.getId();
+ PropertyInfo primaryTypeInfo = new PropertyInfo(new PropertyId(desiredUuid,
primaryTypeProp.getName()),
+ primaryTypeDefinitionId,
PropertyType.NAME, primaryTypeProp, false);
+ properties.put(primaryTypeProp.getName(), primaryTypeInfo);
+
+ // Create the property info for the "dna:nodeDefinition" child
property ...
+ JcrPropertyDefinition nodeDefnDefn =
findBestPropertyDefintion(nodeDefinitionProp, primaryTypeName);
+ if (nodeDefnDefn != null) {
+ PropertyDefinitionId nodeDefnDefinitionId = nodeDefnDefn.getId();
+ PropertyInfo nodeDefinitionInfo = new PropertyInfo(new
PropertyId(desiredUuid, nodeDefinitionProp.getName()),
+ nodeDefnDefinitionId,
PropertyType.STRING, nodeDefinitionProp,
+ true);
+ properties.put(nodeDefinitionProp.getName(), nodeDefinitionInfo);
+ }
+
+ // Now create the child node info, putting it in the changed map (and not the
cache map!) ...
+ NodeInfo info = new ImmutableNodeInfo(location, primaryTypeName, null,
definition.getId(), node.getUuid(), null,
+ properties);
+ ChangedNodeInfo changedInfo = new ChangedNodeInfo(info);
+ changedNodes.put(desiredUuid, changedInfo);
+
+ // ---------------------------------------
+ // Now record the changes to the store ...
+ // ---------------------------------------
+ Graph.Create<Graph.Batch> create =
operations().createUnder(currentLocation)
+ .nodeNamed(name)
+ .with(desiredUuid)
+ .with(primaryTypeProp);
+ if (nodeDefnDefn != null) {
+ create = create.with(nodeDefinitionProp);
+ }
+ create.and();
+ return result;
+ }
+
+ /**
+ * Destroy the child node with the supplied UUID and all nodes that exist below
it, including any nodes that were created
+ * and haven't been persisted.
+ *
+ * @param nodeUuid the UUID of the child node; may not be null
+ * @return true if the child was successfully removed, or false if the node did
not exist as a child
+ */
+ public boolean destroyChild( UUID nodeUuid ) {
+ ChildNode deleted = node.removeChild(nodeUuid, pathFactory);
+
+ if (deleted != null) {
+ // Recursively mark the cached/changed information as deleted ...
+ deleteNodeInfos(nodeUuid);
+
+ // Now make the request to the source ...
+ Path childPath = pathFactory.create(currentLocation.getPath(),
deleted.getSegment());
+ Location locationOfChild = Location.create(childPath, nodeUuid);
+ operations().delete(locationOfChild);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Find the best property definition in this node's
+ *
+ * @param dnaProperty the new property that is to be set on this node
+ * @return the property definition that allows setting this property; never null
+ * @throws ConstraintViolationException if setting the property would violates
this node's definition or the property's
+ * definition
+ */
+ protected JcrPropertyDefinition findBestPropertyDefintion( Property dnaProperty )
throws ConstraintViolationException {
+ // First check the primary type ...
+ JcrPropertyDefinition definition = findBestPropertyDefintion(dnaProperty,
node.getPrimaryTypeName());
+ if (definition != null) {
+ // TODO: Does this definition allow this value? ...
+ return definition;
+ }
+ // Check the mixin types ...
+ for (Name mixinTypeName : node.getMixinTypeNames()) {
+ definition = findBestPropertyDefintion(dnaProperty, mixinTypeName);
+ if (definition != null) {
+ // TODO: Does this definition allow this value? ...
+ return definition;
+ }
+ }
+
+ // Nothing was found yet, so check the residual property definitions,
starting with the primary type ...
+ definition = findBestPropertyDefintion(dnaProperty, residualName);
+ if (definition != null) {
+ // TODO: Does this definition allow this value? ...
+ return definition;
+ }
+ // Check the mixin types ...
+ for (Name mixinTypeName : node.getMixinTypeNames()) {
+ definition = findBestPropertyDefintion(dnaProperty, mixinTypeName);
+ if (definition != null) {
+ // TODO: Does this definition allow this value? ...
+ return definition;
+ }
+ }
+
+ // No definition that allowed the values ...
+ throw new ConstraintViolationException();
+ }
+
+ /**
+ * Find the best property definition in the named node type.
+ *
+ * @param dnaProperty the new property that is to be set on this node
+ * @param nodeTypeName the name of the node type that should be checked; may not
be null
+ * @return the property definition that allows setting this property; or null if
no valid property definition could be
+ * found in the given node type
+ * @see #findBestPropertyDefintion(Property)
+ */
+ protected JcrPropertyDefinition findBestPropertyDefintion( Property dnaProperty,
+ Name nodeTypeName ) {
+ JcrNodeType nodeType = nodeTypes().getNodeType(nodeTypeName);
+ Name name = dnaProperty.getName();
+ JcrPropertyDefinition definition = null;
+ if (dnaProperty.isSingle()) {
+ // First look for a single-valued property definition with a matching
name ...
+ definition = nodeType.getPropertyDefinition(name, false);
+ if (definition != null) return definition;
+
+ // Then look for a residual definition ...
+ definition =
nodeType.getPropertyDefinition(JcrNodeType.RESIDUAL_ITEM_NAME, false);
+ if (definition != null) return definition;
+ }
+
+ // Either the DNA property has 0 or 2+ values (and we couldn't use a
single-valued definition)
+ // OR there was 1 value and we couldn't find a single-valued definition.
+ // So, we need to look for a multi-valued property definition ...
+
+ // First look for a definition that matches by name ...
+ definition = nodeType.getPropertyDefinition(name, true);
+ if (definition != null) return definition;
+
+ // Then look for a residual definition ...
+ definition = nodeType.getPropertyDefinition(JcrNodeType.RESIDUAL_ITEM_NAME,
true);
+ if (definition != null) return definition;
+
+ // Nothing found yet ...
+ if (INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS) {
+ // We can use the "nt:unstructured" property definitions for
any property ...
+ JcrNodeType unstructured =
nodeTypes().getNodeType(JcrNtLexicon.UNSTRUCTURED);
+ definition =
unstructured.getPropertyDefinition(JcrNodeType.RESIDUAL_ITEM_NAME, true);
+ if (definition != null) return definition;
+ }
+
+ // No property definition could be found ...
+ return null;
+ }
+
+ /**
+ * @param childName
+ * @param numberOfExistingChildrenWithName
+ * @return the most specific node definition for the child; never null
+ * @throws ConstraintViolationException if the new child would violates this
node's definition
+ */
+ protected JcrNodeDefinition findBestChildNodeDefinition( Name childName,
+ int
numberOfExistingChildrenWithName )
+ throws ConstraintViolationException {
+
+ // No definition that allowed the values ...
+ throw new ConstraintViolationException();
+ }
+
+ protected boolean isAncestor( UUID uuid ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
+ UUID ancestor = node.getParent();
+ while (ancestor != null) {
+ if (ancestor.equals(uuid)) return true;
+ NodeInfo info = findNodeInfo(ancestor);
+ ancestor = info.getParent();
+ }
+ return false;
+ }
+ }
+
+ /**
* Utility method that creates and caches the appropriate kind of AbstractJcrNode
implementation for node given by the
* supplied information.
*
@@ -387,20 +844,18 @@
* @param uuid the UUID for the node; may not be null
* @return the information for the node with the supplied UUID, or null if the
information is not in the cache
* @throws ItemNotFoundException if there is no node with the supplied UUID
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
* @throws RepositoryException if any other error occurs while reading information
from the repository
* @see #findNodeInfoInCache(UUID)
* @see #findNodeInfo(UUID, Path)
* @see #findNodeInfoForRoot()
*/
- NodeInfo findNodeInfo( UUID uuid ) throws ItemNotFoundException, RepositoryException
{
- // See if we already have something in the changed nodes ...
- NodeInfo info = changedNodes.get(uuid);
+ NodeInfo findNodeInfo( UUID uuid ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
+ // See if we already have something in the cache ...
+ NodeInfo info = findNodeInfoInCache(uuid);
if (info == null) {
- // Or in the cache ...
- info = cachedNodes.get(uuid);
- if (info == null) {
- info = loadFromGraph(uuid, null);
- }
+ // Nope, so go ahead and load it ...
+ info = loadFromGraph(uuid, null);
}
return info;
}
@@ -414,13 +869,20 @@
* @see #findNodeInfo(UUID)
* @see #findNodeInfo(UUID, Path)
* @see #findNodeInfoForRoot()
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
*/
- NodeInfo findNodeInfoInCache( UUID uuid ) {
+ NodeInfo findNodeInfoInCache( UUID uuid ) throws InvalidItemStateException {
// See if we already have something in the changed nodes ...
NodeInfo info = changedNodes.get(uuid);
if (info == null) {
// Or in the cache ...
info = cachedNodes.get(uuid);
+ if (info == null) {
+ // Finally check if the node was deleted ...
+ if (deletedNodes.containsKey(uuid)) {
+ throw new InvalidItemStateException();
+ }
+ }
}
return info;
}
@@ -452,11 +914,13 @@
* @return the information for the referenced node; never null
* @throws ItemNotFoundException if the reference node with the supplied UUID does
not exist
* @throws PathNotFoundException if the node given by the relative path does not
exist
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
* @throws RepositoryException if any other error occurs while reading information
from the repository
* @see #findNodeInfoForRoot()
*/
NodeInfo findNodeInfo( UUID node,
- Path relativePath ) throws ItemNotFoundException,
PathNotFoundException, RepositoryException {
+ Path relativePath )
+ throws ItemNotFoundException, InvalidItemStateException, PathNotFoundException,
RepositoryException {
// The relative path must be normalized ...
assert relativePath.isNormalized();
@@ -583,19 +1047,21 @@
* @param propertyId the identifier for the property; may not be null
* @return the property information, or null if the node does not contain the
specified property
* @throws PathNotFoundException if the node containing this property does not exist
+ * @throws InvalidItemStateException if the node with the UUID has been deleted in
this session
* @throws RepositoryException if there is an error while obtaining the information
*/
- PropertyInfo findPropertyInfo( PropertyId propertyId ) throws PathNotFoundException,
RepositoryException {
+ PropertyInfo findPropertyInfo( PropertyId propertyId )
+ throws PathNotFoundException, InvalidItemStateException, RepositoryException {
NodeInfo info = findNodeInfo(propertyId.getNodeId());
return info.getProperty(propertyId.getPropertyName());
}
- Path getPathFor( UUID uuid ) throws ItemNotFoundException, RepositoryException {
+ Path getPathFor( UUID uuid ) throws ItemNotFoundException, InvalidItemStateException,
RepositoryException {
if (uuid == root) return rootPath;
return getPathFor(findNodeInfo(uuid));
}
- Path getPathFor( NodeInfo info ) throws ItemNotFoundException, RepositoryException {
+ Path getPathFor( NodeInfo info ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
if (info != null && info.getUuid() == root) return rootPath;
LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>();
while (info != null) {
@@ -619,7 +1085,7 @@
return getPathFor(findPropertyInfo(propertyId));
}
- protected Name getNameOf( UUID nodeUuid ) throws ItemNotFoundException,
RepositoryException {
+ protected Name getNameOf( UUID nodeUuid ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
findNodeInfoForRoot();
if (nodeUuid == root) return nameFactory.create("");
// Get the parent ...
@@ -629,7 +1095,7 @@
return child.getName();
}
- protected int getSnsIndexOf( UUID nodeUuid ) throws ItemNotFoundException,
RepositoryException {
+ protected int getSnsIndexOf( UUID nodeUuid ) throws ItemNotFoundException,
InvalidItemStateException, RepositoryException {
findNodeInfoForRoot();
if (nodeUuid == root) return 1;
// Get the parent ...
@@ -718,7 +1184,9 @@
if (uuid != null) {
// Check for an identification property ...
uuidProperty = location.getIdProperty(JcrLexicon.UUID);
- if (uuidProperty == null) uuidProperty =
location.getIdProperty(DnaLexicon.UUID);
+ if (uuidProperty == null) {
+ uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
+ }
}
if (uuidProperty == null) {
uuidProperty = graphNode.getProperty(JcrLexicon.UUID);
@@ -843,9 +1311,12 @@
}
// The process the mixin types ...
org.jboss.dna.graph.property.Property mixinTypesProperty =
graphProperties.get(JcrLexicon.MIXIN_TYPES);
+ Set<Name> mixinTypeNames = null;
if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) {
for (Object mixinTypeValue : mixinTypesProperty) {
Name mixinTypeName = nameFactory.create(mixinTypeValue);
+ if (mixinTypeNames == null) mixinTypeNames = new HashSet<Name>();
+ mixinTypeNames.add(mixinTypeName);
if (!referenceable &&
JcrMixLexicon.REFERENCEABLE.equals(mixinTypeName)) referenceable = true;
String mixinTypeNameString = mixinTypeName.getString(namespaces);
NodeType mixinType = nodeTypes().getNodeType(mixinTypeNameString);
@@ -958,7 +1429,7 @@
List<Location> locations = graphNode.getChildren();
Children children = locations.isEmpty() ? new EmptyChildren(parentUuid) : new
ImmutableChildren(parentUuid, locations);
props = Collections.unmodifiableMap(props);
- return new ImmutableNodeInfo(location, primaryTypeName, definition.getId(),
parentUuid, children, props);
+ return new ImmutableNodeInfo(location, primaryTypeName, mixinTypeNames,
definition.getId(), parentUuid, children, props);
}
/**
@@ -985,4 +1456,73 @@
// TODO: should this also check the mixins?
return primaryType.findBestNodeDefinitionForChild(childName,
primaryTypeOfChild);
}
+
+ /**
+ * This method finds the {@link NodeInfo} for the node with the supplied UUID and
marks it as being deleted, and does the same
+ * for all decendants (e.g., children, grandchildren, great-grandchildren, etc.) that
have been cached or changed.
+ * <p>
+ * Note that this method only processes those nodes that are actually represented in
this cache. Any branches that are not
+ * loaded are not processed. This is an acceptable assumption, since all ancestors of
a cached node should also be cached.
+ * </p>
+ * <p>
+ * Also be aware that the returned count of deleted node info representations will
only reflect the total number of nodes in
+ * the branch if and only if all branch nodes were cached. In all other cases, the
count returned will be fewer than the
+ * number of actual nodes in the branch.
+ * </p>
+ *
+ * @param uuid the UUID of the node that should be marked as deleted; may not be
null
+ * @return the number of node info representations that were marked as deleted
+ */
+ protected int deleteNodeInfos( UUID uuid ) {
+ Queue<UUID> nodesToDelete = new LinkedList<UUID>();
+ int numDeleted = 0;
+ nodesToDelete.add(uuid);
+ while (!nodesToDelete.isEmpty()) {
+ UUID toDelete = nodesToDelete.remove();
+ // Remove the node info from the changed map ...
+ NodeInfo info = changedNodes.remove(toDelete);
+ if (info == null) {
+ // Wasn't changed, so remove it from the cache map ...
+ info = cachedNodes.remove(toDelete);
+ }
+ // Whether or not we found an info, add it to the deleted map ...
+ this.deletedNodes.put(toDelete, info);
+
+ if (info != null) {
+ // Get all the children and add them to the queue ...
+ for (ChildNode child : info.getChildren()) {
+ nodesToDelete.add(child.getUuid());
+ }
+ }
+ ++numDeleted;
+ }
+ return numDeleted;
+ }
+
+ /**
+ * Find the best {@link JcrNodeDefinition child definition} for a child with the
specified name given the named primary type
+ * and mixin types.
+ *
+ * @param childName the name of the child that is to be added
+ * @param childPrimaryTypeName the name of the child's primary type, or null if
the child's primary type is not known
+ * @param requiresMultipleSns true if there is at least one existing child with the
same name, requiring the child node type
+ * to allow same-name-siblings, or false if the child will be the first child
with the supplied name
+ * @param primaryTypeName the name of the primary type for the parent; may not be
null
+ * @param mixinTypeNames the names of the mixin types for the parent; may be null or
empty if the parent has no mixins
+ * @return the child node definition that can be used (which may be a residual
definition), or null if there is no such node
+ * type
+ */
+ protected JcrNodeDefinition findBestChildDefinition( Name childName,
+ Name childPrimaryTypeName,
+ boolean requiresMultipleSns,
+ Name primaryTypeName,
+ Collection<Name>
mixinTypeNames ) {
+ // First check the primary type for a child definition with the supplied name
...
+ JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
+ JcrNodeDefinition definition =
primaryType.findBestNodeDefinitionForChild(childName, childPrimaryTypeName);
+ if (definition != null) {
+ // definition.
+ }
+ return null;
+ }
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -28,11 +28,15 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.JcrLexicon;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NameFactory;
import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.ValueFactories;
+import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.property.Path.Segment;
+import org.jboss.dna.jcr.DnaLexicon;
import org.jboss.dna.jcr.NodeDefinitionId;
/**
@@ -41,7 +45,6 @@
* Each instance maintains a reference to the original (usually immutable) NodeInfo
representation that was probably read from the
* repository.
*/
-@Immutable
public class ChangedNodeInfo implements NodeInfo {
protected static final PropertyInfo DELETED_PROPERTY = null;
@@ -68,6 +71,18 @@
private Map<Name, PropertyInfo> changedProperties;
/**
+ * The updated list of mixin node type names. This is merely a cached version of
what's already in the
+ * {@link JcrLexicon#MIXIN_TYPES "jcr:mixinTypes"} property.
+ */
+ private Set<Name> changedMixinTypeNames;
+
+ /**
+ * The updated node definition, which may be changed when this node is moved to a
different parent (with a different node
+ * type)
+ */
+ private NodeDefinitionId changedDefinitionId;
+
+ /**
* Create an immutable NodeInfo instance.
*
* @param original the original node information, may not be null
@@ -135,13 +150,39 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.cache.NodeInfo#getMixinTypeNames()
+ */
+ public Set<Name> getMixinTypeNames() {
+ if (changedMixinTypeNames != null) return changedMixinTypeNames;
+ return original.getMixinTypeNames();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.jcr.cache.NodeInfo#getDefinitionId()
*/
public NodeDefinitionId getDefinitionId() {
+ if (changedDefinitionId != null) return changedDefinitionId;
return original.getDefinitionId();
}
/**
+ * Set the identifier of the node definition for this node. This should normally be
changed by
+ * {@link #setProperty(PropertyInfo, ValueFactories) setting} the {@link
DnaLexicon#NODE_DEFINITON} property. However, since
+ * that property is not always allowed, this method provides a way to set it locally
(without requiring a property).
+ *
+ * @param definitionId the new property definition identifier; may not be null
+ * @see #setProperty(PropertyInfo, ValueFactories)
+ */
+ public void setDefinitionId( NodeDefinitionId definitionId ) {
+ if (!getDefinitionId().equals(definitionId)) {
+ assert definitionId != null;
+ changedDefinitionId = definitionId;
+ }
+ }
+
+ /**
* {@inheritDoc}
*
* @see org.jboss.dna.jcr.cache.NodeInfo#getChildren()
@@ -172,43 +213,42 @@
/**
* Remove a child from the children. This method only uses the child's UUID to
identify the contained ChildNode instance that
* should be removed.
- * <p>
- * Note that this method returns the new {@link Children} container, which is the
same as would be returned by
- * {@link #getChildren()} called immediately after this method.
- * </p>
*
* @param childUUID the UUID of the child that is to be removed; may not be null
* @param factory the path factory that should be used to create a {@link Segment}
for replacement {@link ChildNode} objects
* for nodes with the same name that and higher same-name-sibiling indexes.
- * @return the Children object that has the modified children
+ * @return the child node that was removed, or null if no such child could be
removed
*/
- public Children removeChild( UUID childUUID,
- PathFactory factory ) {
+ public ChildNode removeChild( UUID childUUID,
+ PathFactory factory ) {
+ ChildNode deleted = null;
if (changedChildren == null) {
// Create the changed children. First check whether there are 0 or 1 child
...
Children existing = original.getChildren();
int numExisting = existing.size();
if (numExisting == 0) {
// nothing to do, so return the original's children
- return existing;
+ return null;
}
- if (existing.getChild(childUUID) == null) {
- // The requested child doesn't exist in the children, so return
silently ...
- return existing;
+ deleted = existing.getChild(childUUID);
+ if (deleted == null) {
+ // The requested child doesn't exist in the children, so return ...
+ return null;
}
if (numExisting == 1) {
// We're removing the only child in the original ...
changedChildren = new ChangedChildren(existing.getParentUuid());
- return changedChildren;
+ return existing.getChild(childUUID);
}
// There is at least one child, so create the new children container ...
assert existing instanceof InternalChildren;
InternalChildren internal = (InternalChildren)existing;
changedChildren = internal.without(childUUID, factory);
} else {
+ deleted = changedChildren.getChild(childUUID);
changedChildren = changedChildren.without(childUUID, factory);
}
- return changedChildren;
+ return deleted;
}
/**
@@ -271,8 +311,10 @@
return original.getProperty(name);
}
- public PropertyInfo setProperty( PropertyInfo newProperty ) {
+ public PropertyInfo setProperty( PropertyInfo newProperty,
+ ValueFactories factories ) {
Name name = newProperty.getPropertyName();
+ PropertyInfo previous = null;
if (changedProperties == null) {
// There were no changes made yet ...
@@ -281,18 +323,33 @@
changedProperties.put(name, newProperty);
// And return the original property (or null if there was none) ...
- return original.getProperty(name);
+ previous = original.getProperty(name);
+ } else if (changedProperties.containsKey(name)) {
+ // The property was already changed, in which case we need to return the
changed one ...
+ previous = changedProperties.put(name, newProperty);
+ } else {
+ // Otherwise, the property was not yet changed or deleted ...
+ previous = original.getProperty(name);
+ changedProperties.put(name, newProperty);
}
- // The property may already have been changed, in which case we need to return
the changed one ...
- if (changedProperties.containsKey(name)) {
- PropertyInfo changed = changedProperties.put(name, null);
- // The named property was indeed deleted ...
- return changed;
+ // If this property was the "jcr:mixinTypes" property, update the
cached values ...
+ if (name.equals(JcrLexicon.MIXIN_TYPES)) {
+ if (changedMixinTypeNames == null) {
+ changedMixinTypeNames = new HashSet<Name>();
+ } else {
+ changedMixinTypeNames.clear();
+ }
+ NameFactory nameFactory = factories.getNameFactory();
+ for (Object value : newProperty.getProperty()) {
+ changedMixinTypeNames.add(nameFactory.create(value));
+ }
+ } else if (name.equals(DnaLexicon.NODE_DEFINITON)) {
+ ValueFactory<String> stringFactory = factories.getStringFactory();
+ String value =
stringFactory.create(newProperty.getProperty().getFirstValue());
+ changedDefinitionId = NodeDefinitionId.fromString(value,
factories.getNameFactory());
}
- // Otherwise, the property was not yet changed or deleted ...
- PropertyInfo changed = original.getProperty(name);
- changedProperties.put(name, newProperty);
- return changed;
+
+ return previous;
}
public PropertyInfo removeProperty( Name name ) {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/Children.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/Children.java 2009-03-18 17:05:16
UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/Children.java 2009-03-20 18:22:45
UTC (rev 784)
@@ -73,4 +73,12 @@
*/
Iterator<ChildNode> getChildren( Name name );
+ /**
+ * Get the number of same-name-siblings that all share the supplied name.
+ *
+ * @param name the name for the children; may not be null
+ * @return the number of same-name-siblings with the supplied name
+ */
+ int getCountOfSameNameSiblingsWithName( Name name );
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -102,6 +102,15 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.jcr.cache.Children#getCountOfSameNameSiblingsWithName(org.jboss.dna.graph.property.Name)
+ */
+ public int getCountOfSameNameSiblingsWithName( Name name ) {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.jcr.cache.InternalChildren#with(org.jboss.dna.graph.property.Name,
java.util.UUID,
* org.jboss.dna.graph.property.PathFactory)
*/
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -170,6 +170,15 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.jcr.cache.Children#getCountOfSameNameSiblingsWithName(org.jboss.dna.graph.property.Name)
+ */
+ public int getCountOfSameNameSiblingsWithName( Name name ) {
+ return this.childrenByName.get(name).size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.jcr.cache.InternalChildren#with(org.jboss.dna.graph.property.Name,
java.util.UUID,
* org.jboss.dna.graph.property.PathFactory)
*/
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -23,6 +23,7 @@
*/
package org.jboss.dna.jcr.cache;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -43,19 +44,22 @@
private final NodeDefinitionId definition;
private final Children children;
private final Map<Name, PropertyInfo> properties;
+ private final Set<Name> mixinTypeNames;
/**
* Create an immutable NodeInfo instance.
*
* @param originalLocation the original location
* @param primaryTypeName the name of the node's primary type
+ * @param mixinTypeNames the names of the mixin types for this node, or null if there
are none
* @param definition the definition used when creating the node
* @param parent the parent
- * @param children the immutable children
- * @param properties the unmodifiable map of properties
+ * @param children the immutable children; may be null if there are no children
+ * @param properties the unmodifiable map of properties; may be null if there are no
properties
*/
public ImmutableNodeInfo( Location originalLocation,
Name primaryTypeName,
+ Set<Name> mixinTypeNames,
NodeDefinitionId definition,
UUID parent,
Children children,
@@ -65,11 +69,17 @@
this.definition = definition;
this.parent = parent;
this.uuid = this.originalLocation.getUuid();
- this.children = children;
+ this.children = children != null ? children : new EmptyChildren(this.uuid);
+ if (properties == null) properties = Collections.emptyMap();
this.properties = properties;
+ if (mixinTypeNames == null) mixinTypeNames = Collections.emptySet();
+ this.mixinTypeNames = mixinTypeNames;
assert this.uuid != null;
assert this.definition != null;
assert this.primaryTypeName != null;
+ assert this.children != null;
+ assert this.mixinTypeNames != null;
+ assert this.properties != null;
}
/**
@@ -111,6 +121,15 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.cache.NodeInfo#getMixinTypeNames()
+ */
+ public Set<Name> getMixinTypeNames() {
+ return mixinTypeNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.jcr.cache.NodeInfo#getDefinitionId()
*/
public NodeDefinitionId getDefinitionId() {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java 2009-03-18 17:05:16
UTC (rev 783)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java 2009-03-20 18:22:45
UTC (rev 784)
@@ -55,6 +55,13 @@
public Name getPrimaryTypeName();
/**
+ * Get the names of the mixin types for this node.
+ *
+ * @return the unmodifiable set of mixin type names; never null but possibly empty
+ */
+ public Set<Name> getMixinTypeNames();
+
+ /**
* @return definition
*/
public NodeDefinitionId getDefinitionId();
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-03-20
18:22:45 UTC (rev 784)
@@ -66,6 +66,10 @@
nodeDefinitionCouldNotBeDeterminedForNode = Unable to determine a valid node definition
for the node "{0}" in workspace "{1}"
missingNodeTypeForExistingNode = Missing primary node type "{0}" for node {1}
in workspace "{2}"
+unableToRemoveRootNode = Unable to remove the root node in workspace "{1}"
+unableToMoveNodeToBeChildOfDecendent = Node "{0}" in workspace "{2}"
cannot be moved under a decendant node ("{1}")
+nodeHasAlreadyBeenRemovedFromThisSession = Node "{0}" in workspace "{1}
has already been removed from this session
+
REP_NAME_DESC = DNA Repository
REP_VENDOR_DESC = JBoss - A division of Red Hat Middleware LLC
SPEC_NAME_DESC = Content Repository for Java Technology API
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -72,6 +72,18 @@
public boolean isNode() {
return false;
}
+
+ public void refresh( boolean keepChanges ) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void save() {
+ throw new UnsupportedOperationException();
+ }
};
}
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -100,6 +100,10 @@
public String getPath() throws RepositoryException {
return cache.getPathFor(nodeInfo()).getString(namespaces());
}
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
}
private ExecutionContext context;
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java 2009-03-18
17:05:16 UTC (rev 783)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -25,8 +25,11 @@
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
import static org.hamcrest.core.IsSame.sameInstance;
+import static org.jboss.dna.graph.IsNodeWithChildren.hasChildren;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.hasItems;
import static org.mockito.Mockito.stub;
import java.io.File;
@@ -39,16 +42,20 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import javax.jcr.InvalidItemStateException;
import javax.jcr.nodetype.NodeType;
import org.jboss.dna.common.statistic.Stopwatch;
import org.jboss.dna.common.util.StringUtil;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.Node;
import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathNotFoundException;
import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.jcr.SessionCache.NodeEditor;
import org.jboss.dna.jcr.cache.Children;
import org.jboss.dna.jcr.cache.NodeInfo;
import org.jboss.dna.jcr.cache.PropertyInfo;
@@ -63,7 +70,6 @@
*/
public class SessionCacheTest {
- private String workspaceName;
private ExecutionContext context;
private JcrNodeTypeManager nodeTypes;
private Stopwatch sw;
@@ -85,8 +91,6 @@
context = new ExecutionContext();
context.getNamespaceRegistry().register("vehix",
"http://example.com/vehicles");
- workspaceName = "theWorkspace";
-
stub(session.getExecutionContext()).toReturn(context);
stub(session.namespaces()).toReturn(context.getNamespaceRegistry());
@@ -97,15 +101,9 @@
nodeTypes = new JcrNodeTypeManager(session, nodeTypeSource);
stub(session.nodeTypeManager()).toReturn(nodeTypes);
- InMemoryRepositorySource source = new InMemoryRepositorySource();
- source.setName("store");
- source.setDefaultWorkspaceName(workspaceName);
- store = Graph.create(source, context);
-
- // Import the "cars.xml" file into the repository
- store.importXmlFrom(new
File("src/test/resources/vehicles.xml")).into("/");
-
- cache = new SessionCache(session, workspaceName, context, nodeTypes, store);
+ // Now set up the graph and session cache ...
+ store = getGraph("vehicles"); // imports the
"/src/test/resources/vehicles.xml" file
+ cache = getCache("vehicles");
}
/**
@@ -164,8 +162,8 @@
File file ) throws IOException, SAXException {
InMemoryRepositorySource source = new InMemoryRepositorySource();
source.setName(repositoryName);
- source.setDefaultWorkspaceName(workspaceName);
Graph graph = Graph.create(source, context);
+ graph.createWorkspace().named(workspaceName);
if (file != null) {
graph.importXmlFrom(file).into("/");
@@ -229,6 +227,71 @@
}
}
+ protected void assertDoesExist( Graph graph,
+ Location location ) {
+ Node node = store.getNodeAt(location);
+ assertThat(node, is(notNullValue()));
+ assertThat(node.getLocation(), is(location));
+ }
+
+ protected void assertDoesNotExist( Graph graph,
+ Location location ) {
+ try {
+ store.getNodeAt(location);
+ fail("Shouldn't have found the node " + location);
+ } catch (PathNotFoundException e) {
+ // expected
+ }
+ }
+
+ protected void assertDoesNotExist( Graph graph,
+ Path path ) {
+ try {
+ store.getNodeAt(path);
+ fail("Shouldn't have found the node " + path);
+ } catch (PathNotFoundException e) {
+ // expected
+ }
+ }
+
+ protected void assertDoesNotExist( Graph graph,
+ UUID uuid ) {
+ try {
+ store.getNodeAt(uuid);
+ fail("Shouldn't have found the node " + uuid);
+ } catch (PathNotFoundException e) {
+ // expected
+ }
+ }
+
+ protected void assertDoNotExist( Graph graph,
+ List<Location> locations ) {
+ for (Location location : locations)
+ assertDoesNotExist(graph, location);
+ }
+
+ protected void assertDoesNotExist( SessionCache cache,
+ UUID uuid ) throws InvalidItemStateException {
+ assertThat(cache.findNodeInfoInCache(uuid), is(nullValue()));
+ }
+
+ protected void assertIsDeleted( SessionCache cache,
+ UUID uuid ) {
+ try {
+ cache.findNodeInfoInCache(uuid);
+ fail("Shouldn't have found the node " + uuid);
+ } catch (InvalidItemStateException err) {
+ // expected
+ }
+ }
+
+ protected void assertDoesExist( SessionCache cache,
+ UUID uuid ) throws InvalidItemStateException {
+ NodeInfo info = cache.findNodeInfoInCache(uuid);
+ assertThat(info, is(notNullValue()));
+ assertThat(info.getUuid(), is(uuid));
+ }
+
@Test
public void shouldCreateWithValidParameters() {
assertThat(cache, is(notNullValue()));
@@ -386,10 +449,6 @@
@Test
public void shouldFindInfoForNodeUsingRelativePathFromNonRoot() throws Exception {
- String sourceName = "vehicles";
- Graph store = getGraph(sourceName);
- SessionCache cache = getCache(sourceName);
-
// Verify that the node does exist in the source ...
Path carsAbsolutePath = path("/vehix:Vehicles/vehix:Cars");
Node carsNode = store.getNodeAt(carsAbsolutePath);
@@ -430,10 +489,6 @@
@Test
public void shouldFindJcrNodeUsingAbsolutePaths() throws Exception {
- String sourceName = "vehicles";
- Graph store = getGraph(sourceName);
- SessionCache cache = getCache(sourceName);
-
// Verify that the node does exist in the source ...
Path carsAbsolutePath = path("/vehix:Vehicles/vehix:Cars");
Node carsNode = store.getNodeAt(carsAbsolutePath);
@@ -477,4 +532,99 @@
assertThat(cache.getPathFor(lr3), is(lr3AbsolutePath));
assertThat(cache.getPathFor(b787), is(b787AbsolutePath));
}
+
+ @Test
+ public void shouldDeleteLeafNode() throws Exception {
+ // Find the state of some Graph nodes we'll be using in the test ...
+ Node utility =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility");
+ Node lr2Node =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover
LR2");
+ Node lr3Node =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover
LR3");
+ int numChildrenOfUtility = utility.getChildren().size();
+ assertThat(numChildrenOfUtility, is(4));
+ assertThat(utility.getChildren(), hasChildren(segment("vehix:Land Rover
LR2"),
+ segment("vehix:Land Rover
LR3"),
+ segment("vehix:Hummer
H3"),
+ segment("vehix:Ford
F-150")));
+
+ // Now get the editor for the 'vehix:Utility' node ...
+ NodeEditor editor = cache.getEditorFor(utility.getLocation().getUuid());
+ assertThat(editor, is(notNullValue()));
+
+ // Destroy the LR3 node, which is a leaf ...
+ editor.destroyChild(lr3Node.getLocation().getUuid());
+
+ // Verify that the store has not yet been changed ...
+ assertDoesExist(store, lr3Node.getLocation());
+
+ // Save the session and verify that the node was deleted ...
+ cache.save();
+ Node utilityNode2 = store.getNodeAt(utility.getLocation());
+ assertThat(utilityNode2.getChildren().size(), is(numChildrenOfUtility - 1));
+ assertThat(utilityNode2.getChildren(), hasChildren(segment("vehix:Land Rover
LR2"), // no LR3!
+ segment("vehix:Hummer
H3"),
+ segment("vehix:Ford
F-150")));
+ // Should no longer find the LR3 node in the graph ...
+ assertDoesNotExist(store, lr3Node.getLocation().getUuid());
+ assertDoesExist(store, lr2Node.getLocation());
+ }
+
+ @Test
+ public void shouldDeleteNonLeafNode() throws Exception {
+ // Find the state of some Graph nodes we'll be using in the test ...
+ Node carsNode = store.getNodeAt("/vehix:Vehicles/vehix:Cars");
+ Node utility =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility");
+ Node hybrid =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Hybrid");
+ Node luxury =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Luxury");
+ Node sports =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Sports");
+ Node lr3 =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover
LR3");
+ Node lr2 =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover
LR2");
+ Node f150 =
store.getNodeAt("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Ford F-150");
+ int numChildrenOfCars = carsNode.getChildren().size();
+ assertThat(numChildrenOfCars, is(4));
+ assertThat(carsNode.getChildren(),
hasChildren(segment("vehix:Hybrid"),
+
segment("vehix:Sports"),
+
segment("vehix:Luxury"),
+
segment("vehix:Utility")));
+
+ // Load the LR2 and Hummer nodes ...
+ NodeInfo h3i = cache.findNodeInfo(utility.getLocation().getUuid(),
path("vehix:Hummer H3"));
+ NodeInfo lr2i = cache.findNodeInfo(utility.getLocation().getUuid(),
path("vehix:Land Rover LR2"));
+ assertThat(h3i, is(notNullValue()));
+ assertThat(lr2i, is(notNullValue()));
+ assertThat(lr2i.getUuid(), is(lr2.getLocation().getUuid()));
+
+ // Now get the editor for the 'vehix:Cars' node ...
+ NodeEditor editor = cache.getEditorFor(carsNode.getLocation().getUuid());
+ assertThat(editor, is(notNullValue()));
+
+ // Destroy the Utility node, which is NOT a leaf ...
+ editor.destroyChild(utility.getLocation().getUuid());
+
+ // Verify that the store has not yet been changed ...
+ assertDoesExist(store, utility.getLocation());
+
+ // ... but the utility node and its two loaded children have been marked for
deletion ...
+ assertIsDeleted(cache, utility.getLocation().getUuid());
+ assertIsDeleted(cache, h3i.getUuid());
+ assertIsDeleted(cache, lr2i.getUuid());
+
+ // Save the session and verify that the Utility node and its children were
deleted ...
+ cache.save();
+ Node carsNode2 = store.getNodeAt(carsNode.getLocation());
+ assertThat(carsNode2.getChildren().size(), is(numChildrenOfCars - 1));
+ assertThat(carsNode2.getChildren(),
+ hasChildren(segment("vehix:Hybrid"),
segment("vehix:Sports"), segment("vehix:Luxury")));
+ // Should no longer find the Utility node in the graph ...
+ assertDoesNotExist(store, utility.getLocation());
+ assertDoesNotExist(cache, utility.getLocation().getUuid());
+ assertDoesNotExist(cache, h3i.getUuid());
+ assertDoesNotExist(cache, lr2i.getUuid());
+ assertDoesNotExist(cache, lr3.getLocation().getUuid());
+ assertDoesNotExist(cache, f150.getLocation().getUuid());
+ assertDoNotExist(store, utility.getChildren());
+ assertDoesExist(store, hybrid.getLocation());
+ assertDoesExist(store, luxury.getLocation());
+ assertDoesExist(store, sports.getLocation());
+ }
+
}
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java
===================================================================
---
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java 2009-03-18
17:05:16 UTC (rev 783)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java 2009-03-20
18:22:45 UTC (rev 784)
@@ -37,13 +37,16 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.stub;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.JcrLexicon;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.Path.Segment;
import org.jboss.dna.jcr.NodeDefinitionId;
import org.junit.Before;
@@ -63,6 +66,7 @@
private NodeDefinitionId definitionId;
private ChangedChildren children;
private Map<Name, PropertyInfo> properties;
+ private Set<Name> mixinTypeNames;
private ChangedNodeInfo changes;
@Before
@@ -78,7 +82,8 @@
definitionId = new NodeDefinitionId(name("acme:geniusContainerType"),
name("acme:geniuses"));
children = new ChangedChildren(uuid);
properties = new HashMap<Name, PropertyInfo>();
- original = new ImmutableNodeInfo(location, primaryTypeName, definitionId, uuid,
children, properties);
+ mixinTypeNames = new HashSet<Name>();
+ original = new ImmutableNodeInfo(location, primaryTypeName, mixinTypeNames,
definitionId, uuid, children, properties);
// Create the changed node representation ...
changes = new ChangedNodeInfo(original);
@@ -116,7 +121,7 @@
Name propName = name(name);
PropertyInfo propertyInfo = mock(PropertyInfo.class);
stub(propertyInfo.getPropertyName()).toReturn(propName);
- changes.setProperty(propertyInfo);
+ changes.setProperty(propertyInfo, context.getValueFactories());
return propertyInfo;
}
@@ -137,7 +142,7 @@
// Note that it may not be the same ChildNode instance if another SNS node with
smaller index was removed
assertThat(changes.getChildren().getChild(child.getUuid()), is(notNullValue()));
// Now remove the child, making sure the result is the same as the next
'getChildren()' call ...
- assertThat(changes.removeChild(child.getUuid(), pathFactory),
is(sameInstance(changes.getChildren())));
+ assertThat(changes.removeChild(child.getUuid(), pathFactory),
is(notNullValue()));
// Verify it no longer exists ...
assertThat(changes.getChildren().getChild(child.getUuid()), is(nullValue()));
}
@@ -158,6 +163,28 @@
}
@Test
+ public void shouldHaveMixinTypeNamesFromOriginal() {
+ assertThat(changes.getMixinTypeNames(), is(sameInstance(mixinTypeNames)));
+ }
+
+ @Test
+ public void shouldUpdateMixinTypeNamesWhenSettingJcrMixinTypeProperty() {
+ // Create the DNA property ...
+ Property mixinTypes = context.getPropertyFactory().create(JcrLexicon.MIXIN_TYPES,
"dna:type1", "dna:type2");
+
+ // Modify the property ...
+ PropertyInfo newPropertyInfo = mock(PropertyInfo.class);
+ stub(newPropertyInfo.getPropertyName()).toReturn(mixinTypes.getName());
+ stub(newPropertyInfo.getProperty()).toReturn(mixinTypes);
+ PropertyInfo previous = changes.setProperty(newPropertyInfo,
context.getValueFactories());
+ assertThat(previous, is(nullValue()));
+
+ // Verify that the mixin types were updated ...
+ assertThat(changes.getProperty(name("jcr:mixinTypes")),
is(sameInstance(newPropertyInfo)));
+ assertThat(changes.getMixinTypeNames(), hasItems(name("dna:type2"),
name("dna:type1")));
+ }
+
+ @Test
public void shouldHaveNodeDefinitionIdFromOriginal() {
assertThat(changes.getDefinitionId(), is(sameInstance(definitionId)));
}
@@ -428,7 +455,8 @@
// Create a bogus node that has a new UUID but with the same segment as
'childA3' ...
Children before = changes.getChildren();
int beforeSize = before.size();
- Children after = changes.removeChild(UUID.randomUUID(), pathFactory);
+ assertThat(changes.removeChild(UUID.randomUUID(), pathFactory),
is(nullValue()));
+ Children after = changes.getChildren();
assertThat(after.size(), is(beforeSize));
assertThat(after, is(sameInstance(before)));
assertThat(after, is(sameInstance(changes.getChildren())));
@@ -451,7 +479,8 @@
// Create a bogus node that has a new UUID but with the same segment as
'childA3' ...
Children before = changes.getChildren();
int beforeSize = before.size();
- Children after = changes.removeChild(UUID.randomUUID(), pathFactory);
+ assertThat(changes.removeChild(UUID.randomUUID(), pathFactory),
is(nullValue()));
+ Children after = changes.getChildren();
assertThat(after.size(), is(beforeSize));
assertThat(after, is(sameInstance(before)));
assertThat(after, is(sameInstance(changes.getChildren())));
@@ -471,7 +500,7 @@
// Modify the property ...
PropertyInfo newPropertyInfo = mock(PropertyInfo.class);
stub(newPropertyInfo.getPropertyName()).toReturn(name("test"));
- PropertyInfo previous = changes.setProperty(newPropertyInfo);
+ PropertyInfo previous = changes.setProperty(newPropertyInfo,
context.getValueFactories());
assertThat(previous, is(sameInstance(propertyInfo)));
// Verify we can find the new property ...