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