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.connector.store.jpa.model.basic;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.io.OutputStream;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.Iterator;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.ListIterator;
43  import java.util.Map;
44  import java.util.Set;
45  import java.util.UUID;
46  import java.util.zip.GZIPInputStream;
47  import java.util.zip.GZIPOutputStream;
48  import javax.persistence.EntityManager;
49  import javax.persistence.NoResultException;
50  import javax.persistence.Query;
51  import net.jcip.annotations.Immutable;
52  import net.jcip.annotations.NotThreadSafe;
53  import org.modeshape.common.util.IoUtil;
54  import org.modeshape.common.util.Logger;
55  import org.modeshape.common.util.StringUtil;
56  import org.modeshape.connector.store.jpa.JpaConnectorI18n;
57  import org.modeshape.connector.store.jpa.model.common.NamespaceEntity;
58  import org.modeshape.connector.store.jpa.model.common.WorkspaceEntity;
59  import org.modeshape.connector.store.jpa.util.Namespaces;
60  import org.modeshape.connector.store.jpa.util.RequestProcessorCache;
61  import org.modeshape.connector.store.jpa.util.Serializer;
62  import org.modeshape.connector.store.jpa.util.Workspaces;
63  import org.modeshape.connector.store.jpa.util.Serializer.LargeValues;
64  import org.modeshape.graph.ExecutionContext;
65  import org.modeshape.graph.JcrLexicon;
66  import org.modeshape.graph.Location;
67  import org.modeshape.graph.ModeShapeLexicon;
68  import org.modeshape.graph.NodeConflictBehavior;
69  import org.modeshape.graph.connector.UuidAlreadyExistsException;
70  import org.modeshape.graph.observe.Observer;
71  import org.modeshape.graph.property.Binary;
72  import org.modeshape.graph.property.Name;
73  import org.modeshape.graph.property.NameFactory;
74  import org.modeshape.graph.property.Path;
75  import org.modeshape.graph.property.PathFactory;
76  import org.modeshape.graph.property.PathNotFoundException;
77  import org.modeshape.graph.property.Property;
78  import org.modeshape.graph.property.PropertyFactory;
79  import org.modeshape.graph.property.PropertyType;
80  import org.modeshape.graph.property.Reference;
81  import org.modeshape.graph.property.ReferentialIntegrityException;
82  import org.modeshape.graph.property.UuidFactory;
83  import org.modeshape.graph.property.ValueFactories;
84  import org.modeshape.graph.property.ValueFactory;
85  import org.modeshape.graph.property.ValueFormatException;
86  import org.modeshape.graph.request.CloneBranchRequest;
87  import org.modeshape.graph.request.CloneWorkspaceRequest;
88  import org.modeshape.graph.request.CopyBranchRequest;
89  import org.modeshape.graph.request.CreateNodeRequest;
90  import org.modeshape.graph.request.CreateWorkspaceRequest;
91  import org.modeshape.graph.request.DeleteBranchRequest;
92  import org.modeshape.graph.request.DeleteChildrenRequest;
93  import org.modeshape.graph.request.DestroyWorkspaceRequest;
94  import org.modeshape.graph.request.GetWorkspacesRequest;
95  import org.modeshape.graph.request.InvalidRequestException;
96  import org.modeshape.graph.request.InvalidWorkspaceException;
97  import org.modeshape.graph.request.MoveBranchRequest;
98  import org.modeshape.graph.request.ReadAllChildrenRequest;
99  import org.modeshape.graph.request.ReadAllPropertiesRequest;
100 import org.modeshape.graph.request.ReadBlockOfChildrenRequest;
101 import org.modeshape.graph.request.ReadBranchRequest;
102 import org.modeshape.graph.request.ReadNextBlockOfChildrenRequest;
103 import org.modeshape.graph.request.ReadNodeRequest;
104 import org.modeshape.graph.request.ReadPropertyRequest;
105 import org.modeshape.graph.request.Request;
106 import org.modeshape.graph.request.UpdatePropertiesRequest;
107 import org.modeshape.graph.request.VerifyWorkspaceRequest;
108 import org.modeshape.graph.request.processor.RequestProcessor;
109 
110 /**
111  * A {@link RequestProcessor} implementation for the JPA connector.
112  */
113 @NotThreadSafe
114 public class BasicRequestProcessor extends RequestProcessor {
115 
116     protected final EntityManager entities;
117     protected final ValueFactory<String> stringFactory;
118     protected final PathFactory pathFactory;
119     protected final PropertyFactory propertyFactory;
120     protected final NameFactory nameFactory;
121     protected final UuidFactory uuidFactory;
122     protected final Namespaces namespaces;
123     protected final Workspaces workspaces;
124     protected final UUID rootNodeUuid;
125     protected final String rootNodeUuidString;
126     protected final String nameOfDefaultWorkspace;
127     protected final String[] predefinedWorkspaceNames;
128     protected final boolean creatingWorkspacesAllowed;
129     protected final Serializer serializer;
130     protected final long largeValueMinimumSizeInBytes;
131     protected final boolean compressData;
132     protected final Logger logger;
133     protected final RequestProcessorCache cache;
134     protected final boolean enforceReferentialIntegrity;
135     private final Set<Long> workspaceIdsWithChangedReferences = new HashSet<Long>();
136 
137     private enum UuidConflictBehavior {
138         ALWAYS_CREATE_NEW_UUID,
139         REPLACE_EXISTING_NODE,
140         THROW_EXCEPTION
141     }
142 
143     /**
144      * @param sourceName
145      * @param context
146      * @param observer
147      * @param entityManager
148      * @param rootNodeUuid
149      * @param nameOfDefaultWorkspace
150      * @param predefinedWorkspaceNames
151      * @param largeValueMinimumSizeInBytes
152      * @param creatingWorkspacesAllowed
153      * @param compressData
154      * @param enforceReferentialIntegrity
155      */
156     public BasicRequestProcessor( String sourceName,
157                                   ExecutionContext context,
158                                   Observer observer,
159                                   EntityManager entityManager,
160                                   UUID rootNodeUuid,
161                                   String nameOfDefaultWorkspace,
162                                   String[] predefinedWorkspaceNames,
163                                   long largeValueMinimumSizeInBytes,
164                                   boolean creatingWorkspacesAllowed,
165                                   boolean compressData,
166                                   boolean enforceReferentialIntegrity ) {
167         super(sourceName, context, observer);
168         assert entityManager != null;
169         assert rootNodeUuid != null;
170         assert predefinedWorkspaceNames != null;
171         this.entities = entityManager;
172         ValueFactories valuesFactory = context.getValueFactories();
173         this.stringFactory = valuesFactory.getStringFactory();
174         this.pathFactory = valuesFactory.getPathFactory();
175         this.propertyFactory = context.getPropertyFactory();
176         this.nameFactory = valuesFactory.getNameFactory();
177         this.uuidFactory = valuesFactory.getUuidFactory();
178         this.namespaces = new Namespaces(entityManager);
179         this.workspaces = new Workspaces(entityManager);
180         this.rootNodeUuid = rootNodeUuid;
181         this.rootNodeUuidString = this.rootNodeUuid.toString();
182         this.nameOfDefaultWorkspace = nameOfDefaultWorkspace;
183         this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
184         this.largeValueMinimumSizeInBytes = largeValueMinimumSizeInBytes;
185         this.compressData = compressData;
186         this.enforceReferentialIntegrity = enforceReferentialIntegrity;
187         this.serializer = new Serializer(context, true);
188         this.logger = getExecutionContext().getLogger(getClass());
189         this.cache = new RequestProcessorCache(this.pathFactory);
190         this.predefinedWorkspaceNames = predefinedWorkspaceNames;
191 
192         // Start the transaction ...
193         this.entities.getTransaction().begin();
194     }
195 
196     /**
197      * {@inheritDoc}
198      * 
199      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateNodeRequest)
200      */
201     @Override
202     public void process( CreateNodeRequest request ) {
203         logger.trace(request.toString());
204         Location actualLocation = null;
205         try {
206             // Find the workspace ...
207             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
208             if (workspace == null) return;
209             Long workspaceId = workspace.getId();
210             assert workspaceId != null;
211 
212             // Create nodes have to be defined via a path ...
213             Location parentLocation = request.under();
214             ActualLocation actual = getActualLocation(workspace, parentLocation);
215             String parentUuidString = actual.uuid;
216             assert parentUuidString != null;
217 
218             // We need to look for an existing UUID property in the request,
219             // so since we have to iterate through the properties, go ahead an serialize them right away ...
220             String uuidString = null;
221             UUID uuid = null;
222             for (Property property : request.properties()) {
223                 if (property.getName().equals(ModeShapeLexicon.UUID)) {
224                     uuid = uuidFactory.create(property.getFirstValue());
225                     uuidString = stringFactory.create(property.getFirstValue());
226                     break;
227                 }
228             }
229 
230             if (uuid == null) {
231                 for (Property property : request.properties()) {
232                     if (property.getName().equals(JcrLexicon.UUID)) {
233                         uuid = uuidFactory.create(property.getFirstValue());
234                         uuidString = stringFactory.create(property.getFirstValue());
235                         break;
236                     }
237                 }
238             }
239 
240             switch (request.conflictBehavior()) {
241                 case DO_NOT_REPLACE:
242                 case UPDATE:
243                     if (uuid != null) {
244                         ActualLocation existing = getActualLocation(workspace, Location.create(uuid));
245 
246                         if (existing != null) {
247                             if (NodeConflictBehavior.UPDATE.equals(request.conflictBehavior())) {
248                                 createProperties(workspace, uuidString, request.properties());
249                             }
250 
251                             request.setActualLocationOfNode(existing.location);
252                             return;
253                         }
254                     }
255 
256                     Name newName = request.named();
257                     for (Location childLocation : getAllChildren(workspaceId, actual)) {
258                         if (newName.equals(childLocation.getPath().getLastSegment().getName())) {
259                             if (NodeConflictBehavior.UPDATE.equals(request.conflictBehavior())) {
260                                 createProperties(workspace, uuidString, request.properties());
261                             }
262                             request.setActualLocationOfNode(childLocation);
263                             recordChange(request);
264                             return;
265                         }
266                     }
267 
268                     break;
269 
270                 case REPLACE:
271                     if (uuid != null) {
272                         ActualLocation existing = getActualLocation(workspace, Location.create(uuid));
273 
274                         if (existing != null) {
275                             delete(request, existing.location, workspace.getName(), true);
276                         }
277                     }
278                     break;
279                 case APPEND:
280                     break;
281             }
282 
283             if (uuidString == null) uuidString = UUID.randomUUID().toString();
284             assert uuidString != null;
285             createProperties(workspace, uuidString, request.properties());
286 
287             // Find or create the namespace for the child ...
288             Name childName = request.named();
289             String childNsUri = childName.getNamespaceUri();
290             NamespaceEntity ns = namespaces.get(childNsUri, true);
291             assert ns != null;
292             final Path parentPath = actual.location.getPath();
293             assert parentPath != null;
294 
295             // Figure out the next SNS index and index-in-parent for this new child ...
296             actualLocation = addNewChild(workspaceId, actual, uuidString, childName, true).location;
297 
298             // Since we've just created this node, we know about all the children (actually, there are none).
299             cache.setAllChildren(workspace.getId(), actualLocation.getPath(), new LinkedList<Location>());
300 
301             // Flush the entities ...
302             // entities.flush();
303 
304         } catch (Throwable e) { // Includes PathNotFoundException
305             request.setError(e);
306             logger.trace(e, "Problem " + request);
307             return;
308         }
309         request.setActualLocationOfNode(actualLocation);
310         recordChange(request);
311     }
312 
313     /**
314      * Create a new child with the supplied UUID and name under the supplied parent. If the parent is null, then the child will be
315      * the root node.
316      * 
317      * @param workspaceId the ID of the workspace in which the child is to be created
318      * @param parent the actual location of the parent, or null if the child is to be the root of the workspace
319      * @param childUuid the UUID of the child
320      * @param childName the name of the child
321      * @param allowSameNameChildrenInNewNode
322      * @return the actual location of the new child
323      */
324     protected ActualLocation addNewChild( Long workspaceId,
325                                           ActualLocation parent,
326                                           String childUuid,
327                                           Name childName,
328                                           boolean allowSameNameChildrenInNewNode ) {
329         int nextSnsIndex = 1; // SNS index is 1-based
330         int nextIndexInParent = 0; // index-in-parent is 0-based
331         String childNsUri = childName.getNamespaceUri();
332         NamespaceEntity ns = namespaces.get(childNsUri, true);
333         assert ns != null;
334 
335         // If the parent is null, the create a root node ...
336         Path parentPath = null;
337         String parentUuid = null;
338         ChildEntity parentEntity = null;
339         if (parent == null) {
340             return new ActualLocation(Location.create(pathFactory.createRootPath(), UUID.fromString(childUuid)), childUuid, null);
341         }
342         parentPath = parent.location.getPath();
343         parentUuid = parent.uuid;
344         parentEntity = parent.childEntity; // may be null
345 
346         assert workspaceId != null;
347 
348         ChildId id = new ChildId(workspaceId, childUuid);
349         ChildEntity entity = null;
350 
351         // Look in the cache for the children of the parent node.
352         LinkedList<Location> childrenOfParent = cache.getAllChildren(workspaceId, parentPath);
353 
354         // Now create the entity ...
355         if (parentEntity == null || parentEntity.getAllowsSameNameChildren()) {
356             // The parent DOES allow same-name-siblings, so we need to find the SNS index ...
357 
358             if (childrenOfParent != null) {
359                 // The cache had the complete list of children for the parent node, which means
360                 // we know about all of the children and can walk the children to figure out the next indexes.
361                 nextIndexInParent = childrenOfParent.size();
362                 if (nextIndexInParent > 0) {
363                     // Since we want the last indexes, process the list backwards ...
364                     ListIterator<Location> iter = childrenOfParent.listIterator(childrenOfParent.size());
365                     while (iter.hasPrevious()) {
366                         Location existing = iter.previous();
367                         Path.Segment segment = existing.getPath().getLastSegment();
368                         if (segment.getName().equals(childName)) {
369                             // Otherwise the name matched, so get the indexes ...
370                             nextSnsIndex = segment.getIndex() + 1;
371 
372                             // We've found the last match, so no need to loop further
373                             break;
374                         }
375                     }
376                 }
377             } else {
378                 // The cache did not have the complete list of children for the parent node,
379                 // so we need to look the values up by querying the database ...
380 
381                 // Find the largest SNS index in the existing ChildEntity objects with the same name ...
382                 String childLocalName = childName.getLocalName();
383                 Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex");
384                 query.setParameter("workspaceId", workspaceId);
385                 query.setParameter("parentUuid", parentUuid);
386                 query.setParameter("ns", ns.getId());
387                 query.setParameter("childName", childLocalName);
388                 try {
389                     Integer result = (Integer)query.getSingleResult();
390                     nextSnsIndex = result != null ? result + 1 : 1; // SNS index is 1-based
391                 } catch (NoResultException e) {
392                 }
393 
394                 // Find the largest child index in the existing ChildEntity objects ...
395                 query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
396                 query.setParameter("workspaceId", workspaceId);
397                 query.setParameter("parentUuid", parentUuid);
398                 try {
399                     Integer result = (Integer)query.getSingleResult();
400                     nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based
401                 } catch (NoResultException e) {
402                 }
403             }
404 
405             // Create the new ChildEntity ...
406             entity = new ChildEntity(id, parentUuid, nextIndexInParent, ns, childName.getLocalName(), nextSnsIndex);
407         } else {
408             // The parent does not allow same-name-siblings, so we only need to find the next index ...
409             // Find the largest child index in the existing ChildEntity objects ...
410             if (childrenOfParent != null) {
411                 // The cache had the complete list of children for the parent node, which means
412                 // we know about all of the children and can walk the children to figure out the next indexes.
413                 nextIndexInParent = childrenOfParent.size();
414             } else {
415                 // We don't have the children cached, so we need to do a query ...
416                 Query query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
417                 query.setParameter("workspaceId", workspaceId);
418                 query.setParameter("parentUuid", parentUuid);
419                 try {
420                     Integer result = (Integer)query.getSingleResult();
421                     nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based
422                 } catch (NoResultException e) {
423                 }
424             }
425 
426             // Create the new child entity ...
427             entity = new ChildEntity(id, parentUuid, nextIndexInParent, ns, childName.getLocalName(), 1);
428         }
429 
430         // Persist the new child entity ...
431         entity.setAllowsSameNameChildren(allowSameNameChildrenInNewNode);
432         entities.persist(entity);
433 
434         // Set the actual path, regardless of the supplied path...
435         Path path = pathFactory.create(parentPath, childName, nextSnsIndex);
436         Location actualLocation = Location.create(path, UUID.fromString(childUuid));
437 
438         // Finally, update the cache with the information we know ...
439         if (childrenOfParent != null) {
440             // Add to the cached list of children ...
441             childrenOfParent.add(actualLocation);
442         } else {
443             cache.setAllChildren(workspaceId, parentPath, null);
444         }
445         return new ActualLocation(actualLocation, entity.getId().getChildUuidString(), entity);
446     }
447 
448     protected class NextChildIndexes {
449         protected final int nextIndexInParent;
450         protected final int nextSnsIndex;
451 
452         protected NextChildIndexes( int nextIndexInParent,
453                                     int nextSnsIndex ) {
454             this.nextIndexInParent = nextIndexInParent;
455             this.nextSnsIndex = nextSnsIndex;
456         }
457     }
458 
459     /**
460      * {@inheritDoc}
461      * 
462      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadNodeRequest)
463      */
464     @Override
465     public void process( ReadNodeRequest request ) {
466         logger.trace(request.toString());
467         Location actualLocation = null;
468         try {
469             // Find the workspace ...
470             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
471             if (workspace == null) return;
472             Long workspaceId = workspace.getId();
473             assert workspaceId != null;
474 
475             Location location = request.at();
476             ActualLocation actual = getActualLocation(workspace, location);
477             String parentUuidString = actual.uuid;
478             actualLocation = actual.location;
479 
480             // Record the UUID as a property, since it's not stored in the serialized properties...
481             // This gets pulled from the actual location uuid instead of the property to avoid the
482             // having to add the dna:uuid onto locations that already have a jcr:uuid
483             request.addProperty(propertyFactory.create(ModeShapeLexicon.UUID, UUID.fromString(parentUuidString)));
484 
485             // Find the properties entity for this node ...
486             Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
487             query.setParameter("workspaceId", workspaceId);
488             query.setParameter("uuid", parentUuidString);
489             try {
490                 PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();
491 
492                 // Deserialize the properties ...
493                 boolean compressed = entity.isCompressed();
494                 Collection<Property> properties = new LinkedList<Property>();
495                 byte[] data = entity.getData();
496                 if (data != null) {
497                     LargeValueSerializer largeValues = new LargeValueSerializer(entity);
498                     ByteArrayInputStream bais = new ByteArrayInputStream(data);
499                     InputStream is = compressed ? new GZIPInputStream(bais) : bais;
500                     ObjectInputStream ois = new ObjectInputStream(is);
501                     try {
502                         serializer.deserializeAllProperties(ois, properties, largeValues);
503                         for (Property property : properties) {
504                             request.addProperty(property);
505                         }
506                     } finally {
507                         ois.close();
508                     }
509                 }
510 
511             } catch (NoResultException e) {
512                 // No properties, but that's okay...
513             }
514 
515             // Get the children for this node ...
516             for (Location childLocation : getAllChildren(workspaceId, actual)) {
517                 request.addChild(childLocation);
518             }
519         } catch (NoResultException e) {
520             // there are no properties (probably not expected, but still okay) ...
521         } catch (Throwable e) { // Includes PathNotFoundException
522             request.setError(e);
523             return;
524         }
525         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
526         setCacheableInfo(request);
527     }
528 
529     /**
530      * {@inheritDoc}
531      * 
532      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadAllChildrenRequest)
533      */
534     @Override
535     public void process( ReadAllChildrenRequest request ) {
536         logger.trace(request.toString());
537         Location actualLocation = null;
538         try {
539             // Find the workspace ...
540             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
541             if (workspace == null) return;
542             Long workspaceId = workspace.getId();
543             assert workspaceId != null;
544 
545             Location location = request.of();
546             ActualLocation actual = getActualLocation(workspace, location);
547             actualLocation = actual.location;
548 
549             // Get the children for this node ...
550             for (Location childLocation : getAllChildren(workspaceId, actual)) {
551                 request.addChild(childLocation);
552             }
553         } catch (NoResultException e) {
554             // there are no properties (probably not expected, but still okay) ...
555         } catch (Throwable e) { // Includes PathNotFoundException
556             request.setError(e);
557             return;
558         }
559         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
560         setCacheableInfo(request);
561     }
562 
563     /**
564      * Utility method to obtain all of the children for a node, either from the cache (if all children are known to this
565      * processor) or by querying the database (and caching the list of children).
566      * 
567      * @param workspaceId the ID of the workspace; may not be null
568      * @param parent the actual location of the parent node; may not be null
569      * @return the list of child locations
570      */
571     protected LinkedList<Location> getAllChildren( Long workspaceId,
572                                                    ActualLocation parent ) {
573         assert parent != null;
574         Path parentPath = parent.location.getPath();
575         assert parentPath != null;
576         LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
577         if (cachedChildren != null) {
578             // The cache has all of the children for the node ...
579             return cachedChildren;
580         }
581 
582         // Not found in the cache, so query the database ...
583         Query query = entities.createNamedQuery("ChildEntity.findAllUnderParent");
584         query.setParameter("workspaceId", workspaceId);
585         query.setParameter("parentUuidString", parent.uuid);
586         LinkedList<Location> childLocations = new LinkedList<Location>();
587         @SuppressWarnings( "unchecked" )
588         List<ChildEntity> children = query.getResultList();
589         for (ChildEntity child : children) {
590             String namespaceUri = child.getChildNamespace().getUri();
591             String localName = child.getChildName();
592             Name childName = nameFactory.create(namespaceUri, localName);
593             int sns = child.getSameNameSiblingIndex();
594             Path childPath = pathFactory.create(parentPath, childName, sns);
595             String childUuidString = child.getId().getChildUuidString();
596             Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
597             childLocations.add(childLocation);
598         }
599         // Update the cache ...
600         cache.setAllChildren(workspaceId, parentPath, childLocations);
601         return childLocations;
602     }
603 
604     /**
605      * {@inheritDoc}
606      * 
607      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadBlockOfChildrenRequest)
608      */
609     @Override
610     public void process( ReadBlockOfChildrenRequest request ) {
611         logger.trace(request.toString());
612         Location actualLocation = null;
613         final int startingIndex = request.startingAtIndex();
614         try {
615             // Find the workspace ...
616             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
617             if (workspace == null) return;
618             Long workspaceId = workspace.getId();
619             assert workspaceId != null;
620 
621             Location parentLocation = request.of();
622             ActualLocation actualParent = getActualLocation(workspace, parentLocation);
623             actualLocation = actualParent.location;
624 
625             Path parentPath = actualParent.location.getPath();
626             assert parentPath != null;
627             LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
628             if (cachedChildren != null) {
629                 // The cache has all of the children for the node ...
630                 if (startingIndex < cachedChildren.size()) {
631                     ListIterator<Location> iter = cachedChildren.listIterator(startingIndex);
632                     for (int i = 0; i != request.count() && iter.hasNext(); ++i) {
633                         Location child = iter.next();
634                         request.addChild(child);
635                     }
636                 }
637             } else {
638                 // Nothing was cached, so we need to search the database for the children ...
639                 Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent");
640                 query.setParameter("workspaceId", workspaceId);
641                 query.setParameter("parentUuidString", actualParent.uuid);
642                 query.setParameter("firstIndex", startingIndex);
643                 query.setParameter("afterIndex", startingIndex + request.count());
644                 @SuppressWarnings( "unchecked" )
645                 List<ChildEntity> children = query.getResultList();
646                 for (ChildEntity child : children) {
647                     String namespaceUri = child.getChildNamespace().getUri();
648                     String localName = child.getChildName();
649                     Name childName = nameFactory.create(namespaceUri, localName);
650                     int sns = child.getSameNameSiblingIndex();
651                     Path childPath = pathFactory.create(parentPath, childName, sns);
652                     String childUuidString = child.getId().getChildUuidString();
653                     Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
654                     request.addChild(childLocation);
655                 }
656                 // Do not update the cache, since we don't know all of the children.
657             }
658 
659         } catch (NoResultException e) {
660             // there are no properties (probably not expected, but still okay) ...
661         } catch (Throwable e) { // Includes PathNotFoundException
662             request.setError(e);
663             return;
664         }
665         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
666         setCacheableInfo(request);
667     }
668 
669     /**
670      * {@inheritDoc}
671      * 
672      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadNextBlockOfChildrenRequest)
673      */
674     @Override
675     public void process( ReadNextBlockOfChildrenRequest request ) {
676         logger.trace(request.toString());
677         Location actualLocation = null;
678         final Location previousSibling = request.startingAfter();
679         final int count = request.count();
680         try {
681             // Find the workspace ...
682             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
683             if (workspace == null) return;
684             Long workspaceId = workspace.getId();
685             assert workspaceId != null;
686 
687             ActualLocation actualSibling = getActualLocation(workspace, previousSibling);
688             actualLocation = actualSibling.location;
689             if (!actualLocation.getPath().isRoot()) {
690                 // First look in the cache for the children of the parent ...
691                 Path parentPath = actualSibling.location.getPath().getParent();
692                 assert parentPath != null;
693                 LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
694                 if (cachedChildren != null) {
695                     // The cache has all of the children for the node.
696                     // First find the location of the previous sibling ...
697                     boolean accumulate = false;
698                     int counter = 0;
699                     for (Location child : cachedChildren) {
700                         if (accumulate) {
701                             // We're accumulating children ...
702                             request.addChild(child);
703                             ++counter;
704                             if (counter <= count) continue;
705                             break;
706                         }
707                         // Haven't found the previous sibling yet ...
708                         if (child.isSame(previousSibling)) {
709                             accumulate = true;
710                         }
711                     }
712                 } else {
713                     // The children were not found in the cache, so we have to search the database.
714                     // We don't know the UUID of the parent, so find the previous sibling and
715                     // then get the starting index and the parent UUID ...
716                     ChildEntity previousChild = actualSibling.childEntity;
717                     if (previousChild == null) {
718                         Query query = entities.createNamedQuery("ChildEntity.findByChildUuid");
719                         query.setParameter("workspaceId", workspaceId);
720                         query.setParameter("childUuidString", actualSibling.uuid);
721                         previousChild = (ChildEntity)query.getSingleResult();
722                     }
723                     int startingIndex = previousChild.getIndexInParent() + 1;
724                     String parentUuid = previousChild.getParentUuidString();
725 
726                     // Now search the database for the children ...
727                     Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent");
728                     query.setParameter("workspaceId", workspaceId);
729                     query.setParameter("parentUuidString", parentUuid);
730                     query.setParameter("firstIndex", startingIndex);
731                     query.setParameter("afterIndex", startingIndex + request.count());
732                     @SuppressWarnings( "unchecked" )
733                     List<ChildEntity> children = query.getResultList();
734                     LinkedList<Location> allChildren = null;
735                     if (startingIndex == 1 && children.size() < request.count()) {
736                         // The previous child was the first sibling, and we got fewer children than
737                         // the max count. This means we know all of the children, so accumulate the locations
738                         // so they can be cached ...
739                         allChildren = new LinkedList<Location>();
740                         allChildren.add(actualSibling.location);
741                     }
742                     for (ChildEntity child : children) {
743                         String namespaceUri = child.getChildNamespace().getUri();
744                         String localName = child.getChildName();
745                         Name childName = nameFactory.create(namespaceUri, localName);
746                         int sns = child.getSameNameSiblingIndex();
747                         Path childPath = pathFactory.create(parentPath, childName, sns);
748                         String childUuidString = child.getId().getChildUuidString();
749                         Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
750                         request.addChild(childLocation);
751                         if (allChildren != null) {
752                             // We're going to cache the results, so add this child ...
753                             allChildren.add(childLocation);
754                         }
755                     }
756 
757                     if (allChildren != null) {
758                         cache.setAllChildren(workspaceId, parentPath, allChildren);
759                     }
760                 }
761             }
762 
763         } catch (NoResultException e) {
764             // there are no properties (probably not expected, but still okay) ...
765         } catch (Throwable e) { // Includes PathNotFoundException
766             request.setError(e);
767             return;
768         }
769         if (actualLocation != null) request.setActualLocationOfStartingAfterNode(actualLocation);
770         setCacheableInfo(request);
771     }
772 
773     /**
774      * {@inheritDoc}
775      * 
776      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadAllPropertiesRequest)
777      */
778     @Override
779     public void process( ReadAllPropertiesRequest request ) {
780         logger.trace(request.toString());
781         Location actualLocation = null;
782         try {
783             // Find the workspace ...
784             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
785             if (workspace == null) return;
786             Long workspaceId = workspace.getId();
787             assert workspaceId != null;
788 
789             Location location = request.at();
790             ActualLocation actual = getActualLocation(workspace, location);
791             String uuidString = actual.uuid;
792             actualLocation = actual.location;
793 
794             // Record the UUID as a property, since it's not stored in the serialized properties...
795             request.addProperty(actualLocation.getIdProperty(ModeShapeLexicon.UUID));
796 
797             // Find the properties entity for this node ...
798             Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
799             query.setParameter("workspaceId", workspaceId);
800             query.setParameter("uuid", uuidString);
801             PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();
802 
803             // Deserialize the properties ...
804             boolean compressed = entity.isCompressed();
805             int propertyCount = entity.getPropertyCount();
806             Collection<Property> properties = new ArrayList<Property>(propertyCount);
807             byte[] data = entity.getData();
808             if (data != null) {
809                 LargeValueSerializer largeValues = new LargeValueSerializer(entity);
810                 ByteArrayInputStream bais = new ByteArrayInputStream(data);
811                 InputStream is = compressed ? new GZIPInputStream(bais) : bais;
812                 ObjectInputStream ois = new ObjectInputStream(is);
813                 try {
814                     serializer.deserializeAllProperties(ois, properties, largeValues);
815                     for (Property property : properties) {
816                         request.addProperty(property);
817                     }
818                 } finally {
819                     ois.close();
820                 }
821             }
822         } catch (NoResultException e) {
823             // there are no properties (probably not expected, but still okay) ...
824         } catch (Throwable e) { // Includes PathNotFoundException
825             request.setError(e);
826             return;
827         }
828         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
829         setCacheableInfo(request);
830     }
831 
832     /**
833      * {@inheritDoc}
834      * 
835      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadPropertyRequest)
836      */
837     @Override
838     public void process( ReadPropertyRequest request ) {
839         logger.trace(request.toString());
840         // Process the one property that's requested ...
841         Location actualLocation = null;
842         try {
843             // Find the workspace ...
844             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
845             if (workspace == null) return;
846             Long workspaceId = workspace.getId();
847             assert workspaceId != null;
848 
849             // Small optimization ...
850             final Name propertyName = request.named();
851             if (ModeShapeLexicon.UUID.equals(propertyName) || JcrLexicon.UUID.equals(propertyName)) {
852                 try {
853                     // Just get the UUID ...
854                     Location location = request.on();
855                     ActualLocation actual = getActualLocation(workspace, location); // verifies the UUID
856                     UUID uuid = actual.location.getUuid();
857                     Property uuidProperty = getExecutionContext().getPropertyFactory().create(propertyName, uuid);
858                     request.setProperty(uuidProperty);
859                     request.setActualLocationOfNode(actual.location);
860                     setCacheableInfo(request);
861                 } catch (Throwable e) { // Includes PathNotFoundException
862                     request.setError(e);
863                 }
864                 return;
865             }
866 
867             Location location = request.on();
868             ActualLocation actual = getActualLocation(workspace, location);
869             String uuidString = actual.uuid;
870             actualLocation = actual.location;
871 
872             // Find the properties entity for this node ...
873             Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
874             query.setParameter("workspaceId", workspaceId);
875             query.setParameter("uuid", uuidString);
876             PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();
877 
878             // Deserialize the stream of properties, but only materialize the one property ...
879             boolean compressed = entity.isCompressed();
880             int propertyCount = entity.getPropertyCount();
881             Collection<Property> properties = new ArrayList<Property>(propertyCount);
882             byte[] data = entity.getData();
883             if (data != null) {
884                 LargeValueSerializer largeValues = new LargeValueSerializer(entity);
885                 ByteArrayInputStream bais = new ByteArrayInputStream(data);
886                 InputStream is = compressed ? new GZIPInputStream(bais) : bais;
887                 ObjectInputStream ois = new ObjectInputStream(is);
888                 try {
889                     Serializer.LargeValues skippedLargeValues = Serializer.NO_LARGE_VALUES;
890                     serializer.deserializeSomeProperties(ois, properties, largeValues, skippedLargeValues, propertyName);
891                     for (Property property : properties) {
892                         request.setProperty(property); // should be only one property
893                     }
894                 } finally {
895                     ois.close();
896                 }
897             }
898         } catch (NoResultException e) {
899             // there are no properties (probably not expected, but still okay) ...
900         } catch (Throwable e) { // Includes PathNotFoundException
901             request.setError(e);
902             return;
903         }
904         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
905         setCacheableInfo(request);
906     }
907 
908     /**
909      * {@inheritDoc}
910      * 
911      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.UpdatePropertiesRequest)
912      */
913     @Override
914     public void process( UpdatePropertiesRequest request ) {
915         logger.trace(request.toString());
916         Location actualLocation = null;
917         Set<Name> createdProperties = null;
918         try {
919             // Find the workspace ...
920             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
921             if (workspace == null) return;
922             Long workspaceId = workspace.getId();
923             assert workspaceId != null;
924 
925             Location location = request.on();
926             ActualLocation actual = getActualLocation(workspace, location);
927             actualLocation = actual.location;
928 
929             // Find the properties entity for this node ...
930             Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
931             query.setParameter("workspaceId", workspaceId);
932             query.setParameter("uuid", actual.uuid);
933             PropertiesEntity entity = null;
934             try {
935                 entity = (PropertiesEntity)query.getSingleResult();
936 
937                 // Prepare the streams so we can deserialize all existing properties and reserialize the old and updated
938                 // properties ...
939                 boolean compressed = entity.isCompressed();
940                 byte[] originalData = entity.getData();
941                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
942                 ObjectOutputStream oos;
943                 GZIPOutputStream gos = null;
944                 if (compressed) {
945                     gos = new GZIPOutputStream(baos);
946                     oos = new ObjectOutputStream(gos);
947                 } else {
948                     oos = new ObjectOutputStream(baos);
949                 }
950                 // OutputStream os = compressed ? new GZIPOutputStream(baos) : baos;
951                 // oos = new ObjectOutputStream(os);
952                 int numProps = 0;
953                 LargeValueSerializer largeValues = null;
954                 Map<Name, Property> props = request.properties();
955                 References refs = enforceReferentialIntegrity ? new References() : null;
956                 if (originalData == null) {
957                     largeValues = new LargeValueSerializer(entity);
958                     numProps = props.size();
959                     createdProperties = props.keySet(); // all properties were created
960                     serializer.serializeProperties(oos, numProps, props.values(), largeValues, refs);
961                     if (gos != null) gos.finish();
962                 } else {
963                     boolean hadLargeValues = !entity.getLargeValues().isEmpty();
964                     Set<String> largeValueHashesWritten = hadLargeValues ? new HashSet<String>() : null;
965                     largeValues = new LargeValueSerializer(entity, largeValueHashesWritten);
966                     ByteArrayInputStream bais = new ByteArrayInputStream(originalData);
967                     InputStream is = compressed ? new GZIPInputStream(bais) : bais;
968                     ObjectInputStream ois = new ObjectInputStream(is);
969                     SkippedLargeValues removedValues = new SkippedLargeValues(largeValues);
970                     createdProperties = new HashSet<Name>();
971                     try {
972                         Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES;
973                         numProps = serializer.reserializeProperties(ois,
974                                                                     oos,
975                                                                     props,
976                                                                     largeValues,
977                                                                     removedValues,
978                                                                     createdProperties,
979                                                                     refValues);
980                         if (gos != null) gos.finish();
981                     } finally {
982                         try {
983                             ois.close();
984                         } finally {
985                             oos.close();
986                         }
987                     }
988 
989                     // The new large values were recorded and associated with the properties entity during reserialization.
990                     // However, any values no longer used now need to be removed ...
991                     if (hadLargeValues) {
992                         // Remove any large value from the 'skipped' list that was also written ...
993                         removedValues.skippedKeys.removeAll(largeValueHashesWritten);
994                         for (String oldHexKey : removedValues.skippedKeys) {
995                             LargeValueId id = new LargeValueId(oldHexKey);
996                             entity.getLargeValues().remove(id);
997                         }
998                     }
999 
1000                     if (refs != null) {
1001                         // Remove any existing references ...
1002                         if (refs.hasRemoved()) {
1003                             for (Reference reference : refs.getRemoved()) {
1004                                 String toUuid = resolveToUuid(workspace, reference);
1005                                 if (toUuid != null) {
1006                                     ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid);
1007                                     ReferenceEntity refEntity = entities.find(ReferenceEntity.class, id);
1008                                     if (refEntity != null) {
1009                                         entities.remove(refEntity);
1010                                         workspaceIdsWithChangedReferences.add(workspaceId);
1011                                     }
1012                                 }
1013                             }
1014                         }
1015                     }
1016                 }
1017                 entity.setPropertyCount(numProps);
1018                 entity.setData(baos.toByteArray());
1019                 entity.setCompressed(compressData);
1020 
1021                 if (refs != null && refs.hasWritten()) {
1022                     // If there were references from the updated node ...
1023                     Set<Reference> newReferences = refs.getWritten();
1024                     // Remove any reference that was written (and not removed) ...
1025                     newReferences.removeAll(refs.getRead());
1026                     if (newReferences.size() != 0) {
1027                         // Now save the new references ...
1028                         for (Reference reference : newReferences) {
1029                             String toUuid = resolveToUuid(workspace, reference);
1030                             if (toUuid != null) {
1031                                 ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid);
1032                                 ReferenceEntity refEntity = new ReferenceEntity(id);
1033                                 entities.persist(refEntity);
1034                                 workspaceIdsWithChangedReferences.add(workspaceId);
1035                             }
1036                         }
1037                     }
1038                 }
1039             } catch (NoResultException e) {
1040                 // there are no properties yet ...
1041                 createProperties(workspace, actual.uuid, request.properties().values());
1042                 createdProperties = request.properties().keySet();
1043             }
1044 
1045         } catch (Throwable e) { // Includes PathNotFoundException
1046             request.setError(e);
1047             return;
1048         }
1049         if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
1050         request.setNewProperties(createdProperties);
1051         recordChange(request);
1052     }
1053 
1054     /**
1055      * {@inheritDoc}
1056      * 
1057      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadBranchRequest)
1058      */
1059     @Override
1060     public void process( ReadBranchRequest request ) {
1061         logger.trace(request.toString());
1062         Location actualLocation = null;
1063         try {
1064             // Find the workspace ...
1065             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
1066             if (workspace == null) return;
1067             Long workspaceId = workspace.getId();
1068             assert workspaceId != null;
1069 
1070             Location location = request.at();
1071             ActualLocation actual = getActualLocation(workspace, location);
1072             actualLocation = actual.location;
1073             Path path = actualLocation.getPath();
1074 
1075             // Record the location of each node by its UUID; we'll use this when processing the properties ...
1076             Map<String, Location> locationsByUuid = new HashMap<String, Location>();
1077             locationsByUuid.put(actual.uuid, location);
1078 
1079             // Compute the subgraph, including the root ...
1080             int maxDepth = request.maximumDepth();
1081             SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
1082                                                        entities,
1083                                                        workspaceId,
1084                                                        actualLocation.getUuid(),
1085                                                        path,
1086                                                        maxDepth);
1087 
1088             try {
1089                 // Record all of the children ...
1090                 Path parent = path;
1091                 String parentUuid = actual.uuid;
1092                 Location parentLocation = actualLocation;
1093                 List<Location> children = new LinkedList<Location>();
1094                 Map<Location, List<Location>> childrenByParentLocation = new HashMap<Location, List<Location>>();
1095                 childrenByParentLocation.put(parentLocation, children);
1096                 boolean includeChildrenOfNodesAtMaxDepth = true;
1097                 for (ChildEntity child : query.getNodes(false, includeChildrenOfNodesAtMaxDepth)) {
1098                     String namespaceUri = child.getChildNamespace().getUri();
1099                     String localName = child.getChildName();
1100                     Name childName = nameFactory.create(namespaceUri, localName);
1101                     int sns = child.getSameNameSiblingIndex();
1102                     // Figure out who the parent is ...
1103                     String childParentUuid = child.getParentUuidString();
1104                     if (!parentUuid.equals(childParentUuid)) {
1105                         // Find the correct parent ...
1106                         parentLocation = locationsByUuid.get(childParentUuid);
1107                         parent = parentLocation.getPath();
1108                         parentUuid = childParentUuid;
1109                         // See if there is already a list of children for this parent ...
1110                         children = childrenByParentLocation.get(parentLocation);
1111                         if (children == null) {
1112                             children = new LinkedList<Location>();
1113                             childrenByParentLocation.put(parentLocation, children);
1114                         }
1115                     }
1116                     assert children != null;
1117                     Path childPath = pathFactory.create(parent, childName, sns);
1118                     String childUuidString = child.getId().getChildUuidString();
1119                     Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
1120                     locationsByUuid.put(childUuidString, childLocation);
1121                     children.add(childLocation);
1122                 }
1123                 // Now add the list of children to the results ...
1124                 for (Map.Entry<Location, List<Location>> entry : childrenByParentLocation.entrySet()) {
1125                     // Don't add if there are no children ...
1126                     if (!entry.getValue().isEmpty()) {
1127                         request.setChildren(entry.getKey(), entry.getValue());
1128                     }
1129                 }
1130 
1131                 // Note that we've found children for nodes that are at the maximum depth. This is so that the nodes
1132                 // in the subgraph all have the correct children. However, we don't want to store the properties for
1133                 // any node whose depth is greater than the maximum depth. Therefore, only get the properties that
1134                 // include nodes within the maximum depth...
1135                 includeChildrenOfNodesAtMaxDepth = false;
1136 
1137                 // Now record all of the properties ...
1138                 for (PropertiesEntity props : query.getProperties(true, includeChildrenOfNodesAtMaxDepth)) {
1139                     boolean compressed = props.isCompressed();
1140                     int propertyCount = props.getPropertyCount();
1141                     Collection<Property> properties = new ArrayList<Property>(propertyCount);
1142                     Location nodeLocation = locationsByUuid.get(props.getId().getUuidString());
1143                     assert nodeLocation != null;
1144                     // Record the UUID as a property, since it's not stored in the serialized properties...
1145                     properties.add(actualLocation.getIdProperty(ModeShapeLexicon.UUID));
1146                     // Deserialize all the properties (except the UUID)...
1147                     byte[] data = props.getData();
1148                     if (data != null) {
1149                         LargeValueSerializer largeValues = new LargeValueSerializer(props);
1150                         ByteArrayInputStream bais = new ByteArrayInputStream(data);
1151                         InputStream is = compressed ? new GZIPInputStream(bais) : bais;
1152                         ObjectInputStream ois = new ObjectInputStream(is);
1153                         try {
1154                             serializer.deserializeAllProperties(ois, properties, largeValues);
1155                             request.setProperties(nodeLocation, properties);
1156                         } finally {
1157                             ois.close();
1158                         }
1159                     }
1160                 }
1161             } finally {
1162                 // Close and release the temporary data used for this operation ...
1163                 query.close();
1164             }
1165 
1166         } catch (Throwable e) { // Includes PathNotFoundException
1167             request.setError(e);
1168             return;
1169         }
1170         request.setActualLocationOfNode(actualLocation);
1171         setCacheableInfo(request);
1172     }
1173 
1174     private ActualLocation copyNode( EntityManager entities,
1175                                      WorkspaceEntity fromWorkspace,
1176                                      WorkspaceEntity intoWorkspace,
1177                                      ChildEntity original,
1178                                      ActualLocation actualNewParent,
1179                                      UuidConflictBehavior uuidConflictBehavior,
1180                                      Name desiredName,
1181                                      Path.Segment desiredSegment,
1182                                      Map<String, String> oldUuidsToNewUuids,
1183                                      Map<Location, ChildEntity> addedLocations,
1184                                      Map<String, Location> deletedLocations ) {
1185         assert fromWorkspace != null;
1186         assert intoWorkspace != null;
1187         assert original != null;
1188         assert (desiredName != null ? desiredSegment == null : desiredSegment != null) : "Either desiredName or desiredSegment must not be null";
1189         assert oldUuidsToNewUuids != null;
1190 
1191         // Assume that UUID isn't changing. If the conflict behavior says that it does change, the switch statement will handle it
1192         String newUuid = original.getId().getChildUuidString();
1193         ActualLocation newLocation = null;
1194         ActualLocation existingLocation = null;
1195 
1196         UUID oldUuid = UUID.fromString(original.getId().getChildUuidString());
1197 
1198         switch (uuidConflictBehavior) {
1199             case ALWAYS_CREATE_NEW_UUID:
1200                 newUuid = UUID.randomUUID().toString();
1201 
1202                 break;
1203             case THROW_EXCEPTION:
1204                 try {
1205                     existingLocation = getActualLocation(intoWorkspace, Location.create(oldUuid));
1206                     String pathAsString = existingLocation.toString();
1207                     throw new UuidAlreadyExistsException(this.getSourceName(), oldUuid, pathAsString, intoWorkspace.getName());
1208                 } catch (PathNotFoundException pnfe) {
1209 
1210                 }
1211                 break;
1212             case REPLACE_EXISTING_NODE:
1213                 try {
1214                     if (desiredSegment != null) {
1215                         Location nLocation = Location.create(pathFactory.create(actualNewParent.location.getPath(),
1216                                                                                 desiredSegment));
1217                         existingLocation = getActualLocation(intoWorkspace, nLocation);
1218                         nLocation = Location.create(nLocation.getPath(), UUID.fromString(existingLocation.uuid));
1219                         assert nLocation.getUuid() != null;
1220                         deletedLocations.putAll(computeDeletedLocations(intoWorkspace, nLocation, true));
1221                         newUuid = existingLocation.childEntity.getId().getChildUuidString();
1222 
1223                     } else {
1224                         existingLocation = getActualLocation(intoWorkspace, Location.create(oldUuid));
1225                         deletedLocations.putAll(computeDeletedLocations(intoWorkspace, existingLocation.location, true));
1226                     }
1227                 } catch (PathNotFoundException pnfe) {
1228                 }
1229                 break;
1230             default:
1231                 throw new IllegalStateException("Unexpected UuidConflictBehavior value: " + uuidConflictBehavior);
1232         }
1233         oldUuidsToNewUuids.put(original.getId().getChildUuidString(), newUuid);
1234 
1235         if (existingLocation != null && existingLocation.childEntity.getParentUuidString().equals(actualNewParent.uuid)) {
1236             if (desiredName == null) {
1237                 assert desiredSegment != null;
1238                 desiredName = desiredSegment.getName();
1239             }
1240             NamespaceEntity namespace = NamespaceEntity.findByUri(entities, desiredName.getNamespaceUri());
1241 
1242             ChildEntity existingChild = existingLocation.childEntity;
1243             existingChild.setChildName(desiredName.getLocalName());
1244             existingChild.setChildNamespace(namespace);
1245             existingChild.setParentUuidString(actualNewParent.uuid);
1246             existingChild.setAllowsSameNameChildren(original.getAllowsSameNameChildren());
1247             existingChild.setIndexInParent(original.getIndexInParent());
1248             existingChild.setSameNameSiblingIndex(original.getSameNameSiblingIndex());
1249             Location parentLocation = actualNewParent.location;
1250             assert parentLocation.hasPath();
1251 
1252             Name segmentName = nameFactory.create(existingChild.getChildNamespace().getUri(), existingChild.getChildName());
1253             Path.Segment newSegment = pathFactory.createSegment(segmentName, existingChild.getSameNameSiblingIndex());
1254             Path newPath = pathFactory.create(parentLocation.getPath(), newSegment);
1255             newLocation = new ActualLocation(Location.create(newPath, UUID.fromString(newUuid)), newUuid, existingChild);
1256 
1257         } else {
1258             // Now add the new copy of the original ...
1259             boolean allowSnS = original.getAllowsSameNameChildren();
1260             if (desiredName == null) {
1261                 assert desiredSegment != null;
1262                 desiredName = desiredSegment.getName();
1263             }
1264             newLocation = addNewChild(intoWorkspace.getId(), actualNewParent, newUuid, desiredName, allowSnS);
1265         }
1266 
1267         assert newLocation != null;
1268         addedLocations.put(newLocation.location, newLocation.childEntity);
1269 
1270         return newLocation;
1271     }
1272 
1273     /**
1274      * {@inheritDoc}
1275      * 
1276      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CopyBranchRequest)
1277      */
1278     @Override
1279     public void process( CopyBranchRequest request ) {
1280         logger.trace(request.toString());
1281         Location actualFromLocation = null;
1282         Location actualToLocation = null;
1283         try {
1284             // Find the workspaces ...
1285             WorkspaceEntity fromWorkspace = getExistingWorkspace(request.fromWorkspace(), request);
1286             if (fromWorkspace == null) return;
1287             WorkspaceEntity intoWorkspace = getExistingWorkspace(request.intoWorkspace(), request);
1288             if (intoWorkspace == null) return;
1289             Long fromWorkspaceId = fromWorkspace.getId();
1290             Long intoWorkspaceId = intoWorkspace.getId();
1291             assert fromWorkspaceId != null;
1292             assert intoWorkspaceId != null;
1293 
1294             Location fromLocation = request.from();
1295             ActualLocation actualFrom = getActualLocation(fromWorkspace, fromLocation);
1296             actualFromLocation = actualFrom.location;
1297             Path fromPath = actualFromLocation.getPath();
1298 
1299             Location newParentLocation = request.into();
1300             ActualLocation actualNewParent = getActualLocation(intoWorkspace, newParentLocation);
1301             assert actualNewParent != null;
1302 
1303             // Create a map that we'll use to record the new UUID for each of the original nodes ...
1304             Map<String, String> originalToNewUuid = new HashMap<String, String>();
1305 
1306             // Compute the subgraph, including the top node in the subgraph ...
1307             SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
1308                                                        entities,
1309                                                        fromWorkspaceId,
1310                                                        actualFromLocation.getUuid(),
1311                                                        fromPath,
1312                                                        0);
1313             try {
1314                 // Walk through the original nodes, creating new ChildEntity object (i.e., copy) for each original ...
1315                 List<ChildEntity> originalNodes = query.getNodes(true, true);
1316                 Iterator<ChildEntity> originalIter = originalNodes.iterator();
1317                 Map<Location, ChildEntity> addedLocations = new HashMap<Location, ChildEntity>();
1318                 Map<String, Location> deletedLocations = new HashMap<String, Location>();
1319 
1320                 // Start with the original (top-level) node first, since we need to add it to the list of children ...
1321                 if (originalIter.hasNext()) {
1322                     ChildEntity original = originalIter.next();
1323 
1324                     Name desiredName = request.desiredName();
1325                     if (desiredName == null) desiredName = fromPath.getLastSegment().getName();
1326                     actualToLocation = this.copyNode(entities,
1327                                                      fromWorkspace,
1328                                                      intoWorkspace,
1329                                                      original,
1330                                                      actualNewParent,
1331                                                      UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID,
1332                                                      desiredName,
1333                                                      null,
1334                                                      originalToNewUuid,
1335                                                      addedLocations,
1336                                                      deletedLocations).location;
1337                 }
1338 
1339                 // Now create copies of all children in the subgraph.
1340                 while (originalIter.hasNext()) {
1341                     ChildEntity original = originalIter.next();
1342                     String newParentUuidOfCopy = originalToNewUuid.get(original.getParentUuidString());
1343                     assert newParentUuidOfCopy != null;
1344 
1345                     actualNewParent = getActualLocation(intoWorkspace, Location.create(UUID.fromString(newParentUuidOfCopy)));
1346 
1347                     Name desiredName = nameFactory.create(original.getChildNamespace().getUri(), original.getChildName());
1348                     this.copyNode(entities,
1349                                   fromWorkspace,
1350                                   intoWorkspace,
1351                                   original,
1352                                   actualNewParent,
1353                                   UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID,
1354                                   desiredName,
1355                                   null,
1356                                   originalToNewUuid,
1357                                   addedLocations,
1358                                   deletedLocations);
1359                 }
1360                 entities.flush();
1361 
1362                 Set<String> newNodesWithReferenceProperties = new HashSet<String>();
1363                 // Now create copies of all the intra-subgraph references, replacing the UUIDs on both ends ...
1364                 for (ReferenceEntity reference : query.getInternalReferences()) {
1365                     String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
1366                     assert newFromUuid != null;
1367                     String newToUuid = originalToNewUuid.get(reference.getId().getToUuidString());
1368                     assert newToUuid != null;
1369                     ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, newToUuid));
1370                     entities.persist(copy);
1371                     newNodesWithReferenceProperties.add(newFromUuid);
1372                 }
1373 
1374                 // Now create copies of all the references owned by the subgraph but pointing to non-subgraph nodes,
1375                 // so we only replaced the 'from' UUID ...
1376                 for (ReferenceEntity reference : query.getOutwardReferences()) {
1377                     String oldToUuid = reference.getId().getToUuidString();
1378                     String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
1379                     assert newFromUuid != null;
1380 
1381                     ActualLocation refTargetLocation = getActualLocation(intoWorkspace,
1382                                                                          Location.create(UUID.fromString(oldToUuid)));
1383                     if (refTargetLocation == null) {
1384                         // Some of the references that remain will be invalid, since they point to nodes that
1385                         // have just been deleted. Build up the information necessary to produce a useful exception ...
1386                         ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
1387                         Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
1388                         UUID fromUuid = UUID.fromString(reference.getId().getFromUuidString());
1389                         ActualLocation actualRefFromLocation = getActualLocation(intoWorkspace, Location.create(fromUuid));
1390                         Location refFromLocation = actualRefFromLocation.location;
1391                         List<Reference> refs = invalidRefs.get(fromLocation);
1392                         if (refs == null) {
1393                             refs = new ArrayList<Reference>();
1394                             invalidRefs.put(refFromLocation, refs);
1395                         }
1396                         UUID toUuid = UUID.fromString(oldToUuid);
1397                         refs.add(refFactory.create(toUuid));
1398 
1399                         String msg = JpaConnectorI18n.invalidReferences.text(reference.getId().getFromUuidString());
1400                         throw new ReferentialIntegrityException(invalidRefs, msg);
1401                     }
1402 
1403                     ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, oldToUuid));
1404                     entities.persist(copy);
1405                     newNodesWithReferenceProperties.add(newFromUuid);
1406                 }
1407 
1408                 Set<PropertiesEntity> addedProps = new HashSet<PropertiesEntity>();
1409                 // Now process the properties, creating a copy (note references are not changed) ...
1410                 for (PropertiesEntity original : query.getProperties(true, true)) {
1411                     // Find the UUID of the copy ...
1412                     String copyUuid = originalToNewUuid.get(original.getId().getUuidString());
1413                     assert copyUuid != null;
1414 
1415                     // Create the copy ...
1416                     boolean compressed = original.isCompressed();
1417                     byte[] originalData = original.getData();
1418                     NodeId propertiesId = new NodeId(intoWorkspaceId, copyUuid);
1419                     PropertiesEntity copy = entities.find(PropertiesEntity.class, propertiesId);
1420 
1421                     if (copy == null) {
1422                         copy = new PropertiesEntity(propertiesId);
1423                     }
1424                     copy.setCompressed(compressed);
1425                     if (newNodesWithReferenceProperties.contains(copyUuid)) {
1426 
1427                         // This node has internal or outward references that must be adjusted ...
1428                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1429                         OutputStream os = compressed ? new GZIPOutputStream(baos) : baos;
1430                         ObjectOutputStream oos = new ObjectOutputStream(os);
1431                         ByteArrayInputStream bais = new ByteArrayInputStream(originalData);
1432                         InputStream is = compressed ? new GZIPInputStream(bais) : bais;
1433                         ObjectInputStream ois = new ObjectInputStream(is);
1434                         try {
1435                             serializer.adjustReferenceProperties(ois, oos, originalToNewUuid);
1436                         } finally {
1437                             try {
1438                                 ois.close();
1439                             } finally {
1440                                 oos.close();
1441                             }
1442                         }
1443                         copy.setData(baos.toByteArray());
1444                     } else {
1445                         // No references to adjust, so just copy the original data ...
1446                         copy.setData(originalData);
1447                     }
1448                     copy.setPropertyCount(original.getPropertyCount());
1449                     copy.setReferentialIntegrityEnforced(original.isReferentialIntegrityEnforced());
1450                     addedProps.add(copy);
1451                     entities.persist(copy);
1452                 }
1453                 entities.flush();
1454             } finally {
1455                 // Close and release the temporary data used for this operation ...
1456                 query.close();
1457             }
1458 
1459         } catch (Throwable e) { // Includes PathNotFoundException
1460             request.setError(e);
1461             return;
1462         }
1463         request.setActualLocations(actualFromLocation, actualToLocation);
1464         recordChange(request);
1465     }
1466 
1467     /**
1468      * {@inheritDoc}
1469      * 
1470      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneBranchRequest)
1471      */
1472     @Override
1473     public void process( CloneBranchRequest request ) {
1474         logger.trace(request.toString());
1475         Location actualFromLocation = null;
1476         Location actualToLocation = null;
1477         Set<Location> removedLocations = null;
1478         try {
1479             // Find the workspaces ...
1480             WorkspaceEntity fromWorkspace = getExistingWorkspace(request.fromWorkspace(), request);
1481             if (fromWorkspace == null) return;
1482             WorkspaceEntity intoWorkspace = getExistingWorkspace(request.intoWorkspace(), request);
1483             if (intoWorkspace == null) return;
1484             Long fromWorkspaceId = fromWorkspace.getId();
1485             Long intoWorkspaceId = intoWorkspace.getId();
1486             assert fromWorkspaceId != null;
1487             assert intoWorkspaceId != null;
1488 
1489             Location fromLocation = request.from();
1490             ActualLocation actualFrom = getActualLocation(fromWorkspace, fromLocation);
1491             actualFromLocation = actualFrom.location;
1492             Path fromPath = actualFromLocation.getPath();
1493 
1494             Location newParentLocation = request.into();
1495             ActualLocation actualNewParent = getActualLocation(intoWorkspace, newParentLocation);
1496             assert actualNewParent != null;
1497 
1498             // Create a map that we'll use to record the new UUID for each of the original nodes ...
1499             Map<String, String> originalToNewUuid = new HashMap<String, String>();
1500 
1501             // Compute the subgraph, including the top node in the subgraph ...
1502             SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
1503                                                        entities,
1504                                                        fromWorkspaceId,
1505                                                        actualFromLocation.getUuid(),
1506                                                        fromPath,
1507                                                        0);
1508 
1509             UuidConflictBehavior conflictBehavior = request.removeExisting() ? UuidConflictBehavior.REPLACE_EXISTING_NODE : UuidConflictBehavior.THROW_EXCEPTION;
1510             try {
1511                 // Walk through the original nodes, creating new ChildEntity object (i.e., copy) for each original ...
1512                 List<ChildEntity> originalNodes = query.getNodes(true, true);
1513                 Iterator<ChildEntity> originalIter = originalNodes.iterator();
1514                 Map<Location, ChildEntity> addedLocations = new HashMap<Location, ChildEntity>();
1515                 Map<String, Location> deletedLocations = new HashMap<String, Location>();
1516 
1517                 // Start with the original (top-level) node first, since we need to add it to the list of children ...
1518                 if (originalIter.hasNext()) {
1519                     ChildEntity original = originalIter.next();
1520 
1521                     Name desiredName = request.desiredName();
1522                     actualToLocation = this.copyNode(entities,
1523                                                      fromWorkspace,
1524                                                      intoWorkspace,
1525                                                      original,
1526                                                      actualNewParent,
1527                                                      conflictBehavior,
1528                                                      desiredName,
1529                                                      desiredName != null ? null : fromPath.getLastSegment(),
1530                                                      originalToNewUuid,
1531                                                      addedLocations,
1532                                                      deletedLocations).location;
1533                 }
1534 
1535                 // Now create copies of all children in the subgraph.
1536                 while (originalIter.hasNext()) {
1537                     ChildEntity original = originalIter.next();
1538                     String newParentUuidOfCopy = originalToNewUuid.get(original.getParentUuidString());
1539                     assert newParentUuidOfCopy != null;
1540 
1541                     actualNewParent = getActualLocation(intoWorkspace, Location.create(UUID.fromString(newParentUuidOfCopy)));
1542 
1543                     Name desiredName = nameFactory.create(original.getChildNamespace().getUri(), original.getChildName());
1544                     this.copyNode(entities,
1545                                   fromWorkspace,
1546                                   intoWorkspace,
1547                                   original,
1548                                   actualNewParent,
1549                                   conflictBehavior,
1550                                   desiredName,
1551                                   null,
1552                                   originalToNewUuid,
1553                                   addedLocations,
1554                                   deletedLocations);
1555                 }
1556                 entities.flush();
1557 
1558                 Set<String> newNodesWithReferenceProperties = new HashSet<String>();
1559                 // Now create copies of all the intra-subgraph references, replacing the UUIDs on both ends ...
1560                 for (ReferenceEntity reference : query.getInternalReferences()) {
1561                     String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
1562                     assert newFromUuid != null;
1563                     String newToUuid = originalToNewUuid.get(reference.getId().getToUuidString());
1564                     assert newToUuid != null;
1565                     ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, newToUuid));
1566                     entities.persist(copy);
1567                     newNodesWithReferenceProperties.add(newFromUuid);
1568                 }
1569 
1570                 // Now create copies of all the references owned by the subgraph but pointing to non-subgraph nodes,
1571                 // so we only replaced the 'from' UUID ...
1572                 for (ReferenceEntity reference : query.getOutwardReferences()) {
1573                     String oldToUuid = reference.getId().getToUuidString();
1574                     String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
1575                     assert newFromUuid != null;
1576 
1577                     ActualLocation refTargetLocation = getActualLocation(intoWorkspace,
1578                                                                          Location.create(UUID.fromString(oldToUuid)));
1579                     if (refTargetLocation == null) {
1580                         // Some of the references that remain will be invalid, since they point to nodes that
1581                         // have just been deleted. Build up the information necessary to produce a useful exception ...
1582                         ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
1583                         Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
1584                         UUID fromUuid = UUID.fromString(reference.getId().getFromUuidString());
1585                         ActualLocation actualRefFromLocation = getActualLocation(intoWorkspace, Location.create(fromUuid));
1586                         Location refFromLocation = actualRefFromLocation.location;
1587                         List<Reference> refs = invalidRefs.get(fromLocation);
1588                         if (refs == null) {
1589                             refs = new ArrayList<Reference>();
1590                             invalidRefs.put(refFromLocation, refs);
1591                         }
1592                         UUID toUuid = UUID.fromString(oldToUuid);
1593                         refs.add(refFactory.create(toUuid));
1594 
1595                         String msg = JpaConnectorI18n.invalidReferences.text(reference.getId().getFromUuidString());
1596                         throw new ReferentialIntegrityException(invalidRefs, msg);
1597                     }
1598 
1599                     ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, oldToUuid));
1600                     entities.persist(copy);
1601                     newNodesWithReferenceProperties.add(newFromUuid);
1602                 }
1603 
1604                 Set<PropertiesEntity> addedProps = new HashSet<PropertiesEntity>();
1605                 // Now process the properties, creating a copy (note references are not changed) ...
1606                 for (PropertiesEntity original : query.getProperties(true, true)) {
1607                     // Find the UUID of the copy ...
1608                     String copyUuid = originalToNewUuid.get(original.getId().getUuidString());
1609                     assert copyUuid != null;
1610 
1611                     // Create the copy ...
1612                     boolean compressed = original.isCompressed();
1613                     byte[] originalData = original.getData();
1614                     NodeId propertiesId = new NodeId(intoWorkspaceId, copyUuid);
1615                     PropertiesEntity copy = entities.find(PropertiesEntity.class, propertiesId);
1616 
1617                     if (copy == null) {
1618                         copy = new PropertiesEntity(propertiesId);
1619                     }
1620                     copy.setCompressed(compressed);
1621                     if (newNodesWithReferenceProperties.contains(copyUuid)) {
1622 
1623                         // This node has internal or outward references that must be adjusted ...
1624                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1625                         OutputStream os = compressed ? new GZIPOutputStream(baos) : baos;
1626                         ObjectOutputStream oos = new ObjectOutputStream(os);
1627                         ByteArrayInputStream bais = new ByteArrayInputStream(originalData);
1628                         InputStream is = compressed ? new GZIPInputStream(bais) : bais;
1629                         ObjectInputStream ois = new ObjectInputStream(is);
1630                         try {
1631                             serializer.adjustReferenceProperties(ois, oos, originalToNewUuid);
1632                         } finally {
1633                             try {
1634                                 ois.close();
1635                             } finally {
1636                                 oos.close();
1637                             }
1638                         }
1639                         copy.setData(baos.toByteArray());
1640                     } else {
1641                         // No references to adjust, so just copy the original data ...
1642                         copy.setData(originalData);
1643                     }
1644                     copy.setPropertyCount(original.getPropertyCount());
1645                     copy.setReferentialIntegrityEnforced(original.isReferentialIntegrityEnforced());
1646                     addedProps.add(copy);
1647                     entities.persist(copy);
1648                 }
1649                 entities.flush();
1650 
1651                 if (request.removeExisting()) {
1652                     /*
1653                     * We may have deleted some old copies of nodes and replaced them with new copies.
1654                     * Now we need to clean up any nodes that were descendants of the old copies of the
1655                     * nodes but are not descendants of the new copies.
1656                     */
1657                     Map<String, Location> netDeletedLocations = new HashMap<String, Location>(deletedLocations);
1658                     netDeletedLocations.values().removeAll(addedLocations.keySet());
1659 
1660                     if (netDeletedLocations.size() > 0) {
1661                         // Verify referential integrity: that none of the deleted nodes are referenced by nodes not being deleted.
1662                         List<ReferenceEntity> invalidReferences = ReferenceEntity.getReferencesToUuids(intoWorkspace.getId(),
1663                                                                                                        netDeletedLocations.keySet(),
1664                                                                                                        entities);
1665 
1666                         for (Iterator<ReferenceEntity> iter = invalidReferences.iterator(); iter.hasNext();) {
1667                             ReferenceEntity invalidRef = iter.next();
1668                             if (netDeletedLocations.keySet().contains(invalidRef.getId().getFromUuidString())) {
1669                                 iter.remove();
1670                             }
1671                         }
1672 
1673                         if (invalidReferences.size() > 0) {
1674                             // Some of the references that remain will be invalid, since they point to nodes that
1675                             // have just been deleted. Build up the information necessary to produce a useful exception ...
1676                             ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
1677                             Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
1678                             for (ReferenceEntity entity : invalidReferences) {
1679                                 UUID fromUuid = UUID.fromString(entity.getId().getFromUuidString());
1680                                 ActualLocation actualRefFromLocation = getActualLocation(intoWorkspace, Location.create(fromUuid));
1681                                 Location refFromLocation = actualRefFromLocation.location;
1682                                 List<Reference> refs = invalidRefs.get(fromLocation);
1683                                 if (refs == null) {
1684                                     refs = new ArrayList<Reference>();
1685                                     invalidRefs.put(refFromLocation, refs);
1686                                 }
1687                                 UUID toUuid = UUID.fromString(entity.getId().getToUuidString());
1688                                 refs.add(refFactory.create(toUuid));
1689                             }
1690                             String msg = JpaConnectorI18n.unableToDeleteBecauseOfReferences.text();
1691                             throw new ReferentialIntegrityException(invalidRefs, msg);
1692                         }
1693 
1694                         /*
1695                          * This list of values that were deleted is expected to be fairly small
1696                          */
1697                         for (Location location : netDeletedLocations.values()) {
1698                             ActualLocation node = getActualLocation(intoWorkspace, location);
1699                             ChildEntity entity = node.childEntity;
1700 
1701                             if (entity != null) {
1702                                 entities.remove(node.childEntity);
1703                             }
1704                             PropertiesEntity.deletePropertiesFor(intoWorkspace.getId(), node.uuid, entities);
1705 
1706                         }
1707                         // Remove from the cache of children locations all entries for deleted nodes ...
1708                         cache.removeBranch(intoWorkspaceId, netDeletedLocations.values());
1709                     }
1710 
1711                     if (!deletedLocations.isEmpty()) {
1712                         removedLocations = Collections.unmodifiableSet(new HashSet<Location>(deletedLocations.values()));
1713                     }
1714                     LargeValueEntity.deleteUnused(entities);
1715                 }
1716 
1717                 // cache.clear(intoWorkspace.getId());
1718 
1719             } finally {
1720                 // Close and release the temporary data used for this operation ...
1721                 query.close();
1722             }
1723 
1724         } catch (Throwable e) { // Includes PathNotFoundException
1725             request.setError(e);
1726             return;
1727         }
1728         request.setActualLocations(actualFromLocation, actualToLocation);
1729         request.setRemovedNodes(removedLocations);
1730         recordChange(request);
1731     }
1732 
1733     /**
1734      * {@inheritDoc}
1735      * 
1736      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DeleteBranchRequest)
1737      */
1738     @Override
1739     public void process( DeleteBranchRequest request ) {
1740         logger.trace(request.toString());
1741         Location location = delete(request, request.at(), request.inWorkspace(), true);
1742         if (location != null) {
1743             request.setActualLocationOfNode(location);
1744             recordChange(request);
1745         }
1746     }
1747 
1748     /**
1749      * {@inheritDoc}
1750      * 
1751      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DeleteChildrenRequest)
1752      */
1753     @Override
1754     public void process( DeleteChildrenRequest request ) {
1755         logger.trace(request.toString());
1756         Location location = delete(request, request.at(), request.inWorkspace(), false);
1757         if (location != null) {
1758             request.setActualLocationOfNode(location);
1759             recordChange(request);
1760         }
1761     }
1762 
1763     protected Location delete( Request request,
1764                                Location location,
1765                                String workspaceName,
1766                                boolean deleteTopOfBranch ) {
1767         Location actualLocation = null;
1768         try {
1769             // Find the workspace ...
1770             WorkspaceEntity workspace = getExistingWorkspace(workspaceName, request);
1771             if (workspace == null) return null;
1772             Long workspaceId = workspace.getId();
1773             assert workspaceId != null;
1774 
1775             ActualLocation actual = getActualLocation(workspace, location);
1776             actualLocation = actual.location;
1777             Path path = actualLocation.getPath();
1778 
1779             // Compute the subgraph, including the top node in the subgraph ...
1780             SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
1781                                                        entities,
1782                                                        workspaceId,
1783                                                        actualLocation.getUuid(),
1784                                                        path,
1785                                                        0);
1786             try {
1787                 ChildEntity deleted = query.getNode();
1788                 String parentUuidString = deleted.getParentUuidString();
1789                 String childName = deleted.getChildName();
1790                 long nsId = deleted.getChildNamespace().getId();
1791                 int indexInParent = deleted.getIndexInParent();
1792 
1793                 // Get the locations of all deleted nodes, which will be required by events ...
1794                 List<Location> deletedLocations = query.getNodeLocations(true, true);
1795 
1796                 // Now delete the subgraph ...
1797                 query.deleteSubgraph(deleteTopOfBranch);
1798 
1799                 // Verify referential integrity: that none of the deleted nodes are referenced by nodes not being deleted.
1800                 List<ReferenceEntity> invalidReferences = query.getInwardReferences();
1801                 if (invalidReferences.size() > 0) {
1802                     // Some of the references that remain will be invalid, since they point to nodes that
1803                     // have just been deleted. Build up the information necessary to produce a useful exception ...
1804                     ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
1805                     Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
1806                     for (ReferenceEntity entity : invalidReferences) {
1807                         UUID fromUuid = UUID.fromString(entity.getId().getFromUuidString());
1808                         ActualLocation actualFromLocation = getActualLocation(workspace, Location.create(fromUuid));
1809                         Location fromLocation = actualFromLocation.location;
1810                         List<Reference> refs = invalidRefs.get(fromLocation);
1811                         if (refs == null) {
1812                             refs = new ArrayList<Reference>();
1813                             invalidRefs.put(fromLocation, refs);
1814                         }
1815                         UUID toUuid = UUID.fromString(entity.getId().getToUuidString());
1816                         refs.add(refFactory.create(toUuid));
1817                     }
1818                     String msg = JpaConnectorI18n.unableToDeleteBecauseOfReferences.text();
1819                     throw new ReferentialIntegrityException(invalidRefs, msg);
1820                 }
1821 
1822                 if (deleteTopOfBranch) {
1823                     // And adjust the SNS index and indexes ...
1824                     ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities,
1825                                                                         workspaceId,
1826                                                                         parentUuidString,
1827                                                                         childName,
1828                                                                         nsId,
1829                                                                         indexInParent);
1830                     entities.flush();
1831                 }
1832 
1833                 // Remove from the cache of children locations all entries for deleted nodes ...
1834                 cache.removeBranch(workspaceId, deletedLocations);
1835             } finally {
1836                 // Close and release the temporary data used for this operation ...
1837                 query.close();
1838             }
1839 
1840         } catch (Throwable e) { // Includes PathNotFoundException
1841             request.setError(e);
1842             return null;
1843         }
1844         return actualLocation;
1845     }
1846 
1847     protected Map<String, Location> computeDeletedLocations( WorkspaceEntity workspace,
1848                                                              Location topNodeLocation,
1849                                                              boolean deleteTopOfBranch ) {
1850         Location actualLocation = null;
1851 
1852         // Find the workspace ...
1853         if (workspace == null) return null;
1854         Long workspaceId = workspace.getId();
1855         assert workspaceId != null;
1856 
1857         ActualLocation actual = getActualLocation(workspace, topNodeLocation);
1858         actualLocation = actual.location;
1859         Path path = actualLocation.getPath();
1860 
1861         // Compute the subgraph, including the top node in the subgraph ...
1862         SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
1863                                                    entities,
1864                                                    workspaceId,
1865                                                    actualLocation.getUuid(),
1866                                                    path,
1867                                                    0);
1868         try {
1869             // Get the locations of all deleted nodes, which will be required by events ...
1870             List<Location> deletedLocations = query.getNodeLocations(true, true);
1871 
1872             Map<String, Location> results = new HashMap<String, Location>(deletedLocations.size());
1873             for (Location location : deletedLocations) {
1874                 results.put(location.getUuid().toString(), location);
1875             }
1876 
1877             return results;
1878         } finally {
1879             // Close and release the temporary data used for this operation ...
1880             query.close();
1881         }
1882     }
1883 
1884     private Location moveNodeToLastChild( WorkspaceEntity workspace,
1885                                           ActualLocation actualFromLocation,
1886                                           ActualLocation actualIntoLocation,
1887                                           Name desiredName ) {
1888         long workspaceId = workspace.getId();
1889         Path oldPath = actualFromLocation.location.getPath();
1890 
1891         ChildEntity fromEntity = actualFromLocation.childEntity;
1892 
1893         // Now we know that the new parent is not the existing parent ...
1894         final int oldIndex = fromEntity.getIndexInParent();
1895         final String oldParentUuid = fromEntity.getParentUuidString();
1896 
1897         String toUuidString = actualIntoLocation.uuid;
1898 
1899         // Make sure the child name is set correctly ...
1900         String childOldLocalName = fromEntity.getChildName();
1901         String childLocalName = null;
1902         NamespaceEntity ns = null;
1903         Name childName = desiredName;
1904         if (childName != null) {
1905             childLocalName = desiredName.getLocalName();
1906             String childNsUri = childName.getNamespaceUri();
1907             ns = namespaces.get(childNsUri, true);
1908         } else {
1909             childName = oldPath.getLastSegment().getName();
1910             childLocalName = fromEntity.getChildName();
1911             ns = fromEntity.getChildNamespace();
1912         }
1913 
1914         int nextSnsIndex = 1;
1915         int nextIndexInParent = 1;
1916 
1917         // Find the largest SNS index in the existing ChildEntity objects with the same name ...
1918         Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex");
1919         query.setParameter("workspaceId", workspaceId);
1920         query.setParameter("parentUuid", toUuidString);
1921         query.setParameter("ns", ns.getId());
1922         query.setParameter("childName", childLocalName);
1923         try {
1924             Integer index = (Integer)query.getSingleResult();
1925             if (index != null) nextSnsIndex = index.intValue() + 1;
1926         } catch (NoResultException e) {
1927         }
1928 
1929         // Find the largest child index in the existing ChildEntity objects ...
1930         query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
1931         query.setParameter("workspaceId", workspaceId);
1932         query.setParameter("parentUuid", toUuidString);
1933         try {
1934             Integer index = (Integer)query.getSingleResult();
1935             if (index != null) nextIndexInParent = index + 1;
1936         } catch (NoResultException e) {
1937         }
1938 
1939         fromEntity.setParentUuidString(toUuidString);
1940         fromEntity.setChildName(childLocalName);
1941         fromEntity.setChildNamespace(ns);
1942         fromEntity.setIndexInParent(nextIndexInParent);
1943         fromEntity.setSameNameSiblingIndex(nextSnsIndex);
1944 
1945         // Flush the entities to the database ...
1946         entities.flush();
1947 
1948         // Determine the new location ...
1949         Path newParentPath = actualIntoLocation.location.getPath();
1950         Path newPath = pathFactory.create(newParentPath, childName, nextSnsIndex);
1951         Location newLocation = actualFromLocation.location.with(newPath);
1952 
1953         // And adjust the SNS index and indexes ...
1954         ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities,
1955                                                             workspaceId,
1956                                                             oldParentUuid,
1957                                                             childOldLocalName,
1958                                                             ns.getId(),
1959                                                             oldIndex);
1960 
1961         // Update the cache ...
1962         cache.moveNode(workspaceId, actualFromLocation.location, oldIndex, newLocation);
1963 
1964         return newLocation;
1965     }
1966 
1967     @SuppressWarnings( "unchecked" )
1968     private Location moveNodeBefore( WorkspaceEntity workspace,
1969                                      ActualLocation actualFromLocation,
1970                                      ActualLocation actualIntoLocation,
1971                                      ActualLocation actualBeforeLocation ) {
1972 
1973         long workspaceId = workspace.getId();
1974 
1975         ChildEntity fromEntity = actualFromLocation.childEntity;
1976         assert fromEntity != null;
1977 
1978         Path oldPath = actualFromLocation.location.getPath();
1979         String oldParentUuid = fromEntity.getParentUuidString();
1980 
1981         String toUuidString = actualIntoLocation.uuid;
1982         Location beforeLocation = actualBeforeLocation.location;
1983         ChildEntity beforeEntity = actualBeforeLocation.childEntity;
1984 
1985         /*
1986          * getActualLocation (which populates actualBeforeLocation) can return null for the childEntity
1987          * if the location was retrieved from the cache.  Since we need the beforeEntity, try to force the load.
1988          */
1989         if (beforeEntity == null) {
1990             beforeEntity = findNode(workspaceId, actualBeforeLocation.uuid);
1991         }
1992 
1993         assert beforeEntity != null;
1994 
1995         Name childName = oldPath.getLastSegment().getName();
1996         String childLocalName = fromEntity.getChildName();
1997         NamespaceEntity ns = fromEntity.getChildNamespace();
1998 
1999         boolean sameParent = oldParentUuid.equals(toUuidString);
2000 
2001         if (sameParent) {
2002             int oldIndex;
2003             if (fromEntity.getIndexInParent() < beforeEntity.getIndexInParent()) {
2004                 oldIndex = beforeEntity.getIndexInParent();
2005 
2006                 int snsCount = ChildEntity.adjustSnsIndexesAndIndexes(entities,
2007                                                                       workspaceId,
2008                                                                       fromEntity.getParentUuidString(),
2009                                                                       fromEntity.getIndexInParent(),
2010                                                                       beforeEntity.getIndexInParent(),
2011                                                                       fromEntity.getChildNamespace().getId(),
2012                                                                       fromEntity.getChildName(),
2013                                                                       -1);
2014 
2015                 fromEntity.setIndexInParent(oldIndex);
2016                 fromEntity.setSameNameSiblingIndex(fromEntity.getSameNameSiblingIndex() + snsCount);
2017             } else {
2018                 oldIndex = beforeEntity.getIndexInParent();
2019 
2020                 int snsCount = ChildEntity.adjustSnsIndexesAndIndexes(entities,
2021                                                                       workspaceId,
2022                                                                       fromEntity.getParentUuidString(),
2023                                                                       beforeEntity.getIndexInParent() - 1,
2024                                                                       fromEntity.getIndexInParent() - 1,
2025                                                                       fromEntity.getChildNamespace().getId(),
2026                                                                       fromEntity.getChildName(),
2027                                                                       +1);
2028 
2029                 fromEntity.setIndexInParent(oldIndex);
2030                 fromEntity.setSameNameSiblingIndex(fromEntity.getSameNameSiblingIndex() - snsCount);
2031 
2032             }
2033 
2034             Path newPath = pathFactory.create(oldPath.getParent(),
2035                                               pathFactory.createSegment(fromEntity.getChildName(),
2036                                                                         fromEntity.getSameNameSiblingIndex()));
2037             return actualFromLocation.location.with(newPath);
2038         }
2039 
2040         int oldIndex = fromEntity.getIndexInParent();
2041 
2042         /*
2043          * This is a sub-optimal approach, particularly for inserts to the front
2044          * of a long list of child nodes, but it guarantees that we won't have
2045          * the JPA-cached entities and the database out of sync.
2046          */
2047 
2048         Query query = entities.createNamedQuery("ChildEntity.findAllUnderParent");
2049         query.setParameter("workspaceId", workspaceId);
2050         query.setParameter("parentUuidString", toUuidString);
2051 
2052         int nextIndexInParent = 0;
2053         int nextSnsIndex = 1;
2054         try {
2055             List<ChildEntity> children = query.getResultList();
2056             Path beforePath = beforeLocation.getPath();
2057             Path.Segment beforeSegment = beforePath.getLastSegment();
2058 
2059             boolean foundBefore = false;
2060             for (ChildEntity child : children) {
2061                 NamespaceEntity namespace = child.getChildNamespace();
2062                 if (namespace.getUri().equals(ns.getUri()) && child.getChildName().equals(childLocalName)
2063                     && child.getSameNameSiblingIndex() == beforeSegment.getIndex()) {
2064                     foundBefore = true;
2065                     nextIndexInParent = child.getIndexInParent();
2066                     nextSnsIndex = beforeSegment.getIndex();
2067                 }
2068 
2069                 if (foundBefore) {
2070                     child.setIndexInParent(child.getIndexInParent() + 1);
2071                     if (child.getChildName().equals(childLocalName) && namespace.getUri().equals(ns.getUri())) {
2072                         child.setSameNameSiblingIndex(child.getSameNameSiblingIndex() + 1);
2073                     }
2074                     entities.persist(child);
2075                 }
2076             }
2077 
2078         } catch (NoResultException e) {
2079         }
2080 
2081         fromEntity.setParentUuidString(toUuidString);
2082         fromEntity.setChildName(childLocalName);
2083         fromEntity.setChildNamespace(ns);
2084         fromEntity.setIndexInParent(nextIndexInParent);
2085         fromEntity.setSameNameSiblingIndex(nextSnsIndex);
2086 
2087         // Flush the entities to the database ...
2088         entities.flush();
2089 
2090         // Determine the new location ...
2091         Path newParentPath = actualIntoLocation.location.getPath();
2092         Path newPath = pathFactory.create(newParentPath, childName, nextSnsIndex);
2093         Location oldLocation = actualFromLocation.location;
2094         Location newLocation = oldLocation.with(newPath);
2095 
2096         // And adjust the SNS index and indexes ...
2097         ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities, workspaceId, oldParentUuid, childLocalName, ns.getId(), -1);
2098 
2099         // Update the cache ...
2100         cache.moveNode(workspaceId, oldLocation, oldIndex, newLocation);
2101 
2102         return newLocation;
2103     }
2104 
2105     /**
2106      * {@inheritDoc}
2107      * 
2108      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.MoveBranchRequest)
2109      */
2110     @Override
2111     public void process( MoveBranchRequest request ) {
2112         logger.trace(request.toString());
2113         Location actualOldLocation = null;
2114         Location actualNewLocation = null;
2115         try {
2116             // Find the workspaces ...
2117             WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
2118             if (workspace == null) return;
2119             Long workspaceId = workspace.getId();
2120             assert workspaceId != null;
2121 
2122             Location fromLocation = request.from();
2123             ActualLocation actualLocation = getActualLocation(workspace, fromLocation);
2124             if (actualLocation.childEntity == null) {
2125                 actualLocation = new ActualLocation(actualLocation.location, actualLocation.uuid, findNode(workspaceId,
2126                                                                                                            actualLocation.uuid));
2127             }
2128 
2129             actualOldLocation = actualLocation.location;
2130             Path oldPath = actualOldLocation.getPath();
2131 
2132             // It's not possible to move the root node
2133             if (oldPath.isRoot()) {
2134                 String msg = JpaConnectorI18n.unableToMoveRootNode.text(getSourceName());
2135                 throw new InvalidRequestException(msg);
2136             }
2137 
2138             if (request.hasNoEffect()) {
2139                 actualNewLocation = actualOldLocation;
2140             } else {
2141                 // We have to proceed as normal ...
2142 
2143                 Location beforeLocation = request.before();
2144                 if (beforeLocation == null) {
2145                     ActualLocation actualIntoLocation = getActualLocation(workspace, request.into());
2146 
2147                     actualNewLocation = moveNodeToLastChild(workspace, actualLocation, actualIntoLocation, request.desiredName());
2148                 } else {
2149 
2150                     ActualLocation actualBeforeLocation = getActualLocation(workspace, beforeLocation);
2151                     ActualLocation actualIntoLocation = getActualLocation(workspace, Location.create(beforeLocation.getPath()
2152                                                                                                                    .getParent()));
2153 
2154                     actualNewLocation = moveNodeBefore(workspace, actualLocation, actualIntoLocation, actualBeforeLocation);
2155                 }
2156             }
2157         } catch (Throwable e) { // Includes PathNotFoundException
2158         // System.err.flush();
2159         // System.out.flush();
2160         // e.printStackTrace();
2161         // System.err.flush();
2162             request.setError(e);
2163             return;
2164         }
2165         request.setActualLocations(actualOldLocation, actualNewLocation);
2166         recordChange(request);
2167     }
2168 
2169     /**
2170      * {@inheritDoc}
2171      * 
2172      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.VerifyWorkspaceRequest)
2173      */
2174     @Override
2175     public void process( VerifyWorkspaceRequest request ) {
2176         // Find the workspace ...
2177         String workspaceName = request.workspaceName();
2178         if (workspaceName == null) workspaceName = nameOfDefaultWorkspace;
2179         WorkspaceEntity workspace = getExistingWorkspace(workspaceName, request);
2180         if (workspace != null) {
2181             Long workspaceId = workspace.getId();
2182             assert workspaceId != null;
2183             ActualLocation actual = getActualLocation(workspace, Location.create(pathFactory.createRootPath()));
2184             request.setActualRootLocation(actual.location);
2185             request.setActualWorkspaceName(workspace.getName());
2186         }
2187     }
2188 
2189     /**
2190      * {@inheritDoc}
2191      * 
2192      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.GetWorkspacesRequest)
2193      */
2194     @Override
2195     public void process( GetWorkspacesRequest request ) {
2196         // Return the set of available workspace names, even if new workspaces can be created ...
2197         Set<String> names = workspaces.getWorkspaceNames();
2198         // Add in the names of the predefined workspaces (in case they weren't yet accessed) ...
2199         for (String name : this.predefinedWorkspaceNames) {
2200             names.add(name);
2201         }
2202         request.setAvailableWorkspaceNames(Collections.unmodifiableSet(names));
2203         setCacheableInfo(request);
2204     }
2205 
2206     /**
2207      * {@inheritDoc}
2208      * 
2209      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateWorkspaceRequest)
2210      */
2211     @Override
2212     public void process( CreateWorkspaceRequest request ) {
2213         String name = request.desiredNameOfNewWorkspace();
2214         if (!creatingWorkspacesAllowed) {
2215             String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName());
2216             request.setError(new InvalidRequestException(msg));
2217             return;
2218         }
2219         Set<String> existingNames = workspaces.getWorkspaceNames();
2220         int counter = 0;
2221         while (existingNames.contains(name)) {
2222             switch (request.conflictBehavior()) {
2223                 case CREATE_WITH_ADJUSTED_NAME:
2224                     name = request.desiredNameOfNewWorkspace() + ++counter;
2225                     break;
2226                 case DO_NOT_CREATE:
2227                 default:
2228                     String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name);
2229                     request.setError(new InvalidWorkspaceException(msg));
2230                     return;
2231             }
2232         }
2233         // Create the workspace ...
2234         WorkspaceEntity entity = workspaces.create(name);
2235         request.setActualWorkspaceName(entity.getName());
2236         // Create the root node ...
2237         Location root = Location.create(pathFactory.createRootPath());
2238         request.setActualRootLocation(getActualLocation(entity, root).location);
2239         recordChange(request);
2240     }
2241 
2242     /**
2243      * {@inheritDoc}
2244      * 
2245      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneWorkspaceRequest)
2246      */
2247     @SuppressWarnings( "unchecked" )
2248     @Override
2249     public void process( CloneWorkspaceRequest request ) {
2250         if (!creatingWorkspacesAllowed) {
2251             String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName());
2252             request.setError(new InvalidRequestException(msg));
2253             return;
2254         }
2255         Set<String> existingNames = workspaces.getWorkspaceNames();
2256         String name = request.desiredNameOfTargetWorkspace();
2257         int counter = 0;
2258         while (existingNames.contains(name)) {
2259             switch (request.targetConflictBehavior()) {
2260                 case CREATE_WITH_ADJUSTED_NAME:
2261                     name = request.desiredNameOfTargetWorkspace() + ++counter;
2262                     break;
2263                 case DO_NOT_CREATE:
2264                 default:
2265                     String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name);
2266                     request.setError(new InvalidWorkspaceException(msg));
2267                     return;
2268             }
2269         }
2270         String fromWorkspaceName = request.nameOfWorkspaceToBeCloned();
2271         WorkspaceEntity fromWorkspace = workspaces.get(fromWorkspaceName, false);
2272         if (fromWorkspace == null) {
2273             switch (request.cloneConflictBehavior()) {
2274                 case SKIP_CLONE:
2275                     break;
2276                 case DO_NOT_CLONE:
2277                 default:
2278                     String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), fromWorkspaceName);
2279                     request.setError(new InvalidRequestException(msg));
2280                     return;
2281             }
2282         }
2283 
2284         // Create the workspace ...
2285         WorkspaceEntity intoWorkspace = workspaces.create(name);
2286         String newWorkspaceName = intoWorkspace.getName();
2287         request.setActualWorkspaceName(newWorkspaceName);
2288 
2289         if (fromWorkspace != null) {
2290             // Copy the workspace into the new workspace, via bulk insert statements ..
2291             Long fromWorkspaceId = fromWorkspace.getId();
2292             Long intoWorkspaceId = intoWorkspace.getId();
2293             Query query = entities.createNamedQuery("ChildEntity.findInWorkspace");
2294             query.setParameter("workspaceId", fromWorkspaceId);
2295             List<ChildEntity> childEntities = query.getResultList();
2296             for (ChildEntity child : childEntities) {
2297                 ChildId origId = child.getId();
2298                 ChildId copyId = new ChildId(intoWorkspaceId, origId.getChildUuidString());
2299                 ChildEntity copy = new ChildEntity(copyId, child.getParentUuidString(), child.getIndexInParent(),
2300                                                    child.getChildNamespace(), child.getChildName());
2301                 copy.setAllowsSameNameChildren(child.getAllowsSameNameChildren());
2302                 copy.setSameNameSiblingIndex(child.getSameNameSiblingIndex());
2303                 entities.persist(copy);
2304             }
2305             entities.flush();
2306 
2307             query = entities.createNamedQuery("PropertiesEntity.findInWorkspace");
2308             query.setParameter("workspaceId", fromWorkspaceId);
2309             List<PropertiesEntity> properties = query.getResultList();
2310             for (PropertiesEntity property : properties) {
2311                 NodeId copyId = new NodeId(intoWorkspaceId, property.getId().getUuidString());
2312                 PropertiesEntity copy = new PropertiesEntity(copyId);
2313                 copy.setCompressed(property.isCompressed());
2314                 copy.setData(property.getData());
2315                 copy.setPropertyCount(property.getPropertyCount());
2316                 copy.setReferentialIntegrityEnforced(property.isReferentialIntegrityEnforced());
2317                 Collection<LargeValueId> ids = property.getLargeValues();
2318                 if (ids.size() != 0) {
2319                     copy.getLargeValues().addAll(ids);
2320                 }
2321                 entities.persist(copy);
2322             }
2323             entities.flush();
2324 
2325             query = entities.createNamedQuery("ReferenceEntity.findInWorkspace");
2326             query.setParameter("workspaceId", fromWorkspaceId);
2327             List<ReferenceEntity> references = query.getResultList();
2328             for (ReferenceEntity reference : references) {
2329                 ReferenceId from = reference.getId();
2330                 ReferenceId copy = new ReferenceId(fromWorkspaceId, from.getFromUuidString(), from.getToUuidString());
2331                 entities.persist(new ReferenceEntity(copy));
2332             }
2333             entities.flush();
2334         }
2335 
2336         // Finish up the request ...
2337         Location root = Location.create(pathFactory.createRootPath(), rootNodeUuid);
2338         request.setActualRootLocation(getActualLocation(intoWorkspace, root).location);
2339         recordChange(request);
2340     }
2341 
2342     /**
2343      * {@inheritDoc}
2344      * 
2345      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DestroyWorkspaceRequest)
2346      */
2347     @Override
2348     public void process( DestroyWorkspaceRequest request ) {
2349         // Verify the workspace exists ...
2350         WorkspaceEntity workspace = getExistingWorkspace(request.workspaceName(), request);
2351         if (workspace == null) return;
2352         Long workspaceId = workspace.getId();
2353         assert workspaceId != null;
2354 
2355         // Get the actual location of the root node ...
2356         ActualLocation actual = getActualLocation(workspace, Location.create(pathFactory.createRootPath()));
2357 
2358         // Delete the workspace ...
2359         workspaces.destroy(workspace.getName());
2360 
2361         // Delete all the entities from this workspace ...
2362         Query delete = entities.createQuery("delete PropertiesEntity entity where entity.id.workspaceId = :workspaceId");
2363         delete.setParameter("workspaceId", workspaceId);
2364         delete.executeUpdate();
2365 
2366         delete = entities.createQuery("delete ChildEntity entity where entity.id.workspaceId = :workspaceId");
2367         delete.setParameter("workspaceId", workspaceId);
2368         delete.executeUpdate();
2369 
2370         delete = entities.createQuery("delete ReferenceEntity entity where entity.id.workspaceId = :workspaceId");
2371         delete.setParameter("workspaceId", workspaceId);
2372         delete.executeUpdate();
2373 
2374         // Delete unused large values ...
2375         LargeValueEntity.deleteUnused(entities);
2376 
2377         // Finish the request ...
2378         request.setActualRootLocation(actual.location);
2379         recordChange(request);
2380     }
2381 
2382     /**
2383      * {@inheritDoc}
2384      * 
2385      * @throws ReferentialIntegrityException if the integrity of the references has been compromised
2386      * @see org.modeshape.graph.request.processor.RequestProcessor#close()
2387      */
2388     @Override
2389     public void close() {
2390         // Verify that the references are valid so far ...
2391         verifyReferences();
2392 
2393         // Close any resources used by the superclass ...
2394         super.close();
2395     }
2396 
2397     protected WorkspaceEntity getExistingWorkspace( String workspaceName,
2398                                                     Request request ) {
2399         WorkspaceEntity workspace = workspaces.get(workspaceName, false);
2400         if (workspace == null) {
2401             // Is this a predefined workspace?
2402             for (String name : predefinedWorkspaceNames) {
2403                 if (workspaceName.equals(name)) {
2404                     // Create it anyway ...
2405                     return workspaces.create(workspaceName);
2406                 }
2407             }
2408             String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName);
2409             request.setError(new InvalidWorkspaceException(msg));
2410         }
2411         return workspace;
2412     }
2413 
2414     /**
2415      * {@link ReferenceEntity Reference entities} are added and removed in the appropriate <code>process(...)</code> methods.
2416      * However, this method is typically called in {@link BasicRequestProcessor#close()} and performs the following steps:
2417      * <ol>
2418      * <li>Remove all references that have a "from" node that is under the versions branch.</li>
2419      * <li>Verify that all remaining references have a valid and existing "to" node</li>
2420      * </ol>
2421      * 
2422      * @throws ReferentialIntegrityException if the integrity of the references has been compromised
2423      */
2424     protected void verifyReferences() throws ReferentialIntegrityException {
2425         if (!enforceReferentialIntegrity) return;
2426         if (!workspaceIdsWithChangedReferences.isEmpty()) {
2427 
2428             Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
2429             for (Long workspaceId : workspaceIdsWithChangedReferences) {
2430 
2431                 // Remove all references that have a "from" node that doesn't support referential integrity ...
2432                 ReferenceEntity.deleteUnenforcedReferences(workspaceId, entities);
2433 
2434                 // Verify that all references are resolved to existing nodes ...
2435                 int numUnresolved = ReferenceEntity.countAllReferencesResolved(workspaceId, entities);
2436                 if (numUnresolved != 0) {
2437                     List<ReferenceEntity> references = ReferenceEntity.verifyAllReferencesResolved(workspaceId, entities);
2438                     ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
2439                     for (ReferenceEntity entity : references) {
2440                         ReferenceId id = entity.getId();
2441                         UUID fromUuid = UUID.fromString(id.getFromUuidString());
2442                         Location location = Location.create(fromUuid);
2443                         WorkspaceEntity dummyWorkspaceReference = new WorkspaceEntity();
2444                         dummyWorkspaceReference.setId(id.getWorkspaceId());
2445                         dummyWorkspaceReference.setName("<unknown>");
2446                         location = getActualLocation(dummyWorkspaceReference, location).location;
2447                         List<Reference> refs = invalidRefs.get(location);
2448                         if (refs == null) {
2449                             refs = new ArrayList<Reference>();
2450                             invalidRefs.put(location, refs);
2451                         }
2452                         UUID toUuid = UUID.fromString(id.getToUuidString());
2453                         refs.add(refFactory.create(toUuid));
2454                     }
2455                 }
2456             }
2457 
2458             workspaceIdsWithChangedReferences.clear();
2459 
2460             if (!invalidRefs.isEmpty()) {
2461                 String msg = JpaConnectorI18n.invalidReferences.text(getSourceName());
2462                 throw new ReferentialIntegrityException(invalidRefs, msg);
2463             }
2464         }
2465     }
2466 
2467     protected String createProperties( WorkspaceEntity workspace,
2468                                        String uuidString,
2469                                        Collection<Property> properties ) throws IOException {
2470         assert uuidString != null;
2471 
2472         // Create the PropertiesEntity ...
2473         NodeId nodeId = new NodeId(workspace.getId(), uuidString);
2474         PropertiesEntity props = new PropertiesEntity(nodeId);
2475 
2476         // If there are properties ...
2477         boolean processProperties = true;
2478         if (properties.isEmpty()) processProperties = false;
2479         else if (properties.size() == 1 && properties.iterator().next().getName().equals(JcrLexicon.NAME)) processProperties = false;
2480 
2481         if (processProperties) {
2482             References refs = enforceReferentialIntegrity ? new References() : null;
2483             LargeValueSerializer largeValues = new LargeValueSerializer(props);
2484             ByteArrayOutputStream baos = new ByteArrayOutputStream();
2485             OutputStream os = compressData ? new GZIPOutputStream(baos) : baos;
2486             ObjectOutputStream oos = new ObjectOutputStream(os);
2487             int numProperties = properties.size();
2488             try {
2489                 Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES;
2490                 serializer.serializeProperties(oos, numProperties, properties, largeValues, refValues);
2491             } finally {
2492                 oos.close();
2493             }
2494 
2495             props.setData(baos.toByteArray());
2496             props.setPropertyCount(numProperties);
2497 
2498             // Record the changes to the references ...
2499             if (refs != null && refs.hasWritten()) {
2500                 for (Reference reference : refs.getWritten()) {
2501                     String toUuid = resolveToUuid(workspace, reference);
2502                     if (toUuid != null) {
2503                         ReferenceId id = new ReferenceId(workspace.getId(), uuidString, toUuid);
2504                         ReferenceEntity refEntity = new ReferenceEntity(id);
2505                         entities.persist(refEntity);
2506                         workspaceIdsWithChangedReferences.add(workspace.getId());
2507                     }
2508                 }
2509             }
2510         } else {
2511             props.setData(null);
2512             props.setPropertyCount(0);
2513         }
2514         props.setCompressed(compressData);
2515         props.setReferentialIntegrityEnforced(true);
2516 
2517         entities.persist(props);
2518 
2519         // References will be persisted in the commit ...
2520         return uuidString;
2521     }
2522 
2523     /**
2524      * Attempt to resolve the reference.
2525      * 
2526      * @param workspace the workspace in which the reference occurs; may not be null
2527      * @param reference the reference
2528      * @return the UUID of the node to which the reference points, or null if the reference could not be resolved
2529      */
2530     protected String resolveToUuid( WorkspaceEntity workspace,
2531                                     Reference reference ) {
2532         // See if the reference is by UUID ...
2533         try {
2534             UUID uuid = uuidFactory.create(reference);
2535             ActualLocation actualLocation = getActualLocation(workspace, Location.create(uuid));
2536             return actualLocation.uuid;
2537         } catch (ValueFormatException e) {
2538             // Unknown kind of reference, which we don't track
2539         } catch (PathNotFoundException e) {
2540             // Unable to resolve reference ...
2541         }
2542         // Unable to resolve reference ...
2543         return null;
2544     }
2545 
2546     /**
2547      * Utility method to look up the actual information given a supplied location. This method verifies that the location actually
2548      * represents an existing node, or it throws a {@link PathNotFoundException}. In all cases, the resulting information contains
2549      * the correct path and the correct UUID.
2550      * <p>
2551      * Note that this method sometimes performs "unnecessary" work when the location contains both a path to a node and the node's
2552      * corresponding UUID. Strictly speaking, this method would need to do very little. However, in such cases, this method does
2553      * verify that the information is still correct (ensuring that calls to use the {@link ChildEntity} will be correct). So,
2554      * while this work <i>may</i> be unnecessary, it does ensure that the location is consistent and correct (something that is
2555      * not unnecessary).
2556      * </p>
2557      * <p>
2558      * There are cases when a request containing a Path and a UUID are no longer correct. The node may have been just moved by
2559      * another request (perhaps from a different client), or there may be an error in the component making the request. In these
2560      * cases, this method assumes that the path is incorrect (since paths may change) and finds the <i>correct path</i> given the
2561      * UUID.
2562      * </p>
2563      * <p>
2564      * This method will also find the path when the location contains just the UUID.
2565      * </p>
2566      * 
2567      * @param workspace the workspace; may not be null
2568      * @param original the original location; may not be null
2569      * @return the actual location, which includes the verified location and additional information needed by this method that may
2570      *         be usable after this method is called
2571      * @throws PathNotFoundException if the location does not represent a location that could be found
2572      */
2573     protected ActualLocation getActualLocation( WorkspaceEntity workspace,
2574                                                 Location original ) throws PathNotFoundException {
2575         assert original != null;
2576 
2577         long workspaceId = workspace.getId();
2578 
2579         // Look for the UUID in the original ...
2580         Property uuidProperty = original.getIdProperty(ModeShapeLexicon.UUID);
2581         String uuidString = uuidProperty != null && !uuidProperty.isEmpty() ? stringFactory.create(uuidProperty.getFirstValue()) : null;
2582 
2583         Path path = original.getPath();
2584         if (path != null) {
2585             // See if the location is already in the cache ...
2586             Location cached = cache.getLocationFor(workspaceId, path);
2587             if (cached != null) {
2588                 return new ActualLocation(original, cached.getUuid().toString(), null);
2589             }
2590         }
2591 
2592         // If the original location has a UUID, then use that to find the child entity that represents the location ...
2593         if (uuidString != null) {
2594             // The original has a UUID, so use that to find the child entity.
2595             // Then walk up the ancestors and build the path.
2596             String nodeUuidString = uuidString;
2597             LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>();
2598             ChildEntity entity = null;
2599             ChildEntity originalEntity = null;
2600             while (uuidString != null && !uuidString.equals(this.rootNodeUuidString)) {
2601                 Query query = entities.createNamedQuery("ChildEntity.findByChildUuid");
2602                 query.setParameter("workspaceId", workspaceId);
2603                 query.setParameter("childUuidString", uuidString);
2604                 try {
2605                     // Find the parent of the UUID ...
2606                     entity = (ChildEntity)query.getSingleResult();
2607                     if (originalEntity == null) originalEntity = entity;
2608                     String localName = entity.getChildName();
2609                     String uri = entity.getChildNamespace().getUri();
2610                     int sns = entity.getSameNameSiblingIndex();
2611                     Name name = nameFactory.create(uri, localName);
2612                     segments.addFirst(pathFactory.createSegment(name, sns));
2613                     uuidString = entity.getParentUuidString();
2614                 } catch (NoResultException e) {
2615                     if (!uuidString.equals(this.rootNodeUuidString)) {
2616                         String workspaceName = workspace.getName();
2617                         String msg = JpaConnectorI18n.invalidUuidForWorkspace.text(uuidString, workspaceName);
2618                         throw new PathNotFoundException(original, pathFactory.createRootPath(), msg);
2619                     }
2620                 }
2621             }
2622             Path fullPath = pathFactory.createAbsolutePath(segments);
2623             Location newLocation = original.with(fullPath);
2624             cache.addNewNode(workspaceId, newLocation);
2625             return new ActualLocation(newLocation, nodeUuidString, originalEntity);
2626         }
2627 
2628         // There is no UUID, so look for a path ...
2629         if (path == null) {
2630             String propName = ModeShapeLexicon.UUID.getString(getExecutionContext().getNamespaceRegistry());
2631             String msg = JpaConnectorI18n.locationShouldHavePathAndOrProperty.text(getSourceName(), propName);
2632             throw new PathNotFoundException(original, pathFactory.createRootPath(), msg);
2633         }
2634 
2635         // Walk the child entities, starting at the root, down the to the path ...
2636         if (path.isRoot()) {
2637             Location newLocation = original.with(rootNodeUuid);
2638             cache.addNewNode(workspaceId, newLocation);
2639             return new ActualLocation(newLocation, rootNodeUuidString, null);
2640         }
2641         // See if the parent location is known in the cache ...
2642         Location cachedParent = cache.getLocationFor(workspaceId, path.getParent());
2643         if (cachedParent != null) {
2644             // We know the UUID of the parent, so we can find the child a little faster ...
2645             ChildEntity child = findByPathSegment(workspaceId, cachedParent.getUuid().toString(), path.getLastSegment());
2646 
2647             // If there is no matching child, throw an exception
2648             if (child == null) {
2649                 // Could not find the node given the supplied path, so find the lowest path that does exist ...
2650                 throw new PathNotFoundException(original, cachedParent.getPath(), JpaConnectorI18n.nodeDoesNotExist.text(path));
2651             }
2652 
2653             uuidString = child.getId().getChildUuidString();
2654             Location newLocation = original.with(UUID.fromString(uuidString));
2655             cache.addNewNode(workspaceId, newLocation);
2656             return new ActualLocation(original, uuidString, child);
2657         }
2658 
2659         // We couldn't find the parent, so we need to search by path ...
2660         String parentUuid = this.rootNodeUuidString;
2661         ChildEntity child = null;
2662         for (Path.Segment segment : path) {
2663             child = findByPathSegment(workspaceId, parentUuid, segment);
2664             if (child == null) {
2665                 // Unable to complete the path, so prepare the exception by determining the lowest path that exists ...
2666                 Path lowest = path;
2667                 while (lowest.getLastSegment() != segment) {
2668                     lowest = lowest.getParent();
2669                 }
2670                 lowest = lowest.getParent();
2671                 throw new PathNotFoundException(original, lowest);
2672             }
2673             parentUuid = child.getId().getChildUuidString();
2674         }
2675         assert child != null;
2676         uuidString = child.getId().getChildUuidString();
2677         Location newLocation = original.with(UUID.fromString(uuidString));
2678         cache.addNewNode(workspaceId, newLocation);
2679         return new ActualLocation(newLocation, uuidString, child);
2680     }
2681 
2682     protected ChildEntity findNode( long workspaceId,
2683                                     String uuidString ) {
2684         Query query = entities.createNamedQuery("ChildEntity.findByChildUuid");
2685         query.setParameter("workspaceId", workspaceId);
2686         query.setParameter("childUuidString", uuidString);
2687         try {
2688             // Find the parent of the UUID ...
2689             return (ChildEntity)query.getSingleResult();
2690         } catch (NoResultException e) {
2691             return null;
2692         }
2693     }
2694 
2695     /**
2696      * Find the node with the supplied path segment that is a child of the supplied parent.
2697      * 
2698      * @param workspaceId the ID of the workspace
2699      * @param parentUuid the UUID of the parent node, in string form
2700      * @param pathSegment the path segment of the child
2701      * @return the existing namespace, or null if one does not exist
2702      * @throws IllegalArgumentException if the manager or URI are null
2703      */
2704     protected ChildEntity findByPathSegment( Long workspaceId,
2705                                              String parentUuid,
2706                                              Path.Segment pathSegment ) {
2707         assert namespaces != null;
2708         assert parentUuid != null;
2709         assert pathSegment != null;
2710         assert workspaceId != null;
2711         Name name = pathSegment.getName();
2712         String localName = name.getLocalName();
2713         String nsUri = name.getNamespaceUri();
2714         NamespaceEntity ns = namespaces.get(nsUri, false);
2715         if (ns == null) {
2716             // The namespace can't be found, then certainly the node won't be found ...
2717             return null;
2718         }
2719         int snsIndex = pathSegment.hasIndex() ? pathSegment.getIndex() : 1;
2720         Query query = entities.createNamedQuery("ChildEntity.findByPathSegment");
2721         query.setParameter("workspaceId", workspaceId);
2722         query.setParameter("parentUuidString", parentUuid);
2723         query.setParameter("ns", ns.getId());
2724         query.setParameter("childName", localName);
2725         query.setParameter("sns", snsIndex);
2726         try {
2727             return (ChildEntity)query.getSingleResult();
2728         } catch (NoResultException e) {
2729             return null;
2730         }
2731     }
2732 
2733     protected String createHexValuesString( Collection<String> hexValues ) {
2734         if (hexValues == null || hexValues.isEmpty()) return null;
2735         StringBuilder sb = new StringBuilder();
2736         boolean first = true;
2737         for (String hexValue : hexValues) {
2738             if (first) {
2739                 first = false;
2740             } else {
2741                 sb.append(',');
2742             }
2743             sb.append(hexValue);
2744         }
2745         return sb.toString();
2746     }
2747 
2748     protected Collection<String> createHexValues( String hexValuesString ) {
2749         return Arrays.asList(hexValuesString.split(","));
2750     }
2751 
2752     protected class LargeValueSerializer implements LargeValues {
2753         private final PropertiesEntity properties;
2754         private final Set<String> written;
2755 
2756         public LargeValueSerializer( PropertiesEntity entity ) {
2757             this.properties = entity;
2758             this.written = null;
2759         }
2760 
2761         public LargeValueSerializer( PropertiesEntity entity,
2762                                      Set<String> written ) {
2763             this.properties = entity;
2764             this.written = written;
2765         }
2766 
2767         /**
2768          * {@inheritDoc}
2769          * 
2770          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
2771          */
2772         public long getMinimumSize() {
2773             return largeValueMinimumSizeInBytes;
2774         }
2775 
2776         /**
2777          * {@inheritDoc}
2778          * 
2779          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#read(org.modeshape.graph.property.ValueFactories,
2780          *      byte[], long)
2781          */
2782         public Object read( ValueFactories valueFactories,
2783                             byte[] hash,
2784                             long length ) throws IOException {
2785             String hashStr = StringUtil.getHexString(hash);
2786             // Find the large value ...
2787             LargeValueId largeValueId = new LargeValueId(hashStr);
2788             LargeValueEntity entity = entities.find(LargeValueEntity.class, largeValueId);
2789             if (entity != null) {
2790                 // Find the large value from the existing property entity ...
2791                 byte[] data = entity.getData();
2792                 if (entity.isCompressed()) {
2793                     InputStream stream = new GZIPInputStream(new ByteArrayInputStream(data));
2794                     try {
2795                         data = IoUtil.readBytes(stream);
2796                     } finally {
2797                         stream.close();
2798                     }
2799                 }
2800                 return valueFactories.getValueFactory(entity.getType()).create(data);
2801             }
2802             throw new IOException(JpaConnectorI18n.unableToReadLargeValue.text(getSourceName(), hashStr));
2803         }
2804 
2805         /**
2806          * {@inheritDoc}
2807          * 
2808          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#write(byte[], long,
2809          *      org.modeshape.graph.property.PropertyType, java.lang.Object)
2810          */
2811         public void write( byte[] hash,
2812                            long length,
2813                            PropertyType type,
2814                            Object value ) throws IOException {
2815             if (value == null) return;
2816             String hashStr = StringUtil.getHexString(hash);
2817             if (written != null) written.add(hashStr);
2818 
2819             // Look for an existing value in the collection ...
2820             final LargeValueId id = new LargeValueId(hashStr);
2821             for (LargeValueId existing : properties.getLargeValues()) {
2822                 if (existing.equals(id)) {
2823                     // Already associated with this properties entity
2824                     return;
2825                 }
2826             }
2827             LargeValueEntity entity = entities.find(LargeValueEntity.class, id);
2828             if (entity == null) {
2829                 // We have to create the large value entity ...
2830                 entity = new LargeValueEntity();
2831                 entity.setCompressed(true);
2832                 entity.setId(id);
2833                 entity.setLength(length);
2834                 entity.setType(type);
2835                 ValueFactories factories = getExecutionContext().getValueFactories();
2836                 byte[] bytes = null;
2837                 switch (type) {
2838                     case BINARY:
2839                         Binary binary = factories.getBinaryFactory().create(value);
2840                         InputStream stream = null;
2841                         try {
2842                             binary.acquire();
2843                             stream = binary.getStream();
2844                             if (compressData) stream = new GZIPInputStream(stream);
2845                             bytes = IoUtil.readBytes(stream);
2846                         } finally {
2847                             try {
2848                                 if (stream != null) stream.close();
2849                             } finally {
2850                                 binary.release();
2851                             }
2852                         }
2853                         break;
2854                     case URI:
2855                         // This will be treated as a string ...
2856                     default:
2857                         String str = factories.getStringFactory().create(value);
2858                         if (compressData) {
2859                             ByteArrayOutputStream bs = new ByteArrayOutputStream();
2860                             OutputStream strStream = new GZIPOutputStream(bs);
2861                             try {
2862                                 IoUtil.write(str, strStream);
2863                             } finally {
2864                                 strStream.close();
2865                             }
2866                             bytes = bs.toByteArray();
2867                         } else {
2868                             bytes = str.getBytes();
2869                         }
2870                         break;
2871                 }
2872                 entity.setData(bytes);
2873                 entities.persist(entity);
2874             }
2875             // Now associate the large value with the properties entity ...
2876             assert id.getHash() != null;
2877             properties.getLargeValues().add(id);
2878         }
2879 
2880     }
2881 
2882     protected class RecordingLargeValues implements LargeValues {
2883         protected final Collection<String> readKeys = new HashSet<String>();
2884         protected final Collection<String> writtenKeys = new HashSet<String>();
2885         protected final LargeValues delegate;
2886 
2887         RecordingLargeValues( LargeValues delegate ) {
2888             assert delegate != null;
2889             this.delegate = delegate;
2890         }
2891 
2892         /**
2893          * {@inheritDoc}
2894          * 
2895          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
2896          */
2897         public long getMinimumSize() {
2898             return delegate.getMinimumSize();
2899         }
2900 
2901         /**
2902          * {@inheritDoc}
2903          * 
2904          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#read(org.modeshape.graph.property.ValueFactories,
2905          *      byte[], long)
2906          */
2907         public Object read( ValueFactories valueFactories,
2908                             byte[] hash,
2909                             long length ) throws IOException {
2910             String key = StringUtil.getHexString(hash);
2911             readKeys.add(key);
2912             return delegate.read(valueFactories, hash, length);
2913         }
2914 
2915         public void write( byte[] hash,
2916                            long length,
2917                            PropertyType type,
2918                            Object value ) throws IOException {
2919             String key = StringUtil.getHexString(hash);
2920             writtenKeys.add(key);
2921             delegate.write(hash, length, type, value);
2922         }
2923     }
2924 
2925     protected class SkippedLargeValues implements LargeValues {
2926         protected Collection<String> skippedKeys = new HashSet<String>();
2927         protected final LargeValues delegate;
2928 
2929         SkippedLargeValues( LargeValues delegate ) {
2930             assert delegate != null;
2931             this.delegate = delegate;
2932         }
2933 
2934         /**
2935          * {@inheritDoc}
2936          * 
2937          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
2938          */
2939         public long getMinimumSize() {
2940             return delegate.getMinimumSize();
2941         }
2942 
2943         /**
2944          * {@inheritDoc}
2945          * 
2946          * @see org.modeshape.connector.store.jpa.util.Serializer.LargeValues#read(org.modeshape.graph.property.ValueFactories,
2947          *      byte[], long)
2948          */
2949         public Object read( ValueFactories valueFactories,
2950                             byte[] hash,
2951                             long length ) throws IOException {
2952             String key = StringUtil.getHexString(hash);
2953             skippedKeys.add(key);
2954             return null;
2955         }
2956 
2957         public void write( byte[] hash,
2958                            long length,
2959                            PropertyType type,
2960                            Object value ) {
2961             throw new UnsupportedOperationException();
2962         }
2963     }
2964 
2965     @Immutable
2966     protected static class ActualLocation {
2967         /** The actual location */
2968         protected final Location location;
2969         /** The string-form of the UUID, supplied as a convenience. */
2970         protected final String uuid;
2971         /** The ChildEntity that represents the location, which may be null if the location represents the root node */
2972         protected final ChildEntity childEntity;
2973 
2974         protected ActualLocation( Location location,
2975                                   String uuid,
2976                                   ChildEntity childEntity ) {
2977             assert location != null;
2978             assert uuid != null;
2979             this.location = location;
2980             this.uuid = uuid;
2981             this.childEntity = childEntity;
2982         }
2983 
2984         /**
2985          * {@inheritDoc}
2986          * 
2987          * @see java.lang.Object#toString()
2988          */
2989         @Override
2990         public String toString() {
2991             return this.location.toString() + " (uuid=" + uuid + ") " + childEntity;
2992         }
2993     }
2994 
2995     protected class References implements Serializer.ReferenceValues {
2996         private Set<Reference> read;
2997         private Set<Reference> removed;
2998         private Set<Reference> written;
2999 
3000         protected References() {
3001         }
3002 
3003         /**
3004          * {@inheritDoc}
3005          * 
3006          * @see org.modeshape.connector.store.jpa.util.Serializer.ReferenceValues#read(org.modeshape.graph.property.Reference)
3007          */
3008         public void read( Reference reference ) {
3009             if (read == null) read = new HashSet<Reference>();
3010             read.add(reference);
3011         }
3012 
3013         /**
3014          * {@inheritDoc}
3015          * 
3016          * @see org.modeshape.connector.store.jpa.util.Serializer.ReferenceValues#remove(org.modeshape.graph.property.Reference)
3017          */
3018         public void remove( Reference reference ) {
3019             if (removed == null) removed = new HashSet<Reference>();
3020             removed.add(reference);
3021         }
3022 
3023         /**
3024          * {@inheritDoc}
3025          * 
3026          * @see org.modeshape.connector.store.jpa.util.Serializer.ReferenceValues#write(org.modeshape.graph.property.Reference)
3027          */
3028         public void write( Reference reference ) {
3029             if (written == null) written = new HashSet<Reference>();
3030             written.add(reference);
3031         }
3032 
3033         public boolean hasRead() {
3034             return read != null;
3035         }
3036 
3037         public boolean hasRemoved() {
3038             return removed != null;
3039         }
3040 
3041         public boolean hasWritten() {
3042             return written != null;
3043         }
3044 
3045         /**
3046          * @return read
3047          */
3048         public Set<Reference> getRead() {
3049             if (read != null) return read;
3050             return Collections.emptySet();
3051         }
3052 
3053         /**
3054          * @return removed
3055          */
3056         public Set<Reference> getRemoved() {
3057             if (removed != null) return removed;
3058             return Collections.emptySet();
3059         }
3060 
3061         /**
3062          * @return written
3063          */
3064         public Set<Reference> getWritten() {
3065             if (written != null) return written;
3066             return Collections.emptySet();
3067         }
3068     }
3069 }