View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.connector.base;
25  
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.ListIterator;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.UUID;
37  import net.jcip.annotations.NotThreadSafe;
38  import org.modeshape.common.util.StringUtil;
39  import org.modeshape.graph.ExecutionContext;
40  import org.modeshape.graph.GraphI18n;
41  import org.modeshape.graph.Location;
42  import org.modeshape.graph.connector.RepositorySourceException;
43  import org.modeshape.graph.connector.UuidAlreadyExistsException;
44  import org.modeshape.graph.property.Name;
45  import org.modeshape.graph.property.NamespaceRegistry;
46  import org.modeshape.graph.property.Path;
47  import org.modeshape.graph.property.PathNotFoundException;
48  import org.modeshape.graph.property.Property;
49  import org.modeshape.graph.property.PropertyType;
50  import org.modeshape.graph.property.Reference;
51  import org.modeshape.graph.property.UuidFactory;
52  import org.modeshape.graph.property.ValueFactory;
53  import org.modeshape.graph.property.Path.Segment;
54  import org.modeshape.graph.query.QueryResults;
55  import org.modeshape.graph.request.AccessQueryRequest;
56  import org.modeshape.graph.request.FullTextSearchRequest;
57  
58  /**
59   * An implementation of {@link Transaction} that maintains a cache of nodes by their hash (or {@link UUID}).
60   * 
61   * @param <WorkspaceType> the type of workspace
62   * @param <NodeType> the type of node
63   */
64  @NotThreadSafe
65  public abstract class MapTransaction<NodeType extends MapNode, WorkspaceType extends MapWorkspace<NodeType>>
66      extends BaseTransaction<NodeType, WorkspaceType> {
67  
68      /** The set of changes to the workspaces that have been made by this transaction */
69      private Map<String, WorkspaceChanges> changesByWorkspaceName;
70  
71      /**
72       * Create a new transaction.
73       * 
74       * @param context the execution context for this transaction; may not be null
75       * @param repository the repository against which the transaction will be operating; may not be null
76       * @param rootNodeUuid the UUID of the root node; may not be null
77       */
78      protected MapTransaction( ExecutionContext context,
79                                Repository<NodeType, WorkspaceType> repository,
80                                UUID rootNodeUuid ) {
81          super(context, repository, rootNodeUuid);
82      }
83  
84      /**
85       * Get the changes for the supplied workspace, optionally creating the necessary object if it does not yet exist. The changes
86       * object is used to record the changes made to the workspace by operations within this transaction, which are either pushed
87       * into the workspace upon {@link #commit()} or cleared upon {@link #rollback()}.
88       * 
89       * @param workspace the workspace
90       * @param createIfMissing true if the changes object should be created if it does not yet exist, or false otherwise
91       * @return the changes object; may be null if <code>createIfMissing</code> is <code>false</code> and the changes object does
92       *         not yet exist, or never null if <code>createIfMissing</code> is <code>true</code>
93       */
94      protected WorkspaceChanges getChangesFor( WorkspaceType workspace,
95                                                boolean createIfMissing ) {
96          if (changesByWorkspaceName == null) {
97              if (!createIfMissing) return null;
98              WorkspaceChanges changes = new WorkspaceChanges(workspace);
99              changesByWorkspaceName = new HashMap<String, WorkspaceChanges>();
100             changesByWorkspaceName.put(workspace.getName(), changes);
101             return changes;
102         }
103         WorkspaceChanges changes = changesByWorkspaceName.get(workspace.getName());
104         if (changes == null && createIfMissing) {
105             changes = new WorkspaceChanges(workspace);
106             changesByWorkspaceName.put(workspace.getName(), changes);
107         }
108         return changes;
109     }
110 
111     /**
112      * {@inheritDoc}
113      * 
114      * @see org.modeshape.graph.connector.base.Transaction#getNode(org.modeshape.graph.connector.base.Workspace,
115      *      org.modeshape.graph.Location)
116      */
117     public NodeType getNode( WorkspaceType workspace,
118                              Location location ) {
119         assert location != null;
120         // First look for the UUID ...
121         UUID uuid = location.getUuid();
122         if (uuid != null) {
123             WorkspaceChanges changes = getChangesFor(workspace, false);
124             NodeType node = null;
125             if (changes != null) {
126                 // See if the node we're looking for was deleted ...
127                 if (changes.isRemoved(uuid)) {
128                     // This node was removed within this transaction ...
129                     Path lowestExisting = null;
130                     if (location.hasPath()) {
131                         getNode(workspace, location.getPath(), location); // should fail
132                         assert false;
133                     }
134                     lowestExisting = pathFactory.createRootPath();
135                     throw new PathNotFoundException(location, lowestExisting, GraphI18n.nodeDoesNotExist.text(readable(location)));
136                 }
137                 // Not deleted, but maybe changed in this transaction ...
138                 node = changes.getChangedOrAdded(uuid);
139                 if (node != null) return node;
140             }
141             // It hasn't been loaded already, so attempt to load it from the map owned by the workspace ...
142             node = workspace.getNode(uuid);
143             if (node != null) return node;
144         }
145         // Otherwise, look by path ...
146         if (location.hasPath()) {
147             return getNode(workspace, location.getPath(), location);
148         }
149         // Unable to find by UUID or by path, so fail ...
150         Path lowestExisting = pathFactory.createRootPath();
151         throw new PathNotFoundException(location, lowestExisting, GraphI18n.nodeDoesNotExist.text(readable(location)));
152     }
153 
154     /**
155      * Attempt to find the node with the supplied UUID.
156      * 
157      * @param workspace the workspace; may not be null
158      * @param uuid the UUID of the node; may not be null
159      * @return the node, or null if no such node exists
160      */
161     protected NodeType findNode( WorkspaceType workspace,
162                                  UUID uuid ) {
163         WorkspaceChanges changes = getChangesFor(workspace, false);
164         NodeType node = null;
165         if (changes != null) {
166             // See if the node we're looking for was deleted ...
167             if (changes.isRemoved(uuid)) {
168                 // This node was removed within this transaction ...
169                 return null;
170             }
171             // Not deleted, but maybe changed in this transaction ...
172             node = changes.getChangedOrAdded(uuid);
173             if (node != null) return node;
174         }
175         // It hasn't been loaded already, so attempt to load it from the map owned by the workspace ...
176         node = workspace.getNode(uuid);
177         return node;
178     }
179 
180     /**
181      * Destroy the node.
182      * 
183      * @param workspace the workspace; never null
184      * @param node the node to be destroyed
185      */
186     protected void destroyNode( WorkspaceType workspace,
187                                 NodeType node ) {
188         WorkspaceChanges changes = getChangesFor(workspace, true);
189         destroyNode(changes, workspace, node);
190     }
191 
192     /**
193      * Destroy the node and it's contents.
194      * 
195      * @param changes the record of the workspace changes; never null
196      * @param workspace the workspace; never null
197      * @param node the node to be destroyed
198      */
199     private void destroyNode( WorkspaceChanges changes,
200                               WorkspaceType workspace,
201                               NodeType node ) {
202         // First destroy the children ...
203         for (UUID childUuid : node.getChildren()) {
204             NodeType child = changes.getChangedOrAdded(childUuid);
205             if (child == null) {
206                 // The node was not changed or added, so go back to the workspace ...
207                 child = workspace.getNode(childUuid);
208             }
209             destroyNode(changes, workspace, child);
210         }
211         // Then remove the requested node ...
212         UUID uuid = node.getUuid();
213         changes.removed(uuid);
214     }
215 
216     /**
217      * {@inheritDoc}
218      * 
219      * @see org.modeshape.graph.connector.base.Transaction#addChild(org.modeshape.graph.connector.base.Workspace,
220      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, int, java.util.UUID, java.lang.Iterable)
221      */
222     @SuppressWarnings( "unchecked" )
223     public NodeType addChild( WorkspaceType workspace,
224                               NodeType parent,
225                               Name name,
226                               int index,
227                               UUID uuid,
228                               Iterable<Property> properties ) {
229         if (uuid == null) {
230             uuid = UUID.randomUUID();
231         }
232         WorkspaceChanges changes = getChangesFor(workspace, true);
233 
234         // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes
235         if (!parent.hasChanges()) {
236             parent = findNode(workspace, parent.getUuid());
237         }
238 
239         NodeType newNode = null;
240         if (index < 0) {
241             // Figure out the SNS of the new node ...
242             int snsIndex = 1;
243             for (NodeType child : getChildren(workspace, parent)) {
244                 if (child.getName().getName().equals(name)) ++snsIndex;
245             }
246 
247             // Create the new node ...
248             newNode = createNode(uuid, pathFactory.createSegment(name, snsIndex), parent.getUuid(), properties);
249             // And add to the parent ...
250             parent = (NodeType)parent.withChild(uuid);
251         } else {
252             int snsIndex = 0;
253             ListIterator<NodeType> existingSiblings = getChildren(workspace, parent).listIterator(index);
254             while (existingSiblings.hasNext()) {
255                 NodeType existingSibling = existingSiblings.next();
256                 Segment existingSegment = existingSibling.getName();
257                 if (existingSegment.getName().equals(name)) {
258                     int existingIndex = existingSegment.getIndex();
259                     if (snsIndex == 0) snsIndex = existingIndex;
260                     existingSibling = (NodeType)existingSibling.withName(pathFactory.createSegment(name, existingIndex + 1));
261                     changes.changed(existingSibling);
262                 }
263             }
264             // Create the new node ...
265             newNode = createNode(uuid, pathFactory.createSegment(name, snsIndex + 1), parent.getUuid(), properties);
266             // And add to the parent ...
267             parent = (NodeType)parent.withChild(index, uuid);
268         }
269         changes.created(newNode);
270         changes.changed(parent);
271         return newNode;
272     }
273 
274     /**
275      * {@inheritDoc}
276      * 
277      * @see org.modeshape.graph.connector.base.Transaction#addChild(org.modeshape.graph.connector.base.Workspace,
278      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Node,
279      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name)
280      */
281     @SuppressWarnings( "unchecked" )
282     public Location addChild( WorkspaceType workspace,
283                               NodeType parent,
284                               NodeType newChild,
285                               NodeType beforeOtherChild,
286                               Name desiredName ) {
287         // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes
288         if (!parent.hasChanges()) {
289             parent = findNode(workspace, parent.getUuid());
290         }
291 
292         // Get some information about the child ...
293         Segment newChildSegment = newChild.getName();
294         Name newChildName = newChildSegment.getName();
295         int snsIndex = newChildSegment.getIndex();
296         UUID newChildUuid = newChild.getUuid();
297 
298         // Find the existing parent of the new child ...
299         NodeType oldParent = getParent(workspace, newChild);
300 
301         // Find the changes for this workspace ...
302         WorkspaceChanges changes = getChangesFor(workspace, true);
303 
304         // if (oldParent == parent && beforeOtherChild == null) {
305         // // this node is being renamed, so find the correct index ...
306         // index = parent.getChildren().indexOf(newChildUuid);
307         // assert index >= 0;
308         // } else if (oldParent != null) {
309         int oldIndex = -1;
310         if (oldParent != null) {
311             // Remove the node from it's parent ...
312             oldIndex = oldParent.getChildren().indexOf(newChildUuid);
313             if (oldParent == parent) {
314                 oldParent = (NodeType)oldParent.withoutChild(newChildUuid);
315                 changes.changed(oldParent);
316                 parent = oldParent;
317             } else {
318                 oldParent = (NodeType)oldParent.withoutChild(newChildUuid);
319                 changes.changed(oldParent);
320             }
321 
322             // Now find any siblings with the same name that appear after the node in the parent's list of children ...
323             List<NodeType> siblings = getChildren(workspace, oldParent);
324             for (ListIterator<NodeType> iter = siblings.listIterator(oldIndex); iter.hasNext();) {
325                 NodeType sibling = iter.next();
326                 if (sibling.getName().getName().equals(newChildName)) {
327                     sibling = (NodeType)sibling.withName(pathFactory.createSegment(newChildName, snsIndex++));
328                     changes.changed(sibling);
329                 }
330             }
331         }
332 
333         // Find the index of the other child ...
334         int index = parent.getChildren().size();
335         if (beforeOtherChild != null) {
336             if (!beforeOtherChild.getParent().equals(parent.getUuid())) {
337                 // The other child doesn't exist in the parent ...
338                 throw new RepositorySourceException(null);
339             }
340             UUID otherChild = beforeOtherChild.getUuid();
341             index = parent.getChildren().indexOf(otherChild);
342             if (index == -1) index = parent.getChildren().size();
343         }
344         assert index >= 0;
345 
346         // Determine the desired new name for the node ...
347         newChildName = desiredName != null ? desiredName : newChildName;
348 
349         // Find the SNS index for the new child ...
350         ListIterator<NodeType> existingSiblings = getChildren(workspace, parent).listIterator(); // makes a copy
351         int i = 0;
352         snsIndex = 1;
353         Segment childName = null;
354         while (existingSiblings.hasNext()) {
355             NodeType existingSibling = existingSiblings.next();
356             Segment existingSegment = existingSibling.getName();
357             if (i < index) {
358                 // Nodes before the insertion point
359                 if (existingSegment.getName().equals(newChildName)) {
360                     ++snsIndex;
361                 }
362             } else {
363                 if (i == index) {
364                     // Add the child node ...
365                     childName = pathFactory.createSegment(newChildName, snsIndex);
366                 }
367                 if (existingSegment.getName().equals(newChildName)) {
368                     existingSibling = (NodeType)existingSibling.withName(pathFactory.createSegment(newChildName, ++snsIndex));
369                     changes.changed(existingSibling);
370                 }
371             }
372             ++i;
373         }
374         if (childName == null) {
375             // Must be appending the child ...
376             childName = pathFactory.createSegment(newChildName, snsIndex);
377         }
378 
379         // Change the name of the new node ...
380         newChild = (NodeType)newChild.withName(childName).withParent(parent.getUuid());
381         parent = (NodeType)parent.withChild(index, newChildUuid);
382         changes.changed(newChild);
383         changes.changed(parent);
384         return Location.create(pathFor(workspace, newChild), newChildUuid);
385     }
386 
387     /**
388      * Create a new instance of the node, given the supplied UUID. This method should do nothing but instantiate the new node; the
389      * caller will add to the appropriate maps.
390      * 
391      * @param uuid the desired UUID; never null
392      * @param name the name of the new node; may be null if the name is not known
393      * @param parentUuid the UUID of the parent node; may be null if this is the root node
394      * @param properties the properties; may be null if there are no properties
395      * @return the new node; never null
396      */
397     @SuppressWarnings( "unchecked" )
398     protected NodeType createNode( UUID uuid,
399                                    Segment name,
400                                    UUID parentUuid,
401                                    Iterable<Property> properties ) {
402         return (NodeType)new MapNode(uuid, name, parentUuid, properties, null);
403     }
404 
405     /**
406      * {@inheritDoc}
407      * 
408      * @see org.modeshape.graph.connector.base.Transaction#getChild(org.modeshape.graph.connector.base.Workspace,
409      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Path.Segment)
410      */
411     public NodeType getChild( WorkspaceType workspace,
412                               NodeType parent,
413                               Segment childSegment ) {
414         List<NodeType> children = new Children(parent.getChildren(), workspace); // don't make a copy
415         for (NodeType child : children) {
416             if (child.getName().equals(childSegment)) return child;
417         }
418         return null;
419     }
420 
421     /**
422      * {@inheritDoc}
423      * 
424      * @see org.modeshape.graph.connector.base.Transaction#getChildren(org.modeshape.graph.connector.base.Workspace,
425      *      org.modeshape.graph.connector.base.Node)
426      */
427     public List<NodeType> getChildren( WorkspaceType workspace,
428                                        NodeType node ) {
429         List<UUID> children = node.getChildren(); // make a copy
430         if (children.isEmpty()) return Collections.emptyList();
431         return new Children(children, workspace);
432     }
433 
434     /**
435      * {@inheritDoc}
436      * 
437      * @see org.modeshape.graph.connector.base.Transaction#getParent(org.modeshape.graph.connector.base.Workspace,
438      *      org.modeshape.graph.connector.base.Node)
439      */
440     public NodeType getParent( WorkspaceType workspace,
441                                NodeType node ) {
442         UUID parentUuid = node.getParent();
443         if (parentUuid == null) return null;
444         return getNode(workspace, Location.create(parentUuid));
445     }
446 
447     /**
448      * {@inheritDoc}
449      * 
450      * @see org.modeshape.graph.connector.base.Transaction#removeAllChildren(org.modeshape.graph.connector.base.Workspace,
451      *      org.modeshape.graph.connector.base.Node)
452      */
453     @SuppressWarnings( "unchecked" )
454     public void removeAllChildren( WorkspaceType workspace,
455                                    NodeType node ) {
456         for (NodeType child : getChildren(workspace, node)) {
457             destroyNode(workspace, child);
458         }
459         node = (NodeType)node.withoutChildren();
460         getChangesFor(workspace, true).changed(node);
461     }
462 
463     /**
464      * {@inheritDoc}
465      * 
466      * @see org.modeshape.graph.connector.base.Transaction#removeNode(org.modeshape.graph.connector.base.Workspace,
467      *      org.modeshape.graph.connector.base.Node)
468      */
469     @SuppressWarnings( "unchecked" )
470     public Location removeNode( WorkspaceType workspace,
471                                 NodeType node ) {
472         NodeType parent = getParent(workspace, node);
473         if (parent == null) {
474             // The root node is being removed, which means we should just delete everything (except the root) ...
475             WorkspaceChanges changes = getChangesFor(workspace, true);
476             changes.removeAll(createNode(rootNodeUuid, null, null, null));
477             return Location.create(pathFactory.createRootPath(), rootNodeUuid);
478         }
479         Location result = Location.create(pathFor(workspace, node), node.getUuid());
480 
481         // Find the index of the node in it's parent ...
482         int index = parent.getChildren().indexOf(node.getUuid());
483         assert index != -1;
484         Name name = node.getName().getName();
485         int snsIndex = node.getName().getIndex();
486         WorkspaceChanges changes = getChangesFor(workspace, true);
487         // Remove the node from the parent ...
488         parent = (NodeType)parent.withoutChild(node.getUuid());
489         changes.changed(parent);
490 
491         // Now find any siblings with the same name that appear after the node in the parent's list of children ...
492         List<NodeType> siblings = getChildren(workspace, parent);
493         if (index < siblings.size()) {
494             for (ListIterator<NodeType> iter = siblings.listIterator(index); iter.hasNext();) {
495                 NodeType sibling = iter.next();
496                 if (sibling.getName().getName().equals(name)) {
497                     sibling = (NodeType)sibling.withName(pathFactory.createSegment(name, snsIndex++));
498                     changes.changed(sibling);
499                 }
500             }
501         }
502         // Destroy the subgraph starting at the node, and record the change on the parent ...
503         destroyNode(changes, workspace, node);
504         return result;
505     }
506 
507     /**
508      * {@inheritDoc}
509      * 
510      * @see org.modeshape.graph.connector.base.Transaction#removeProperty(org.modeshape.graph.connector.base.Workspace,
511      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name)
512      */
513     @SuppressWarnings( "unchecked" )
514     public NodeType removeProperty( WorkspaceType workspace,
515                                     NodeType node,
516                                     Name propertyName ) {
517         NodeType copy = (NodeType)node.withoutProperty(propertyName);
518         if (copy != node) {
519             WorkspaceChanges changes = getChangesFor(workspace, true);
520             changes.changed(copy);
521         }
522         return copy;
523     }
524 
525     /**
526      * {@inheritDoc}
527      * 
528      * @see org.modeshape.graph.connector.base.Transaction#setProperties(org.modeshape.graph.connector.base.Workspace,
529      *      org.modeshape.graph.connector.base.Node, java.lang.Iterable, java.lang.Iterable, boolean)
530      */
531     @SuppressWarnings( "unchecked" )
532     public NodeType setProperties( WorkspaceType workspace,
533                                    NodeType node,
534                                    Iterable<Property> propertiesToSet,
535                                    Iterable<Name> propertiesToRemove,
536                                    boolean removeAllExisting ) {
537         NodeType copy = (NodeType)node.withProperties(propertiesToSet, propertiesToRemove, removeAllExisting);
538         if (copy != node) {
539             WorkspaceChanges changes = getChangesFor(workspace, true);
540             changes.changed(copy);
541         }
542         return copy;
543     }
544 
545     /**
546      * {@inheritDoc}
547      * 
548      * @see org.modeshape.graph.connector.base.Transaction#copyNode(org.modeshape.graph.connector.base.Workspace,
549      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Workspace,
550      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, boolean)
551      */
552     @SuppressWarnings( "unchecked" )
553     public NodeType copyNode( WorkspaceType originalWorkspace,
554                               NodeType original,
555                               WorkspaceType newWorkspace,
556                               NodeType newParent,
557                               Name desiredName,
558                               boolean recursive ) {
559         if (desiredName == null) desiredName = original.getName().getName();
560         // Create a copy of the original under the new parent ...
561         UUID uuid = UUID.randomUUID();
562         NodeType copy = addChild(newWorkspace, newParent, desiredName, -1, uuid, original.getProperties().values());
563 
564         Map<UUID, UUID> oldToNewUuids = new HashMap<UUID, UUID>();
565         oldToNewUuids.put(original.getUuid(), uuid);
566 
567         if (recursive) {
568             WorkspaceChanges changes = getChangesFor(newWorkspace, true);
569             // Walk through the original branch in its workspace ...
570             for (NodeType originalChild : getChildren(originalWorkspace, original)) {
571                 NodeType newChild = copyBranch(originalWorkspace,
572                                                originalChild,
573                                                changes,
574                                                newWorkspace,
575                                                copy,
576                                                false,
577                                                oldToNewUuids);
578                 copy = (NodeType)copy.withChild(newChild.getUuid());
579             }
580         }
581 
582         // Record the latest changes on the newly-created node ..
583         WorkspaceChanges changes = getChangesFor(newWorkspace, true);
584         changes.changed(copy);
585 
586         // Now, adjust any references in the new subgraph to objects in the original subgraph
587         // (because they were internal references, and need to be internal to the new subgraph)
588         UuidFactory uuidFactory = context.getValueFactories().getUuidFactory();
589         ValueFactory<Reference> referenceFactory = context.getValueFactories().getReferenceFactory();
590         for (Map.Entry<UUID, UUID> oldToNew : oldToNewUuids.entrySet()) {
591             NodeType newNode = findNode(newWorkspace, oldToNew.getValue());
592             assert newNode != null;
593             // Iterate over the properties of the new ...
594             for (Map.Entry<Name, Property> entry : newNode.getProperties().entrySet()) {
595                 Property property = entry.getValue();
596                 // Now see if any of the property values are references ...
597                 List<Object> newValues = new ArrayList<Object>();
598                 boolean foundReference = false;
599                 for (Iterator<?> iter = property.getValues(); iter.hasNext();) {
600                     Object value = iter.next();
601                     PropertyType type = PropertyType.discoverType(value);
602                     if (type == PropertyType.REFERENCE) {
603                         UUID oldReferencedUuid = uuidFactory.create(value);
604                         UUID newReferencedUuid = oldToNewUuids.get(oldReferencedUuid);
605                         if (newReferencedUuid != null) {
606                             newValues.add(referenceFactory.create(newReferencedUuid));
607                             foundReference = true;
608                         }
609                     } else {
610                         newValues.add(value);
611                     }
612                 }
613                 // If we found at least one reference, we have to build a new Property object ...
614                 if (foundReference) {
615                     Property newProperty = propertyFactory.create(property.getName(), newValues);
616                     newNode = (NodeType)newNode.withProperty(newProperty);
617                     changes.changed(newNode);
618                 }
619             }
620         }
621 
622         return copy;
623     }
624 
625     protected void print( WorkspaceType workspace,
626                           NodeType node,
627                           int level ) {
628         StringBuilder sb = new StringBuilder();
629         sb.append(StringUtil.createString(' ', level * 2));
630         sb.append(readable(node.getName())).append(" (").append(node.getUuid()).append(") {");
631         boolean first = true;
632         for (Property property : node.getProperties().values()) {
633             if (first) first = false;
634             else sb.append(',');
635             sb.append(readable(property.getName())).append('=');
636             if (property.isMultiple()) sb.append(property.getValuesAsArray());
637             else sb.append(readable(property.getFirstValue()));
638         }
639         sb.append('}');
640         System.out.println(sb);
641         for (NodeType child : getChildren(workspace, node)) {
642             print(workspace, child, level + 1);
643         }
644     }
645 
646     /**
647      * {@inheritDoc}
648      * 
649      * @see org.modeshape.graph.connector.base.Transaction#cloneNode(org.modeshape.graph.connector.base.Workspace,
650      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Workspace,
651      *      org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, org.modeshape.graph.property.Path.Segment,
652      *      boolean, java.util.Set)
653      */
654     @SuppressWarnings( "unchecked" )
655     public NodeType cloneNode( WorkspaceType originalWorkspace,
656                                NodeType original,
657                                WorkspaceType newWorkspace,
658                                NodeType newParent,
659                                Name desiredName,
660                                Segment desiredSegment,
661                                boolean removeExisting,
662                                java.util.Set<Location> removedExistingNodes )
663         throws org.modeshape.graph.connector.UuidAlreadyExistsException {
664 
665         WorkspaceChanges changes = getChangesFor(newWorkspace, true);
666         NodeType newNode = null;
667 
668         // System.out.println("Original workspace under " + pathFor(originalWorkspace, original));
669         // print(originalWorkspace, original, 0);
670         // System.out.println("New workspace under " + pathFor(newWorkspace, newParent));
671         // print(newWorkspace, newParent, 0);
672         Set<UUID> uuidsInFromBranch = getUuidsUnderNode(originalWorkspace, original);
673         NodeType existing = null;
674         if (removeExisting) {
675             // Remove all of the nodes that have a UUID from under the original node, but DO NOT yet remove
676             // a node that has the UUID of the original node (as this will be handled later) ...
677             for (UUID uuid : uuidsInFromBranch) {
678                 if (null != (existing = findNode(newWorkspace, uuid))) {
679                     if (removedExistingNodes != null) {
680                         Path path = pathFor(newWorkspace, existing);
681                         removedExistingNodes.add(Location.create(path, uuid));
682                     }
683                     removeNode(newWorkspace, existing);
684                 }
685             }
686             // System.out.println("New workspace under " + pathFor(newWorkspace, newParent) + " (after removing existing nodes)");
687             // print(newWorkspace, newParent, 0);
688         } else {
689             uuidsInFromBranch.add(original.getUuid()); // uuidsInFromBranch does not include the UUID of the original
690             for (UUID uuid : uuidsInFromBranch) {
691                 if (null != (existing = findNode(newWorkspace, uuid))) {
692                     NamespaceRegistry namespaces = context.getNamespaceRegistry();
693                     String path = pathFor(newWorkspace, existing).getString(namespaces);
694                     throw new UuidAlreadyExistsException(getRepository().getSourceName(), uuid, path, newWorkspace.getName());
695                 }
696             }
697         }
698 
699         UUID uuid = original.getUuid();
700         if (desiredSegment != null) {
701             // Look for an existing child under 'newParent' that has that name ...
702             int index = 0;
703             List<NodeType> children = getChildren(newWorkspace, newParent);
704             NodeType existingWithSameName = null;
705             for (NodeType child : children) {
706                 if (child.getName().equals(desiredSegment)) {
707                     existingWithSameName = child;
708                     break;
709                 }
710                 ++index;
711             }
712             if (index == children.size()) {
713                 // Couldn't find the desired segment, so just append ...
714                 newNode = addChild(newWorkspace, newParent, original.getName().getName(), -1, uuid, original.getProperties()
715                                                                                                             .values());
716             } else {
717                 // Create the new node with the desired name ...
718                 newNode = createNode(uuid, desiredSegment, newParent.getUuid(), original.getProperties().values());
719 
720                 // Destroy the existing node ...
721                 assert existingWithSameName != null;
722                 destroyNode(newWorkspace, existingWithSameName);
723 
724                 // Replace the existing node ...
725                 newParent = (NodeType)newParent.withoutChild(existingWithSameName.getUuid()).withChild(index, uuid);
726             }
727         } else {
728             // Need to remove the existing node with the same UUID ...
729             existing = findNode(newWorkspace, original.getUuid());
730             if (existing != null) {
731                 if (removedExistingNodes != null) {
732                     Path path = pathFor(newWorkspace, existing);
733                     removedExistingNodes.add(Location.create(path, original.getUuid()));
734                 }
735                 removeNode(newWorkspace, existing);
736             }
737 
738             if (desiredName != null) {
739                 // Simply add a new node with the desired name ...
740                 newNode = addChild(newWorkspace, newParent, desiredName, -1, uuid, original.getProperties().values());
741             } else {
742                 // Simply append and use the original's name ...
743                 newNode = addChild(newWorkspace, newParent, original.getName().getName(), -1, uuid, original.getProperties()
744                                                                                                             .values());
745             }
746         }
747 
748         // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes
749         if (!newParent.hasChanges()) {
750             newParent = findNode(newWorkspace, newParent.getUuid());
751         }
752 
753         // Walk through the original branch in its workspace ...
754         for (NodeType originalChild : getChildren(originalWorkspace, original)) {
755             NodeType newChild = copyBranch(originalWorkspace, originalChild, changes, newWorkspace, newNode, true, null);
756             newNode = (NodeType)newNode.withChild(newChild.getUuid());
757         }
758         changes.created(newNode);
759         changes.changed(newParent);
760 
761         return newNode;
762     }
763 
764     /**
765      * Returns all of the UUIDs in the branch rooted at {@code node}. The UUID of {@code node} <i>will</i> be included in the set
766      * of returned UUIDs.
767      * 
768      * @param workspace the workspace
769      * @param node the root of the branch
770      * @return all of the UUIDs in the branch rooted at {@code node}
771      */
772     protected Set<UUID> getUuidsUnderNode( WorkspaceType workspace,
773                                            NodeType node ) {
774         Set<UUID> uuids = new HashSet<UUID>();
775         uuidsUnderNode(workspace, node, uuids);
776         return uuids;
777     }
778 
779     private void uuidsUnderNode( WorkspaceType workspace,
780                                  NodeType node,
781                                  Set<UUID> accumulator ) {
782         for (NodeType child : getChildren(workspace, node)) {
783             accumulator.add(child.getUuid());
784             uuidsUnderNode(workspace, child, accumulator);
785         }
786     }
787 
788     @SuppressWarnings( "unchecked" )
789     protected NodeType copyBranch( WorkspaceType originalWorkspace,
790                                    NodeType original,
791                                    WorkspaceChanges newWorkspaceChanges,
792                                    WorkspaceType newWorkspace,
793                                    NodeType newParent,
794                                    boolean reuseUuid,
795                                    Map<UUID, UUID> oldToNewUuids ) {
796         // Create the new node (or reuse the original if we can) ...
797         UUID copyUuid = reuseUuid ? original.getUuid() : UUID.randomUUID();
798         NodeType copy = createNode(copyUuid, original.getName(), newParent.getUuid(), original.getProperties().values());
799         newWorkspaceChanges.created(copy);
800         if (!reuseUuid) {
801             assert oldToNewUuids != null;
802             oldToNewUuids.put(original.getUuid(), copy.getUuid());
803         }
804 
805         // Walk through the children and call this method recursively ...
806         for (NodeType originalChild : getChildren(originalWorkspace, original)) {
807             NodeType newChild = copyBranch(originalWorkspace,
808                                            originalChild,
809                                            newWorkspaceChanges,
810                                            newWorkspace,
811                                            copy,
812                                            reuseUuid,
813                                            oldToNewUuids);
814             copy = (NodeType)copy.withChild(newChild.getUuid());
815         }
816         newWorkspaceChanges.changed(copy);
817         return copy;
818     }
819 
820     /**
821      * {@inheritDoc}
822      * <p>
823      * This implementation does not support querying the repository contents, so this method returns null. Subclasses can override
824      * this if they do support querying.
825      * </p>
826      * 
827      * @see org.modeshape.graph.connector.base.Transaction#query(org.modeshape.graph.connector.base.Workspace,
828      *      org.modeshape.graph.request.AccessQueryRequest)
829      */
830     public QueryResults query( WorkspaceType workspace,
831                                AccessQueryRequest accessQuery ) {
832         return null;
833     }
834 
835     /**
836      * {@inheritDoc}
837      * <p>
838      * This implementation does not support searching the repository contents, so this method returns null. Subclasses can
839      * override this if they do support searching.
840      * </p>
841      * 
842      * @see org.modeshape.graph.connector.base.Transaction#search(org.modeshape.graph.connector.base.Workspace,
843      *      org.modeshape.graph.request.FullTextSearchRequest)
844      */
845     public QueryResults search( WorkspaceType workspace,
846                                 FullTextSearchRequest search ) {
847         return null;
848     }
849 
850     /**
851      * {@inheritDoc}
852      * 
853      * @see org.modeshape.graph.connector.base.BaseTransaction#commit()
854      */
855     @Override
856     public void commit() {
857         super.commit();
858         // Push all of the changes or added nodes onto the workspace ...
859         if (changesByWorkspaceName != null) {
860             for (WorkspaceChanges changes : changesByWorkspaceName.values()) {
861                 changes.commit();
862             }
863             changesByWorkspaceName.clear();
864         }
865     }
866 
867     /**
868      * {@inheritDoc}
869      * 
870      * @see org.modeshape.graph.connector.base.BaseTransaction#rollback()
871      */
872     @Override
873     public void rollback() {
874         super.rollback();
875         if (changesByWorkspaceName != null) {
876             changesByWorkspaceName.clear();
877         }
878     }
879 
880     /**
881      * Record of the changes made to a particular workspace.
882      */
883     protected class WorkspaceChanges {
884         private final WorkspaceType workspace;
885         private final Map<UUID, NodeType> changedOrAddedNodes = new HashMap<UUID, NodeType>();
886         private final Set<UUID> removedNodes = new HashSet<UUID>();
887         private boolean removeAll = false;
888 
889         protected WorkspaceChanges( WorkspaceType workspace ) {
890             this.workspace = workspace;
891         }
892 
893         public WorkspaceType getWorkspace() {
894             return workspace;
895         }
896 
897         public void removeAll( NodeType newRootNode ) {
898             changedOrAddedNodes.clear();
899             removedNodes.clear();
900             removeAll = true;
901             changedOrAddedNodes.put(newRootNode.getUuid(), newRootNode);
902         }
903 
904         public boolean isRemoved( UUID uuid ) {
905             return removedNodes.contains(uuid);
906         }
907 
908         public NodeType getChangedOrAdded( UUID uuid ) {
909             return changedOrAddedNodes.get(uuid);
910         }
911 
912         public void removed( UUID uuid ) {
913             removedNodes.add(uuid);
914             changedOrAddedNodes.remove(uuid);
915         }
916 
917         public void created( NodeType node ) {
918             UUID uuid = node.getUuid();
919             removedNodes.remove(uuid);
920             changedOrAddedNodes.put(uuid, node);
921         }
922 
923         public void changed( NodeType node ) {
924             UUID uuid = node.getUuid();
925             assert !removedNodes.contains(uuid); // should not be removed
926             changedOrAddedNodes.put(uuid, node);
927         }
928 
929         @SuppressWarnings( "unchecked" )
930         public void commit() {
931             if (removeAll) {
932                 workspace.removeAll();
933             }
934             for (NodeType changed : changedOrAddedNodes.values()) {
935                 workspace.putNode((NodeType)changed.freeze());
936             }
937             for (UUID uuid : removedNodes) {
938                 // the node may not exist in the workspace (i.e., it was created and then deleted in the txn)
939                 // but this method call won't care ...
940                 workspace.removeNode(uuid);
941             }
942         }
943     }
944 
945     protected class Children implements List<NodeType> {
946         private final List<UUID> uuids;
947         private final WorkspaceType workspace;
948         private final List<NodeType> cache;
949 
950         protected Children( List<UUID> uuids,
951                             WorkspaceType workspace ) {
952             this.uuids = uuids;
953             this.workspace = workspace;
954             this.cache = new ArrayList<NodeType>(uuids.size());
955         }
956 
957         /**
958          * {@inheritDoc}
959          * 
960          * @see java.util.List#size()
961          */
962         public int size() {
963             return uuids.size();
964         }
965 
966         /**
967          * {@inheritDoc}
968          * 
969          * @see java.util.List#contains(java.lang.Object)
970          */
971         public boolean contains( Object o ) {
972             if (o instanceof MapNode) {
973                 return uuids.contains(((MapNode)o).getUuid());
974             }
975             return false;
976         }
977 
978         /**
979          * {@inheritDoc}
980          * 
981          * @see java.util.List#containsAll(java.util.Collection)
982          */
983         public boolean containsAll( Collection<?> c ) {
984             for (Object o : c) {
985                 if (!contains(o)) return false;
986             }
987             return true;
988         }
989 
990         /**
991          * {@inheritDoc}
992          * 
993          * @see java.util.List#get(int)
994          */
995         public NodeType get( int index ) {
996             NodeType result = null;
997             if (cache.size() > index) {
998                 result = cache.get(index);
999             }
1000             if (result == null) {
1001                 UUID uuid = uuids.get(index);
1002                 // Do the fast lookup first, but this returns a null if not found ...
1003                 result = findNode(workspace, uuid);
1004                 if (result == null) {
1005                     // Wasn't found, so we need to throw an exception ...
1006                     result = getNode(workspace, Location.create(uuid));
1007                 }
1008                 while (cache.size() <= index) {
1009                     cache.add(null);
1010                 }
1011                 cache.set(index, result);
1012             }
1013             return result;
1014         }
1015 
1016         /**
1017          * {@inheritDoc}
1018          * 
1019          * @see java.util.List#indexOf(java.lang.Object)
1020          */
1021         public int indexOf( Object o ) {
1022             if (o instanceof MapNode) {
1023                 return uuids.indexOf(((MapNode)o).getUuid());
1024             }
1025             return -1;
1026         }
1027 
1028         /**
1029          * {@inheritDoc}
1030          * 
1031          * @see java.util.List#lastIndexOf(java.lang.Object)
1032          */
1033         public int lastIndexOf( Object o ) {
1034             // The list of UUIDs cannot contain duplicates, so just do the simple thing here ...
1035             return indexOf(0);
1036         }
1037 
1038         /**
1039          * {@inheritDoc}
1040          * 
1041          * @see java.util.List#isEmpty()
1042          */
1043         public boolean isEmpty() {
1044             return uuids.isEmpty();
1045         }
1046 
1047         /**
1048          * {@inheritDoc}
1049          * 
1050          * @see java.util.List#iterator()
1051          */
1052         public Iterator<NodeType> iterator() {
1053             return listIterator(0);
1054         }
1055 
1056         /**
1057          * {@inheritDoc}
1058          * 
1059          * @see java.util.List#listIterator()
1060          */
1061         public ListIterator<NodeType> listIterator() {
1062             return listIterator(0);
1063         }
1064 
1065         /**
1066          * {@inheritDoc}
1067          * 
1068          * @see java.util.List#listIterator(int)
1069          */
1070         public ListIterator<NodeType> listIterator( final int index ) {
1071             final int maxIndex = size();
1072             return new ListIterator<NodeType>() {
1073                 private int current = index;
1074 
1075                 public boolean hasNext() {
1076                     return current < maxIndex;
1077                 }
1078 
1079                 public NodeType next() {
1080                     return get(current++);
1081                 }
1082 
1083                 public void remove() {
1084                     throw new UnsupportedOperationException();
1085                 }
1086 
1087                 public void add( NodeType e ) {
1088                     throw new UnsupportedOperationException();
1089                 }
1090 
1091                 public boolean hasPrevious() {
1092                     return current > 0;
1093                 }
1094 
1095                 public int nextIndex() {
1096                     return current;
1097                 }
1098 
1099                 public NodeType previous() {
1100                     return get(--current);
1101                 }
1102 
1103                 public int previousIndex() {
1104                     return current - 1;
1105                 }
1106 
1107                 public void set( NodeType e ) {
1108                     throw new UnsupportedOperationException();
1109                 }
1110             };
1111         }
1112 
1113         /**
1114          * {@inheritDoc}
1115          * 
1116          * @see java.util.List#subList(int, int)
1117          */
1118         public List<NodeType> subList( int fromIndex,
1119                                        int toIndex ) {
1120             return new Children(uuids.subList(fromIndex, toIndex), workspace);
1121         }
1122 
1123         /**
1124          * {@inheritDoc}
1125          * 
1126          * @see java.util.List#toArray()
1127          */
1128         public Object[] toArray() {
1129             final int length = uuids.size();
1130             Object[] result = new Object[length];
1131             for (int i = 0; i != length; ++i) {
1132                 result[i] = get(i);
1133             }
1134             return result;
1135         }
1136 
1137         /**
1138          * {@inheritDoc}
1139          * 
1140          * @see java.util.List#toArray(T[])
1141          */
1142         @SuppressWarnings( "unchecked" )
1143         public <T> T[] toArray( T[] a ) {
1144             final int length = uuids.size();
1145             for (int i = 0; i != length; ++i) {
1146                 a[i] = (T)get(i);
1147             }
1148             return a;
1149         }
1150 
1151         /**
1152          * {@inheritDoc}
1153          * 
1154          * @see java.util.List#add(java.lang.Object)
1155          */
1156         public boolean add( NodeType e ) {
1157             throw new UnsupportedOperationException();
1158         }
1159 
1160         /**
1161          * {@inheritDoc}
1162          * 
1163          * @see java.util.List#add(int, java.lang.Object)
1164          */
1165         public void add( int index,
1166                          NodeType element ) {
1167             throw new UnsupportedOperationException();
1168         }
1169 
1170         /**
1171          * {@inheritDoc}
1172          * 
1173          * @see java.util.List#addAll(java.util.Collection)
1174          */
1175         public boolean addAll( Collection<? extends NodeType> c ) {
1176             throw new UnsupportedOperationException();
1177         }
1178 
1179         /**
1180          * {@inheritDoc}
1181          * 
1182          * @see java.util.List#addAll(int, java.util.Collection)
1183          */
1184         public boolean addAll( int index,
1185                                Collection<? extends NodeType> c ) {
1186             throw new UnsupportedOperationException();
1187         }
1188 
1189         /**
1190          * {@inheritDoc}
1191          * 
1192          * @see java.util.List#clear()
1193          */
1194         public void clear() {
1195             throw new UnsupportedOperationException();
1196         }
1197 
1198         /**
1199          * {@inheritDoc}
1200          * 
1201          * @see java.util.List#remove(java.lang.Object)
1202          */
1203         public boolean remove( Object o ) {
1204             throw new UnsupportedOperationException();
1205         }
1206 
1207         /**
1208          * {@inheritDoc}
1209          * 
1210          * @see java.util.List#remove(int)
1211          */
1212         public NodeType remove( int index ) {
1213             throw new UnsupportedOperationException();
1214         }
1215 
1216         /**
1217          * {@inheritDoc}
1218          * 
1219          * @see java.util.List#removeAll(java.util.Collection)
1220          */
1221         public boolean removeAll( Collection<?> c ) {
1222             throw new UnsupportedOperationException();
1223         }
1224 
1225         /**
1226          * {@inheritDoc}
1227          * 
1228          * @see java.util.List#retainAll(java.util.Collection)
1229          */
1230         public boolean retainAll( Collection<?> c ) {
1231             throw new UnsupportedOperationException();
1232         }
1233 
1234         /**
1235          * {@inheritDoc}
1236          * 
1237          * @see java.util.List#set(int, java.lang.Object)
1238          */
1239         public NodeType set( int index,
1240                              NodeType element ) {
1241             throw new UnsupportedOperationException();
1242         }
1243     }
1244 }