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    * Unless otherwise indicated, all code in ModeShape is licensed
10   * 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.jcr;
25  
26  import java.lang.ref.SoftReference;
27  import java.security.AccessControlException;
28  import java.util.ArrayList;
29  import java.util.Calendar;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.UUID;
39  import javax.jcr.AccessDeniedException;
40  import javax.jcr.InvalidItemStateException;
41  import javax.jcr.Item;
42  import javax.jcr.ItemExistsException;
43  import javax.jcr.ItemNotFoundException;
44  import javax.jcr.NoSuchWorkspaceException;
45  import javax.jcr.PathNotFoundException;
46  import javax.jcr.PropertyType;
47  import javax.jcr.RepositoryException;
48  import javax.jcr.Session;
49  import javax.jcr.Value;
50  import javax.jcr.nodetype.ConstraintViolationException;
51  import javax.jcr.nodetype.NoSuchNodeTypeException;
52  import javax.jcr.nodetype.NodeType;
53  import javax.jcr.nodetype.PropertyDefinition;
54  import javax.jcr.version.VersionException;
55  import net.jcip.annotations.Immutable;
56  import net.jcip.annotations.ThreadSafe;
57  import org.modeshape.common.i18n.I18n;
58  import org.modeshape.common.util.Logger;
59  import org.modeshape.graph.ExecutionContext;
60  import org.modeshape.graph.Graph;
61  import org.modeshape.graph.Location;
62  import org.modeshape.graph.connector.RepositorySourceException;
63  import org.modeshape.graph.property.Binary;
64  import org.modeshape.graph.property.BinaryFactory;
65  import org.modeshape.graph.property.DateTime;
66  import org.modeshape.graph.property.Name;
67  import org.modeshape.graph.property.NameFactory;
68  import org.modeshape.graph.property.NamespaceRegistry;
69  import org.modeshape.graph.property.Path;
70  import org.modeshape.graph.property.PathFactory;
71  import org.modeshape.graph.property.Property;
72  import org.modeshape.graph.property.PropertyFactory;
73  import org.modeshape.graph.property.ValueFactories;
74  import org.modeshape.graph.property.ValueFactory;
75  import org.modeshape.graph.property.ValueFormatException;
76  import org.modeshape.graph.request.InvalidWorkspaceException;
77  import org.modeshape.graph.session.GraphSession;
78  import org.modeshape.graph.session.InvalidStateException;
79  import org.modeshape.graph.session.ValidationException;
80  import org.modeshape.graph.session.GraphSession.Node;
81  import org.modeshape.graph.session.GraphSession.NodeId;
82  import org.modeshape.graph.session.GraphSession.PropertyInfo;
83  import org.modeshape.graph.session.GraphSession.Status;
84  import org.modeshape.jcr.JcrRepository.Option;
85  
86  /**
87   * The class that manages the session's information that has been locally-cached after reading from the underlying {@link Graph
88   * repository} or modified by the session but not yet saved or commited to the repository.
89   * <p>
90   * The cached information is broken into several different categories that are each described below.
91   * </p>
92   * <h3>JCR API objects</h3>
93   * <p>
94   * Clients using the ModeShape JCR implementation obtain a {@link JcrSession JCR Session} (which generally owns this cache
95   * instance) as well as the JCR {@link JcrNode Node} and {@link AbstractJcrProperty Property} instances. This cache ensures that
96   * the same JCR Node or Property objects are always returned for the same item in the repository, ensuring that the "==" operator
97   * always holds true for the same item. However, as soon as all (client) references to these objects are garbage collected, this
98   * class is free to also release those objects and, when needed, recreate new implementation objects.
99   * </p>
100  * <p>
101  * This approach helps reduce memory utilization since any unused items are available for garbage collection, but it also
102  * guarantees that once a client maintains a reference to an item, the same Java object will always be used for any references to
103  * that item.
104  * </p>
105  * <h3>Cached nodes</h3>
106  * <p>
107  * The session cache is also responsible for maintaining a local cache of node information retrieved from the underlying
108  * repository, reducing the need to request information any more than necessary. This information includes that obtained directly
109  * from the repository store, including node properties, children, and references to the parent. It also includes computed
110  * information, such as the NodeDefinition for a node, the name of the primary type and mixin types, and the original
111  * {@link Location} of the node in the repository.
112  * </p>
113  * <h3>Transient changes</h3>
114  * <p>
115  * Any time content is changed in the session, those changes are held within the session until they are saved either by
116  * {@link Session#save() saving the session} or {@link Item#save() saving an individual item} (which includes any content below
117  * that item). This cache maintains all these transient changes, and when requested will send the change requests down the
118  * repository. At any point, these transient changes may be rolled back (or "released"), again either for the
119  * {@link Session#refresh(boolean) whole session} or for {@link Item#refresh(boolean) individual items}.
120  * </p>
121  */
122 @ThreadSafe
123 class SessionCache {
124 
125     /**
126      * Hidden flag that controls whether properties that appear on ModeShape nodes but not allowed by the node type or mixins
127      * should be included anyway. This is currently {@value} .
128      */
129     protected static final boolean INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS = true;
130 
131     protected static final Set<Name> EMPTY_NAMES = Collections.emptySet();
132 
133     private final JcrSession session;
134     private final String workspaceName;
135     protected final ExecutionContext context;
136     protected final ValueFactories factories;
137     protected final PathFactory pathFactory;
138     protected final NameFactory nameFactory;
139     protected final ValueFactory<String> stringFactory;
140     protected final NamespaceRegistry namespaces;
141     protected final PropertyFactory propertyFactory;
142     private final Graph store;
143     protected final Name defaultPrimaryTypeName;
144     protected final Property defaultPrimaryTypeProperty;
145     protected final Path rootPath;
146     protected final Name residualName;
147 
148     private final GraphSession<JcrNodePayload, JcrPropertyPayload> graphSession;
149 
150     public SessionCache( JcrSession session ) {
151         this(session, session.workspace().getName(), session.getExecutionContext(), session.nodeTypeManager(), session.graph());
152     }
153 
154     public SessionCache( JcrSession session,
155                          String workspaceName,
156                          ExecutionContext context,
157                          JcrNodeTypeManager nodeTypes,
158                          Graph store ) {
159         assert session != null;
160         assert workspaceName != null;
161         assert context != null;
162         assert store != null;
163         this.session = session;
164         this.workspaceName = workspaceName;
165         this.store = store;
166         this.context = context;
167         this.factories = context.getValueFactories();
168         this.pathFactory = this.factories.getPathFactory();
169         this.nameFactory = this.factories.getNameFactory();
170         this.stringFactory = context.getValueFactories().getStringFactory();
171         this.namespaces = context.getNamespaceRegistry();
172         this.propertyFactory = context.getPropertyFactory();
173         this.defaultPrimaryTypeName = JcrNtLexicon.UNSTRUCTURED;
174         this.defaultPrimaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, this.defaultPrimaryTypeName);
175         this.rootPath = pathFactory.createRootPath();
176         this.residualName = nameFactory.create(JcrNodeType.RESIDUAL_ITEM_NAME);
177 
178         // Create the graph session, customized for JCR ...
179         this.graphSession = new GraphSession<JcrNodePayload, JcrPropertyPayload>(this.store, this.workspaceName,
180                                                                                  new JcrNodeOperations(), new JcrAuthorizer());
181         // Set the read-depth if we can...
182         try {
183             int depth = Integer.parseInt(session.repository().getOptions().get(Option.READ_DEPTH));
184             if (depth > 0) this.graphSession.setDepthForLoadingNodes(depth);
185         } catch (RuntimeException e) {
186         }
187     }
188 
189     final GraphSession<JcrNodePayload, JcrPropertyPayload> graphSession() {
190         return graphSession;
191     }
192 
193     JcrSession session() {
194         return session;
195     }
196 
197     String workspaceName() {
198         return workspaceName;
199     }
200 
201     String sourceName() {
202         return store.getSourceName();
203     }
204 
205     ExecutionContext context() {
206         return context;
207     }
208 
209     ValueFactories factories() {
210         return factories;
211     }
212 
213     PathFactory pathFactory() {
214         return pathFactory;
215     }
216 
217     NameFactory nameFactory() {
218         return nameFactory;
219     }
220 
221     ValueFactory<String> stringFactory() {
222         return factories.getStringFactory();
223     }
224 
225     JcrNodeTypeManager nodeTypes() {
226         return session.nodeTypeManager();
227     }
228 
229     final String readable( Name name ) {
230         return name.getString(namespaces);
231     }
232 
233     final String readable( Path.Segment segment ) {
234         return segment.getString(namespaces);
235     }
236 
237     final String readable( Path path ) {
238         return path.getString(namespaces);
239     }
240 
241     final String readable( Location location ) {
242         return location.getString(namespaces);
243     }
244 
245     final String readable( Iterable<Name> names ) {
246         StringBuilder sb = new StringBuilder();
247         sb.append('[');
248         boolean first = true;
249         for (Name name : names) {
250             if (first) {
251                 first = false;
252             } else {
253                 sb.append(", ");
254             }
255             sb.append(name.getString(namespaces));
256         }
257         sb.append(']');
258         return sb.toString();
259     }
260 
261     /**
262      * Returns whether the session cache has any pending changes that need to be executed.
263      * 
264      * @return true if there are pending changes, or false if there is currently no changes
265      */
266     boolean hasPendingChanges() {
267         return graphSession.hasPendingChanges();
268     }
269 
270     /**
271      * Refreshes (removes the cached state) for all cached nodes.
272      * <p>
273      * If {@code keepChanges == true}, modified nodes will not have their state refreshed.
274      * </p>
275      * 
276      * @param keepChanges indicates whether changed nodes should be kept or refreshed from the repository.
277      */
278     public void refresh( boolean keepChanges ) {
279         graphSession.refresh(keepChanges);
280     }
281 
282     /**
283      * Refreshes (removes the cached state) for the node with the given UUID and any of its descendants.
284      * <p>
285      * If {@code keepChanges == true}, modified nodes will not have their state refreshed.
286      * </p>
287      * 
288      * @param nodeId the identifier of the node that is to be saved; may not be null
289      * @param absolutePath the absolute path to the node; may not be null
290      * @param keepChanges indicates whether changed nodes should be kept or refreshed from the repository.
291      * @throws InvalidItemStateException if the node being refreshed no longer exists
292      * @throws RepositoryException if any error resulting while saving the changes to the repository
293      */
294     public void refresh( NodeId nodeId,
295                          Path absolutePath,
296                          boolean keepChanges ) throws InvalidItemStateException, RepositoryException {
297         assert nodeId != null;
298         try {
299             Node<JcrNodePayload, JcrPropertyPayload> node = graphSession.findNodeWith(nodeId, absolutePath);
300             graphSession.refresh(node, keepChanges);
301         } catch (InvalidStateException e) {
302             throw new InvalidItemStateException(e.getLocalizedMessage());
303         } catch (org.modeshape.graph.property.PathNotFoundException e) {
304             throw new InvalidItemStateException(e.getLocalizedMessage());
305         } catch (RepositorySourceException e) {
306             throw new RepositoryException(e.getLocalizedMessage());
307         }
308     }
309 
310     /**
311      * Refreshes the properties for the node with the given UUID.
312      * 
313      * @param location the location of the node that is to be refreshed; may not be null
314      * @throws InvalidItemStateException if the node being refreshed no longer exists
315      * @throws RepositoryException if any error resulting while saving the changes to the repository
316      */
317     public void refreshProperties( Location location ) throws InvalidItemStateException, RepositoryException {
318         assert location != null;
319         try {
320             Node<JcrNodePayload, JcrPropertyPayload> node = graphSession.findNodeWith(location);
321 
322             graphSession.refreshProperties(node);
323         } catch (InvalidStateException e) {
324             throw new InvalidItemStateException(e.getLocalizedMessage());
325         } catch (org.modeshape.graph.property.PathNotFoundException e) {
326             throw new InvalidItemStateException(e.getLocalizedMessage());
327         } catch (RepositorySourceException e) {
328             throw new RepositoryException(e.getLocalizedMessage());
329         }
330     }
331 
332     /**
333      * Find the best definition for the child node with the given name on the node with the given UUID.
334      * 
335      * @param parent the parent node; may not be null
336      * @param newNodeName the name of the potential new child node; may not be null
337      * @param newNodePrimaryTypeName the primary type of the potential new child node; may not be null
338      * @return the definition that best fits the new node name and type
339      * @throws ItemExistsException if there is no definition that allows same-name siblings for the name and type and the parent
340      *         node already has a child node with the given name
341      * @throws ConstraintViolationException if there is no definition for the name and type among the parent node's primary and
342      *         mixin types
343      * @throws RepositoryException if any other error occurs
344      */
345     protected JcrNodeDefinition findBestNodeDefinition( Node<JcrNodePayload, JcrPropertyPayload> parent,
346                                                         Name newNodeName,
347                                                         Name newNodePrimaryTypeName )
348         throws ItemExistsException, ConstraintViolationException, RepositoryException {
349         assert parent != null;
350         assert newNodeName != null;
351 
352         Name primaryTypeName = parent.getPayload().getPrimaryTypeName();
353         List<Name> mixinTypeNames = parent.getPayload().getMixinTypeNames();
354 
355         // Need to add one to speculate that this node will be added
356         int snsCount = parent.getChildrenCount(newNodeName) + 1;
357         JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(primaryTypeName,
358                                                                            mixinTypeNames,
359                                                                            newNodeName,
360                                                                            newNodePrimaryTypeName,
361                                                                            snsCount,
362                                                                            true);
363         if (definition == null) {
364             if (snsCount > 1) {
365                 definition = nodeTypes().findChildNodeDefinition(primaryTypeName,
366                                                                  mixinTypeNames,
367                                                                  newNodeName,
368                                                                  newNodePrimaryTypeName,
369                                                                  1,
370                                                                  true);
371 
372                 if (definition != null) {
373                     throw new ItemExistsException(JcrI18n.noSnsDefinition.text(readable(newNodeName),
374                                                                                readable(parent.getPath()),
375                                                                                readable(primaryTypeName),
376                                                                                readable(mixinTypeNames)));
377                 }
378             }
379 
380             throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node",
381                                                                              readable(newNodeName),
382                                                                              readable(parent.getPath()),
383                                                                              readable(primaryTypeName),
384                                                                              readable(mixinTypeNames)));
385         }
386 
387         return definition;
388     }
389 
390     /**
391      * Save any changes that have been accumulated by this session.
392      * 
393      * @throws IllegalArgumentException if the identifier and path are both node
394      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
395      * @throws AccessDeniedException if the caller does not have privilege to perform the operation
396      * @throws ConstraintViolationException if there was a constraint violation
397      * @throws RepositoryException if any error resulting while saving the changes to the repository
398      */
399     public void save() throws ItemNotFoundException, AccessDeniedException, ConstraintViolationException, RepositoryException {
400         try {
401             graphSession.save();
402         } catch (ValidationException e) {
403             throw new ConstraintViolationException(e.getLocalizedMessage(), e);
404         } catch (InvalidStateException e) {
405             throw new InvalidItemStateException(e.getLocalizedMessage(), e);
406         } catch (RepositorySourceException e) {
407             throw new RepositoryException(e.getLocalizedMessage(), e);
408         } catch (AccessControlException e) {
409             throw new AccessDeniedException(e.getMessage(), e);
410         }
411     }
412 
413     /**
414      * Save any changes to the identified node or its descendants. The supplied node may not have been deleted or created in this
415      * session since the last save operation.
416      * 
417      * @param nodeId the identifier of the node that is to be saved; may not be null
418      * @param absolutePath the absolute path to the node; may not be null
419      * @throws IllegalArgumentException if the identifier and path are both node
420      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
421      * @throws AccessDeniedException if the caller does not have privilege to perform the operation
422      * @throws ConstraintViolationException if there was a constraint violation
423      * @throws RepositoryException if any error resulting while saving the changes to the repository
424      */
425     public void save( NodeId nodeId,
426                       Path absolutePath )
427         throws ItemNotFoundException, AccessDeniedException, ConstraintViolationException, RepositoryException {
428         assert nodeId != null;
429         try {
430             Node<JcrNodePayload, JcrPropertyPayload> node = graphSession.findNodeWith(nodeId, absolutePath);
431             assert node != null;
432             graphSession.save(node);
433         } catch (ValidationException e) {
434             throw new ConstraintViolationException(e.getLocalizedMessage(), e);
435         } catch (InvalidStateException e) {
436             throw new InvalidItemStateException(e.getLocalizedMessage(), e);
437         } catch (RepositorySourceException e) {
438             throw new RepositoryException(e.getLocalizedMessage(), e);
439         } catch (AccessControlException e) {
440             throw new AccessDeniedException(e.getMessage(), e);
441         }
442     }
443 
444     /**
445      * Find the session's node for the given identifier and path.
446      * 
447      * @param id the identifier for the node
448      * @param absolutePath the absolute path to the node; may not be null
449      * @return the existing node implementation
450      * @throws IllegalArgumentException if the identifier and path are both node
451      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
452      * @throws AccessDeniedException if the caller does not have privilege to read the node
453      * @throws RepositoryException if an error resulting in finding this node in the repository
454      */
455     public Node<JcrNodePayload, JcrPropertyPayload> findNode( NodeId id,
456                                                               Path absolutePath )
457         throws ItemNotFoundException, AccessDeniedException, RepositoryException {
458         try {
459             return graphSession.findNodeWith(id, absolutePath);
460         } catch (org.modeshape.graph.property.PathNotFoundException e) {
461             throw new ItemNotFoundException(e.getMessage(), e);
462         } catch (RepositorySourceException e) {
463             throw new RepositoryException(e.getMessage(), e);
464         } catch (AccessControlException e) {
465             throw new AccessDeniedException(e.getMessage(), e);
466         }
467     }
468 
469     /**
470      * Find the session's node for the given location.
471      * 
472      * @param location the location for the node
473      * @return the existing node implementation
474      * @throws IllegalArgumentException if the identifier and path are both node
475      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
476      * @throws AccessDeniedException if the caller does not have privilege to read the node
477      * @throws RepositoryException if an error resulting in finding this node in the repository
478      */
479     public Node<JcrNodePayload, JcrPropertyPayload> findNodeWith( Location location )
480         throws ItemNotFoundException, AccessDeniedException, RepositoryException {
481         try {
482             return graphSession.findNodeWith(location);
483         } catch (org.modeshape.graph.property.PathNotFoundException e) {
484             throw new ItemNotFoundException(e.getMessage(), e);
485         } catch (RepositorySourceException e) {
486             throw new RepositoryException(e.getMessage(), e);
487         } catch (AccessControlException e) {
488             throw new AccessDeniedException(e.getMessage(), e);
489         }
490     }
491 
492     /**
493      * Find the session's node for the given identifier and path.
494      * 
495      * @param from the identifier of the reference node; may be null if the root node is to be used as the reference
496      * @param fromAbsolutePath the absolute path of the reference node; may not be null
497      * @param relativePath the relative (but normalized) path from the reference node, but which may be an absolute (and
498      *        normalized) path only when the reference node is the root node; may not be null
499      * @return the existing node implementation
500      * @throws IllegalArgumentException if the identifier and path are both node
501      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
502      * @throws PathNotFoundException if the node given by the relative path does not exist
503      * @throws AccessDeniedException if the caller does not have privilege to read the node
504      * @throws RepositoryException if an error resulting in finding this node in the repository
505      */
506     public Node<JcrNodePayload, JcrPropertyPayload> findNode( NodeId from,
507                                                               Path fromAbsolutePath,
508                                                               Path relativePath )
509         throws PathNotFoundException, ItemNotFoundException, AccessDeniedException, RepositoryException {
510         // Find the reference node ...
511         Node<JcrNodePayload, JcrPropertyPayload> referenceNode = findNode(from, fromAbsolutePath);
512         try {
513             return graphSession.findNodeRelativeTo(referenceNode, relativePath);
514         } catch (org.modeshape.graph.property.PathNotFoundException e) {
515             throw new PathNotFoundException(e.getMessage(), e);
516         } catch (RepositorySourceException e) {
517             throw new RepositoryException(e.getMessage(), e);
518         } catch (AccessControlException e) {
519             throw new AccessDeniedException(e.getMessage(), e);
520         }
521     }
522 
523     /**
524      * Find the root node associated with this workspace.
525      * 
526      * @return the root node; never null
527      * @throws RepositoryException if an error resulting in finding this node in the repository
528      */
529     public JcrRootNode findJcrRootNode() throws RepositoryException {
530         return (JcrRootNode)graphSession.getRoot().getPayload().getJcrNode();
531     }
532 
533     /**
534      * Find the JCR {@link JcrNode Node implementation} for the given identifier and path.
535      * 
536      * @param location the location of the node
537      * @return the existing node implementation
538      * @throws IllegalArgumentException if the identifier and path are both node
539      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
540      * @throws AccessDeniedException if the caller does not have privilege to read the node
541      * @throws RepositoryException if an error resulting in finding this node in the repository
542      */
543     public AbstractJcrNode findJcrNode( Location location )
544         throws ItemNotFoundException, AccessDeniedException, RepositoryException {
545         try {
546             return graphSession.findNodeWith(location).getPayload().getJcrNode();
547         } catch (org.modeshape.graph.property.PathNotFoundException e) {
548             throw new ItemNotFoundException(e.getMessage(), e);
549         } catch (RepositorySourceException e) {
550             throw new RepositoryException(e.getMessage(), e);
551         } catch (AccessControlException e) {
552             throw new AccessDeniedException(e.getMessage(), e);
553         }
554     }
555 
556     /**
557      * Find the JCR {@link JcrNode Node implementation} for the given identifier and path.
558      * 
559      * @param id the identifier for the node
560      * @param absolutePath the absolute path to the node; may not be null
561      * @return the existing node implementation
562      * @throws IllegalArgumentException if the identifier and path are both node
563      * @throws ItemNotFoundException if a node with the supplied identifier and path could not be found
564      * @throws AccessDeniedException if the caller does not have privilege to read the node
565      * @throws RepositoryException if an error resulting in finding this node in the repository
566      */
567     public AbstractJcrNode findJcrNode( NodeId id,
568                                         Path absolutePath )
569         throws ItemNotFoundException, AccessDeniedException, RepositoryException {
570         return findNode(id, absolutePath).getPayload().getJcrNode();
571     }
572 
573     /**
574      * Find the JCR {@link AbstractJcrNode Node implementation} for the node given by the UUID of a reference node and a relative
575      * path from the reference node. The relative path should already have been {@link Path#getNormalizedPath() normalized}.
576      * 
577      * @param from the identifier of the reference node; may be null if the root node is to be used as the reference
578      * @param fromAbsolutePath the absolute path of the reference node; may not be null
579      * @param relativePath the relative (but normalized) path from the reference node, but which may be an absolute (and
580      *        normalized) path only when the reference node is the root node; may not be null
581      * @return the information for the referenced node; never null
582      * @throws ItemNotFoundException if the reference node with the supplied identifier and path does not exist
583      * @throws PathNotFoundException if the node given by the relative path does not exist
584      * @throws InvalidItemStateException if the reference node has been deleted in this session
585      * @throws AccessDeniedException if the caller does not have privilege to read the reference or result node
586      * @throws RepositoryException if any other error occurs while reading information from the repository
587      */
588     public AbstractJcrNode findJcrNode( NodeId from,
589                                         Path fromAbsolutePath,
590                                         Path relativePath )
591         throws ItemNotFoundException, PathNotFoundException, InvalidItemStateException, AccessDeniedException,
592         RepositoryException {
593         // Find the reference node ...
594         Node<JcrNodePayload, JcrPropertyPayload> referenceNode = findNode(from, fromAbsolutePath);
595         try {
596             // Find the reference node ...
597             return graphSession.findNodeRelativeTo(referenceNode, relativePath).getPayload().getJcrNode();
598         } catch (org.modeshape.graph.property.PathNotFoundException e) {
599             throw new PathNotFoundException(e.getMessage(), e);
600         } catch (RepositorySourceException e) {
601             throw new RepositoryException(e.getMessage(), e);
602         } catch (AccessControlException e) {
603             throw new AccessDeniedException(e.getMessage(), e);
604         }
605     }
606 
607     /**
608      * Find the JCR {@link javax.jcr.Property} with the givenname on the node with the supplied ID and/or at the absolute path.
609      * 
610      * @param id the identifier of the node, or null if the path should be used
611      * @param absolutePath the absolute path to the node; should not be null
612      * @param propertyName the property name; may not be null
613      * @return the property, or null if there is no such property
614      * @throws PathNotFoundException if the node does not exist
615      * @throws AccessDeniedException if the caller cannot read the property
616      * @throws RepositoryException if there is a problem reading the node and/or property
617      */
618     public AbstractJcrProperty findJcrProperty( NodeId id,
619                                                 Path absolutePath,
620                                                 Name propertyName )
621         throws PathNotFoundException, AccessDeniedException, RepositoryException {
622         // Find the node that owns the property ...
623         Node<JcrNodePayload, JcrPropertyPayload> node = findNode(id, absolutePath);
624         PropertyInfo<JcrPropertyPayload> propertyInfo = node.getProperty(propertyName);
625         if (propertyInfo != null) {
626             if (propertyName.equals(JcrLexicon.UUID) && !isReferenceable(node)) return null;
627             return propertyInfo.getPayload().getJcrProperty();
628         }
629         return null;
630     }
631 
632     /**
633      * Find the properties for the node given by the supplied identifier and/or path.
634      * 
635      * @param id the identifier of the node, or null if the path should be used
636      * @param absolutePath the absolute path to the node; should not be null
637      * @return an immutable snapshot of the properties in the node
638      * @throws PathNotFoundException if the node does not exist
639      * @throws AccessDeniedException if the caller cannot read the property
640      * @throws RepositoryException if there is a problem reading the node and/or property
641      */
642     public Collection<AbstractJcrProperty> findJcrPropertiesFor( NodeId id,
643                                                                  Path absolutePath )
644         throws PathNotFoundException, AccessDeniedException, RepositoryException {
645         try {
646             Node<JcrNodePayload, JcrPropertyPayload> node = graphSession.findNodeWith(id, absolutePath);
647             Collection<AbstractJcrProperty> result = new ArrayList<AbstractJcrProperty>(node.getPropertyCount());
648             for (org.modeshape.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> property : node.getProperties()) {
649                 Name propertyName = property.getName();
650                 if (propertyName.equals(JcrLexicon.UUID) && !isReferenceable(node)) continue;
651                 if (!propertyName.getNamespaceUri().equals(ModeShapeIntLexicon.Namespace.URI)) {
652                     AbstractJcrProperty prop = property.getPayload().getJcrProperty();
653                     if (prop != null) result.add(prop);
654                 }
655             }
656             return result;
657         } catch (org.modeshape.graph.property.PathNotFoundException e) {
658             throw new PathNotFoundException(e.getMessage(), e);
659         } catch (RepositorySourceException e) {
660             throw new RepositoryException(e.getMessage(), e);
661         } catch (AccessControlException e) {
662             throw new AccessDeniedException(e.getMessage(), e);
663         }
664     }
665 
666     /**
667      * Find the JCR {@link AbstractJcrItem Item implementation} for the node or property given by the UUID of a reference node and
668      * a relative path from the reference node to the desired item. The relative path should already have been
669      * {@link Path#getNormalizedPath() normalized}.
670      * 
671      * @param from the identifier of the reference node; may be null if the root node is to be used as the reference
672      * @param fromAbsolutePath the absolute path of the reference node; may be null if the root node is to be used as the
673      *        reference
674      * @param relativePath the relative (but normalized) path from the reference node to the desired item, but which may be an
675      *        absolute (and normalized) path only when the reference node is the root node; may not be null
676      * @return the information for the referenced item; never null
677      * @throws ItemNotFoundException if the reference node with the supplied UUID does not exist, or if an item given by the
678      *         supplied relative path does not exist
679      * @throws InvalidItemStateException if the node with the UUID has been deleted in this session
680      * @throws RepositoryException if any other error occurs while reading information from the repository
681      */
682     public AbstractJcrItem findJcrItem( NodeId from,
683                                         Path fromAbsolutePath,
684                                         Path relativePath )
685         throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
686         if (from == null && fromAbsolutePath == null) {
687             from = graphSession.getRoot().getNodeId();
688         }
689         // A pathological case is an empty relative path ...
690         if (relativePath.size() == 0) {
691             return findJcrNode(from, fromAbsolutePath);
692         }
693         if (relativePath.size() == 1) {
694             Path.Segment segment = relativePath.getLastSegment();
695             if (segment.isSelfReference()) return findJcrNode(from, fromAbsolutePath);
696             if (segment.isParentReference()) {
697                 return findJcrNode(from, fromAbsolutePath, relativePath);
698             }
699         }
700 
701         // Peek into the last segment of the path to see whether it uses a SNS index (and it's > 1) ...
702         Path.Segment lastSegment = relativePath.getLastSegment();
703         if (lastSegment.getIndex() > 1) {
704             // Only nodes can have SNS index (but an index of 1 is the default)...
705             return findJcrNode(from, fromAbsolutePath);
706         }
707 
708         Node<JcrNodePayload, JcrPropertyPayload> fromNode = null;
709         Node<JcrNodePayload, JcrPropertyPayload> parent = null;
710         try {
711             fromNode = graphSession.findNodeWith(from, fromAbsolutePath);
712             if (from == null) from = fromNode.getNodeId();
713             assert from != null;
714             if (relativePath.size() == 1) {
715                 // The referenced node must be the parent ...
716                 parent = fromNode;
717             } else {
718                 // We know that the parent of the referenced item should be a node (if the path is right) ...
719                 parent = graphSession.findNodeRelativeTo(fromNode, relativePath.getParent());
720             }
721         } catch (org.modeshape.graph.property.PathNotFoundException e) {
722             throw new ItemNotFoundException(e.getMessage(), e);
723         } catch (RepositorySourceException e) {
724             throw new RepositoryException(e.getMessage(), e);
725         } catch (AccessControlException e) {
726             throw new AccessDeniedException(e.getMessage(), e);
727         }
728 
729         // JSR-170 doesn't allow children and proeprties to have the same name, but this is relaxed in JSR-283.
730         // But JSR-283 Section 3.3.4 states "The method Session.getItem will return the item at the specified path
731         // if there is only one such item, if there is both a node and a property at the specified path, getItem
732         // will return the node." Therefore, look for a child first ...
733         if (parent.hasChild(lastSegment)) {
734             // There is a child!
735             Node<JcrNodePayload, JcrPropertyPayload> child = parent.getChild(lastSegment);
736             return child.getPayload().getJcrNode();
737         }
738 
739         // Otherwise it should be a property ...
740         org.modeshape.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> propertyInfo = parent.getProperty(lastSegment.getName());
741         if (propertyInfo != null) {
742             return propertyInfo.getPayload().getJcrProperty();
743         }
744 
745         // It was not found, so prepare a good exception message ...
746         String msg = null;
747         if (from.equals(graphSession.getRoot().getNodeId())) {
748             // The reference node was the root, so use this fact to convert the path to an absolute path in the message
749             Path absolutePath = rootPath.resolve(relativePath);
750             msg = JcrI18n.itemNotFoundAtPath.text(readable(absolutePath), workspaceName);
751         } else {
752             // Find the path of the reference node ...
753             Path referenceNodePath = fromNode.getPath();
754             msg = JcrI18n.itemNotFoundAtPathRelativeToReferenceNode.text(readable(relativePath),
755                                                                          readable(referenceNodePath),
756                                                                          workspaceName);
757         }
758         throw new ItemNotFoundException(msg);
759     }
760 
761     /**
762      * Determine whether the node's primary type or any of the mixins are or extend the node type with the supplied name. This
763      * method is semantically equivalent to but slightly more efficient than the {@link javax.jcr.Node#isNodeType(String)
764      * equivalent in the JCR API}.
765      * 
766      * @param node the node to be evaluated
767      * @param nodeType the name of the node type
768      * @return true if this node is of the node type given by the supplied name, or false otherwise
769      * @throws RepositoryException if there is an exception
770      */
771     public final boolean isNodeType( Node<JcrNodePayload, JcrPropertyPayload> node,
772                                      Name nodeType ) throws RepositoryException {
773         Name primaryTypeName = node.getPayload().getPrimaryTypeName();
774         JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
775         if (primaryType.isNodeType(nodeType)) {
776             return true;
777         }
778         JcrNodeTypeManager nodeTypes = session().nodeTypeManager();
779         for (Name mixinTypeName : node.getPayload().getMixinTypeNames()) {
780             JcrNodeType mixinType = nodeTypes.getNodeType(mixinTypeName);
781             if (mixinType != null && mixinType.isNodeType(nodeType)) {
782                 return true;
783             }
784         }
785         return false;
786     }
787 
788     public boolean isReferenceable( Node<JcrNodePayload, JcrPropertyPayload> node ) throws RepositoryException {
789         return isNodeType(node, JcrMixLexicon.REFERENCEABLE);
790     }
791 
792     public boolean isVersionable( Node<JcrNodePayload, JcrPropertyPayload> node ) throws RepositoryException {
793         return isNodeType(node, JcrMixLexicon.VERSIONABLE);
794     }
795 
796     /**
797      * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties or children on the node identified by the
798      * supplied identifier and path. The node must exist prior to this call, either as a node that exists in the workspace or as a
799      * node that was created within this session but not yet persisted to the workspace. This method returns an editor that
800      * batches all changes in transient storage from where they can be persisted to the repository by
801      * {@link javax.jcr.Session#save() saving the session} or by {@link javax.jcr.Item#save() saving an ancestor}.
802      * 
803      * @param node the node
804      * @return the editor; never null
805      */
806     public NodeEditor getEditorFor( Node<JcrNodePayload, JcrPropertyPayload> node ) {
807         return new NodeEditor(node);
808     }
809 
810     /**
811      * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties or children on the node identified by the
812      * supplied identifier and path. The node must exist prior to this call, either as a node that exists in the workspace or as a
813      * node that was created within this session but not yet persisted to the workspace. This method returns an editor that
814      * batches all changes in transient storage from where they can be persisted to the repository by
815      * {@link javax.jcr.Session#save() saving the session} or by {@link javax.jcr.Item#save() saving an ancestor}.
816      * 
817      * @param id the identifier for the node
818      * @param absolutePath the absolute path to the node; may not be null
819      * @return the editor; never null
820      * @throws ItemNotFoundException if no such node could be found in the session or workspace
821      * @throws AccessDeniedException if the caller does not have privilege to read the reference or result node
822      * @throws InvalidItemStateException if the item has been marked for deletion within this session
823      * @throws RepositoryException if any other error occurs while reading information from the repository
824      */
825     public NodeEditor getEditorFor( NodeId id,
826                                     Path absolutePath )
827         throws ItemNotFoundException, AccessDeniedException, InvalidItemStateException, RepositoryException {
828         Node<JcrNodePayload, JcrPropertyPayload> node = this.graphSession.findNodeWith(id, absolutePath);
829         return new NodeEditor(node);
830     }
831 
832     /**
833      * Returns the absolute path of the node in the specified workspace that corresponds to this node.
834      * <p>
835      * The corresponding node is defined as the node in srcWorkspace with the same UUID as this node or, if this node has no UUID,
836      * the same path relative to the nearest ancestor that does have a UUID, or the root node, whichever comes first. This is
837      * qualified by the requirement that referencable nodes only correspond with other referencables and non-referenceables with
838      * other non-referenceables.
839      * </p>
840      * 
841      * @param workspaceName the name of the workspace
842      * @param uuid the UUID of the corresponding node, or the UUID of the closest ancestor that is referenceable
843      * @param relativePath the relative path from the referenceable node, or null if the supplied UUID identifies the
844      *        corresponding node
845      * @return the absolute path to the corresponding node in the workspace; never null
846      * @throws NoSuchWorkspaceException if the specified workspace does not exist
847      * @throws ItemNotFoundException if no corresponding node exists
848      * @throws AccessDeniedException if the current session does not have sufficient rights to perform this operation
849      * @throws RepositoryException if another exception occurs
850      */
851     Path getPathForCorrespondingNode( String workspaceName,
852                                       UUID uuid,
853                                       Path relativePath )
854         throws NoSuchWorkspaceException, AccessDeniedException, ItemNotFoundException, RepositoryException {
855         assert workspaceName != null;
856         assert uuid != null || relativePath != null;
857 
858         try {
859             try {
860                 store.useWorkspace(workspaceName);
861             } catch (InvalidWorkspaceException iwe) {
862                 throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(store.getSourceName(), workspaceName));
863             }
864             org.modeshape.graph.Node node;
865             if (uuid != null) {
866                 node = store.getNodeAt(uuid);
867 
868                 if (relativePath != null) {
869                     Path nodePath = node.getLocation().getPath();
870                     Path absolutePath = relativePath.resolveAgainst(nodePath);
871                     node = store.getNodeAt(absolutePath);
872                 }
873 
874             } else {
875                 Path absolutePath = pathFactory.createAbsolutePath(relativePath.getSegmentsList());
876                 node = store.getNodeAt(absolutePath);
877             }
878             assert node != null;
879 
880             Path path = node.getLocation().getPath();
881             try {
882                 this.session().checkPermission(workspaceName, path, "read");
883             } catch (AccessControlException ace) {
884                 throw new AccessDeniedException(ace);
885             }
886             return path;
887         } catch (org.modeshape.graph.property.PathNotFoundException pnfe) {
888             throw new ItemNotFoundException(pnfe);
889         } finally {
890             store.useWorkspace(this.workspaceName);
891         }
892     }
893 
894     /**
895      * An interface used to manipulate a node's properties and children.
896      */
897     public final class NodeEditor {
898         private final Node<JcrNodePayload, JcrPropertyPayload> node;
899 
900         protected NodeEditor( Node<JcrNodePayload, JcrPropertyPayload> node ) {
901             this.node = node;
902         }
903 
904         /**
905          * Checks whether there is an existing property with this name that does not match the given cardinality. If such a
906          * property exists, a {@code javax.jcr.ValueFormatException} is thrown, as per section 7.1.5 of the JCR 1.0.1
907          * specification.
908          * 
909          * @param propertyName the name of the property
910          * @param isMultiple whether the property must have multiple values
911          * @throws javax.jcr.ValueFormatException if the property exists but has the opposite cardinality
912          * @throws RepositoryException if any other error occurs
913          */
914         private void checkCardinalityOfExistingProperty( Name propertyName,
915                                                          boolean isMultiple )
916             throws javax.jcr.ValueFormatException, RepositoryException {
917             // Check for existing single-valued property - can't set multiple values on single-valued property
918             PropertyInfo<JcrPropertyPayload> propInfo = this.node.getProperty(propertyName);
919             if (propInfo != null && propInfo.isMultiValued() != isMultiple) {
920                 String workspaceName = SessionCache.this.workspaceName();
921                 String propName = readable(propertyName);
922                 String path = readable(node.getPath());
923                 if (isMultiple) {
924                     I18n msg = JcrI18n.unableToSetSingleValuedPropertyUsingMultipleValues;
925                     throw new javax.jcr.ValueFormatException(msg.text(propName, path, workspaceName));
926                 }
927                 I18n msg = JcrI18n.unableToSetMultiValuedPropertyUsingSingleValue;
928                 throw new javax.jcr.ValueFormatException(msg.text(propName, path, workspaceName));
929             }
930 
931         }
932 
933         /**
934          * @return if this node (or its nearest versionable ancestor) is checked out.
935          * @throws RepositoryException if there is an error accessing the repository
936          * @see javax.jcr.Node#isCheckedOut()
937          */
938         boolean isCheckedOut() throws RepositoryException {
939             for (Node<JcrNodePayload, JcrPropertyPayload> curr = node; curr.getParent() != null; curr = curr.getParent()) {
940                 if (isNodeType(curr, JcrMixLexicon.VERSIONABLE)) {
941                     PropertyInfo<JcrPropertyPayload> prop = curr.getProperty(JcrLexicon.IS_CHECKED_OUT);
942 
943                     // This prop can only be null if the node has not been saved since it was made versionable.
944                     return prop == null || prop.getPayload().getJcrProperty().getBoolean();
945                 }
946             }
947 
948             return true;
949         }
950 
951         /**
952          * Set the value for the property. If the property does not exist, it will be added. If the property does exist, the
953          * existing values will be replaced with the supplied value.
954          * 
955          * @param name the property name; may not be null
956          * @param value the new property values; may not be null
957          * @return the JCR property object for the property; never null
958          * @throws ConstraintViolationException if the property could not be set because of a node type constraint or property
959          *         definition constraint
960          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
961          * @throws RepositoryException if any other error occurs
962          */
963         public AbstractJcrProperty setProperty( Name name,
964                                                 JcrValue value )
965             throws AccessDeniedException, ConstraintViolationException, RepositoryException {
966             return setProperty(name, value, true);
967         }
968 
969         /**
970          * Set the value for the property. If the property does not exist, it will be added. If the property does exist, the
971          * existing values will be replaced with the supplied value. Protected property definitions may be considered, based on
972          * the {@code skipProtected} flag.
973          * 
974          * @param name the property name; may not be null
975          * @param value the new property values; may not be null
976          * @param skipProtected indicates whether protected property definitions should be ignored
977          * @return the JCR property object for the property; never null
978          * @throws ConstraintViolationException if the property could not be set because of a node type constraint or property
979          *         definition constraint
980          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
981          * @throws VersionException if this node is not checked out
982          * @throws RepositoryException if any other error occurs
983          */
984         public AbstractJcrProperty setProperty( Name name,
985                                                 JcrValue value,
986                                                 boolean skipProtected )
987             throws AccessDeniedException, ConstraintViolationException, VersionException, RepositoryException {
988             assert name != null;
989             assert value != null;
990 
991             /*
992              * Skip this check for protected nodes.  They can't be modified by users and, in some cases (e.g., jcr:isLocked),
993              * may be able to be modified for checked-in nodes.
994              */
995             if (!isCheckedOut() && skipProtected) {
996                 String path = node.getLocation().getPath().getString(context().getNamespaceRegistry());
997                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(path));
998             }
999 
1000             checkCardinalityOfExistingProperty(name, false);
1001 
1002             JcrPropertyDefinition definition = null;
1003 
1004             // Look for an existing property ...
1005             PropertyInfo<JcrPropertyPayload> existing = node.getProperty(name);
1006             if (existing != null) {
1007 
1008                 // We're replacing an existing property, but we still need to check that the property definition
1009                 // (still) defines a type. So, find the property definition for the existing property ...
1010                 definition = nodeTypes().getPropertyDefinition(existing.getPayload().getPropertyDefinitionId());
1011 
1012                 if (definition != null) {
1013                     // The definition's require type must match the value's ...
1014                     if (definition.getRequiredType() != PropertyType.UNDEFINED && definition.getRequiredType() != value.getType()) {
1015                         // The property type is not right, so we have to check if we can cast.
1016                         // It's easier and could save more work if we just find a new property definition that works ...
1017                         definition = null;
1018                     } else {
1019                         // The types match, so see if the value satisfies the constraints ...
1020                         if (!definition.satisfiesConstraints(value)) definition = null;
1021                     }
1022                 }
1023             }
1024             JcrNodePayload payload = node.getPayload();
1025             if (definition == null) {
1026                 // Look for a definition ...
1027                 definition = nodeTypes().findPropertyDefinition(payload.getPrimaryTypeName(),
1028                                                                 payload.getMixinTypeNames(),
1029                                                                 name,
1030                                                                 value,
1031                                                                 true,
1032                                                                 skipProtected);
1033                 /*
1034                  * findPropertyDefinition checks constraints for all property types except REFERENCE.  To avoid unnecessary loading of nodes,
1035                  * REFERENCE constraints are only checked when the property is first set.
1036                  */
1037                 boolean referencePropMissedConstraints = definition != null
1038                                                          && definition.getRequiredType() == PropertyType.REFERENCE
1039                                                          && !definition.canCastToTypeAndSatisfyConstraints(value);
1040                 if (definition == null || referencePropMissedConstraints) {
1041                     throw new ConstraintViolationException(JcrI18n.noDefinition.text("property",
1042                                                                                      readable(name),
1043                                                                                      readable(node.getPath()),
1044                                                                                      readable(payload.getPrimaryTypeName()),
1045                                                                                      readable(payload.getMixinTypeNames())));
1046                 }
1047             } else {
1048                 // Check that the existing definition isn't protected
1049                 if (skipProtected && definition.isProtected()) throw new ConstraintViolationException(
1050                                                                                                       JcrI18n.noDefinition.text("property",
1051                                                                                                                                 readable(name),
1052                                                                                                                                 readable(node.getPath()),
1053                                                                                                                                 readable(payload.getPrimaryTypeName()),
1054                                                                                                                                 readable(payload.getMixinTypeNames())));
1055             }
1056             // Create the ModeShape property ...
1057             Object objValue = value.value();
1058             int propertyType = definition.getRequiredType();
1059             if (propertyType == PropertyType.UNDEFINED || propertyType == value.getType()) {
1060                 // Can use the values as is ...
1061                 propertyType = value.getType();
1062             } else {
1063                 // A cast is required ...
1064                 org.modeshape.graph.property.PropertyType dnaPropertyType = PropertyTypeUtil.dnaPropertyTypeFor(propertyType);
1065                 ValueFactory<?> factory = factories().getValueFactory(dnaPropertyType);
1066                 objValue = factory.create(objValue);
1067             }
1068             Property dnaProp = propertyFactory.create(name, objValue);
1069 
1070             try {
1071                 // Create (or reuse) the JCR Property object ...
1072                 AbstractJcrProperty jcrProp = null;
1073                 if (existing != null) {
1074                     jcrProp = existing.getPayload().getJcrProperty();
1075                 } else {
1076                     AbstractJcrNode jcrNode = payload.getJcrNode();
1077                     if (definition.isMultiple()) {
1078                         jcrProp = new JcrMultiValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
1079                     } else {
1080                         jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
1081                     }
1082                 }
1083                 assert jcrProp != null;
1084                 JcrPropertyPayload propPayload = new JcrPropertyPayload(definition.getId(), propertyType, jcrProp);
1085                 node.setProperty(dnaProp, definition.isMultiple(), propPayload);
1086                 return jcrProp;
1087             } catch (ValidationException e) {
1088                 throw new ConstraintViolationException(e.getMessage(), e);
1089             } catch (RepositorySourceException e) {
1090                 throw new RepositoryException(e.getMessage(), e);
1091             } catch (AccessControlException e) {
1092                 throw new AccessDeniedException(e.getMessage(), e);
1093             }
1094         }
1095 
1096         /**
1097          * Set the values for the property. If the property does not exist, it will be added. If the property does exist, the
1098          * existing values will be replaced with those that are supplied.
1099          * <p>
1100          * This method will not set protected property definitions and should be used in almost all cases.
1101          * </p>
1102          * 
1103          * @param name the property name; may not be null
1104          * @param values new property values, all of which must have the same {@link Value#getType() property type}; may not be
1105          *        null but may be empty
1106          * @param valueType
1107          * @return the JCR property object for the property; never null
1108          * @throws ConstraintViolationException if the property could not be set because of a node type constraint or property
1109          *         definition constraint
1110          * @throws javax.jcr.ValueFormatException
1111          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
1112          * @throws RepositoryException if any other error occurs
1113          */
1114         public AbstractJcrProperty setProperty( Name name,
1115                                                 Value[] values,
1116                                                 int valueType )
1117             throws AccessDeniedException, ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException {
1118             return setProperty(name, values, valueType, true);
1119         }
1120 
1121         /**
1122          * Set the values for the property. If the property does not exist, it will be added. If the property does exist, the
1123          * existing values will be replaced with those that are supplied.
1124          * 
1125          * @param name the property name; may not be null
1126          * @param values new property values, all of which must have the same {@link Value#getType() property type}; may not be
1127          *        null but may be empty
1128          * @param skipProtected if true, attempts to set protected properties will fail. If false, attempts to set protected
1129          *        properties will be allowed.
1130          * @param valueType
1131          * @return the JCR property object for the property; never null
1132          * @throws ConstraintViolationException if the property could not be set because of a node type constraint or property
1133          *         definition constraint
1134          * @throws javax.jcr.ValueFormatException
1135          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
1136          * @throws VersionException if this node is not checked out
1137          * @throws RepositoryException if any other error occurs
1138          */
1139         public AbstractJcrProperty setProperty( Name name,
1140                                                 Value[] values,
1141                                                 int valueType,
1142                                                 boolean skipProtected )
1143             throws AccessDeniedException, ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException,
1144             VersionException {
1145             assert name != null;
1146             assert values != null;
1147 
1148             /*
1149              * Skip this check for protected nodes.  They can't be modified by users and, in some cases (e.g., jcr:isLocked),
1150              * may be able to be modified for checked-in nodes.
1151              */
1152             if (!isCheckedOut() && skipProtected) {
1153                 String path = node.getLocation().getPath().getString(context().getNamespaceRegistry());
1154                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(path));
1155             }
1156 
1157             checkCardinalityOfExistingProperty(name, true);
1158 
1159             int len = values.length;
1160             Value[] newValues = null;
1161             if (len == 0) {
1162                 newValues = JcrMultiValueProperty.EMPTY_VALUES;
1163             } else {
1164                 List<Value> valuesWithDesiredType = new ArrayList<Value>(len);
1165                 int expectedType = -1;
1166                 for (int i = 0; i != len; ++i) {
1167                     Value value = values[i];
1168 
1169                     if (value == null) continue;
1170                     if (expectedType == -1) {
1171                         expectedType = value.getType();
1172                     } else if (value.getType() != expectedType) {
1173                         // Make sure the type of each value is the same, as per Javadoc in section 7.1.5 of the JCR 1.0.1 spec
1174                         StringBuilder sb = new StringBuilder();
1175                         sb.append('[');
1176                         for (int j = 0; j != values.length; ++j) {
1177                             if (j != 0) sb.append(",");
1178                             sb.append(values[j].toString());
1179                         }
1180                         sb.append(']');
1181                         String propType = PropertyType.nameFromValue(expectedType);
1182                         I18n msg = JcrI18n.allPropertyValuesMustHaveSameType;
1183                         String path = readable(node.getPath());
1184                         String workspaceName = SessionCache.this.workspaceName();
1185                         throw new javax.jcr.ValueFormatException(msg.text(readable(name), values, propType, path, workspaceName));
1186                     }
1187                     if (value.getType() != valueType && valueType != PropertyType.UNDEFINED) {
1188                         value = ((JcrValue)value).asType(valueType);
1189                     }
1190                     valuesWithDesiredType.add(value);
1191                 }
1192                 if (valuesWithDesiredType.isEmpty()) {
1193                     newValues = JcrMultiValueProperty.EMPTY_VALUES;
1194                 } else {
1195                     newValues = valuesWithDesiredType.toArray(new Value[valuesWithDesiredType.size()]);
1196                 }
1197             }
1198 
1199             int numValues = newValues.length;
1200             JcrPropertyDefinition definition = null;
1201 
1202             // Look for an existing property ...
1203             PropertyInfo<JcrPropertyPayload> existing = node.getProperty(name);
1204             if (existing != null) {
1205                 // We're replacing an existing property, but we still need to check that the property definition
1206                 // (still) defines a type. So, find the property definition for the existing property ...
1207                 definition = nodeTypes().getPropertyDefinition(existing.getPayload().getPropertyDefinitionId());
1208 
1209                 if (definition != null) {
1210                     // The definition's require type must match the value's ...
1211                     if (numValues == 0) {
1212                         // Just use the definition as is ...
1213                     } else {
1214                         // Use the property type for the first non-null value ...
1215                         int type = newValues[0].getType();
1216                         if (definition.getRequiredType() != PropertyType.UNDEFINED && definition.getRequiredType() != type) {
1217                             // The property type is not right, so we have to check if we can cast.
1218                             // It's easier and could save more work if we just find a new property definition that works ...
1219                             definition = null;
1220                         } else {
1221                             // The types match, so see if the value satisfies the constraints ...
1222                             if (!definition.satisfiesConstraints(newValues)) definition = null;
1223                         }
1224                     }
1225                 }
1226             }
1227             JcrNodePayload payload = node.getPayload();
1228             if (definition == null) {
1229                 // Look for a definition ...
1230                 definition = nodeTypes().findPropertyDefinition(payload.getPrimaryTypeName(),
1231                                                                 payload.getMixinTypeNames(),
1232                                                                 name,
1233                                                                 newValues,
1234                                                                 skipProtected);
1235                 /*
1236                  * findPropertyDefinition checks constraints for all property types except REFERENCE.  To avoid unnecessary loading of nodes,
1237                  * REFERENCE constraints are only checked when the property is first set.
1238                  */
1239                 boolean referencePropMissedConstraints = definition != null
1240                                                          && definition.getRequiredType() == PropertyType.REFERENCE
1241                                                          && !definition.canCastToTypeAndSatisfyConstraints(newValues);
1242                 if (definition == null || referencePropMissedConstraints) {
1243                     throw new ConstraintViolationException(JcrI18n.noDefinition.text("property",
1244                                                                                      readable(name),
1245                                                                                      readable(node.getPath()),
1246                                                                                      readable(payload.getPrimaryTypeName()),
1247                                                                                      readable(payload.getMixinTypeNames())));
1248                 }
1249             } else {
1250                 // Check that the existing definition isn't protected
1251                 if (skipProtected && definition.isProtected()) throw new ConstraintViolationException(
1252                                                                                                       JcrI18n.noDefinition.text("property",
1253                                                                                                                                 readable(name),
1254                                                                                                                                 readable(node.getPath()),
1255                                                                                                                                 readable(payload.getPrimaryTypeName()),
1256                                                                                                                                 readable(payload.getMixinTypeNames())));
1257             }
1258 
1259             // Create the ModeShape property ...
1260             int type = numValues != 0 ? newValues[0].getType() : definition.getRequiredType();
1261             Object[] objValues = new Object[numValues];
1262             int propertyType = definition.getRequiredType();
1263             if (propertyType == PropertyType.UNDEFINED || propertyType == type) {
1264                 // Can use the values as is ...
1265                 propertyType = type;
1266                 for (int i = 0; i != numValues; ++i) {
1267                     objValues[i] = ((JcrValue)newValues[i]).value();
1268                 }
1269             } else {
1270                 // A cast is required ...
1271                 assert propertyType != type;
1272                 org.modeshape.graph.property.PropertyType dnaPropertyType = PropertyTypeUtil.dnaPropertyTypeFor(propertyType);
1273                 ValueFactory<?> factory = factories().getValueFactory(dnaPropertyType);
1274                 for (int i = 0; i != numValues; ++i) {
1275                     objValues[i] = factory.create(((JcrValue)newValues[i]).value());
1276                 }
1277             }
1278             Property dnaProp = propertyFactory.create(name, objValues);
1279 
1280             try {
1281                 // Create (or reuse) the JCR Property object ...
1282                 AbstractJcrProperty jcrProp = null;
1283                 if (existing != null) {
1284                     jcrProp = existing.getPayload().getJcrProperty();
1285                 } else {
1286                     AbstractJcrNode jcrNode = payload.getJcrNode();
1287                     if (definition.isMultiple()) {
1288                         jcrProp = new JcrMultiValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
1289                     } else {
1290                         jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
1291                     }
1292                 }
1293                 assert jcrProp != null;
1294                 JcrPropertyPayload propPayload = new JcrPropertyPayload(definition.getId(), propertyType, jcrProp);
1295                 node.setProperty(dnaProp, definition.isMultiple(), propPayload);
1296                 return jcrProp;
1297             } catch (ValidationException e) {
1298                 throw new ConstraintViolationException(e.getMessage(), e);
1299             } catch (RepositorySourceException e) {
1300                 throw new RepositoryException(e.getMessage(), e);
1301             } catch (AccessControlException e) {
1302                 throw new AccessDeniedException(e.getMessage(), e);
1303             }
1304         }
1305 
1306         /**
1307          * Remove the existing property with the supplied name.
1308          * 
1309          * @param name the property name; may not be null
1310          * @return true if there was a property with the supplied name, or false if no such property existed
1311          * @throws AccessDeniedException if the current session does not have the requisite permissions to remove this property
1312          * @throws RepositoryException if any other error occurs
1313          */
1314         public boolean removeProperty( Name name ) throws AccessDeniedException, RepositoryException {
1315             try {
1316                 return node.removeProperty(name) != null;
1317             } catch (ValidationException e) {
1318                 throw new ConstraintViolationException(e.getMessage(), e);
1319             } catch (RepositorySourceException e) {
1320                 throw new RepositoryException(e.getMessage(), e);
1321             } catch (AccessControlException e) {
1322                 throw new AccessDeniedException(e.getMessage(), e);
1323             }
1324         }
1325 
1326         /**
1327          * Move the specified child to be located immediately before the other supplied node.
1328          * 
1329          * @param childToBeMoved the path segment specifying the child that is to be moved
1330          * @param before the path segment of the node before which the {@code childToBeMoved} should be placed, or null if the
1331          *        node should be moved to the end
1332          * @throws IllegalArgumentException if either segment is null or does not specify an existing node
1333          * @throws AccessDeniedException if the current session does not have the requisite permissions to remove this property
1334          * @throws RepositoryException if any other error occurs
1335          */
1336         public void orderChildBefore( Path.Segment childToBeMoved,
1337                                       Path.Segment before ) throws AccessDeniedException, RepositoryException {
1338             try {
1339                 node.orderChildBefore(childToBeMoved, before);
1340             } catch (ValidationException e) {
1341                 throw new ConstraintViolationException(e.getMessage(), e);
1342             } catch (RepositorySourceException e) {
1343                 throw new RepositoryException(e.getMessage(), e);
1344             } catch (AccessControlException e) {
1345                 throw new AccessDeniedException(e.getMessage(), e);
1346             }
1347         }
1348 
1349         /**
1350          * Move the child specified by the supplied UUID to be a child of this node, appending the child to the end of the current
1351          * list of children. This method automatically disconnects the node from its current parent.
1352          * 
1353          * @param child the UUID of the existing node; may not be null
1354          * @param newNodeName
1355          * @return the newly-added child in its location under the new parent; never null
1356          * @throws ItemNotFoundException if the specified child node could be found in the session or workspace
1357          * @throws InvalidItemStateException if the specified child has been marked for deletion within this session
1358          * @throws ConstraintViolationException if moving the node into this node violates this node's definition
1359          * @throws RepositoryException if any other error occurs while reading information from the repository
1360          */
1361         public Node<JcrNodePayload, JcrPropertyPayload> moveToBeChild( AbstractJcrNode child,
1362                                                                        Name newNodeName )
1363             throws ItemNotFoundException, InvalidItemStateException, ConstraintViolationException, RepositoryException {
1364 
1365             // Look up the child and verify that the child can move into this node ...
1366             Node<JcrNodePayload, JcrPropertyPayload> existingChild = findNode(child.nodeId, child.location.getPath());
1367             if (existingChild.equals(node) || node.isAtOrBelow(existingChild)) {
1368                 String pathOfChild = readable(existingChild.getPath());
1369                 String thisPath = readable(node.getPath());
1370                 String msg = JcrI18n.unableToMoveNodeToBeChildOfDecendent.text(pathOfChild, thisPath, workspaceName());
1371                 throw new RepositoryException(msg);
1372             }
1373 
1374             // Find the best node definition for the child in the new parent, or throw an exception if there is none ...
1375             JcrNodeDefinition defn = findBestNodeDefinition(node, newNodeName, child.getPrimaryTypeName());
1376 
1377             try {
1378                 // Perform the move ...
1379                 existingChild.moveTo(node, newNodeName);
1380 
1381                 NodeDefinitionId existingChildDefinitionId = existingChild.getPayload().getDefinitionId();
1382                 if (!defn.getId().equals(existingChildDefinitionId)) {
1383                     // The node definition changed, so try to set the property ...
1384                     NodeEditor newChildEditor = getEditorFor(existingChild);
1385                     try {
1386                         JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, defn.getId()
1387                                                                                                                .getString());
1388                         newChildEditor.setProperty(ModeShapeIntLexicon.NODE_DEFINITON, value);
1389                     } catch (ConstraintViolationException e) {
1390                         // We can't set this property on the node (according to the node definition).
1391                         // But we still want the node info to have the correct node definition.
1392                         // When it is reloaded into a cache (after being persisted), the correct node definition
1393                         // will be computed again ...
1394                         existingChild.setPayload(existingChild.getPayload().with(defn.getId()));
1395 
1396                         // And remove the property from the info ...
1397                         newChildEditor.removeProperty(ModeShapeIntLexicon.NODE_DEFINITON);
1398                     }
1399                 }
1400 
1401                 return existingChild;
1402             } catch (ValidationException e) {
1403                 throw new ConstraintViolationException(e.getMessage(), e);
1404             } catch (RepositorySourceException e) {
1405                 throw new RepositoryException(e.getMessage(), e);
1406             } catch (AccessControlException e) {
1407                 throw new AccessDeniedException(e.getMessage(), e);
1408             }
1409         }
1410 
1411         public void addMixin( JcrNodeType mixinCandidateType ) throws javax.jcr.ValueFormatException, RepositoryException {
1412             try {
1413                 PropertyInfo<JcrPropertyPayload> existingMixinProperty = node.getProperty(JcrLexicon.MIXIN_TYPES);
1414 
1415                 // getProperty(JcrLexicon.MIXIN_TYPES);
1416                 Value[] existingMixinValues;
1417                 if (existingMixinProperty != null) {
1418                     existingMixinValues = existingMixinProperty.getPayload().getJcrProperty().getValues();
1419                 } else {
1420                     existingMixinValues = new Value[0];
1421                 }
1422 
1423                 Value[] newMixinValues = new Value[existingMixinValues.length + 1];
1424                 System.arraycopy(existingMixinValues, 0, newMixinValues, 0, existingMixinValues.length);
1425                 newMixinValues[newMixinValues.length - 1] = new JcrValue(factories(), SessionCache.this, PropertyType.NAME,
1426                                                                          mixinCandidateType.getInternalName());
1427 
1428                 setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, PropertyType.NAME, false);
1429 
1430                 // ------------------------------------------------------------------------------
1431                 // Create any auto-created properties/nodes from new type
1432                 // ------------------------------------------------------------------------------
1433                 autoCreateItemsFor(mixinCandidateType);
1434 
1435                 if (mixinCandidateType.isNodeType(JcrMixLexicon.REFERENCEABLE)) {
1436                     // This node is now referenceable, so make sure there is a UUID property ...
1437                     UUID uuid = node.getLocation().getUuid();
1438                     if (uuid == null) uuid = (UUID)node.getLocation().getIdProperty(JcrLexicon.UUID).getFirstValue();
1439                     if (uuid == null) uuid = UUID.randomUUID();
1440                     JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, uuid);
1441                     setProperty(JcrLexicon.UUID, value, false);
1442                 }
1443             } catch (RepositorySourceException e) {
1444                 throw new RepositoryException(e.getMessage(), e);
1445             } catch (AccessControlException e) {
1446                 throw new AccessDeniedException(e.getMessage(), e);
1447             }
1448         }
1449 
1450         private void autoCreateItemsFor( JcrNodeType nodeType )
1451             throws InvalidItemStateException, ConstraintViolationException, AccessDeniedException, RepositoryException {
1452 
1453             for (JcrPropertyDefinition propertyDefinition : nodeType.allPropertyDefinitions()) {
1454                 if (propertyDefinition.isAutoCreated() && !propertyDefinition.isProtected()) {
1455                     PropertyInfo<JcrPropertyPayload> autoCreatedProp = node.getProperty(propertyDefinition.getInternalName());
1456                     if (autoCreatedProp == null) {
1457                         // We have to 'auto-create' the property ...
1458                         if (propertyDefinition.getDefaultValues() != null) {
1459                             if (propertyDefinition.isMultiple()) {
1460                                 setProperty(propertyDefinition.getInternalName(),
1461                                             propertyDefinition.getDefaultValues(),
1462                                             propertyDefinition.getRequiredType());
1463                             } else {
1464                                 assert propertyDefinition.getDefaultValues().length == 1;
1465                                 setProperty(propertyDefinition.getInternalName(),
1466                                             (JcrValue)propertyDefinition.getDefaultValues()[0]);
1467                             }
1468                         }
1469                         // otherwise, we don't care
1470                     }
1471                 }
1472             }
1473 
1474             for (JcrNodeDefinition nodeDefinition : nodeType.allChildNodeDefinitions()) {
1475                 if (nodeDefinition.isAutoCreated() && !nodeDefinition.isProtected()) {
1476                     Name nodeName = nodeDefinition.getInternalName();
1477                     if (node.getChildrenCount(nodeName) == 0) {
1478                         assert nodeDefinition.getDefaultPrimaryType() != null;
1479                         createChild(nodeName, null, ((JcrNodeType)nodeDefinition.getDefaultPrimaryType()).getInternalName());
1480                     }
1481                 }
1482             }
1483         }
1484 
1485         /**
1486          * Create a new node as a child of this node, using the supplied name and (optionally) the supplied UUID.
1487          * 
1488          * @param name the name for the new child; may not be null
1489          * @param desiredUuid the desired UUID, or null if the UUID for the child should be generated automatically
1490          * @param primaryTypeName the name of the primary type for the new node
1491          * @return the representation of the newly-created child
1492          * @throws InvalidItemStateException if the specified child has been marked for deletion within this session
1493          * @throws ConstraintViolationException if moving the node into this node violates this node's definition
1494          * @throws NoSuchNodeTypeException if the node type for the primary type could not be found
1495          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
1496          * @throws RepositoryException if any other error occurs while reading information from the repository
1497          */
1498         public JcrNode createChild( Name name,
1499                                     UUID desiredUuid,
1500                                     Name primaryTypeName )
1501             throws InvalidItemStateException, ConstraintViolationException, AccessDeniedException, RepositoryException {
1502 
1503             if (desiredUuid == null) desiredUuid = UUID.randomUUID();
1504             try {
1505 
1506                 // Verify that this node accepts a child of the supplied name (given any existing SNS nodes) ...
1507                 int numSns = node.getChildrenCount(name) + 1;
1508                 JcrNodePayload payload = node.getPayload();
1509                 JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(payload.getPrimaryTypeName(),
1510                                                                                    payload.getMixinTypeNames(),
1511                                                                                    name,
1512                                                                                    primaryTypeName,
1513                                                                                    numSns,
1514                                                                                    true);
1515                 // Make sure there was a valid child node definition ...
1516                 if (definition == null) {
1517 
1518                     // Check if the definition would have worked with less SNS
1519                     definition = nodeTypes().findChildNodeDefinition(payload.getPrimaryTypeName(),
1520                                                                      payload.getMixinTypeNames(),
1521                                                                      name,
1522                                                                      primaryTypeName,
1523                                                                      numSns - 1,
1524                                                                      true);
1525                     if (definition != null) {
1526                         // Only failed because there was no SNS definition - throw ItemExistsException per 7.1.4 of 1.0.1 spec
1527                         Path pathForChild = pathFactory.create(node.getPath(), name, numSns);
1528                         String msg = JcrI18n.noSnsDefinitionForNode.text(pathForChild, workspaceName());
1529                         throw new ItemExistsException(msg);
1530                     }
1531                     // Didn't work for other reasons - throw ConstraintViolationException
1532                     Path pathForChild = pathFactory.create(node.getPath(), name, numSns);
1533                     String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(pathForChild,
1534                                                                                         workspaceName(),
1535                                                                                         sourceName());
1536 
1537                     throw new ConstraintViolationException(msg);
1538                 }
1539 
1540                 // Find the primary type ...
1541                 JcrNodeType primaryType = null;
1542                 if (primaryTypeName != null) {
1543                     primaryType = nodeTypes().getNodeType(primaryTypeName);
1544                     if (primaryType == null) {
1545                         Path pathForChild = pathFactory.create(node.getPath(), name, numSns);
1546                         I18n msg = JcrI18n.unableToCreateNodeWithPrimaryTypeThatDoesNotExist;
1547                         throw new NoSuchNodeTypeException(msg.text(primaryTypeName, pathForChild, workspaceName()));
1548                     }
1549 
1550                     if (primaryType.isMixin()) {
1551                         I18n msg = JcrI18n.cannotUseMixinTypeAsPrimaryType;
1552                         throw new ConstraintViolationException(msg.text(primaryType.getName()));
1553                     }
1554 
1555                     if (primaryType.isAbstract()) {
1556                         I18n msg = JcrI18n.primaryTypeCannotBeAbstract;
1557                         throw new ConstraintViolationException(msg.text(primaryType.getName()));
1558                     }
1559 
1560                 } else {
1561                     primaryType = (JcrNodeType)definition.getDefaultPrimaryType();
1562                     if (primaryType == null) {
1563                         // There is no default primary type ...
1564                         Path pathForChild = pathFactory.create(node.getPath(), name, numSns);
1565                         I18n msg = JcrI18n.unableToCreateNodeWithNoDefaultPrimaryTypeOnChildNodeDefinition;
1566                         String nodeTypeName = definition.getDeclaringNodeType().getName();
1567                         throw new NoSuchNodeTypeException(msg.text(definition.getName(),
1568                                                                    nodeTypeName,
1569                                                                    pathForChild,
1570                                                                    workspaceName()));
1571                     }
1572                 }
1573                 primaryTypeName = primaryType.getInternalName();
1574 
1575                 // ---------------------------------------------------------
1576                 // Now create the child node representation in the cache ...
1577                 // ---------------------------------------------------------
1578 
1579                 // Create the initial properties ...
1580                 Property primaryTypeProp = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName);
1581                 Property nodeDefinitionProp = propertyFactory.create(ModeShapeIntLexicon.NODE_DEFINITON, definition.getId()
1582                                                                                                                    .getString());
1583 
1584                 // Now add the "jcr:uuid" property if and only if referenceable ...
1585                 Node<JcrNodePayload, JcrPropertyPayload> result = null;
1586                 boolean isReferenceable = primaryType.isNodeType(JcrMixLexicon.REFERENCEABLE);
1587                 Property uuidProperty = null;
1588                 if (desiredUuid != null || isReferenceable) {
1589                     if (desiredUuid == null) {
1590                         desiredUuid = UUID.randomUUID();
1591                     }
1592                     uuidProperty = propertyFactory.create(JcrLexicon.UUID, desiredUuid);
1593                 }
1594                 if (uuidProperty != null) {
1595                     result = node.createChild(name, Collections.singleton(uuidProperty), primaryTypeProp, nodeDefinitionProp);
1596                 } else {
1597                     result = node.createChild(name, primaryTypeProp, nodeDefinitionProp);
1598                 }
1599 
1600                 JcrNode jcrNode = (JcrNode)result.getPayload().getJcrNode();
1601 
1602                 // Fix the "jcr:created", "jcr:createdBy", "jcr:lastModified" and "jcr:lastModifiedBy" properties on the new child
1603                 // ...
1604                 JcrValue now = jcrNode.valueFrom(Calendar.getInstance());
1605                 JcrValue by = jcrNode.valueFrom(session().getUserID());
1606                 boolean isCreatedType = primaryType.isNodeType(JcrMixLexicon.CREATED);
1607                 boolean isHierarchyNode = primaryType.isNodeType(JcrNtLexicon.HIERARCHY_NODE);
1608                 if (isHierarchyNode || isCreatedType) {
1609                     NodeEditor editor = jcrNode.editor();
1610                     if (isHierarchyNode) {
1611                         editor.setProperty(JcrLexicon.CREATED, now, false);
1612                     }
1613                     if (isCreatedType) {
1614                         editor.setProperty(JcrLexicon.CREATED, now, false);
1615                         editor.setProperty(JcrLexicon.CREATED_BY, by, false);
1616                     }
1617                 }
1618 
1619                 // The postCreateChild hook impl should populate the payloads
1620                 jcrNode.editor().autoCreateItemsFor(primaryType);
1621 
1622                 // Finally, return the jcr node ...
1623                 return jcrNode;
1624             } catch (ValidationException e) {
1625                 throw new ConstraintViolationException(e.getMessage(), e);
1626             } catch (RepositorySourceException e) {
1627                 throw new RepositoryException(e.getMessage(), e);
1628             } catch (AccessControlException e) {
1629                 throw new AccessDeniedException(e.getMessage(), e);
1630             }
1631         }
1632 
1633         /**
1634          * Destroy the child node with the supplied UUID and all nodes that exist below it, including any nodes that were created
1635          * and haven't been persisted.
1636          * 
1637          * @param child the child node; may not be null
1638          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
1639          * @throws RepositoryException if any other error occurs
1640          * @return true if the child was successfully removed, or false if the node did not exist as a child
1641          */
1642         public boolean destroyChild( Node<JcrNodePayload, JcrPropertyPayload> child )
1643             throws AccessDeniedException, RepositoryException {
1644             if (!child.getParent().equals(node)) return false;
1645             try {
1646                 child.destroy();
1647             } catch (AccessControlException e) {
1648                 throw new AccessDeniedException(e.getMessage(), e);
1649             }
1650             return true;
1651         }
1652 
1653         /**
1654          * Convenience method that destroys this node.
1655          * 
1656          * @return true if this node was successfully removed
1657          * @throws AccessDeniedException if the current session does not have the requisite privileges to perform this task
1658          * @throws RepositoryException if any other error occurs
1659          */
1660         public boolean destroy() throws AccessDeniedException, RepositoryException {
1661             try {
1662                 node.destroy();
1663             } catch (AccessControlException e) {
1664                 throw new AccessDeniedException(e.getMessage(), e);
1665             }
1666             return true;
1667         }
1668     }
1669 
1670     /**
1671      * Find the best property definition in this node's primary type and mixin types.
1672      * 
1673      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
1674      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
1675      *        to include in the search
1676      * @param dnaProperty the new property that is to be set on this node
1677      * @param propertyType the property type; must be a valid {@link PropertyType} value
1678      * @param isSingle true if the property definition should be single-valued, or false if the property definition should allow
1679      *        multiple values
1680      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
1681      *        this operation is being done from within internal implementations
1682      * @return the property definition that allows setting this property, or null if there is no such definition
1683      */
1684     protected JcrPropertyDefinition findBestPropertyDefintion( Name primaryTypeNameOfParent,
1685                                                                List<Name> mixinTypeNamesOfParent,
1686                                                                Property dnaProperty,
1687                                                                int propertyType,
1688                                                                boolean isSingle,
1689                                                                boolean skipProtected ) {
1690         JcrPropertyDefinition definition = null;
1691         if (propertyType == PropertyType.UNDEFINED) {
1692             propertyType = PropertyTypeUtil.jcrPropertyTypeFor(dnaProperty);
1693         }
1694 
1695         // If single-valued ...
1696         if (isSingle) {
1697             // Create a value for the ModeShape property value ...
1698             Object value = dnaProperty.getFirstValue();
1699             Value jcrValue = new JcrValue(factories(), SessionCache.this, propertyType, value);
1700             definition = nodeTypes().findPropertyDefinition(primaryTypeNameOfParent,
1701                                                             mixinTypeNamesOfParent,
1702                                                             dnaProperty.getName(),
1703                                                             jcrValue,
1704                                                             true,
1705                                                             skipProtected);
1706         } else {
1707             // Create values for the ModeShape property value ...
1708             Value[] jcrValues = new Value[dnaProperty.size()];
1709             int index = 0;
1710             for (Object value : dnaProperty) {
1711                 jcrValues[index++] = new JcrValue(factories(), SessionCache.this, propertyType, value);
1712             }
1713             definition = nodeTypes().findPropertyDefinition(primaryTypeNameOfParent,
1714                                                             mixinTypeNamesOfParent,
1715                                                             dnaProperty.getName(),
1716                                                             jcrValues,
1717                                                             skipProtected);
1718         }
1719 
1720         if (definition != null) return definition;
1721 
1722         // No definition that allowed the values ...
1723         return null;
1724     }
1725 
1726     // Path getPathFor( String workspaceName,
1727     // UUID uuid,
1728     // Path relativePath ) throws NoSuchWorkspaceException, ItemNotFoundException, RepositoryException {
1729     // assert workspaceName != null;
1730     // assert uuid != null || relativePath != null;
1731     //
1732     // Graph graph = operations.getGraph();
1733     // try {
1734     // graph.useWorkspace(workspaceName);
1735     // } catch (InvalidWorkspaceException iwe) {
1736     // throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(graph.getSourceName(), workspaceName));
1737     // }
1738     //
1739     // try {
1740     // org.modeshape.graph.Node node;
1741     // if (uuid != null) {
1742     // node = graph.getNodeAt(uuid);
1743     //
1744     // if (relativePath != null) {
1745     // Path nodePath = node.getLocation().getPath();
1746     // Path absolutePath = relativePath.resolveAgainst(nodePath);
1747     // node = graph.getNodeAt(absolutePath);
1748     // }
1749     //
1750     // } else {
1751     // Path absolutePath = pathFactory.createAbsolutePath(relativePath.getSegmentsList());
1752     // node = graph.getNodeAt(absolutePath);
1753     // }
1754     // assert node != null;
1755     //
1756     // return node.getLocation().getPath();
1757     // } catch (org.modeshape.graph.property.PathNotFoundException pnfe) {
1758     // throw new ItemNotFoundException(pnfe);
1759     // } finally {
1760     // graph.useWorkspace(this.workspaceName);
1761     // }
1762     //
1763     // }
1764     //
1765     // Path getPathFor( UUID uuid ) throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
1766     // if (uuid.equals(root)) return rootPath;
1767     // return getPathFor(findNodeInfo(uuid));
1768     // }
1769     //
1770     // Path getPathFor( NodeInfo info ) throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
1771     // if (info == null) {
1772     // return pathFactory.createRootPath();
1773     // }
1774     // UUID uuid = info.getUuid();
1775     // if (uuid.equals(root)) return rootPath;
1776     //
1777     // // This isn't the root node ...
1778     // Path result = pathCache.get(uuid);
1779     // if (result == null) {
1780     // // We need to build a path using the parent path ...
1781     // UUID parent = info.getParent();
1782     // if (parent == null) {
1783     // // Then this node is the root ...
1784     // root = info.getUuid();
1785     // result = rootPath;
1786     // } else {
1787     // NodeInfo parentInfo = findNodeInfo(parent);
1788     // Path parentPath = getPathFor(parentInfo);
1789     // ChildNode child = parentInfo.getChildren().getChild(info.getUuid());
1790     // result = pathFactory.create(parentPath, child.getSegment());
1791     // }
1792     // pathCache.put(uuid, result);
1793     // }
1794     // assert result != null;
1795     // return result;
1796     // }
1797     //
1798     // Path getPathFor( PropertyInfo propertyInfo ) throws ItemNotFoundException, RepositoryException {
1799     // Path nodePath = getPathFor(propertyInfo.getNodeUuid());
1800     // return pathFactory.create(nodePath, propertyInfo.getPropertyName());
1801     // }
1802     //
1803     // Path getPathFor( PropertyId propertyId ) throws ItemNotFoundException, RepositoryException {
1804     // return getPathFor(findPropertyInfo(propertyId));
1805     // }
1806     //
1807     // protected Name getNameOf( UUID nodeUuid ) throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
1808     // findNodeInfoForRoot();
1809     // if (nodeUuid == root) return nameFactory.create("");
1810     // // Get the parent ...
1811     // NodeInfo info = findNodeInfo(nodeUuid);
1812     // NodeInfo parent = findNodeInfo(info.getParent());
1813     // ChildNode child = parent.getChildren().getChild(info.getUuid());
1814     // return child.getName();
1815     // }
1816     //
1817     // protected int getSnsIndexOf( UUID nodeUuid ) throws ItemNotFoundException, InvalidItemStateException, RepositoryException {
1818     // findNodeInfoForRoot();
1819     // if (nodeUuid == root) return 1;
1820     // // Get the parent ...
1821     // NodeInfo info = findNodeInfo(nodeUuid);
1822     // NodeInfo parent = findNodeInfo(info.getParent());
1823     // ChildNode child = parent.getChildren().getChild(info.getUuid());
1824     // return child.getSnsIndex();
1825     // }
1826     //
1827     // /**
1828     // * Load from the underlying repository graph the information for the node with the supplied UUID. This method returns the
1829     // * information for the requested node (after placing it in the cache), but this method may (at its discretion) also load and
1830     // * cache information for other nodes.
1831     // * <p>
1832     // * Note that this method does not check the cache before loading from the repository graph.
1833     // * </p>
1834     // *
1835     // * @param path the path of the node, if known; may be null only if the UUID is supplied
1836     // * @param uuid the UUID of the node, if known; may be null only if the path is supplied
1837     // * @return the information for the node
1838     // * @throws ItemNotFoundException if the node does not exist in the repository
1839     // * @throws RepositoryException if there was an error obtaining this information from the repository
1840     // */
1841     // protected ImmutableNodeInfo loadFromGraph( Path path,
1842     // UUID uuid ) throws ItemNotFoundException, RepositoryException {
1843     // // Load the node information from the store ...
1844     // try {
1845     // // See if there is a path for this uuid ...
1846     // Location location = Location.create(path, uuid);
1847     // org.modeshape.graph.Node node = store.getNodeAt(location);
1848     // ImmutableNodeInfo info = createNodeInfoFrom(node, null);
1849     // this.cachedNodes.put(info.getUuid(), info);
1850     // return info;
1851     // } catch (org.modeshape.graph.property.PathNotFoundException e) {
1852     // throw new ItemNotFoundException(JcrI18n.itemNotFoundWithUuid.text(uuid, workspaceName, e.getLocalizedMessage()));
1853     // } catch (RepositorySourceException e) {
1854     // throw new RepositoryException(
1855     // JcrI18n.errorWhileFindingNodeWithUuid.text(uuid, workspaceName, e.getLocalizedMessage()),
1856     // e);
1857     // }
1858     // }
1859     //
1860     // /**
1861     // * Load from the underlying repository graph the information for the node with the supplied UUID. This method returns the
1862     // * information for the requested node (after placing it in the cache), but this method may (at its discretion) also load and
1863     // * cache information for other nodes.
1864     // * <p>
1865     // * Note that this method does not check the cache before loading from the repository graph.
1866     // * </p>
1867     // *
1868     // * @param path the path to the node; may not be null
1869     // * @param parentInfo the parent information; may be null if not known
1870     // * @return the information for the node
1871     // * @throws PathNotFoundException if the node does not exist in the repository
1872     // * @throws RepositoryException if there was an error obtaining this information from the repository
1873     // */
1874     // protected ImmutableNodeInfo loadFromGraph( Path path,
1875     // NodeInfo parentInfo ) throws PathNotFoundException, RepositoryException {
1876     // // Load the node information from the store ...
1877     // try {
1878     // org.modeshape.graph.Node node = store.getNodeAt(path);
1879     // ImmutableNodeInfo info = createNodeInfoFrom(node, parentInfo);
1880     // this.cachedNodes.put(info.getUuid(), info);
1881     // return info;
1882     // } catch (org.modeshape.graph.property.PathNotFoundException e) {
1883     // throw new PathNotFoundException(JcrI18n.pathNotFound.text(path, workspaceName));
1884     // } catch (RepositorySourceException e) {
1885     // throw new RepositoryException(JcrI18n.errorWhileFindingNodeWithPath.text(path, workspaceName));
1886     // }
1887     // }
1888 
1889     // /**
1890     // * Create the {@link NodeInfo} object given the ModeShape graph node and the parent node information (if it is available).
1891     // *
1892     // * @param graphNode the ModeShape graph node; may not be null
1893     // * @param parentInfo the information for the parent node, or null if the supplied graph node represents the root node, or if
1894     // * the parent information is not known
1895     // * @return the node information; never null
1896     // * @throws RepositoryException if there is an error determining the child {@link NodeDefinition} for the supplied node,
1897     // * preventing the node information from being constructed
1898     // */
1899     // private ImmutableNodeInfo createNodeInfoFrom( org.modeshape.graph.Node graphNode,
1900     // NodeInfo parentInfo ) throws RepositoryException {
1901     // // Now get the ModeShape node's UUID and find the ModeShape property containing the UUID ...
1902     // Location location = graphNode.getLocation();
1903     // UUID uuid = location.getUuid();
1904     // org.modeshape.graph.property.Property uuidProperty = null;
1905     // if (uuid != null) {
1906     // // Check for an identification property ...
1907     // uuidProperty = location.getIdProperty(JcrLexicon.UUID);
1908     // if (uuidProperty == null) {
1909     // uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
1910     // }
1911     // }
1912     // if (uuidProperty == null) {
1913     // uuidProperty = graphNode.getProperty(JcrLexicon.UUID);
1914     // if (uuidProperty != null) {
1915     // // Grab the first 'good' UUID value ...
1916     // for (Object uuidValue : uuidProperty) {
1917     // try {
1918     // uuid = factories.getUuidFactory().create(uuidValue);
1919     // break;
1920     // } catch (ValueFormatException e) {
1921     // // Ignore; just continue with the next property value
1922     // }
1923     // }
1924     // }
1925     // if (uuid == null) {
1926     // // Look for the ModeShape UUID property ...
1927     // org.modeshape.graph.property.Property dnaUuidProperty = graphNode.getProperty(ModeShapeLexicon.UUID);
1928     // if (dnaUuidProperty != null) {
1929     // // Grab the first 'good' UUID value ...
1930     // for (Object uuidValue : dnaUuidProperty) {
1931     // try {
1932     // uuid = factories.getUuidFactory().create(uuidValue);
1933     // break;
1934     // } catch (ValueFormatException e) {
1935     // // Ignore; just continue with the next property value
1936     // }
1937     // }
1938     // }
1939     // }
1940     // }
1941     // if (uuid == null) uuid = UUID.randomUUID();
1942     // if (uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
1943     //
1944     // // Either the UUID is not known, or there was no node. Either way, we have to create the node ...
1945     // if (uuid == null) uuid = UUID.randomUUID();
1946     //
1947     // // Look for the primary type of the node ...
1948     // Map<Name, Property> graphProperties = graphNode.getPropertiesByName();
1949     // final boolean isRoot = location.getPath().isRoot();
1950     // Name primaryTypeName = null;
1951     // org.modeshape.graph.property.Property primaryTypeProperty = graphNode.getProperty(JcrLexicon.PRIMARY_TYPE);
1952     // if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) {
1953     // try {
1954     // primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue());
1955     // } catch (ValueFormatException e) {
1956     // // use the default ...
1957     // }
1958     // }
1959     // if (primaryTypeName == null) {
1960     // // We have to have a primary type, so use the default ...
1961     // if (isRoot) {
1962     // primaryTypeName = ModeShapeLexicon.ROOT;
1963     // primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName);
1964     // } else {
1965     // primaryTypeName = defaultPrimaryTypeName;
1966     // primaryTypeProperty = defaultPrimaryTypeProperty;
1967     // }
1968     // // We have to add this property to the graph node...
1969     // graphProperties = new HashMap<Name, Property>(graphProperties);
1970     // graphProperties.put(primaryTypeProperty.getName(), primaryTypeProperty);
1971     // }
1972     // assert primaryTypeProperty != null;
1973     // assert primaryTypeProperty.isEmpty() == false;
1974     //
1975     // // Look for a node definition stored on the node ...
1976     // JcrNodeDefinition definition = null;
1977     // org.modeshape.graph.property.Property nodeDefnProperty = graphProperties.get(ModeShapeIntLexicon.NODE_DEFINITON);
1978     // if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) {
1979     // String nodeDefinitionString = stringFactory.create(nodeDefnProperty.getFirstValue());
1980     // NodeDefinitionId id = NodeDefinitionId.fromString(nodeDefinitionString, nameFactory);
1981     // definition = nodeTypes().getNodeDefinition(id);
1982     // }
1983     // // Figure out the node definition for this node ...
1984     // if (isRoot) {
1985     // if (definition == null) definition = nodeTypes().getRootNodeDefinition();
1986     // } else {
1987     // // We need the parent ...
1988     // Path path = location.getPath();
1989     // if (parentInfo == null) {
1990     // Path parentPath = path.getParent();
1991     // parentInfo = findNodeInfo(null, parentPath.getNormalizedPath());
1992     // }
1993     // if (definition == null) {
1994     // Name childName = path.getLastSegment().getName();
1995     // int numExistingChildrenWithSameName = parentInfo.getChildren().getCountOfSameNameSiblingsWithName(childName);
1996     // definition = nodeTypes().findChildNodeDefinition(parentInfo.getPrimaryTypeName(),
1997     // parentInfo.getMixinTypeNames(),
1998     // childName,
1999     // primaryTypeName,
2000     // numExistingChildrenWithSameName,
2001     // false);
2002     // }
2003     // if (definition == null) {
2004     // String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(path, workspaceName);
2005     // throw new RepositorySourceException(sourceName(), msg);
2006     // }
2007     // }
2008     //
2009     // // ------------------------------------------------------
2010     // // Set the node's properties ...
2011     // // ------------------------------------------------------
2012     // boolean referenceable = false;
2013     //
2014     // // Start with the primary type ...
2015     // JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
2016     // if (primaryType == null) {
2017     // Path path = location.getPath();
2018     // String msg = JcrI18n.missingNodeTypeForExistingNode.text(primaryTypeName.getString(namespaces), path, workspaceName);
2019     // throw new RepositorySourceException(sourceName(), msg);
2020     // }
2021     // if (primaryType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true;
2022     //
2023     // // The process the mixin types ...
2024     // Property mixinTypesProperty = graphProperties.get(JcrLexicon.MIXIN_TYPES);
2025     // List<Name> mixinTypeNames = null;
2026     // if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) {
2027     // for (Object mixinTypeValue : mixinTypesProperty) {
2028     // Name mixinTypeName = nameFactory.create(mixinTypeValue);
2029     // if (mixinTypeNames == null) mixinTypeNames = new LinkedList<Name>();
2030     // mixinTypeNames.add(mixinTypeName);
2031     // JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName);
2032     // if (mixinType == null) continue;
2033     // if (!referenceable && mixinType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true;
2034     // }
2035     // }
2036     //
2037     // // Create the set of multi-valued property names ...
2038     // Set<Name> multiValuedPropertyNames = EMPTY_NAMES;
2039     // Set<Name> newSingleMultiPropertyNames = null;
2040     // Property multiValuedPropNamesProp = graphProperties.get(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES);
2041     // if (multiValuedPropNamesProp != null && !multiValuedPropNamesProp.isEmpty()) {
2042     // multiValuedPropertyNames = getSingleMultiPropertyNames(multiValuedPropNamesProp, location.getPath(), uuid);
2043     // }
2044     //
2045     // // Now create the JCR property object wrappers around the other properties ...
2046     // Map<Name, PropertyInfo> props = new HashMap<Name, PropertyInfo>();
2047     // for (Property dnaProp : graphProperties.values()) {
2048     // Name name = dnaProp.getName();
2049     //
2050     // // Is this is single-valued property?
2051     // boolean isSingle = dnaProp.isSingle();
2052     // // Make sure that this isn't a multi-valued property with one value ...
2053     // if (isSingle && multiValuedPropertyNames.contains(name)) isSingle = false;
2054     //
2055     // // Figure out the JCR property type for this property ...
2056     // int propertyType = PropertyTypeUtil.jcrPropertyTypeFor(dnaProp);
2057     // PropertyDefinition propertyDefinition = findBestPropertyDefintion(primaryTypeName,
2058     // mixinTypeNames,
2059     // dnaProp,
2060     // propertyType,
2061     // isSingle,
2062     // false);
2063     //
2064     // // If there still is no property type defined ...
2065     // if (propertyDefinition == null && INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS) {
2066     // // We can use the "nt:unstructured" property definitions for any property ...
2067     // NodeType unstructured = nodeTypes().getNodeType(JcrNtLexicon.UNSTRUCTURED);
2068     // for (PropertyDefinition anyDefinition : unstructured.getDeclaredPropertyDefinitions()) {
2069     // if (anyDefinition.isMultiple()) {
2070     // propertyDefinition = anyDefinition;
2071     // break;
2072     // }
2073     // }
2074     // }
2075     // if (propertyDefinition == null) {
2076     // // We're supposed to skip this property (since we don't have a definition for it) ...
2077     // continue;
2078     // }
2079     //
2080     // // Figure out if this is a multi-valued property ...
2081     // boolean isMultiple = propertyDefinition.isMultiple();
2082     // if (!isMultiple && dnaProp.isEmpty()) {
2083     // // Only multi-valued properties can have no values; so if not multi-valued, then skip ...
2084     // continue;
2085     // }
2086     //
2087     // // Update the list of single-valued multi-property names ...
2088     // if (isMultiple && isSingle) {
2089     // if (newSingleMultiPropertyNames == null) newSingleMultiPropertyNames = new HashSet<Name>();
2090     // newSingleMultiPropertyNames.add(name);
2091     // }
2092     //
2093     // // Figure out the property type ...
2094     // int definitionType = propertyDefinition.getRequiredType();
2095     // if (definitionType != PropertyType.UNDEFINED) {
2096     // propertyType = definitionType;
2097     // }
2098     //
2099     // // Record the property in the node information ...
2100     // PropertyId propId = new PropertyId(uuid, name);
2101     // JcrPropertyDefinition defn = (JcrPropertyDefinition)propertyDefinition;
2102     // PropertyInfo propInfo = new PropertyInfo(propId, defn.getId(), propertyType, dnaProp, defn.isMultiple(), false, false);
2103     // props.put(name, propInfo);
2104     // }
2105     //
2106     // // Now add the "jcr:uuid" property if and only if referenceable ...
2107     // if (referenceable) {
2108     // // We know that this property is single-valued
2109     // JcrValue value = new JcrValue(factories(), this, PropertyType.STRING, uuid);
2110     // PropertyDefinition propertyDefinition = nodeTypes().findPropertyDefinition(primaryTypeName,
2111     // mixinTypeNames,
2112     // JcrLexicon.UUID,
2113     // value,
2114     // false,
2115     // false);
2116     // PropertyId propId = new PropertyId(uuid, JcrLexicon.UUID);
2117     // JcrPropertyDefinition defn = (JcrPropertyDefinition)propertyDefinition;
2118     // PropertyInfo propInfo = new PropertyInfo(propId, defn.getId(), PropertyType.STRING, uuidProperty, defn.isMultiple(),
2119     // false, false);
2120     // props.put(JcrLexicon.UUID, propInfo);
2121     // } else {
2122     // // Make sure there is NOT a "jcr:uuid" property ...
2123     // props.remove(JcrLexicon.UUID);
2124     // }
2125     // // Make sure the "dna:uuid" property did not get in there ...
2126     // props.remove(ModeShapeLexicon.UUID);
2127     //
2128     // // Make sure the single-valued multi-property names are stored as a property ...
2129     // if (newSingleMultiPropertyNames != null) {
2130     // PropertyInfo info = createSingleMultiplePropertyInfo(uuid,
2131     // primaryTypeName,
2132     // mixinTypeNames,
2133     // newSingleMultiPropertyNames);
2134     // props.put(info.getPropertyName(), info);
2135     // }
2136     //
2137     // // Create the node information ...
2138     // UUID parentUuid = parentInfo != null ? parentInfo.getUuid() : null;
2139     // List<Location> locations = graphNode.getChildren();
2140     // Children children = locations.isEmpty() ? new EmptyChildren(parentUuid) : new ImmutableChildren(parentUuid, locations);
2141     // props = Collections.unmodifiableMap(props);
2142     // return new ImmutableNodeInfo(location, primaryTypeName, mixinTypeNames, definition.getId(), parentUuid, children, props);
2143     // }
2144     //
2145     protected final void updateSingleMultipleProperty( Node<JcrNodePayload, JcrPropertyPayload> node,
2146                                                        Name singleMultiPropertyName,
2147                                                        boolean add ) {
2148         PropertyInfo<JcrPropertyPayload> existing = node.getProperty(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES);
2149         Set<Name> singleMultiPropertyNames = null;
2150         if (existing != null) {
2151             singleMultiPropertyNames = new HashSet<Name>();
2152             // Grab the existing values ...
2153             for (Object value : existing.getProperty()) {
2154                 singleMultiPropertyNames.add(nameFactory().create(value));
2155             }
2156             if (add) singleMultiPropertyNames.add(singleMultiPropertyName);
2157             else singleMultiPropertyNames.remove(singleMultiPropertyName);
2158         } else {
2159             if (add) {
2160                 singleMultiPropertyNames = Collections.singleton(singleMultiPropertyName);
2161             } else {
2162                 // supposed to remove the property name, but there isn't a property, so just return
2163                 return;
2164             }
2165         }
2166 
2167         if (singleMultiPropertyNames.isEmpty()) {
2168             // Remove the property ...
2169             assert existing != null;
2170             node.removeProperty(existing.getName());
2171             return;
2172         }
2173         PropertyInfo<JcrPropertyPayload> property = createSingleMultipleProperty(node.getPayload(),
2174                                                                                  existing,
2175                                                                                  singleMultiPropertyNames);
2176         node.setProperty(property.getProperty(), property.isMultiValued(), property.getPayload());
2177     }
2178 
2179     protected PropertyInfo<JcrPropertyPayload> createSingleMultipleProperty( JcrNodePayload nodePayload,
2180                                                                              PropertyInfo<JcrPropertyPayload> existing,
2181                                                                              Set<Name> singleMultiPropertyNames ) {
2182 
2183         int number = singleMultiPropertyNames.size();
2184         // Otherwise, we have to set/update the property ...
2185         String[] names = new String[number];
2186         JcrValue[] values = new JcrValue[number];
2187         if (number == 1) {
2188             String str = singleMultiPropertyNames.iterator().next().getString(namespaces);
2189             names[0] = str;
2190             values[0] = new JcrValue(factories(), this, PropertyType.STRING, str);
2191         } else {
2192             int index = 0;
2193             for (Name name : singleMultiPropertyNames) {
2194                 String str = name.getString(namespaces);
2195                 names[index] = str;
2196                 values[index] = new JcrValue(factories(), this, PropertyType.STRING, str);
2197                 ++index;
2198             }
2199         }
2200         JcrPropertyDefinition definition = nodeTypes().findPropertyDefinition(nodePayload.getPrimaryTypeName(),
2201                                                                               nodePayload.getMixinTypeNames(),
2202                                                                               ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES,
2203                                                                               values,
2204                                                                               false);
2205         Property dnaProp = propertyFactory.create(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES,
2206                                                   singleMultiPropertyNames.iterator());
2207         return createPropertyInfo(nodePayload, dnaProp, definition, PropertyType.STRING, existing);
2208     }
2209 
2210     protected final PropertyInfo<JcrPropertyPayload> createPropertyInfo( JcrNodePayload nodePayload,
2211                                                                          Property dnaProp,
2212                                                                          JcrPropertyDefinition definition,
2213                                                                          int propertyType,
2214                                                                          PropertyInfo<JcrPropertyPayload> existing ) {
2215         // Create (or reuse) the JCR Property object ...
2216         AbstractJcrProperty jcrProp = null;
2217         if (existing != null && existing.getPayload() != null) {
2218             jcrProp = existing.getPayload().getJcrProperty();
2219         } else {
2220             AbstractJcrNode jcrNode = nodePayload.getJcrNode();
2221             if (definition.isMultiple()) {
2222                 jcrProp = new JcrMultiValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
2223             } else {
2224                 jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
2225             }
2226         }
2227         assert jcrProp != null;
2228         JcrPropertyPayload propPayload = new JcrPropertyPayload(definition.getId(), propertyType, jcrProp);
2229         Status status = existing != null ? Status.CHANGED : Status.NEW;
2230         return new GraphSession.PropertyInfo<JcrPropertyPayload>(dnaProp, definition.isMultiple(), status, propPayload);
2231     }
2232 
2233     @Immutable
2234     final class JcrNodeOperations extends GraphSession.NodeOperations<JcrNodePayload, JcrPropertyPayload> {
2235         private final Logger LOGGER = Logger.getLogger(JcrNodeOperations.class);
2236         private final String user = SessionCache.this.session().getUserID();
2237 
2238         private Map<Name, PropertyInfo<JcrPropertyPayload>> buildProperties( org.modeshape.graph.Node persistentNode,
2239                                                                              Node<JcrNodePayload, JcrPropertyPayload> node,
2240                                                                              JcrNodePayload nodePayload,
2241                                                                              boolean referenceable ) {
2242 
2243             AbstractJcrNode jcrNode = nodePayload.getJcrNode();
2244             Name primaryTypeName = nodePayload.getPrimaryTypeName();
2245             List<Name> mixinTypeNames = nodePayload.getMixinTypeNames();
2246 
2247             Location location = persistentNode.getLocation();
2248             Map<Name, Property> graphProperties = persistentNode.getPropertiesByName();
2249 
2250             if (!graphProperties.containsKey(JcrLexicon.PRIMARY_TYPE)) {
2251                 Property primaryTypeProperty;
2252                 // We have to have a primary type, so use the default ...
2253                 if (location.getPath().isRoot()) {
2254                     primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName);
2255                 } else {
2256                     primaryTypeProperty = defaultPrimaryTypeProperty;
2257                 }
2258                 // We have to add this property to the graph node...
2259                 graphProperties = new HashMap<Name, Property>(graphProperties);
2260                 graphProperties.put(primaryTypeProperty.getName(), primaryTypeProperty);
2261             }
2262 
2263             // Create the set of multi-valued property names ...
2264             Set<Name> multiValuedPropertyNames = EMPTY_NAMES;
2265             Set<Name> newSingleMultiPropertyNames = null;
2266             Property multiValuedPropNamesProp = graphProperties.get(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES);
2267             if (multiValuedPropNamesProp != null && !multiValuedPropNamesProp.isEmpty()) {
2268                 multiValuedPropertyNames = getSingleMultiPropertyNames(multiValuedPropNamesProp, location);
2269             }
2270 
2271             // Now create the JCR property object wrappers around the other properties ...
2272             Map<Name, GraphSession.PropertyInfo<JcrPropertyPayload>> props = new HashMap<Name, GraphSession.PropertyInfo<JcrPropertyPayload>>();
2273             for (Property dnaProp : graphProperties.values()) {
2274                 Name name = dnaProp.getName();
2275 
2276                 /*
2277                  * Don't add mode:uuid to the node.  If the node is referenceable, this has already been added as jcr:uuid
2278                  * and if the node is not referenceable, the UUID should not be exposed as public API.
2279                  */
2280                 if (ModeShapeLexicon.UUID.equals(name)) continue;
2281 
2282                 // Is this is single-valued property?
2283                 boolean isSingle = dnaProp.isSingle();
2284                 // Make sure that this isn't a multi-valued property with one value ...
2285                 if (isSingle && multiValuedPropertyNames.contains(name)) isSingle = false;
2286 
2287                 // Figure out the JCR property type for this property ...
2288                 int propertyType = PropertyTypeUtil.jcrPropertyTypeFor(dnaProp);
2289                 PropertyDefinition propertyDefinition = findBestPropertyDefintion(primaryTypeName,
2290                                                                                   mixinTypeNames,
2291                                                                                   dnaProp,
2292                                                                                   propertyType,
2293                                                                                   isSingle,
2294                                                                                   false);
2295 
2296                 // If there still is no property type defined ...
2297                 if (propertyDefinition == null && INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS) {
2298                     // We can use the "nt:unstructured" property definitions for any property ...
2299                     NodeType unstructured = nodeTypes().getNodeType(JcrNtLexicon.UNSTRUCTURED);
2300                     for (PropertyDefinition anyDefinition : unstructured.getDeclaredPropertyDefinitions()) {
2301                         if (anyDefinition.isMultiple()) {
2302                             propertyDefinition = anyDefinition;
2303                             break;
2304                         }
2305                     }
2306                 }
2307                 if (propertyDefinition == null) {
2308                     // We're supposed to skip this property (since we don't have a definition for it) ...
2309                     continue;
2310                 }
2311 
2312                 // Figure out if this is a multi-valued property ...
2313                 boolean isMultiple = propertyDefinition.isMultiple();
2314                 if (!isMultiple && dnaProp.isEmpty()) {
2315                     // Only multi-valued properties can have no values; so if not multi-valued, then skip ...
2316                     continue;
2317                 }
2318 
2319                 // Update the list of single-valued multi-property names ...
2320                 if (isMultiple && isSingle) {
2321                     if (newSingleMultiPropertyNames == null) newSingleMultiPropertyNames = new HashSet<Name>();
2322                     newSingleMultiPropertyNames.add(name);
2323                 }
2324 
2325                 // Figure out the property type ...
2326                 int definitionType = propertyDefinition.getRequiredType();
2327                 if (definitionType != PropertyType.UNDEFINED) {
2328                     propertyType = definitionType;
2329                 }
2330 
2331                 // Record the property in the node information ...
2332                 JcrPropertyDefinition defn = (JcrPropertyDefinition)propertyDefinition;
2333                 AbstractJcrProperty jcrProp = null;
2334                 if (isMultiple) {
2335                     jcrProp = new JcrMultiValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
2336                 } else {
2337                     jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, dnaProp.getName());
2338                 }
2339                 JcrPropertyPayload payload = new JcrPropertyPayload(defn.getId(), propertyType, jcrProp);
2340                 PropertyInfo<JcrPropertyPayload> propInfo = new PropertyInfo<JcrPropertyPayload>(dnaProp, defn.isMultiple(),
2341                                                                                                  Status.UNCHANGED, payload);
2342                 props.put(name, propInfo);
2343             }
2344 
2345             // Now add the "jcr:uuid" property if and only if referenceable ...
2346             if (referenceable) {
2347                 UUID uuid = location.getUuid();
2348                 org.modeshape.graph.property.Property uuidProperty = null;
2349                 if (uuid != null) {
2350                     // Check for an identification property ...
2351                     uuidProperty = location.getIdProperty(JcrLexicon.UUID);
2352                     if (uuidProperty == null) {
2353                         uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
2354                     }
2355                 } else {
2356                     uuidProperty = location.getIdProperty(JcrLexicon.UUID);
2357                     // The Basic model on the JPA connector sometimes returns locations with no UUID for referenceable nodes.
2358                     if (uuidProperty != null) {
2359                         uuid = factories().getUuidFactory().create(uuidProperty.getFirstValue());
2360                     } else {
2361                         uuidProperty = graphProperties.get(ModeShapeLexicon.UUID);
2362                         uuid = factories().getUuidFactory().create(uuidProperty.getFirstValue());
2363                         // Recreate the property below as jcr:uuid
2364                         uuidProperty = null;
2365                     }
2366                 }
2367 
2368                 if (uuid != null && uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid);
2369 
2370                 // We know that this property is single-valued
2371                 JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, uuid);
2372                 JcrPropertyDefinition propDefn = nodeTypes().findPropertyDefinition(primaryTypeName,
2373                                                                                     mixinTypeNames,
2374                                                                                     JcrLexicon.UUID,
2375                                                                                     value,
2376                                                                                     false,
2377                                                                                     false);
2378                 PropertyInfo<JcrPropertyPayload> propInfo = createPropertyInfo(nodePayload,
2379                                                                                uuidProperty,
2380                                                                                propDefn,
2381                                                                                PropertyType.STRING,
2382                                                                                null);
2383                 props.put(JcrLexicon.UUID, propInfo);
2384             } else {
2385                 // Make sure there is NOT a "jcr:uuid" property ...
2386                 props.remove(JcrLexicon.UUID);
2387             }
2388             // Make sure the "dna:uuid" property did not get in there ...
2389             props.remove(ModeShapeLexicon.UUID);
2390 
2391             // Make sure the single-valued multi-property names are stored as a property ...
2392             if (newSingleMultiPropertyNames != null) {
2393                 PropertyInfo<JcrPropertyPayload> info = createSingleMultipleProperty(nodePayload,
2394                                                                                      null,
2395                                                                                      newSingleMultiPropertyNames);
2396                 props.put(info.getName(), info);
2397             }
2398 
2399             return props;
2400         }
2401 
2402         /**
2403          * {@inheritDoc}
2404          * 
2405          * @see org.modeshape.graph.session.GraphSession.Operations#materializeProperties(org.modeshape.graph.Node,
2406          *      org.modeshape.graph.session.GraphSession.Node)
2407          */
2408         @Override
2409         public void materializeProperties( org.modeshape.graph.Node persistentNode,
2410                                            Node<JcrNodePayload, JcrPropertyPayload> node ) {
2411 
2412             JcrNodePayload nodePayload = node.getPayload();
2413             boolean referenceable = false;
2414 
2415             try {
2416                 referenceable = isReferenceable(node);
2417             } catch (RepositoryException re) {
2418                 throw new IllegalStateException(re);
2419             }
2420 
2421             Map<Name, PropertyInfo<JcrPropertyPayload>> props = buildProperties(persistentNode, node, nodePayload, referenceable);
2422             // Set the information on the node ...
2423             node.loadedWith(props);
2424 
2425         }
2426 
2427         /**
2428          * {@inheritDoc}
2429          * 
2430          * @see org.modeshape.graph.session.GraphSession.Operations#materialize(org.modeshape.graph.Node,
2431          *      org.modeshape.graph.session.GraphSession.Node)
2432          */
2433         @Override
2434         public void materialize( org.modeshape.graph.Node persistentNode,
2435                                  Node<JcrNodePayload, JcrPropertyPayload> node ) {
2436             // Now get the ModeShape node's UUID and find the ModeShape property containing the UUID ...
2437             Location location = node.getLocation();
2438 
2439             // Look for the primary type of the node ...
2440             Map<Name, Property> graphProperties = persistentNode.getPropertiesByName();
2441             final boolean isRoot = location.getPath().isRoot();
2442             Name primaryTypeName = null;
2443             org.modeshape.graph.property.Property primaryTypeProperty = graphProperties.get(JcrLexicon.PRIMARY_TYPE);
2444             if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) {
2445                 try {
2446                     primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue());
2447                 } catch (ValueFormatException e) {
2448                     // use the default ...
2449                 }
2450             }
2451             if (primaryTypeName == null) {
2452                 // We have to have a primary type, so use the default ...
2453                 if (isRoot) {
2454                     primaryTypeName = ModeShapeLexicon.ROOT;
2455                     primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName);
2456                 } else {
2457                     primaryTypeName = defaultPrimaryTypeName;
2458                     primaryTypeProperty = defaultPrimaryTypeProperty;
2459                 }
2460             }
2461             assert primaryTypeProperty != null;
2462             assert primaryTypeProperty.isEmpty() == false;
2463 
2464             // Look for a node definition stored on the node ...
2465             JcrNodeDefinition definition = null;
2466             org.modeshape.graph.property.Property nodeDefnProperty = graphProperties.get(ModeShapeIntLexicon.NODE_DEFINITON);
2467             if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) {
2468                 String nodeDefinitionString = stringFactory.create(nodeDefnProperty.getFirstValue());
2469                 NodeDefinitionId id = NodeDefinitionId.fromString(nodeDefinitionString, nameFactory);
2470                 definition = nodeTypes().getNodeDefinition(id);
2471             }
2472             // Figure out the node definition for this node ...
2473             if (definition == null) {
2474                 if (isRoot) {
2475                     try {
2476                         definition = nodeTypes().getRootNodeDefinition();
2477                     } catch (RepositoryException e) {
2478                         // Shouldn't really happen ...
2479                         throw new ValidationException(e.getMessage(), e);
2480                     }
2481                 } else {
2482                     Name childName = node.getName();
2483                     Node<JcrNodePayload, JcrPropertyPayload> parent = node.getParent();
2484                     JcrNodePayload parentInfo = parent.getPayload();
2485                     int numExistingChildrenWithSameName = parent.getChildrenCount(childName);
2486                     // The children include this node, so we need to subtract one from the count so that the
2487                     // number of existing children is either 0 (if there are no other SNS nodes) or 1+
2488                     // (if there are at least 2 SNS nodes)
2489                     --numExistingChildrenWithSameName;
2490                     definition = nodeTypes().findChildNodeDefinition(parentInfo.getPrimaryTypeName(),
2491                                                                      parentInfo.getMixinTypeNames(),
2492                                                                      childName,
2493                                                                      primaryTypeName,
2494                                                                      numExistingChildrenWithSameName,
2495                                                                      false);
2496                 }
2497             }
2498             if (definition == null) {
2499                 String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(readable(node.getPath()),
2500                                                                                     workspaceName(),
2501                                                                                     sourceName());
2502                 throw new ValidationException(msg);
2503             }
2504 
2505             // ------------------------------------------------------
2506             // Set the node's properties ...
2507             // ------------------------------------------------------
2508             boolean referenceable = false;
2509 
2510             // Start with the primary type ...
2511             JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
2512             if (primaryType == null) {
2513                 Path path = location.getPath();
2514                 String msg = JcrI18n.missingNodeTypeForExistingNode.text(readable(primaryTypeName),
2515                                                                          readable(path),
2516                                                                          workspaceName(),
2517                                                                          sourceName());
2518                 throw new ValidationException(msg);
2519             }
2520             if (primaryType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true;
2521 
2522             // The process the mixin types ...
2523             Property mixinTypesProperty = graphProperties.get(JcrLexicon.MIXIN_TYPES);
2524             List<Name> mixinTypeNames = null;
2525             if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) {
2526                 for (Object mixinTypeValue : mixinTypesProperty) {
2527                     Name mixinTypeName = nameFactory.create(mixinTypeValue);
2528                     if (mixinTypeNames == null) mixinTypeNames = new LinkedList<Name>();
2529                     mixinTypeNames.add(mixinTypeName);
2530                     JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName);
2531                     if (mixinType == null) continue;
2532                     if (!referenceable && mixinType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true;
2533                 }
2534             }
2535 
2536             // Create the JCR Node payload object ...
2537             JcrNodePayload nodePayload = new JcrNodePayload(SessionCache.this, node, primaryTypeName, mixinTypeNames,
2538                                                             definition.getId());
2539 
2540             Map<Name, PropertyInfo<JcrPropertyPayload>> props = buildProperties(persistentNode, node, nodePayload, referenceable);
2541 
2542             // Set the information on the node ...
2543             node.loadedWith(persistentNode.getChildren(), props, persistentNode.getExpirationTime());
2544             node.setPayload(nodePayload);
2545         }
2546 
2547         /**
2548          * {@inheritDoc}
2549          * 
2550          * @see org.modeshape.graph.session.GraphSession.NodeOperations#postSetProperty(org.modeshape.graph.session.GraphSession.Node,
2551          *      org.modeshape.graph.property.Name, org.modeshape.graph.session.GraphSession.PropertyInfo)
2552          */
2553         @Override
2554         public void postSetProperty( Node<JcrNodePayload, JcrPropertyPayload> node,
2555                                      Name propertyName,
2556                                      PropertyInfo<JcrPropertyPayload> oldProperty ) {
2557             super.postSetProperty(node, propertyName, oldProperty);
2558 
2559             if (propertyName.equals(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES)) return;
2560             if (propertyName.equals(JcrLexicon.MIXIN_TYPES)) {
2561                 // Add all of the values from the property ...
2562                 Set<Name> mixinTypeNames = new HashSet<Name>();
2563                 NameFactory nameFactory = context().getValueFactories().getNameFactory();
2564                 for (Object value : node.getProperty(propertyName).getProperty()) {
2565                     mixinTypeNames.add(nameFactory.create(value));
2566                 }
2567                 node.setPayload(node.getPayload().with(new ArrayList<Name>(mixinTypeNames)));
2568             }
2569 
2570             // If the property is multi-valued but has only a single value, we need to record that this property
2571             // is actually a multi-valued property definition ...
2572             PropertyInfo<JcrPropertyPayload> changedProperty = node.getProperty(propertyName);
2573             if (changedProperty.isMultiValued()) {
2574                 // We're changing a multi-valued property ...
2575                 if (changedProperty.getProperty().isSingle()) {
2576                     // There's only one actual value in this property, so we record the name of this property in a hidden property
2577                     updateSingleMultipleProperty(node, propertyName, true);
2578                 } else {
2579                     // There are multiple actual values, so we don't need to name this property in the hidden property ...
2580                     updateSingleMultipleProperty(node, propertyName, false);
2581                 }
2582             }
2583         }
2584 
2585         /**
2586          * {@inheritDoc}
2587          * 
2588          * @see org.modeshape.graph.session.GraphSession.NodeOperations#preSave(org.modeshape.graph.session.GraphSession.Node,
2589          *      org.modeshape.graph.property.DateTime)
2590          */
2591         @Override
2592         public void preSave( org.modeshape.graph.session.GraphSession.Node<JcrNodePayload, JcrPropertyPayload> node,
2593                              DateTime saveTime ) throws ValidationException {
2594             JcrNodePayload payload = node.getPayload();
2595 
2596             Name primaryTypeName = payload.getPrimaryTypeName();
2597             List<Name> mixinTypeNames = payload.getMixinTypeNames();
2598             Set<JcrNodeDefinition> satisfiedChildNodes = new HashSet<JcrNodeDefinition>();
2599             Set<JcrPropertyDefinition> satisfiedProperties = new HashSet<JcrPropertyDefinition>();
2600 
2601             // Is this node referenceable ...
2602             boolean referenceable = false;
2603             try {
2604                 referenceable = isReferenceable(node);
2605             } catch (RepositoryException e) {
2606                 throw new ValidationException(e.getLocalizedMessage());
2607             }
2608             for (org.modeshape.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> property : node.getProperties()) {
2609                 if (property.getName().equals(JcrLexicon.UUID) && !referenceable) continue;
2610                 JcrPropertyPayload propPayload = property.getPayload();
2611                 JcrPropertyDefinition definition = findBestPropertyDefintion(primaryTypeName,
2612                                                                              mixinTypeNames,
2613                                                                              property.getProperty(),
2614                                                                              propPayload.getPropertyType(),
2615                                                                              property.getProperty().isSingle(),
2616                                                                              false);
2617                 if (definition == null) {
2618                     throw new ValidationException(JcrI18n.noDefinition.text("property",
2619                                                                             readable(property.getName()),
2620                                                                             readable(node.getPath()),
2621                                                                             readable(primaryTypeName),
2622                                                                             readable(mixinTypeNames)));
2623                 }
2624 
2625                 satisfiedProperties.add(definition);
2626             }
2627 
2628             for (org.modeshape.graph.session.GraphSession.Node<JcrNodePayload, JcrPropertyPayload> child : node.getChildren()) {
2629                 int snsCount = node.getChildrenCount(child.getName());
2630                 JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(primaryTypeName,
2631                                                                                    mixinTypeNames,
2632                                                                                    child.getName(),
2633                                                                                    child.getPayload().getPrimaryTypeName(),
2634                                                                                    snsCount,
2635                                                                                    false);
2636                 if (definition == null) {
2637                     throw new ValidationException(JcrI18n.noDefinition.text("child node",
2638                                                                             readable(child.getName()),
2639                                                                             readable(node.getPath()),
2640                                                                             readable(primaryTypeName),
2641                                                                             readable(mixinTypeNames)));
2642                 }
2643                 satisfiedChildNodes.add(definition);
2644             }
2645 
2646             JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
2647             boolean isLastModifiedType = primaryType.isNodeType(JcrMixLexicon.LAST_MODIFIED);
2648             boolean isCreatedType = primaryType.isNodeType(JcrMixLexicon.CREATED);
2649             boolean isETag = primaryType.isNodeType(JcrMixLexicon.ETAG);
2650             for (JcrPropertyDefinition definition : primaryType.getPropertyDefinitions()) {
2651                 if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) {
2652                     throw new ValidationException(JcrI18n.noDefinition.text("property",
2653                                                                             definition.getName(),
2654                                                                             readable(node.getPath()),
2655                                                                             readable(primaryTypeName),
2656                                                                             readable(mixinTypeNames)));
2657                 }
2658             }
2659             for (JcrNodeDefinition definition : primaryType.getChildNodeDefinitions()) {
2660                 if (definition.isMandatory() && !definition.isProtected() && !satisfiedChildNodes.contains(definition)) {
2661                     throw new ValidationException(JcrI18n.noDefinition.text("child node",
2662                                                                             definition.getName(),
2663                                                                             readable(node.getPath()),
2664                                                                             readable(primaryTypeName),
2665                                                                             readable(mixinTypeNames)));
2666                 }
2667             }
2668 
2669             if (mixinTypeNames != null) {
2670                 for (Name mixinTypeName : mixinTypeNames) {
2671                     JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName);
2672                     isLastModifiedType = isLastModifiedType || mixinType.isNodeType(JcrMixLexicon.LAST_MODIFIED);
2673                     isCreatedType = isCreatedType || mixinType.isNodeType(JcrMixLexicon.CREATED);
2674                     isETag = isETag || mixinType.isNodeType(JcrMixLexicon.ETAG);
2675                     for (JcrPropertyDefinition definition : mixinType.getPropertyDefinitions()) {
2676                         if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) {
2677                             throw new ValidationException(JcrI18n.noDefinition.text("child node",
2678                                                                                     definition.getName(),
2679                                                                                     readable(node.getPath()),
2680                                                                                     readable(primaryTypeName),
2681                                                                                     readable(mixinTypeNames)));
2682                         }
2683                     }
2684                     for (JcrNodeDefinition definition : mixinType.getChildNodeDefinitions()) {
2685                         if (definition.isMandatory() && !definition.isProtected() && !satisfiedChildNodes.contains(definition)) {
2686                             throw new ValidationException(JcrI18n.noDefinition.text("child node",
2687                                                                                     definition.getName(),
2688                                                                                     readable(node.getPath()),
2689                                                                                     readable(primaryTypeName),
2690                                                                                     readable(mixinTypeNames)));
2691                         }
2692                     }
2693 
2694                 }
2695             }
2696 
2697             // Do we need to update the 'jcr:etag' property?
2698             if (isETag) {
2699                 // Per section 3.7.12 of JCR 2, this property should be changed whenever BINARY properties are added, removed, or
2700                 // changed. So, go through the properties (in sorted-name order so it is repeatable) and create this value
2701                 // by simply concatenating the SHA-1 hash of each BINARY value ...
2702                 List<Name> binaryPropertyNames = new ArrayList<Name>();
2703                 for (org.modeshape.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> property : node.getProperties()) {
2704                     if (property.getProperty().size() == 0) continue;
2705                     if (property.getPayload().getPropertyType() != PropertyType.BINARY) continue;
2706                     binaryPropertyNames.add(property.getName());
2707                 }
2708                 StringBuilder sb = new StringBuilder();
2709                 if (!binaryPropertyNames.isEmpty()) {
2710                     Collections.sort(binaryPropertyNames);
2711                     BinaryFactory binaryFactory = context().getValueFactories().getBinaryFactory();
2712                     for (Name name : binaryPropertyNames) {
2713                         org.modeshape.graph.session.GraphSession.PropertyInfo<JcrPropertyPayload> property = node.getProperty(name);
2714                         for (Object value : property.getProperty()) {
2715                             Binary binary = binaryFactory.create(value);
2716                             String hash = new String(binary.getHash()); // doesn't matter what charset, as long as its always the
2717                             // same
2718                             sb.append(hash);
2719                         }
2720                     }
2721                 }
2722                 String etagValue = sb.toString(); // may be empty
2723                 setProperty(node, primaryTypeName, mixinTypeNames, false, JcrLexicon.ETAG, PropertyType.STRING, etagValue);
2724             }
2725 
2726             // See if the node is an instance of 'mix:created'.
2727             // This is done even if the node is not newly-created, because this needs to happen whenever the
2728             // 'mix:created' node type is added as a mixin (which can happen to an existing node).
2729             if (isCreatedType) {
2730                 setPropertyIfAbsent(node, primaryTypeName, mixinTypeNames, false, JcrLexicon.CREATED, PropertyType.DATE, saveTime);
2731                 setPropertyIfAbsent(node,
2732                                     primaryTypeName,
2733                                     mixinTypeNames,
2734                                     false,
2735                                     JcrLexicon.CREATED_BY,
2736                                     PropertyType.STRING,
2737                                     user);
2738             }
2739 
2740             // See if the node is an instance of 'mix:lastModified' ...
2741             if (isLastModifiedType) {
2742                 // Check to see if the 'jcr:lastModified' or 'jcr:lastModifiedBy' properties were explicitly changed ...
2743                 setPropertyIfAbsent(node,
2744                                     primaryTypeName,
2745                                     mixinTypeNames,
2746                                     false,
2747                                     JcrLexicon.LAST_MODIFIED,
2748                                     PropertyType.DATE,
2749                                     saveTime);
2750                 setPropertyIfAbsent(node,
2751                                     primaryTypeName,
2752                                     mixinTypeNames,
2753                                     false,
2754                                     JcrLexicon.LAST_MODIFIED_BY,
2755                                     PropertyType.STRING,
2756                                     user);
2757             }
2758         }
2759 
2760         protected void setPropertyIfAbsent( org.modeshape.graph.session.GraphSession.Node<JcrNodePayload, JcrPropertyPayload> node,
2761                                             Name primaryTypeName,
2762                                             List<Name> mixinTypeNames,
2763                                             boolean skipProtected,
2764                                             Name propertyName,
2765                                             int propertyType,
2766                                             Object value ) {
2767             if (node.getProperty(propertyName) != null) return;
2768             setProperty(node, primaryTypeName, mixinTypeNames, skipProtected, propertyName, propertyType, value);
2769         }
2770 
2771         protected void setProperty( org.modeshape.graph.session.GraphSession.Node<JcrNodePayload, JcrPropertyPayload> node,
2772                                     Name primaryTypeName,
2773                                     List<Name> mixinTypeNames,
2774                                     boolean skipProtected,
2775                                     Name propertyName,
2776                                     int propertyType,
2777                                     Object value ) {
2778             Property graphProp = propertyFactory.create(propertyName, value);
2779             JcrPropertyDefinition propDefn = findBestPropertyDefintion(primaryTypeName,
2780                                                                        mixinTypeNames,
2781                                                                        graphProp,
2782                                                                        propertyType,
2783                                                                        true,
2784                                                                        skipProtected);
2785             AbstractJcrNode jcrNode = node.getPayload().getJcrNode();
2786             AbstractJcrProperty jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, propertyName);
2787             JcrPropertyPayload propPayload = new JcrPropertyPayload(propDefn.getId(), propertyType, jcrProp);
2788             node.setProperty(graphProp, false, propPayload);
2789         }
2790 
2791         @Override
2792         public void compute( Graph.Batch batch,
2793                              Node<JcrNodePayload, JcrPropertyPayload> node ) {
2794             try {
2795                 JcrWorkspace workspace = session().workspace();
2796 
2797                 // Some tests don't set this up.
2798                 if (workspace != null) {
2799                     workspace.versionManager().initializeVersionHistoryFor(batch, node, null, false);
2800                 }
2801             } catch (RepositoryException re) {
2802                 throw new IllegalStateException(re);
2803             }
2804         }
2805 
2806         /**
2807          * {@inheritDoc}
2808          * 
2809          * @see org.modeshape.graph.session.GraphSession.NodeOperations#postCreateChild(org.modeshape.graph.session.GraphSession.Node,
2810          *      org.modeshape.graph.session.GraphSession.Node, java.util.Map)
2811          */
2812         @Override
2813         public void postCreateChild( Node<JcrNodePayload, JcrPropertyPayload> parent,
2814                                      Node<JcrNodePayload, JcrPropertyPayload> child,
2815                                      Map<Name, PropertyInfo<JcrPropertyPayload>> properties ) throws ValidationException {
2816             super.postCreateChild(parent, child, properties);
2817             // Populate the node and properties with the payloads ...
2818 
2819             // Get the 2 properties that WILL be here ...
2820             PropertyInfo<JcrPropertyPayload> primaryTypeInfo = properties.get(JcrLexicon.PRIMARY_TYPE);
2821             PropertyInfo<JcrPropertyPayload> nodeDefnInfo = properties.get(ModeShapeIntLexicon.NODE_DEFINITON);
2822             Name primaryTypeName = nameFactory().create(primaryTypeInfo.getProperty().getFirstValue());
2823             String nodeDefnIdStr = stringFactory().create(nodeDefnInfo.getProperty().getFirstValue());
2824             NodeDefinitionId nodeDefnId = NodeDefinitionId.fromString(nodeDefnIdStr, nameFactory);
2825 
2826             // Now create the payload ...
2827             JcrNodePayload nodePayload = new JcrNodePayload(SessionCache.this, child, primaryTypeName, null, nodeDefnId);
2828             child.setPayload(nodePayload);
2829 
2830             // Now update the property infos for the two mandatory properties ...
2831             JcrNodeType ntBase = nodeTypes().getNodeType(JcrNtLexicon.BASE);
2832             assert ntBase != null;
2833             primaryTypeInfo = createPropertyInfo(child.getPayload(),
2834                                                  primaryTypeInfo.getProperty(),
2835                                                  ntBase.allPropertyDefinitions(JcrLexicon.PRIMARY_TYPE).iterator().next(),
2836                                                  PropertyType.NAME,
2837                                                  primaryTypeInfo);
2838             properties.put(primaryTypeInfo.getName(), primaryTypeInfo);
2839             nodeDefnInfo = createPropertyInfo(child.getPayload(),
2840                                               nodeDefnInfo.getProperty(),
2841                                               ntBase.allPropertyDefinitions(ModeShapeIntLexicon.NODE_DEFINITON).iterator().next(),
2842                                               PropertyType.STRING,
2843                                               nodeDefnInfo);
2844             properties.put(nodeDefnInfo.getName(), nodeDefnInfo);
2845 
2846             // The UUID property is optional ...
2847             PropertyInfo<JcrPropertyPayload> uuidInfo = properties.get(JcrLexicon.UUID);
2848             if (uuidInfo != null) {
2849                 JcrNodeType mixRef = nodeTypes().getNodeType(JcrMixLexicon.REFERENCEABLE);
2850                 assert mixRef != null;
2851                 uuidInfo = createPropertyInfo(child.getPayload(),
2852                                               uuidInfo.getProperty(),
2853                                               mixRef.allPropertyDefinitions(JcrLexicon.UUID).iterator().next(),
2854                                               PropertyType.STRING,
2855                                               uuidInfo);
2856                 properties.put(uuidInfo.getName(), uuidInfo);
2857             }
2858         }
2859 
2860         protected final Set<Name> getSingleMultiPropertyNames( Property dnaProperty,
2861                                                                Location location ) {
2862             Set<Name> multiValuedPropertyNames = new HashSet<Name>();
2863             for (Object value : dnaProperty) {
2864                 try {
2865                     multiValuedPropertyNames.add(nameFactory.create(value));
2866                 } catch (ValueFormatException e) {
2867                     String msg = "{0} value \"{1}\" on {2} in \"{3}\" workspace is not a valid name and is being ignored";
2868                     LOGGER.trace(e,
2869                                  msg,
2870                                  readable(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES),
2871                                  value,
2872                                  readable(location),
2873                                  workspaceName());
2874                 }
2875             }
2876             return multiValuedPropertyNames;
2877         }
2878     }
2879 
2880     @Immutable
2881     final class JcrAuthorizer implements GraphSession.Authorizer {
2882         /**
2883          * {@inheritDoc}
2884          * 
2885          * @see org.modeshape.graph.session.GraphSession.Authorizer#checkPermissions(org.modeshape.graph.property.Path,
2886          *      org.modeshape.graph.session.GraphSession.Authorizer.Action)
2887          */
2888         public void checkPermissions( Path path,
2889                                       Action action ) throws AccessControlException {
2890             String jcrAction = null;
2891             switch (action) {
2892                 case ADD_NODE:
2893                     jcrAction = ModeShapePermissions.ADD_NODE;
2894                     break;
2895                 case READ:
2896                     jcrAction = ModeShapePermissions.READ;
2897                     break;
2898                 case REMOVE:
2899                     jcrAction = ModeShapePermissions.REMOVE;
2900                     break;
2901                 case SET_PROPERTY:
2902                     jcrAction = ModeShapePermissions.SET_PROPERTY;
2903                     break;
2904             }
2905             session().checkPermission(path, jcrAction);
2906         }
2907     }
2908 
2909     @Immutable
2910     final static class JcrPropertyPayload {
2911         private final PropertyDefinitionId propertyDefinitionId;
2912         private final int jcrPropertyType;
2913         private final AbstractJcrProperty jcrProperty;
2914 
2915         JcrPropertyPayload( PropertyDefinitionId propertyDefinitionId,
2916                             int jcrPropertyType,
2917                             AbstractJcrProperty jcrProperty ) {
2918             assert jcrProperty != null;
2919             this.propertyDefinitionId = propertyDefinitionId;
2920             this.jcrPropertyType = jcrPropertyType;
2921             this.jcrProperty = jcrProperty;
2922         }
2923 
2924         /**
2925          * @return jcrProperty
2926          */
2927         public AbstractJcrProperty getJcrProperty() {
2928             return jcrProperty;
2929         }
2930 
2931         /**
2932          * @return jcrPropertyType
2933          */
2934         public int getPropertyType() {
2935             return jcrPropertyType;
2936         }
2937 
2938         /**
2939          * @return propertyDefinitionId
2940          */
2941         public PropertyDefinitionId getPropertyDefinitionId() {
2942             return propertyDefinitionId;
2943         }
2944 
2945         public JcrPropertyPayload with( PropertyDefinitionId propertyDefinitionId ) {
2946             return new JcrPropertyPayload(propertyDefinitionId, jcrPropertyType, jcrProperty);
2947         }
2948 
2949         public JcrPropertyPayload with( int jcrPropertyType ) {
2950             return new JcrPropertyPayload(propertyDefinitionId, jcrPropertyType, jcrProperty);
2951         }
2952 
2953         public JcrPropertyPayload with( AbstractJcrProperty jcrProperty ) {
2954             return new JcrPropertyPayload(propertyDefinitionId, jcrPropertyType, jcrProperty);
2955         }
2956     }
2957 
2958     @Immutable
2959     final static class JcrNodePayload {
2960         private final SessionCache cache;
2961         private final Node<JcrNodePayload, JcrPropertyPayload> owner;
2962         private final Name primaryTypeName;
2963         private final List<Name> mixinTypeNames;
2964         private final NodeDefinitionId nodeDefinitionId;
2965         private SoftReference<AbstractJcrNode> jcrNode;
2966 
2967         JcrNodePayload( SessionCache cache,
2968                         Node<JcrNodePayload, JcrPropertyPayload> owner,
2969                         Name primaryTypeName,
2970                         List<Name> mixinTypeNames,
2971                         NodeDefinitionId nodeDefinitionId ) {
2972             assert owner != null;
2973             assert cache != null;
2974             this.cache = cache;
2975             this.owner = owner;
2976             this.primaryTypeName = primaryTypeName;
2977             this.mixinTypeNames = mixinTypeNames;
2978             this.nodeDefinitionId = nodeDefinitionId;
2979             this.jcrNode = new SoftReference<AbstractJcrNode>(null);
2980         }
2981 
2982         JcrNodePayload( SessionCache cache,
2983                         Node<JcrNodePayload, JcrPropertyPayload> owner,
2984                         Name primaryTypeName,
2985                         List<Name> mixinTypeNames,
2986                         NodeDefinitionId nodeDefinitionId,
2987                         SoftReference<AbstractJcrNode> jcrNode ) {
2988             assert jcrNode != null;
2989             assert owner != null;
2990             assert cache != null;
2991             this.cache = cache;
2992             this.owner = owner;
2993             this.primaryTypeName = primaryTypeName;
2994             this.mixinTypeNames = mixinTypeNames;
2995             this.nodeDefinitionId = nodeDefinitionId;
2996             this.jcrNode = jcrNode;
2997         }
2998 
2999         /**
3000          * @return primaryTypeName
3001          */
3002         public Name getPrimaryTypeName() {
3003             return this.primaryTypeName;
3004         }
3005 
3006         /**
3007          * Get the names of the mixin types for this node.
3008          * 
3009          * @return the unmodifiable list of mixin type names; never null but possibly empty
3010          */
3011         public List<Name> getMixinTypeNames() {
3012             return this.mixinTypeNames != null ? this.mixinTypeNames : Collections.<Name>emptyList();
3013         }
3014 
3015         /**
3016          * @return definition
3017          */
3018         public NodeDefinitionId getDefinitionId() {
3019             return this.nodeDefinitionId;
3020         }
3021 
3022         /**
3023          * Get the JCR node instance.
3024          * 
3025          * @return jcrNode
3026          */
3027         public AbstractJcrNode getJcrNode() {
3028             AbstractJcrNode node = jcrNode.get();
3029             if (node == null) {
3030                 if (owner.isRoot()) {
3031                     node = new JcrRootNode(cache, owner.getNodeId(), owner.getLocation());
3032                 } else {
3033                     node = new JcrNode(cache, owner.getNodeId(), owner.getLocation());
3034                 }
3035                 jcrNode = new SoftReference<AbstractJcrNode>(node);
3036             }
3037 
3038             if (JcrNtLexicon.VERSION.equals(primaryTypeName)) {
3039                 return new JcrVersionNode(jcrNode.get());
3040             }
3041             if (JcrNtLexicon.VERSION_HISTORY.equals(primaryTypeName)) {
3042                 return new JcrVersionHistoryNode(jcrNode.get());
3043             }
3044 
3045             return jcrNode.get();
3046         }
3047 
3048         public JcrNodePayload with( Name primaryTypeName ) {
3049             return new JcrNodePayload(cache, owner, primaryTypeName, mixinTypeNames, nodeDefinitionId, jcrNode);
3050         }
3051 
3052         public JcrNodePayload with( List<Name> mixinTypeNames ) {
3053             return new JcrNodePayload(cache, owner, primaryTypeName, mixinTypeNames, nodeDefinitionId, jcrNode);
3054         }
3055 
3056         public JcrNodePayload with( NodeDefinitionId nodeDefinitionId ) {
3057             return new JcrNodePayload(cache, owner, primaryTypeName, mixinTypeNames, nodeDefinitionId, jcrNode);
3058         }
3059 
3060         public JcrNodePayload with( AbstractJcrNode jcrNode ) {
3061             return new JcrNodePayload(cache, owner, primaryTypeName, mixinTypeNames, nodeDefinitionId,
3062                                       new SoftReference<AbstractJcrNode>(jcrNode));
3063         }
3064     }
3065 
3066 }