View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.io.InputStream;
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.HashSet;
33  import java.util.Iterator;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Set;
37  import java.util.UUID;
38  import java.util.regex.Pattern;
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.ItemVisitor;
45  import javax.jcr.NoSuchWorkspaceException;
46  import javax.jcr.NodeIterator;
47  import javax.jcr.PathNotFoundException;
48  import javax.jcr.Property;
49  import javax.jcr.PropertyIterator;
50  import javax.jcr.PropertyType;
51  import javax.jcr.RepositoryException;
52  import javax.jcr.UnsupportedRepositoryOperationException;
53  import javax.jcr.Value;
54  import javax.jcr.ValueFormatException;
55  import javax.jcr.lock.Lock;
56  import javax.jcr.lock.LockException;
57  import javax.jcr.nodetype.ConstraintViolationException;
58  import javax.jcr.nodetype.NoSuchNodeTypeException;
59  import javax.jcr.nodetype.NodeDefinition;
60  import javax.jcr.nodetype.NodeType;
61  import javax.jcr.nodetype.NodeTypeManager;
62  import javax.jcr.query.Query;
63  import javax.jcr.query.QueryResult;
64  import javax.jcr.version.Version;
65  import javax.jcr.version.VersionException;
66  import net.jcip.annotations.Immutable;
67  import org.modeshape.common.i18n.I18n;
68  import org.modeshape.common.util.CheckArg;
69  import org.modeshape.common.util.HashCode;
70  import org.modeshape.graph.Location;
71  import org.modeshape.graph.connector.RepositorySourceException;
72  import org.modeshape.graph.property.Binary;
73  import org.modeshape.graph.property.DateTime;
74  import org.modeshape.graph.property.Name;
75  import org.modeshape.graph.property.NamespaceRegistry;
76  import org.modeshape.graph.property.Path;
77  import org.modeshape.graph.property.PathFactory;
78  import org.modeshape.graph.property.ValueFactories;
79  import org.modeshape.graph.query.QueryBuilder;
80  import org.modeshape.graph.query.model.QueryCommand;
81  import org.modeshape.graph.session.GraphSession.Node;
82  import org.modeshape.graph.session.GraphSession.NodeId;
83  import org.modeshape.graph.session.GraphSession.PropertyInfo;
84  import org.modeshape.jcr.SessionCache.JcrNodePayload;
85  import org.modeshape.jcr.SessionCache.JcrPropertyPayload;
86  import org.modeshape.jcr.SessionCache.NodeEditor;
87  
88  /**
89   * An abstract implementation of the JCR {@link javax.jcr.Node} interface. Instances of this class are created and managed by the
90   * {@link SessionCache}. Each instance indirectly references the {@link javax.jcr.Node node information} also managed by the
91   * SessionCache, and finds and operates against this information with each method call.
92   */
93  @Immutable
94  abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node {
95  
96      private static final NodeType[] EMPTY_NODE_TYPES = new NodeType[] {};
97  
98      protected final NodeId nodeId;
99      protected final Location location;
100 
101     AbstractJcrNode( SessionCache cache,
102                      NodeId nodeId,
103                      Location location ) {
104         super(cache);
105         this.nodeId = nodeId;
106         this.location = location;
107     }
108 
109     abstract boolean isRoot();
110 
111     public abstract AbstractJcrNode getParent() throws ItemNotFoundException, RepositoryException;
112 
113     final NodeId internalId() {
114         return nodeId;
115     }
116 
117     final Name name() throws RepositoryException {
118         return nodeInfo().getName();
119     }
120 
121     final Path.Segment segment() throws RepositoryException {
122         return nodeInfo().getSegment();
123     }
124 
125     final Node<JcrNodePayload, JcrPropertyPayload> nodeInfo()
126         throws InvalidItemStateException, AccessDeniedException, RepositoryException {
127         try {
128             return cache.findNode(nodeId, location.getPath());
129         } catch (ItemNotFoundException infe) {
130             throw new InvalidItemStateException(infe.getMessage());
131         }
132     }
133 
134     final NodeEditor editorForParent() throws RepositoryException {
135         try {
136             Node<JcrNodePayload, JcrPropertyPayload> parent = nodeInfo().getParent();
137             return cache.getEditorFor(parent);
138         } catch (ItemNotFoundException err) {
139             String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeId, cache.workspaceName());
140             throw new RepositoryException(msg);
141         } catch (InvalidItemStateException err) {
142             String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeId, cache.workspaceName());
143             throw new RepositoryException(msg);
144         }
145     }
146 
147     final NodeEditor editor() throws RepositoryException {
148         try {
149             return cache.getEditorFor(nodeId, location.getPath());
150         } catch (ItemNotFoundException err) {
151             String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeId, cache.workspaceName());
152             throw new RepositoryException(msg);
153         } catch (InvalidItemStateException err) {
154             String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeId, cache.workspaceName());
155             throw new RepositoryException(msg);
156         }
157     }
158 
159     final JcrValue valueFrom( int propertyType,
160                               Object value ) {
161         return new JcrValue(cache.factories(), cache, propertyType, value);
162     }
163 
164     final JcrValue valueFrom( Calendar value ) {
165         ValueFactories factories = cache.factories();
166         DateTime dateTime = factories.getDateFactory().create(value);
167         return new JcrValue(factories, cache, PropertyType.DATE, dateTime);
168     }
169 
170     final JcrValue valueFrom( InputStream value ) {
171         ValueFactories factories = cache.factories();
172         Binary binary = factories.getBinaryFactory().create(value);
173         return new JcrValue(factories, cache, PropertyType.DATE, binary);
174     }
175 
176     final JcrValue valueFrom( javax.jcr.Node value ) throws UnsupportedRepositoryOperationException, RepositoryException {
177         ValueFactories factories = cache.factories();
178         String uuid = factories.getStringFactory().create(value.getUUID());
179         return new JcrValue(factories, cache, PropertyType.REFERENCE, uuid);
180     }
181 
182     final JcrValue[] valuesFrom( int propertyType,
183                                  Object[] values ) {
184         /*
185          * Null values in the array are "compacted" (read: ignored) as per section 7.1.6 in the JCR 1.0.1 specification. 
186          */
187         int len = values.length;
188         ValueFactories factories = cache.factories();
189         List<JcrValue> results = new ArrayList<JcrValue>(len);
190         for (int i = 0; i != len; ++i) {
191             if (values[i] != null) results.add(new JcrValue(factories, cache, propertyType, values[i]));
192         }
193         return results.toArray(new JcrValue[results.size()]);
194     }
195 
196     @Override
197     Path path() throws RepositoryException {
198         // Don't use the path in the location, since it may no longer be valid
199         return nodeInfo().getPath();
200     }
201 
202     boolean isReferenceable() throws RepositoryException {
203         return isNodeType(JcrMixLexicon.REFERENCEABLE);
204     }
205 
206     boolean isLockable() throws RepositoryException {
207         return isNodeType(JcrMixLexicon.LOCKABLE);
208     }
209 
210     UUID uuid() throws RepositoryException {
211         PropertyInfo<JcrPropertyPayload> uuidProp = nodeInfo().getProperty(JcrLexicon.UUID);
212         if (uuidProp == null) {
213             uuidProp = nodeInfo().getProperty(ModeShapeLexicon.UUID);
214         }
215         assert uuidProp != null;
216         assert !uuidProp.getProperty().isEmpty();
217         return context().getValueFactories().getUuidFactory().create(uuidProp.getProperty().getFirstValue());
218     }
219 
220     /**
221      * {@inheritDoc}
222      * 
223      * @see javax.jcr.Node#getUUID()
224      */
225     public String getUUID() throws RepositoryException {
226         // Return "jcr:uuid" only if node is referenceable
227         if (!isReferenceable()) {
228             throw new UnsupportedRepositoryOperationException(JcrI18n.nodeNotReferenceable.text());
229         }
230 
231         return uuid().toString();
232     }
233 
234     /**
235      * {@inheritDoc}
236      * 
237      * @return <code>true</code>
238      * @see javax.jcr.Item#isNode()
239      */
240     public final boolean isNode() {
241         return true;
242     }
243 
244     /**
245      * {@inheritDoc}
246      * 
247      * @return <code>false</code>
248      * @see javax.jcr.Node#isNodeType(java.lang.String)
249      */
250     public boolean isNodeType( String nodeTypeName ) throws RepositoryException {
251         return isNodeType(nameFrom(nodeTypeName));
252     }
253 
254     /**
255      * Determine whether this node's primary type or any of the mixins are or extend the node type with the supplied name. This
256      * method is semantically equivalent to but slightly more efficient than the {@link #isNodeType(String) equivalent in the JCR
257      * API}, especially when the node type name is already a {@link Name} object.
258      * 
259      * @param nodeTypeName the name of the node type
260      * @return true if this node is of the node type given by the supplied name, or false otherwise
261      * @throws RepositoryException if there is an exception
262      */
263     public final boolean isNodeType( Name nodeTypeName ) throws RepositoryException {
264         return cache.isNodeType(nodeInfo(), nodeTypeName);
265     }
266 
267     /**
268      * {@inheritDoc}
269      * 
270      * @see javax.jcr.Node#getDefinition()
271      */
272     public NodeDefinition getDefinition() throws RepositoryException {
273         NodeDefinitionId definitionId = nodeInfo().getPayload().getDefinitionId();
274         return session().nodeTypeManager().getNodeDefinition(definitionId);
275     }
276 
277     /**
278      * {@inheritDoc}
279      * 
280      * @see javax.jcr.Node#getPrimaryNodeType()
281      */
282     public JcrNodeType getPrimaryNodeType() throws RepositoryException {
283         return session().nodeTypeManager().getNodeType(getPrimaryTypeName());
284     }
285 
286     Name getPrimaryTypeName() throws RepositoryException {
287         return nodeInfo().getPayload().getPrimaryTypeName();
288     }
289 
290     /**
291      * {@inheritDoc}
292      * 
293      * @see javax.jcr.Node#getMixinNodeTypes()
294      */
295     public NodeType[] getMixinNodeTypes() throws RepositoryException {
296         NodeTypeManager nodeTypeManager = session().nodeTypeManager();
297         Property mixinTypesProperty = getProperty(JcrLexicon.MIXIN_TYPES);
298         if (mixinTypesProperty == null) return EMPTY_NODE_TYPES;
299         List<NodeType> mixinNodeTypes = new LinkedList<NodeType>();
300         for (Value value : mixinTypesProperty.getValues()) {
301             String nodeTypeName = value.getString();
302             NodeType nodeType = nodeTypeManager.getNodeType(nodeTypeName);
303             if (nodeType != null) mixinNodeTypes.add(nodeType);
304         }
305         return mixinNodeTypes.toArray(new NodeType[mixinNodeTypes.size()]);
306     }
307 
308     List<Name> getMixinTypeNames() throws RepositoryException {
309         return nodeInfo().getPayload().getMixinTypeNames();
310     }
311 
312     /**
313      * {@inheritDoc}
314      * 
315      * @see javax.jcr.Node#getPrimaryItem()
316      */
317     public final Item getPrimaryItem() throws RepositoryException {
318         // Get the primary item name from this node's type ...
319         NodeType primaryType = getPrimaryNodeType();
320         String primaryItemNameString = primaryType.getPrimaryItemName();
321         if (primaryItemNameString == null) {
322             I18n msg = JcrI18n.noPrimaryItemNameDefinedOnPrimaryType;
323             throw new ItemNotFoundException(msg.text(primaryType.getName(), getPath(), cache.workspaceName()));
324         }
325         try {
326             Path primaryItemPath = context().getValueFactories().getPathFactory().create(primaryItemNameString);
327             if (primaryItemPath.size() != 1 || primaryItemPath.isAbsolute()) {
328                 I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid;
329                 throw new ItemNotFoundException(msg.text(primaryType.getName(),
330                                                          primaryItemNameString,
331                                                          getPath(),
332                                                          cache.workspaceName()));
333             }
334             return cache.findJcrItem(nodeId, location.getPath(), primaryItemPath);
335         } catch (ValueFormatException error) {
336             I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid;
337             throw new ItemNotFoundException(msg.text(primaryType.getName(),
338                                                      primaryItemNameString,
339                                                      getPath(),
340                                                      cache.workspaceName()));
341         } catch (PathNotFoundException error) {
342             I18n msg = JcrI18n.primaryItemDoesNotExist;
343             throw new ItemNotFoundException(msg.text(primaryType.getName(),
344                                                      primaryItemNameString,
345                                                      getPath(),
346                                                      cache.workspaceName()));
347         }
348     }
349 
350     /**
351      * {@inheritDoc}
352      * 
353      * @throws IllegalArgumentException if <code>otherItem</code> is <code>null</code>.
354      * @see javax.jcr.Item#isSame(javax.jcr.Item)
355      */
356     @Override
357     public boolean isSame( Item otherItem ) throws RepositoryException {
358         CheckArg.isNotNull(otherItem, "otherItem");
359         if (super.isSame(otherItem) && otherItem instanceof javax.jcr.Node) {
360             if (otherItem instanceof AbstractJcrNode) {
361                 AbstractJcrNode that = (AbstractJcrNode)otherItem;
362                 if (this.isReferenceable() && that.isReferenceable()) {
363                     // Both are referenceable, so compare the UUIDs ...
364                     return getUUID().equals(((AbstractJcrNode)otherItem).getUUID());
365                 }
366 
367                 // One or both are not referenceable, so find the nearest ancestor that is referenceable.
368                 // The correspondence identifier (per Section 4.10.2 of JSR-170, version 1.0.1) for a
369                 // non-referenceable node is the pair of the UUID of the nearest referenceable ancestor and
370                 // the relative path from that referenceable ancestor. Per Section 6.2.8, two non-referenceable
371                 // nodes are the same if they have the same correspondence identifier.
372                 CorrespondenceId thisId = this.getCorrespondenceId();
373                 CorrespondenceId thatId = that.getCorrespondenceId();
374                 return thisId.equals(thatId);
375             }
376             // If not our implementation, let the other item figure out whether we are the same.
377             return otherItem.isSame(this);
378         }
379         return false;
380     }
381 
382     protected CorrespondenceId getCorrespondenceId() throws RepositoryException {
383         if (this.isReferenceable()) return new CorrespondenceId(getUUID());
384         assert !this.isRoot(); // the root must be referenceable
385 
386         // Find the nearest ancestor that is referenceable ...
387         Path currentPath = path();
388         AbstractJcrNode node = this.getParent();
389         int beginIndex = currentPath.size() - 1;
390         while (!node.isRoot() && !node.isReferenceable()) {
391             node = node.getParent();
392             --beginIndex;
393         }
394         // Get the relative path from the ancestor to this node ...
395         Path relativePath = currentPath.relativeTo(node.path());
396         assert !relativePath.isAbsolute();
397         return new CorrespondenceId(node.getUUID(), relativePath);
398     }
399 
400     /**
401      * {@inheritDoc}
402      * 
403      * @see javax.jcr.Node#hasProperties()
404      */
405     public final boolean hasProperties() throws RepositoryException {
406         return nodeInfo().getPropertyCount() > 0;
407     }
408 
409     /**
410      * {@inheritDoc}
411      * 
412      * @throws IllegalArgumentException if <code>relativePath</code> is empty or <code>null</code>.
413      * @see javax.jcr.Node#hasProperty(java.lang.String)
414      */
415     public final boolean hasProperty( String relativePath ) throws RepositoryException {
416         CheckArg.isNotEmpty(relativePath, "relativePath");
417         if (relativePath.indexOf('/') >= 0) {
418             try {
419                 getProperty(relativePath);
420                 return true;
421             } catch (PathNotFoundException e) {
422                 return false;
423             }
424         }
425         if (relativePath.equals(".")) return false;
426         if (relativePath.equals("..")) return false;
427         // Otherwise it should be a property on this node ...
428         return nodeInfo().getProperty(nameFrom(relativePath)) != null;
429     }
430 
431     public final boolean hasProperty( Name name ) throws RepositoryException {
432         return nodeInfo().getProperty(name) != null;
433     }
434 
435     /**
436      * {@inheritDoc}
437      * 
438      * @see javax.jcr.Node#getProperties()
439      */
440     public final PropertyIterator getProperties() throws RepositoryException {
441         return new JcrPropertyIterator(cache.findJcrPropertiesFor(nodeId, location.getPath()));
442     }
443 
444     /**
445      * {@inheritDoc}
446      * 
447      * @see javax.jcr.Node#getProperties(java.lang.String)
448      */
449     public PropertyIterator getProperties( String namePattern ) throws RepositoryException {
450         CheckArg.isNotNull(namePattern, "namePattern");
451         namePattern = namePattern.trim();
452         if (namePattern.length() == 0) return new JcrEmptyPropertyIterator();
453         if ("*".equals(namePattern)) {
454             Collection<AbstractJcrProperty> properties = cache.findJcrPropertiesFor(nodeId, location.getPath());
455             return new JcrPropertyIterator(properties);
456         }
457 
458         return getProperties(namePattern.split("[|]"));
459     }
460 
461     public PropertyIterator getProperties( String[] nameGlobs ) throws RepositoryException {
462         CheckArg.isNotNull(nameGlobs, "nameGlobs");
463         if (nameGlobs.length == 0) return new JcrEmptyPropertyIterator();
464         Collection<AbstractJcrProperty> properties = cache.findJcrPropertiesFor(nodeId, location.getPath());
465 
466         // Figure out the patterns for each of the different disjunctions in the supplied pattern ...
467         List<Object> patterns = createPatternsFor(nameGlobs);
468 
469         boolean foundMatch = false;
470         Collection<AbstractJcrProperty> matchingProperties = new LinkedList<AbstractJcrProperty>();
471         Iterator<AbstractJcrProperty> iter = properties.iterator();
472         while (iter.hasNext()) {
473             AbstractJcrProperty property = iter.next();
474             String propName = property.getName();
475             assert foundMatch == false;
476             for (Object patternOrMatch : patterns) {
477                 if (patternOrMatch instanceof Pattern) {
478                     Pattern pattern = (Pattern)patternOrMatch;
479                     if (pattern.matcher(propName).matches()) {
480                         foundMatch = true;
481                         break;
482                     }
483                 } else {
484                     String match = (String)patternOrMatch;
485                     if (propName.equals(match)) {
486                         foundMatch = true;
487                         break;
488                     }
489                 }
490             }
491             if (foundMatch) {
492                 matchingProperties.add(property);
493                 foundMatch = false; // for the next iteration ..
494             }
495         }
496         return new JcrPropertyIterator(matchingProperties);
497     }
498 
499     /**
500      * Obtain an iterator over the nodes that reference this node.
501      * 
502      * @param maxNumberOfNodes the maximum number of nodes that should be returned, or {@link Integer#MAX_VALUE} (or a negative
503      *        value) for all nodes
504      * @return the iterator over the referencing nodes; never null
505      * @throws RepositoryException if an error occurs while obtaining the information
506      */
507     protected final NodeIterator referencingNodes( int maxNumberOfNodes ) throws RepositoryException {
508         if (!this.isReferenceable()) {
509             return new JcrEmptyNodeIterator();
510         }
511         if (maxNumberOfNodes < 0) maxNumberOfNodes = Integer.MAX_VALUE;
512 
513         // Execute a query that will report all nodes referencing this node ...
514         String uuid = getUUID();
515         QueryBuilder builder = new QueryBuilder(context().getValueFactories().getTypeSystem());
516         QueryCommand query = builder.select("jcr:primaryType")
517                                     .fromAllNodesAs("allNodes")
518                                     .where()
519                                     .referenceValue("allNodes")
520                                     .isEqualTo(uuid)
521                                     .end()
522                                     .limit(maxNumberOfNodes)
523                                     .query();
524         Query jcrQuery = session().workspace().queryManager().createQuery(query);
525         QueryResult result = jcrQuery.execute();
526         return result.getNodes();
527     }
528 
529     /**
530      * Determine whether there is at least one other node that has a reference to this node.
531      * 
532      * @return true if this node is referenced by at least one other node, or false if there are no references to this node
533      * @throws RepositoryException if an error occurs while obtaining the information
534      */
535     protected final boolean hasIncomingReferences() throws RepositoryException {
536         return referencingNodes(1).hasNext();
537     }
538 
539     /**
540      * {@inheritDoc}
541      * 
542      * @see javax.jcr.Node#getReferences()
543      */
544     public final PropertyIterator getReferences() throws RepositoryException {
545         return getReferences(null);
546     }
547 
548     /**
549      * This method returns all REFERENCE properties that refer to this node, have the specified name and that are accessible
550      * through the current Session. If the name parameter is null then all referring REFERENCES are returned regardless of name.
551      * <p>
552      * Some implementations may only return properties that have been persisted. Some may return both properties that have been
553      * persisted and those that have been dispatched but not persisted (for example, those saved within a transaction but not yet
554      * committed) while others implementations may return these two categories of property as well as properties that are still
555      * pending and not yet dispatched.
556      * </p>
557      * <p>
558      * In implementations that support versioning, this method does not return properties that are part of the frozen state of a
559      * version in version storage.
560      * </p>
561      * <p>
562      * If this node has no referring REFERENCE properties with the specified name, an empty iterator is returned. This includes
563      * the case where this node is not referenceable.
564      * </p>
565      * 
566      * @param propertyName - name of referring REFERENCE properties to be returned; if null then all referring REFERENCEs are
567      *        returned.
568      * @return A PropertyIterator.
569      * @throws RepositoryException if an error occurs
570      * @see javax.jcr.Node#getReferences()
571      */
572     public final PropertyIterator getReferences( String propertyName ) throws RepositoryException {
573         if (!this.isReferenceable()) {
574             // This node is not referenceable, so it cannot have any references to it ...
575             return new JcrEmptyPropertyIterator();
576         }
577         NodeIterator iter = referencingNodes(Integer.MAX_VALUE);
578         if (!iter.hasNext()) {
579             return new JcrEmptyPropertyIterator();
580         }
581         String uuid = getUUID();
582         List<Property> references = new LinkedList<Property>();
583         while (iter.hasNext()) {
584             javax.jcr.Node node = iter.nextNode();
585 
586             // Go through the properties and look for reference properties that have a value of this node's UUID ...
587             PropertyIterator propIter = node.getProperties();
588             while (propIter.hasNext()) {
589                 Property prop = propIter.nextProperty();
590                 // Look at the definition's required type ...
591                 int propType = prop.getDefinition().getRequiredType();
592                 if (propType == PropertyType.REFERENCE || propType == PropertyType.UNDEFINED || propType == PropertyType.STRING) {
593                     if (propertyName != null && !propertyName.equals(prop.getName())) continue;
594                     if (prop.getDefinition().isMultiple()) {
595                         for (Value value : prop.getValues()) {
596                             if (uuid.equals(value.getString())) {
597                                 references.add(prop);
598                                 break;
599                             }
600                         }
601                     } else {
602                         Value value = prop.getValue();
603                         if (uuid.equals(value.getString())) {
604                             references.add(prop);
605                         }
606                     }
607                 }
608             }
609         }
610 
611         if (references.isEmpty()) return new JcrEmptyPropertyIterator();
612         return new JcrPropertyIterator(references);
613     }
614 
615     /**
616      * A non-standard method to obtain a property given the {@link Name ModeShape Name} object. This method is faster
617      * 
618      * @param propertyName the property name
619      * @return the JCR property with the supplied name, or null if the property doesn't exist
620      * @throws RepositoryException if there is an error finding the property with the supplied name
621      */
622     public final AbstractJcrProperty getProperty( Name propertyName ) throws RepositoryException {
623         AbstractJcrProperty property = cache.findJcrProperty(nodeId, location.getPath(), propertyName);
624         // Must be referenceable in order to return this property ...
625         if (property != null && JcrLexicon.UUID.equals(propertyName) && !isReferenceable()) return null;
626         return property;
627     }
628 
629     /**
630      * {@inheritDoc}
631      * 
632      * @throws IllegalArgumentException if <code>relativePath</code> is empty or <code>null</code>.
633      * @see javax.jcr.Node#getProperty(java.lang.String)
634      */
635     public final Property getProperty( String relativePath ) throws RepositoryException {
636         CheckArg.isNotEmpty(relativePath, "relativePath");
637         int indexOfFirstSlash = relativePath.indexOf('/');
638         if (indexOfFirstSlash == 0) {
639             // Not a relative path ...
640             throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
641         }
642         Name propertyName = null;
643         if (indexOfFirstSlash != -1) {
644             // We know it's a relative path with more than one segment ...
645             Path path = pathFrom(relativePath).getNormalizedPath();
646             if (path.size() > 1) {
647                 try {
648                     AbstractJcrItem item = cache.findJcrItem(nodeId, location.getPath(), path);
649                     if (item instanceof Property) {
650                         return (Property)item;
651                     }
652                 } catch (ItemNotFoundException e) {
653                     I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode;
654                     throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName()));
655                 }
656                 I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode;
657                 throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName()));
658             }
659             propertyName = path.getLastSegment().getName();
660         } else {
661             propertyName = nameFrom(relativePath);
662         }
663         // It's just a name, so look for it directly ...
664         Property result = getProperty(propertyName);
665         if (result != null) return result;
666         I18n msg = JcrI18n.pathNotFoundRelativeTo;
667         throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName()));
668     }
669 
670     /**
671      * {@inheritDoc}
672      * 
673      * @throws IllegalArgumentException if <code>relativePath</code> is empty or <code>null</code>.
674      * @see javax.jcr.Node#hasNode(java.lang.String)
675      */
676     public final boolean hasNode( String relativePath ) throws RepositoryException {
677         CheckArg.isNotEmpty(relativePath, "relativePath");
678         if (relativePath.equals(".")) return true;
679         if (relativePath.equals("..")) return isRoot() ? false : true;
680         int indexOfFirstSlash = relativePath.indexOf('/');
681         if (indexOfFirstSlash == 0) {
682             // Not a relative path ...
683             throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
684         }
685         if (indexOfFirstSlash != -1) {
686             Path path = pathFrom(relativePath).getNormalizedPath();
687             try {
688                 AbstractJcrNode item = cache.findJcrNode(nodeId, location.getPath(), path);
689                 return item != null;
690             } catch (PathNotFoundException e) {
691                 return false;
692             }
693         }
694         // It's just a name, so look for a child ...
695         try {
696             Path.Segment segment = segmentFrom(relativePath);
697             return nodeInfo().getChild(segment) != null;
698         } catch (org.modeshape.graph.property.PathNotFoundException e) {
699             return false;
700         }
701     }
702 
703     /**
704      * {@inheritDoc}
705      * 
706      * @see javax.jcr.Node#hasNodes()
707      */
708     public final boolean hasNodes() throws RepositoryException {
709         return nodeInfo().getChildrenCount() > 0;
710     }
711 
712     /**
713      * A non-standard method to obtain a child node given the {@link Name ModeShape Name} object. This method is faster
714      * 
715      * @param childNodeName the child node name
716      * @return the child node with the supplied name, or null if no child node exists with that name
717      * @throws RepositoryException if there is an error finding the child node with the supplied name
718      */
719     public final AbstractJcrNode getNode( Name childNodeName ) throws RepositoryException {
720         try {
721             Path childPath = context().getValueFactories().getPathFactory().createRelativePath(childNodeName);
722             return cache.findJcrNode(nodeId, location.getPath(), childPath);
723         } catch (PathNotFoundException infe) {
724             return null;
725         } catch (ItemNotFoundException infe) {
726             return null;
727         }
728     }
729 
730     /**
731      * {@inheritDoc}
732      * 
733      * @throws IllegalArgumentException if <code>relativePath</code> is empty or <code>null</code>.
734      * @see javax.jcr.Node#getNode(java.lang.String)
735      */
736     public final AbstractJcrNode getNode( String relativePath ) throws RepositoryException {
737         CheckArg.isNotEmpty(relativePath, "relativePath");
738         if (relativePath.equals(".")) return this;
739         if (relativePath.equals("..")) return this.getParent();
740         int indexOfFirstSlash = relativePath.indexOf('/');
741         if (indexOfFirstSlash == 0) {
742             // Not a relative path ...
743             throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
744         }
745         Path.Segment segment = null;
746         if (indexOfFirstSlash != -1) {
747             // We know it's a relative path with more than one segment ...
748             Path path = pathFrom(relativePath).getNormalizedPath();
749             if (path.size() == 1) {
750                 if (path.getLastSegment().isSelfReference()) return this;
751                 if (path.getLastSegment().isParentReference()) return this.getParent();
752             }
753             // We know it's a resolved relative path with more than one segment ...
754             if (path.size() > 1) {
755                 AbstractJcrItem item = cache.findJcrNode(nodeId, location.getPath(), path);
756                 if (item instanceof javax.jcr.Node) {
757                     return (AbstractJcrNode)item;
758                 }
759                 I18n msg = JcrI18n.nodeNotFoundAtPathRelativeToReferenceNode;
760                 throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName()));
761             }
762             segment = path.getLastSegment();
763         } else {
764             segment = segmentFrom(relativePath);
765         }
766         // It's just a name, so look for a child ...
767         try {
768             return nodeInfo().getChild(segment).getPayload().getJcrNode();
769         } catch (org.modeshape.graph.property.PathNotFoundException e) {
770             String msg = JcrI18n.childNotFoundUnderNode.text(segment, getPath(), cache.workspaceName());
771             throw new PathNotFoundException(msg);
772         } catch (RepositorySourceException e) {
773             throw new RepositoryException(e.getLocalizedMessage(), e);
774         }
775     }
776 
777     /**
778      * {@inheritDoc}
779      * 
780      * @see javax.jcr.Node#getNodes()
781      */
782     public final NodeIterator getNodes() throws RepositoryException {
783         int childCount = nodeInfo().getChildrenCount();
784         if (childCount == 0) {
785             return new JcrEmptyNodeIterator();
786         }
787         List<AbstractJcrNode> matchingChildren = new LinkedList<AbstractJcrNode>();
788         for (Node<JcrNodePayload, JcrPropertyPayload> child : nodeInfo().getChildren()) {
789             matchingChildren.add(child.getPayload().getJcrNode());
790         }
791         return new JcrChildNodeIterator(matchingChildren, childCount);
792     }
793 
794     /**
795      * {@inheritDoc}
796      * 
797      * @see javax.jcr.Node#getNodes(java.lang.String)
798      */
799     public NodeIterator getNodes( String namePattern ) throws RepositoryException {
800         CheckArg.isNotNull(namePattern, "namePattern");
801         namePattern = namePattern.trim();
802         if (namePattern.length() == 0) return new JcrEmptyNodeIterator();
803         if ("*".equals(namePattern)) return getNodes();
804 
805         return getNodes(namePattern.split("[|]"));
806     }
807 
808     public NodeIterator getNodes( String[] nameGlobs ) throws RepositoryException {
809         CheckArg.isNotNull(nameGlobs, "nameGlobs");
810         if (nameGlobs.length == 0) return new JcrEmptyNodeIterator();
811 
812         List<Object> patterns = createPatternsFor(nameGlobs);
813 
814         List<AbstractJcrNode> matchingChildren = new LinkedList<AbstractJcrNode>();
815         NamespaceRegistry registry = namespaces();
816         boolean foundMatch = false;
817         for (Node<JcrNodePayload, JcrPropertyPayload> child : nodeInfo().getChildren()) {
818             String childName = child.getName().getString(registry);
819             for (Object patternOrMatch : patterns) {
820                 if (patternOrMatch instanceof Pattern) {
821                     Pattern pattern = (Pattern)patternOrMatch;
822                     if (pattern.matcher(childName).matches()) foundMatch = true;
823                 } else {
824                     String match = (String)patternOrMatch;
825                     if (childName.equals(match)) foundMatch = true;
826                 }
827                 if (foundMatch) {
828                     foundMatch = false;
829                     matchingChildren.add(child.getPayload().getJcrNode());
830                     break;
831                 }
832             }
833         }
834         return new JcrChildNodeIterator(matchingChildren, matchingChildren.size());
835     }
836 
837     /**
838      * {@inheritDoc}
839      * 
840      * @throws IllegalArgumentException if <code>visitor</code> is <code>null</code>.
841      * @see javax.jcr.Item#accept(javax.jcr.ItemVisitor)
842      */
843     public final void accept( ItemVisitor visitor ) throws RepositoryException {
844         CheckArg.isNotNull(visitor, "visitor");
845         visitor.visit(this);
846     }
847 
848     /**
849      * {@inheritDoc}
850      * <p>
851      * <b>ModeShape Implementation Notes</b>
852      * </p>
853      * <p>
854      * ModeShape imposes the following additional restrictions on the addition of mixin types in addition to the restrictions
855      * provided by the JCR 1.0 specification:
856      * <ol>
857      * <li>No properties defined by the mixin type can have the same name as any property defined by the node's primary type or
858      * any of its existing mixin types.</li>
859      * <li>No child nodes defined by the mixin type can have the same name as any child node defined by the node's primary type or
860      * any of its existing mixin types.</li>
861      * <li>If the node has a current residual definition for child nodes and/or properties, all nodes and properties that share a
862      * name with a child node definition or property definition from the new mixin type must be compatible with the definition
863      * provided by the new mixin type.</li>
864      * </ol>
865      * </p>
866      * 
867      * @see javax.jcr.Node#canAddMixin(java.lang.String)
868      */
869     public final boolean canAddMixin( String mixinName ) throws NoSuchNodeTypeException, RepositoryException {
870         CheckArg.isNotNull(mixinName, "mixinName");
871         CheckArg.isNotZeroLength(mixinName, "mixinName");
872 
873         session().checkPermission(path(), ModeShapePermissions.SET_PROPERTY);
874 
875         JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
876 
877         if (this.isLocked()) {
878             return false;
879         }
880 
881         if (!isCheckedOut()) {
882             return false;
883         }
884 
885         if (this.getDefinition().isProtected()) {
886             return false;
887         }
888 
889         NodeType primaryType = this.getPrimaryNodeType();
890         NodeType[] mixinTypes = this.getMixinNodeTypes();
891 
892         if (mixinCandidateType.isAbstract()) {
893             return false;
894         }
895 
896         if (!mixinCandidateType.isMixin()) {
897             return false;
898         }
899 
900         /* MODE FLAG - Needs to be uncommented for JCR 2 */
901         // if (isNodeType(mixinCandidateType.getInternalName())) return true;
902         if (mixinCandidateType.conflictsWith(primaryType, mixinTypes)) {
903             return false;
904         }
905 
906         // ------------------------------------------------------------------------------
907         // Check for any existing properties based on residual definitions that conflict
908         // ------------------------------------------------------------------------------
909         for (JcrPropertyDefinition propertyDefinition : mixinCandidateType.propertyDefinitions()) {
910             if (!hasProperty(propertyDefinition.getInternalName())) continue;
911             AbstractJcrProperty existingProp = cache.findJcrProperty(nodeId,
912                                                                      location.getPath(),
913                                                                      propertyDefinition.getInternalName());
914             if (existingProp != null) {
915                 if (propertyDefinition.isMultiple()) {
916                     if (!propertyDefinition.canCastToTypeAndSatisfyConstraints(existingProp.getValues())) {
917                         return false;
918                     }
919                 } else {
920                     if (!propertyDefinition.canCastToTypeAndSatisfyConstraints(existingProp.getValue())) {
921                         return false;
922                     }
923                 }
924             }
925         }
926 
927         // ------------------------------------------------------------------------------
928         // Check for any existing child nodes based on residual definitions that conflict
929         // ------------------------------------------------------------------------------
930         Set<Name> mixinChildNodeNames = new HashSet<Name>();
931         for (JcrNodeDefinition nodeDefinition : mixinCandidateType.childNodeDefinitions()) {
932             mixinChildNodeNames.add(nodeDefinition.getInternalName());
933         }
934 
935         for (Name nodeName : mixinChildNodeNames) {
936             // Need to figure out if the child node requires an SNS definition
937             int snsCount = nodeInfo().getChildrenCount(nodeName);
938             for (Node<JcrNodePayload, JcrPropertyPayload> child : nodeInfo().getChildren(nodeName)) {
939                 JcrNodeDefinition match = this.cache.nodeTypes().findChildNodeDefinition(mixinCandidateType.getInternalName(),
940                                                                                          Collections.<Name>emptyList(),
941                                                                                          nodeName,
942                                                                                          child.getPayload().getPrimaryTypeName(),
943                                                                                          snsCount,
944                                                                                          false);
945 
946                 if (match == null) {
947                     return false;
948                 }
949             }
950 
951         }
952 
953         return true;
954     }
955 
956     /**
957      * {@inheritDoc}
958      * <p>
959      * <b>ModeShape Implementation Notes</b>
960      * </p>
961      * <p>
962      * The criteria noted in {@link #canAddMixin(String)} must be satisifed in addition to the criteria defined in the JCR 1.0
963      * specification.
964      * </p>
965      * 
966      * @see javax.jcr.Node#addMixin(java.lang.String)
967      */
968     public final void addMixin( String mixinName ) throws RepositoryException {
969         CheckArg.isNotNull(mixinName, "mixinName");
970         CheckArg.isNotZeroLength(mixinName, "mixinName");
971 
972         JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
973 
974         // Check this separately since it throws a different type of exception
975         if (this.isLocked() && !holdsLock()) {
976             throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
977         }
978 
979         if (!isCheckedOut()) {
980             throw new VersionException(JcrI18n.nodeIsCheckedIn.text(getPath()));
981         }
982 
983         if (!canAddMixin(mixinName)) {
984             throw new ConstraintViolationException(JcrI18n.cannotAddMixin.text(mixinName));
985         }
986 
987         if (isNodeType(mixinName)) return;
988 
989         this.editor().addMixin(mixinCandidateType);
990     }
991 
992     /**
993      * {@inheritDoc}
994      * <p>
995      * <b>ModeShape Implementation Notes</b>
996      * </p>
997      * <p>
998      * ModeShape allows the removal of a mixin type if and only if all of the node's existing child nodes and properties would
999      * still have a valid definition from the node's primary type or other mixin types. In practice, this means that either the
1000      * node must have a residual definition compatible with any of the remaining child nodes or properties that currently use a
1001      * definition from the to-be-removed mixin type or all of the child nodes and properties that use a definition from the
1002      * to-be-removed mixin type must be removed prior to calling this method.
1003      * </p>
1004      * *
1005      * 
1006      * @see javax.jcr.Node#removeMixin(java.lang.String)
1007      */
1008     public final void removeMixin( String mixinName ) throws RepositoryException {
1009 
1010         if (this.isLocked() && !holdsLock()) {
1011             throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
1012         }
1013 
1014         if (!isCheckedOut()) {
1015             throw new VersionException(JcrI18n.nodeIsCheckedIn.text(getPath()));
1016         }
1017 
1018         if (getDefinition().isProtected()) {
1019             throw new ConstraintViolationException(JcrI18n.cannotRemoveFromProtectedNode.text(getPath()));
1020         }
1021 
1022         Property existingMixinProperty = getProperty(JcrLexicon.MIXIN_TYPES);
1023 
1024         if (existingMixinProperty == null) {
1025             throw new NoSuchNodeTypeException(JcrI18n.invalidMixinTypeForNode.text(mixinName, getPath()));
1026         }
1027 
1028         Value[] existingMixinValues = existingMixinProperty.getValues();
1029 
1030         if (existingMixinValues.length == 0) {
1031             throw new NoSuchNodeTypeException(JcrI18n.invalidMixinTypeForNode.text(mixinName, getPath()));
1032         }
1033 
1034         // ------------------------------------------------------------------------------
1035         // Build the new list of mixin types
1036         // ------------------------------------------------------------------------------
1037 
1038         int newMixinValuesCount = existingMixinValues.length - 1;
1039         Value[] newMixinValues = new Value[newMixinValuesCount];
1040         List<Name> newMixinNames = new ArrayList<Name>(newMixinValuesCount);
1041         Name primaryTypeName = getPrimaryNodeType().getInternalName();
1042 
1043         int j = 0;
1044         for (int i = 0; i < existingMixinValues.length; i++) {
1045             if (!existingMixinValues[i].getString().equals(mixinName)) {
1046                 if (j < newMixinValuesCount) {
1047                     newMixinValues[j++] = existingMixinValues[i];
1048                     newMixinNames.add(cache.nameFactory.create(existingMixinValues[i].getString()));
1049                 } else {
1050                     throw new NoSuchNodeTypeException(JcrI18n.invalidMixinTypeForNode.text(mixinName, getPath()));
1051                 }
1052             }
1053         }
1054 
1055         // ------------------------------------------------------------------------------
1056         // Check that any remaining properties that use the mixin type to be removed
1057         // match the residual definition for the node.
1058         // ------------------------------------------------------------------------------
1059 
1060         for (PropertyIterator iter = getProperties(); iter.hasNext();) {
1061             Property property = iter.nextProperty();
1062             if (mixinName.equals(property.getDefinition().getDeclaringNodeType().getName())) {
1063                 JcrPropertyDefinition match;
1064 
1065                 // Only the residual definition would work - if there were any other definition for this name,
1066                 // the mixin type would not have been added due to the conflict
1067                 if (property.getDefinition().isMultiple()) {
1068                     match = cache.nodeTypes().findPropertyDefinition(primaryTypeName,
1069                                                                      newMixinNames,
1070                                                                      JcrNodeType.RESIDUAL_NAME,
1071                                                                      property.getValues(),
1072                                                                      true);
1073                 } else {
1074                     match = cache.nodeTypes().findPropertyDefinition(primaryTypeName,
1075                                                                      newMixinNames,
1076                                                                      JcrNodeType.RESIDUAL_NAME,
1077                                                                      property.getValue(),
1078                                                                      true,
1079                                                                      true);
1080                 }
1081 
1082                 if (match == null) {
1083                     throw new ConstraintViolationException(JcrI18n.noDefinition.text("property",
1084                                                                                      property.getName(),
1085                                                                                      getPath(),
1086                                                                                      primaryTypeName,
1087                                                                                      newMixinNames));
1088                 }
1089             }
1090         }
1091 
1092         // ------------------------------------------------------------------------------
1093         // Check that any remaining child nodes that use the mixin type to be removed
1094         // match the residual definition for the node.
1095         // ------------------------------------------------------------------------------
1096         for (NodeIterator iter = getNodes(); iter.hasNext();) {
1097             AbstractJcrNode node = (AbstractJcrNode)iter.nextNode();
1098             Name childNodeName = cache.nameFactory.create(node.getName());
1099             int snsCount = node.nodeInfo().getChildrenCount(childNodeName);
1100             if (mixinName.equals(node.getDefinition().getDeclaringNodeType().getName())) {
1101                 // Only the residual definition would work - if there were any other definition for this name,
1102                 // the mixin type would not have been added due to the conflict
1103                 JcrNodeDefinition match = cache.nodeTypes().findChildNodeDefinition(primaryTypeName,
1104                                                                                     newMixinNames,
1105                                                                                     JcrNodeType.RESIDUAL_NAME,
1106                                                                                     node.getPrimaryNodeType().getInternalName(),
1107                                                                                     snsCount,
1108                                                                                     true);
1109 
1110                 if (match == null) {
1111                     throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node",
1112                                                                                      node.getName(),
1113                                                                                      getPath(),
1114                                                                                      primaryTypeName,
1115                                                                                      newMixinNames));
1116                 }
1117             }
1118         }
1119 
1120         editor().setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, PropertyType.NAME, false);
1121     }
1122 
1123     /**
1124      * Attempts to change the primary type of this node. Not yet supported, but some error checking is added.
1125      * 
1126      * @param nodeTypeName the name of the new primary type for this node
1127      * @throws NoSuchNodeTypeException if no node with the given name exists
1128      * @throws VersionException if this node is versionable and checked-in or is non-versionable but its nearest versionable
1129      *         ancestor is checked-in.
1130      * @throws ConstraintViolationException if existing child nodes or properties (or the lack of sufficient child nodes or
1131      *         properties) prevent this node from satisfying the definition of the new primary type
1132      * @throws LockException if a lock prevents the modification of the primary type
1133      * @throws RepositoryException if any other error occurs
1134      */
1135     public void setPrimaryType( String nodeTypeName )
1136         throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
1137 
1138         if (this.isLocked() && !holdsLock()) {
1139             throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
1140         }
1141 
1142         if (!isCheckedOut()) {
1143             throw new VersionException(JcrI18n.nodeIsCheckedIn.text(getPath()));
1144         }
1145 
1146         JcrNodeType nodeType = session().nodeTypeManager().getNodeType(nodeTypeName);
1147 
1148         if (nodeType.equals(getPrimaryNodeType())) return;
1149 
1150         if (nodeType.isMixin()) {
1151             throw new ConstraintViolationException(JcrI18n.cannotUseMixinTypeAsPrimaryType.text(nodeTypeName));
1152         }
1153 
1154         throw new ConstraintViolationException(JcrI18n.setPrimaryTypeNotSupported.text());
1155     }
1156 
1157     /**
1158      * {@inheritDoc}
1159      * 
1160      * @see javax.jcr.Node#addNode(java.lang.String)
1161      */
1162     public final javax.jcr.Node addNode( String relPath )
1163         throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
1164         RepositoryException {
1165         return addNode(relPath, null, null);
1166     }
1167 
1168     /**
1169      * {@inheritDoc}
1170      * 
1171      * @see javax.jcr.Node#addNode(java.lang.String, java.lang.String)
1172      */
1173     public final javax.jcr.Node addNode( String relPath,
1174                                          String primaryNodeTypeName )
1175         throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
1176         RepositoryException {
1177         return this.addNode(relPath, primaryNodeTypeName, null);
1178     }
1179 
1180     /**
1181      * Adds the a new node with the given primary type (if specified) at the given relative path with the given UUID (if
1182      * specified).
1183      * 
1184      * @param relPath the at which the new node should be created
1185      * @param primaryNodeTypeName the desired primary type for the new node; null value indicates that the default primary type
1186      *        from the appropriate definition for this node should be used
1187      * @param desiredUuid the UUID (for the jcr.uuid property) of this node; may be null
1188      * @return the newly created node
1189      * @throws ItemExistsException if an item at the specified path already exists and same-name siblings are not allowed.
1190      * @throws PathNotFoundException if the specified path implies intermediary nodes that do not exist.
1191      * @throws VersionException not thrown at this time, but included for compatibility with the specification
1192      * @throws ConstraintViolationException if the change would violate a node type or implementation-specific constraint.
1193      * @throws LockException not thrown at this time, but included for compatibility with the specification
1194      * @throws RepositoryException if another error occurs
1195      * @see #addNode(String, String)
1196      */
1197     final AbstractJcrNode addNode( String relPath,
1198                                    String primaryNodeTypeName,
1199                                    UUID desiredUuid )
1200         throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
1201         RepositoryException {
1202 
1203         if (isLocked() && !holdsLock()) {
1204             throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
1205         }
1206 
1207         // Determine the path ...
1208         NodeEditor editor = null;
1209         Path path = null;
1210         try {
1211             path = cache.pathFactory().create(relPath);
1212         } catch (org.modeshape.graph.property.ValueFormatException e) {
1213             throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
1214         }
1215         if (path.size() == 0) {
1216             throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
1217         }
1218         if (path.getLastSegment().getIndex() > 1 || relPath.endsWith("]")) {
1219             throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
1220         }
1221         if (path.size() > 1) {
1222             // The only segment in the path is the child name ...
1223             Path parentPath = path.getParent();
1224             try {
1225                 // Find the parent node ...
1226                 Node<JcrNodePayload, JcrPropertyPayload> parentOfNewNode = cache.findNode(nodeId, location.getPath(), parentPath);
1227                 editor = cache.getEditorFor(parentOfNewNode);
1228             } catch (RepositoryException e) {
1229                 // We're going to throw an exception ... the question is which one ...
1230                 try {
1231                     Node<JcrNodePayload, JcrPropertyPayload> grandparent;
1232                     if (parentPath.size() > 1) {
1233                         // Per the TCK, if relPath references a property, then we have to throw a ConstraintViolationException
1234                         // So, if we can't find the parent, try for the parent's parent and see if the last segment of the
1235                         // parent's
1236                         // path contains a property ...
1237                         Path grandparentPath = parentPath.getParent();
1238                         assert grandparentPath != null;
1239 
1240                         grandparent = cache.findNode(nodeId, location.getPath(), grandparentPath); // throws
1241                         // PathNotFoundException
1242                     } else {
1243                         grandparent = this.nodeInfo();
1244                     }
1245 
1246                     if (grandparent.getProperty(parentPath.getLastSegment().getName()) != null) {
1247                         // Need to throw a ConstraintViolationException since the request was to add a child to
1248                         // a property ...
1249                         throw new ConstraintViolationException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
1250                     }
1251                 } catch (PathNotFoundException e2) {
1252                     // eat, since the original exception is what we want ...
1253                 }
1254 
1255                 // Otherwise, just throw the PathNotFoundException ...
1256                 throw e;
1257             }
1258         } else {
1259             assert path.size() == 1;
1260             editor = editor();
1261         }
1262         Name childName = path.getLastSegment().getName();
1263 
1264         // Determine the name for the primary node type
1265         Name childPrimaryTypeName = null;
1266         if (primaryNodeTypeName != null) {
1267             try {
1268                 childPrimaryTypeName = cache.nameFactory().create(primaryNodeTypeName);
1269             } catch (org.modeshape.graph.property.ValueFormatException e) {
1270                 throw new RepositoryException(JcrI18n.invalidNodeTypeNameParameter.text(primaryNodeTypeName,
1271                                                                                         "primaryNodeTypeName"));
1272             }
1273         }
1274 
1275         // Create the child ...
1276         return editor.createChild(childName, desiredUuid, childPrimaryTypeName);
1277     }
1278 
1279     /**
1280      * Performs a "best effort" check on whether a node can be added at the given relative path from this node with the given
1281      * primary node type (if one is specified).
1282      * <p>
1283      * Note that a result of {@code true} from this method does not guarantee that a call to {@code #addNode(String, String)} with
1284      * the same arguments will succeed, but a result of {@code false} guarantees that it would fail (assuming that the current
1285      * repository state does not change).
1286      * </p>
1287      * 
1288      * @param relPath the relative path at which the node would be added; may not be null
1289      * @param primaryNodeTypeName the primary type that would be used for the node; null indicates that a default primary type
1290      *        should be used if possible
1291      * @return false if the node could not be added for any reason; true if the node <i>might</i> be able to be added
1292      * @throws RepositoryException if an error occurs accessing the repository
1293      */
1294     final boolean canAddNode( String relPath,
1295                               String primaryNodeTypeName ) throws RepositoryException {
1296         CheckArg.isNotEmpty(relPath, relPath);
1297 
1298         if (isLocked() && !holdsLock()) {
1299             return false;
1300         }
1301 
1302         // Determine the path ...
1303         Path path = null;
1304         try {
1305             path = cache.pathFactory().create(relPath);
1306         } catch (org.modeshape.graph.property.ValueFormatException e) {
1307             return false;
1308         }
1309         if (path.size() == 0) {
1310             return false;
1311         }
1312         if (path.getLastSegment().getIndex() > 1 || relPath.endsWith("]")) {
1313             return false;
1314         }
1315         if (path.size() > 1) {
1316             // The only segment in the path is the child name ...
1317             Path parentPath = path.getParent();
1318             try {
1319                 // Find the parent node ...
1320                 cache.findNode(nodeId, location.getPath(), parentPath);
1321             } catch (RepositoryException e) {
1322                 return false;
1323             }
1324         }
1325 
1326         // Determine the name for the primary node type
1327         if (primaryNodeTypeName != null) {
1328             if (!session().nodeTypeManager().hasNodeType(primaryNodeTypeName)) return false;
1329 
1330             JcrNodeType nodeType = session().nodeTypeManager().getNodeType(primaryNodeTypeName);
1331             if (nodeType.isAbstract()) return false;
1332             if (nodeType.isMixin()) return false;
1333         }
1334 
1335         return true;
1336     }
1337 
1338     protected final Property removeExistingValuedProperty( String name ) throws ConstraintViolationException, RepositoryException {
1339         AbstractJcrProperty property = cache.findJcrProperty(nodeId, location.getPath(), nameFrom(name));
1340         if (property != null) {
1341             property.remove();
1342             return property;
1343         }
1344         // else the property doesn't exist ...
1345         throw new RepositoryException(JcrI18n.propertyNotFoundOnNode.text(name, getPath(), cache.workspaceName()));
1346     }
1347 
1348     /**
1349      * {@inheritDoc}
1350      * 
1351      * @see javax.jcr.Node#setProperty(java.lang.String, boolean)
1352      */
1353     public final Property setProperty( String name,
1354                                        boolean value )
1355         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1356         return editor().setProperty(nameFrom(name), valueFrom(PropertyType.BOOLEAN, value));
1357     }
1358 
1359     /**
1360      * {@inheritDoc}
1361      * 
1362      * @see javax.jcr.Node#setProperty(java.lang.String, java.util.Calendar)
1363      */
1364     public final Property setProperty( String name,
1365                                        Calendar value )
1366         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1367 
1368         if (value == null) {
1369             return removeExistingValuedProperty(name);
1370         }
1371 
1372         return editor().setProperty(nameFrom(name), valueFrom(value));
1373     }
1374 
1375     /**
1376      * {@inheritDoc}
1377      * 
1378      * @see javax.jcr.Node#setProperty(java.lang.String, double)
1379      */
1380     public final Property setProperty( String name,
1381                                        double value )
1382         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1383         return editor().setProperty(nameFrom(name), valueFrom(PropertyType.DOUBLE, value));
1384     }
1385 
1386     /**
1387      * {@inheritDoc}
1388      * 
1389      * @see javax.jcr.Node#setProperty(java.lang.String, java.io.InputStream)
1390      */
1391     public final Property setProperty( String name,
1392                                        InputStream value )
1393         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1394         if (value == null) {
1395             return removeExistingValuedProperty(name);
1396         }
1397 
1398         return editor().setProperty(nameFrom(name), valueFrom(value));
1399     }
1400 
1401     /**
1402      * {@inheritDoc}
1403      * 
1404      * @see javax.jcr.Node#setProperty(java.lang.String, long)
1405      */
1406     public final Property setProperty( String name,
1407                                        long value )
1408         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1409         return editor().setProperty(nameFrom(name), valueFrom(PropertyType.LONG, value));
1410     }
1411 
1412     /**
1413      * {@inheritDoc}
1414      * 
1415      * @see javax.jcr.Node#setProperty(java.lang.String, javax.jcr.Node)
1416      */
1417     public final Property setProperty( String name,
1418                                        javax.jcr.Node value )
1419         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1420         if (value == null) {
1421             return removeExistingValuedProperty(name);
1422         }
1423 
1424         return editor().setProperty(nameFrom(name), valueFrom(value));
1425     }
1426 
1427     /**
1428      * {@inheritDoc}
1429      * 
1430      * @see javax.jcr.Node#setProperty(java.lang.String, java.lang.String)
1431      */
1432     public final Property setProperty( String name,
1433                                        String value )
1434         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1435         if (value == null) {
1436             return removeExistingValuedProperty(name);
1437         }
1438 
1439         return editor().setProperty(nameFrom(name), valueFrom(PropertyType.STRING, value));
1440     }
1441 
1442     /**
1443      * {@inheritDoc}
1444      * 
1445      * @see javax.jcr.Node#setProperty(java.lang.String, java.lang.String, int)
1446      */
1447     public final Property setProperty( String name,
1448                                        String value,
1449                                        int type )
1450         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1451         if (value == null) {
1452             return removeExistingValuedProperty(name);
1453         }
1454 
1455         return editor().setProperty(nameFrom(name), valueFrom(type, value));
1456     }
1457 
1458     /**
1459      * {@inheritDoc}
1460      * 
1461      * @see javax.jcr.Node#setProperty(java.lang.String, java.lang.String[])
1462      */
1463     public final Property setProperty( String name,
1464                                        String[] values )
1465         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1466         if (values == null) {
1467             return removeExistingValuedProperty(name);
1468         }
1469 
1470         return editor().setProperty(nameFrom(name), valuesFrom(PropertyType.STRING, values), PropertyType.UNDEFINED);
1471     }
1472 
1473     /**
1474      * {@inheritDoc}
1475      * 
1476      * @see javax.jcr.Node#setProperty(java.lang.String, java.lang.String[], int)
1477      */
1478     public final Property setProperty( String name,
1479                                        String[] values,
1480                                        int type )
1481         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1482         if (values == null) {
1483             return removeExistingValuedProperty(name);
1484         }
1485 
1486         return editor().setProperty(nameFrom(name), valuesFrom(type, values), PropertyType.UNDEFINED);
1487     }
1488 
1489     /**
1490      * {@inheritDoc}
1491      * 
1492      * @see javax.jcr.Node#setProperty(java.lang.String, javax.jcr.Value)
1493      */
1494     public final Property setProperty( String name,
1495                                        Value value )
1496         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1497         if (value == null) {
1498             return removeExistingValuedProperty(name);
1499         }
1500 
1501         return editor().setProperty(nameFrom(name), (JcrValue)value);
1502     }
1503 
1504     /**
1505      * {@inheritDoc}
1506      * 
1507      * @see javax.jcr.Node#setProperty(java.lang.String, javax.jcr.Value, int)
1508      */
1509     public final Property setProperty( String name,
1510                                        Value value,
1511                                        int type )
1512         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1513         if (value == null) {
1514             return removeExistingValuedProperty(name);
1515         }
1516 
1517         return editor().setProperty(nameFrom(name), ((JcrValue)value).asType(type));
1518     }
1519 
1520     /**
1521      * {@inheritDoc}
1522      * 
1523      * @see javax.jcr.Node#setProperty(java.lang.String, javax.jcr.Value[])
1524      */
1525     public final Property setProperty( String name,
1526                                        Value[] values )
1527         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1528         if (values == null) {
1529             // If there is an existing property, then remove it ...
1530             return removeExistingValuedProperty(name);
1531         }
1532 
1533         return setProperty(name, values, PropertyType.UNDEFINED);
1534     }
1535 
1536     /**
1537      * {@inheritDoc}
1538      * 
1539      * @see javax.jcr.Node#setProperty(java.lang.String, javax.jcr.Value[], int)
1540      */
1541     public final Property setProperty( String name,
1542                                        Value[] values,
1543                                        int type )
1544         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
1545         if (values == null) {
1546             // If there is an existing property, then remove it ...
1547             return removeExistingValuedProperty(name);
1548         }
1549 
1550         // Set the value, perhaps to an empty array ...
1551         return editor().setProperty(nameFrom(name), values, type);
1552     }
1553 
1554     /**
1555      * Throw an {@link UnsupportedRepositoryOperationException} if this node is not versionable (i.e.,
1556      * isNodeType(JcrMixLexicon.VERSIONABLE) == false).
1557      * 
1558      * @throws UnsupportedRepositoryOperationException if <code>!isNodeType({@link JcrMixLexicon#VERSIONABLE})</code>
1559      * @throws RepositoryException if an error occurs reading the node types for this node
1560      */
1561     private void checkVersionable() throws UnsupportedRepositoryOperationException, RepositoryException {
1562         if (!isNodeType(JcrMixLexicon.VERSIONABLE)) {
1563             throw new UnsupportedRepositoryOperationException(JcrI18n.requiresVersionable.text());
1564         }
1565     }
1566 
1567     /**
1568      * Throw a {@link ConstraintViolationException} if this node is protected (based on the its node definition).
1569      * 
1570      * @throws ConstraintViolationException if this node's definition indicates that the node is protected
1571      * @throws RepositoryException if an error occurs retrieving the definition for this node
1572      */
1573     private void checkNotProtected() throws ConstraintViolationException, RepositoryException {
1574         JcrNodeDefinition nodeDefn = cache.nodeTypes().getNodeDefinition(nodeInfo().getPayload().getDefinitionId());
1575         if (nodeDefn.isProtected()) {
1576             throw new ConstraintViolationException(JcrI18n.cannotRemoveItemWithProtectedDefinition.text(getPath()));
1577         }
1578     }
1579 
1580     final JcrVersionManager versionManager() {
1581         return session().workspace().versionManager();
1582     }
1583 
1584     /**
1585      * {@inheritDoc}
1586      * 
1587      * @see javax.jcr.Node#isCheckedOut()
1588      */
1589     public final boolean isCheckedOut() throws RepositoryException {
1590         return editor().isCheckedOut();
1591     }
1592 
1593     /**
1594      * {@inheritDoc}
1595      * 
1596      * @see javax.jcr.Node#checkin()
1597      */
1598     public final Version checkin() throws RepositoryException {
1599         checkVersionable();
1600 
1601         return versionManager().checkin(this);
1602     }
1603 
1604     /**
1605      * {@inheritDoc}
1606      * 
1607      * @see javax.jcr.Node#checkout()
1608      */
1609     public final void checkout() throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
1610         checkVersionable();
1611 
1612         versionManager().checkout(this);
1613     }
1614 
1615     /**
1616      * {@inheritDoc}
1617      * 
1618      * @see javax.jcr.Node#merge(java.lang.String, boolean)
1619      */
1620     public final NodeIterator merge( String srcWorkspace,
1621                                      boolean bestEffort ) throws ConstraintViolationException, RepositoryException {
1622         CheckArg.isNotNull(srcWorkspace, "source workspace name");
1623 
1624         checkNotProtected();
1625 
1626         return versionManager().merge(this, srcWorkspace, bestEffort);
1627     }
1628 
1629     /**
1630      * {@inheritDoc}
1631      * 
1632      * @see javax.jcr.Node#cancelMerge(javax.jcr.version.Version)
1633      */
1634     public final void cancelMerge( Version version ) throws RepositoryException {
1635         checkVersionable();
1636 
1637         versionManager().cancelMerge(this, version);
1638     }
1639 
1640     /**
1641      * {@inheritDoc}
1642      * 
1643      * @see javax.jcr.Node#doneMerge(javax.jcr.version.Version)
1644      */
1645     public final void doneMerge( Version version ) throws RepositoryException {
1646         checkVersionable();
1647 
1648         versionManager().doneMerge(this, version);
1649     }
1650 
1651     /**
1652      * {@inheritDoc}
1653      * 
1654      * @see javax.jcr.Node#getVersionHistory()
1655      */
1656     public final JcrVersionHistoryNode getVersionHistory() throws RepositoryException {
1657         checkVersionable();
1658 
1659         return versionManager().getVersionHistory(this);
1660     }
1661 
1662     /**
1663      * {@inheritDoc}
1664      * 
1665      * @see javax.jcr.Node#getBaseVersion()
1666      */
1667     public final JcrVersionNode getBaseVersion() throws RepositoryException {
1668         checkVersionable();
1669 
1670         // This can happen if the versionable type was added to the node, but it hasn't been saved yet
1671         if (!hasProperty(JcrLexicon.BASE_VERSION)) {
1672             throw new UnsupportedRepositoryOperationException(JcrI18n.requiresVersionable.text());
1673         }
1674 
1675         return (JcrVersionNode)session().getNodeByUUID(getProperty(JcrLexicon.BASE_VERSION).getString());
1676     }
1677 
1678     /**
1679      * {@inheritDoc}
1680      * 
1681      * @see javax.jcr.Node#restore(java.lang.String, boolean)
1682      */
1683     public final void restore( String versionName,
1684                                boolean removeExisting ) throws RepositoryException {
1685         restore(getVersionHistory().getVersion(versionName), removeExisting);
1686     }
1687 
1688     /**
1689      * {@inheritDoc}
1690      * 
1691      * @see javax.jcr.Node#restore(javax.jcr.version.Version, boolean)
1692      */
1693     public final void restore( Version version,
1694                                boolean removeExisting ) throws RepositoryException {
1695         try {
1696             checkNotProtected();
1697         } catch (ConstraintViolationException cve) {
1698             throw new UnsupportedRepositoryOperationException(cve);
1699         }
1700         versionManager().restore(path(), version, null, removeExisting);
1701     }
1702 
1703     /**
1704      * {@inheritDoc}
1705      * 
1706      * @see javax.jcr.Node#restore(javax.jcr.version.Version, java.lang.String, boolean)
1707      */
1708     public final void restore( Version version,
1709                                String relPath,
1710                                boolean removeExisting ) throws RepositoryException {
1711         checkNotProtected();
1712 
1713         PathFactory pathFactory = context().getValueFactories().getPathFactory();
1714         Path relPathAsPath = pathFactory.create(relPath);
1715         if (relPathAsPath.isAbsolute()) throw new RepositoryException(JcrI18n.invalidRelativePath.text(relPath));
1716         Path actualPath = pathFactory.create(path(), relPathAsPath).getCanonicalPath();
1717 
1718         versionManager().restore(actualPath, version, null, removeExisting);
1719     }
1720 
1721     /**
1722      * {@inheritDoc}
1723      * 
1724      * @see javax.jcr.Node#restoreByLabel(java.lang.String, boolean)
1725      */
1726     public final void restoreByLabel( String versionLabel,
1727                                       boolean removeExisting ) throws RepositoryException {
1728         restore(getVersionHistory().getVersionByLabel(versionLabel), removeExisting);
1729     }
1730 
1731     /**
1732      * {@inheritDoc}
1733      * 
1734      * @return <code>false</code>
1735      * @see javax.jcr.Node#holdsLock()
1736      */
1737     public final boolean holdsLock() {
1738         WorkspaceLockManager.ModeShapeLock lock = session().workspace().lockManager().lockFor(session(), this.location);
1739 
1740         return lock != null && cache.session().lockTokens().contains(lock.getLockToken());
1741     }
1742 
1743     /**
1744      * {@inheritDoc}
1745      * 
1746      * @return <code>false</code>
1747      * @see javax.jcr.Node#isLocked()
1748      */
1749     public final boolean isLocked() throws LockException, RepositoryException {
1750         return lock() != null;
1751     }
1752 
1753     /**
1754      * {@inheritDoc}
1755      * 
1756      * @see javax.jcr.Node#lock(boolean, boolean)
1757      */
1758     public final Lock lock( boolean isDeep,
1759                             boolean isSessionScoped ) throws LockException, RepositoryException {
1760         if (!isLockable()) {
1761             throw new LockException(JcrI18n.nodeNotLockable.text(getPath()));
1762         }
1763 
1764         if (isLocked()) {
1765             throw new LockException(JcrI18n.alreadyLocked.text(this.location));
1766         }
1767 
1768         if (isDeep) {
1769             LinkedList<Node<JcrNodePayload, JcrPropertyPayload>> nodesToVisit = new LinkedList<Node<JcrNodePayload, JcrPropertyPayload>>();
1770             nodesToVisit.add(nodeInfo());
1771 
1772             while (!nodesToVisit.isEmpty()) {
1773                 Node<JcrNodePayload, JcrPropertyPayload> node = nodesToVisit.remove(nodesToVisit.size() - 1);
1774                 if (session().workspace().lockManager().lockFor(session(), node.getLocation()) != null) throw new LockException(
1775                                                                                                                                 JcrI18n.parentAlreadyLocked.text(this.location,
1776                                                                                                                                                                  node.getLocation()));
1777 
1778                 for (Node<JcrNodePayload, JcrPropertyPayload> child : node.getChildren()) {
1779                     nodesToVisit.add(child);
1780                 }
1781             }
1782         }
1783 
1784         WorkspaceLockManager.ModeShapeLock lock = session().workspace().lockManager().lock(session(),
1785                                                                                            this.location,
1786                                                                                            isDeep,
1787                                                                                            isSessionScoped);
1788 
1789         cache.session().addLockToken(lock.getLockToken());
1790         return lock.lockFor(cache);
1791     }
1792 
1793     /**
1794      * {@inheritDoc}
1795      * 
1796      * @see javax.jcr.Node#unlock()
1797      */
1798     public final void unlock() throws LockException, RepositoryException {
1799         WorkspaceLockManager.ModeShapeLock lock = session().workspace().lockManager().lockFor(session(), this.location);
1800 
1801         if (lock == null) {
1802             throw new LockException(JcrI18n.notLocked.text(this.location));
1803         }
1804 
1805         if (!session().lockTokens().contains(lock.getLockToken())) {
1806             try {
1807                 // See if the user has the permission to break someone else's lock
1808                 session().checkPermission(cache.workspaceName(), null, ModeShapePermissions.UNLOCK_ANY);
1809             } catch (AccessControlException iae) {
1810                 throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
1811             }
1812         }
1813 
1814         session().workspace().lockManager().unlock(session().getExecutionContext(), lock);
1815         session().removeLockToken(lock.getLockToken());
1816     }
1817 
1818     private final WorkspaceLockManager.ModeShapeLock lock() throws RepositoryException {
1819         // This can only happen in mocked testing.
1820         if (session() == null || session().workspace() == null) return null;
1821 
1822         WorkspaceLockManager lockManager = session().workspace().lockManager();
1823         WorkspaceLockManager.ModeShapeLock lock = lockManager.lockFor(session(), this.location);
1824         if (lock != null) return lock;
1825 
1826         AbstractJcrNode parent = this;
1827         while (!parent.isRoot()) {
1828             parent = parent.getParent();
1829 
1830             WorkspaceLockManager.ModeShapeLock parentLock = lockManager.lockFor(session(), parent.location);
1831             if (parentLock != null && parentLock.isLive()) {
1832                 return parentLock.isDeep() ? parentLock : null;
1833             }
1834         }
1835         return null;
1836     }
1837 
1838     /**
1839      * {@inheritDoc}
1840      * 
1841      * @see javax.jcr.Node#getLock()
1842      */
1843     public final Lock getLock() throws LockException, RepositoryException {
1844         WorkspaceLockManager.ModeShapeLock lock = lock();
1845 
1846         if (lock == null) throw new LockException(JcrI18n.notLocked.text(this.location));
1847         return lock.lockFor(cache);
1848     }
1849 
1850     /**
1851      * {@inheritDoc}
1852      * 
1853      * @see javax.jcr.Item#isModified()
1854      */
1855     public final boolean isModified() {
1856         try {
1857             Node<JcrNodePayload, JcrPropertyPayload> node = nodeInfo();
1858             // Considered modified if *not* new but changed
1859             return !node.isNew() && node.isChanged(true);
1860         } catch (RepositoryException re) {
1861             throw new IllegalStateException(re);
1862         }
1863     }
1864 
1865     /**
1866      * {@inheritDoc}
1867      * 
1868      * @see javax.jcr.Item#isNew()
1869      */
1870     public final boolean isNew() {
1871         try {
1872             return nodeInfo().isNew();
1873         } catch (RepositoryException re) {
1874             throw new IllegalStateException(re);
1875         }
1876     }
1877 
1878     /**
1879      * {@inheritDoc}
1880      * 
1881      * @see javax.jcr.Node#getCorrespondingNodePath(java.lang.String)
1882      */
1883     public final String getCorrespondingNodePath( String workspaceName )
1884         throws NoSuchWorkspaceException, ItemNotFoundException, RepositoryException {
1885         CheckArg.isNotNull(workspaceName, "workspace name");
1886         NamespaceRegistry namespaces = this.context().getNamespaceRegistry();
1887         return correspondingNodePath(workspaceName).getString(namespaces);
1888     }
1889 
1890     protected final Path correspondingNodePath( String workspaceName )
1891         throws NoSuchWorkspaceException, ItemNotFoundException, RepositoryException {
1892         assert workspaceName != null;
1893         NamespaceRegistry namespaces = this.context().getNamespaceRegistry();
1894 
1895         // Find the closest ancestor (including this node) that is referenceable ...
1896         AbstractJcrNode referenceableRoot = this;
1897         while (!referenceableRoot.isNodeType(JcrMixLexicon.REFERENCEABLE.getString(namespaces))) {
1898             referenceableRoot = referenceableRoot.getParent();
1899         }
1900 
1901         // Find the relative path from the nearest referenceable node to this node (or null if this node is referenceable) ...
1902         Path relativePath = path().equals(referenceableRoot.path()) ? null : path().relativeTo(referenceableRoot.path());
1903         UUID uuid = UUID.fromString(referenceableRoot.getUUID());
1904         return this.cache.getPathForCorrespondingNode(workspaceName, uuid, relativePath);
1905     }
1906 
1907     /**
1908      * {@inheritDoc}
1909      * 
1910      * @see javax.jcr.Node#update(java.lang.String)
1911      */
1912     public final void update( String srcWorkspaceName ) throws NoSuchWorkspaceException, RepositoryException {
1913         CheckArg.isNotNull(srcWorkspaceName, "workspace name");
1914 
1915         if (session().hasPendingChanges()) {
1916             throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text());
1917         }
1918 
1919         checkNotProtected();
1920 
1921         Path correspondingPath = null;
1922         try {
1923             correspondingPath = correspondingNodePath(srcWorkspaceName);
1924         } catch (ItemNotFoundException infe) {
1925             return;
1926         }
1927 
1928         // Need to force remove in case this node is not referenceable
1929         cache.graphSession().immediateClone(correspondingPath, srcWorkspaceName, path(), true, true);
1930 
1931         session().refresh(false);
1932     }
1933 
1934     /**
1935      * {@inheritDoc}
1936      * 
1937      * @throws UnsupportedRepositoryOperationException always
1938      * @see javax.jcr.Node#orderBefore(java.lang.String, java.lang.String)
1939      */
1940     public final void orderBefore( String srcChildRelPath,
1941                                    String destChildRelPath ) throws UnsupportedRepositoryOperationException, RepositoryException {
1942         // This implementation is correct, except for not calling the SessionCache or graph layer to do the re-order
1943         if (!getPrimaryNodeType().hasOrderableChildNodes()) {
1944             throw new UnsupportedRepositoryOperationException(
1945                                                               JcrI18n.notOrderable.text(getPrimaryNodeType().getName(), getPath()));
1946         }
1947 
1948         PathFactory pathFactory = this.cache.pathFactory();
1949         Path srcPath = pathFactory.create(srcChildRelPath);
1950         if (srcPath.isAbsolute() || srcPath.size() != 1) {
1951             throw new ItemNotFoundException(JcrI18n.pathNotFound.text(srcPath.getString(cache.context().getNamespaceRegistry()),
1952                                                                       cache.session().workspace().getName()));
1953         }
1954         // getLastSegment should return the only segment, since we verified that size() == 1
1955         Path.Segment sourceSegment = srcPath.getLastSegment();
1956         try {
1957             nodeInfo().getChild(sourceSegment);
1958         } catch (org.modeshape.graph.property.PathNotFoundException e) {
1959             String workspaceName = this.cache.session().getWorkspace().getName();
1960             throw new ItemNotFoundException(JcrI18n.pathNotFound.text(srcPath, workspaceName));
1961         }
1962 
1963         Path.Segment destSegment = null;
1964 
1965         if (destChildRelPath != null) {
1966             Path destPath = pathFactory.create(destChildRelPath);
1967             if (destPath.isAbsolute() || destPath.size() != 1) {
1968                 throw new ItemNotFoundException(JcrI18n.pathNotFound.text(destPath.getString(cache.context()
1969                                                                                                   .getNamespaceRegistry()),
1970                                                                           cache.session().workspace().getName()));
1971             }
1972 
1973             destSegment = destPath.getLastSegment();
1974 
1975             // getLastSegment should return the only segment, since we verified that size() == 1
1976             try {
1977                 nodeInfo().getChild(destSegment);
1978             } catch (org.modeshape.graph.property.PathNotFoundException e) {
1979                 String workspaceName = this.cache.session().getWorkspace().getName();
1980                 throw new ItemNotFoundException(JcrI18n.pathNotFound.text(destPath, workspaceName));
1981             }
1982         }
1983 
1984         this.editor().orderChildBefore(sourceSegment, destSegment);
1985     }
1986 
1987     protected static List<Object> createPatternsFor( String[] namePatterns ) throws RepositoryException {
1988         List<Object> patterns = new LinkedList<Object>();
1989         for (String stringPattern : namePatterns) {
1990             stringPattern = stringPattern.trim();
1991             int length = stringPattern.length();
1992             if (length == 0) continue;
1993             if (stringPattern.indexOf("*") == -1) {
1994                 // Doesn't use wildcard, so use String not Pattern
1995                 patterns.add(stringPattern);
1996             } else {
1997                 // We need to escape the regular expression characters ...
1998                 StringBuilder sb = new StringBuilder(length);
1999                 for (int i = 0; i != length; i++) {
2000                     char c = stringPattern.charAt(i);
2001                     switch (c) {
2002                         // Per the spec, the the following characters are not allowed in patterns:
2003                         case '/':
2004                         case '[':
2005                         case ']':
2006                         case '\'':
2007                         case '"':
2008                         case '|':
2009                         case '\t':
2010                         case '\n':
2011                         case '\r':
2012                             String msg = JcrI18n.invalidNamePattern.text(c, stringPattern);
2013                             throw new RepositoryException(msg);
2014                             // The following characters must be escaped when used in regular expressions ...
2015                         case '?':
2016                         case '(':
2017                         case ')':
2018                         case '$':
2019                         case '^':
2020                         case '.':
2021                         case '{':
2022                         case '}':
2023                         case '\\':
2024                             sb.append("\\");
2025                             sb.append(c);
2026                             break;
2027                         case '*':
2028                             // replace with the regular expression wildcard
2029                             sb.append(".*");
2030                             break;
2031                         default:
2032                             sb.append(c);
2033                             break;
2034                     }
2035                 }
2036                 String escapedString = sb.toString();
2037                 Pattern pattern = Pattern.compile(escapedString);
2038                 patterns.add(pattern);
2039             }
2040         }
2041         return patterns;
2042     }
2043 
2044     /**
2045      * {@inheritDoc}
2046      * 
2047      * @see javax.jcr.Item#refresh(boolean)
2048      */
2049     public void refresh( boolean keepChanges ) throws RepositoryException {
2050         this.cache.refresh(this.nodeId, location.getPath(), keepChanges);
2051     }
2052 
2053     /**
2054      * {@inheritDoc}
2055      * 
2056      * @see javax.jcr.Item#save()
2057      */
2058     public void save() throws RepositoryException {
2059         session().checkReferentialIntegrityOfChanges(this);
2060         cache.save(nodeId, location.getPath());
2061     }
2062 
2063     @Override
2064     public String toString() {
2065 
2066         try {
2067             PropertyIterator iter = this.getProperties();
2068             StringBuffer propertyBuff = new StringBuffer();
2069             while (iter.hasNext()) {
2070                 AbstractJcrProperty prop = (AbstractJcrProperty)iter.nextProperty();
2071                 propertyBuff.append(prop).append(", ");
2072             }
2073             return this.getPath() + " {" + propertyBuff.toString() + "}";
2074         } catch (RepositoryException re) {
2075             return re.getMessage();
2076         }
2077     }
2078 
2079     /**
2080      * {@inheritDoc}
2081      * 
2082      * @see java.lang.Object#equals(java.lang.Object)
2083      */
2084     @Override
2085     public boolean equals( Object obj ) {
2086         if (obj == this) return true;
2087         if (obj instanceof AbstractJcrNode) {
2088             AbstractJcrNode that = (AbstractJcrNode)obj;
2089             if (this.cache != that.cache) return false;
2090             return this.location.equals(that.location);
2091         }
2092         return false;
2093     }
2094 
2095     /**
2096      * {@inheritDoc}
2097      * 
2098      * @see java.lang.Object#hashCode()
2099      */
2100     @Override
2101     public int hashCode() {
2102         return HashCode.compute(cache, location);
2103     }
2104 }