/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr; import java.io.InputStream; import java.math.BigDecimal; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.MergeException; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.ReferentialIntegrityException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.ActivityViolationException; import javax.jcr.version.OnParentVersionAction; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import org.modeshape.common.annotation.Immutable; import org.modeshape.common.annotation.ThreadSafe; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.CheckArg; import org.modeshape.jcr.JcrSharedNodeCache.SharedSet; import org.modeshape.jcr.NodeTypes.NodeDefinitionSet; import org.modeshape.jcr.api.value.DateTime; import org.modeshape.jcr.cache.CachedNode; import org.modeshape.jcr.cache.CachedNode.ReferenceType; import org.modeshape.jcr.cache.ChildReference; import org.modeshape.jcr.cache.ChildReferences; import org.modeshape.jcr.cache.MutableCachedNode; import org.modeshape.jcr.cache.NodeCache; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.cache.NodeNotFoundInParentException; import org.modeshape.jcr.cache.PropertyTypeUtil; import org.modeshape.jcr.cache.SessionCache; import org.modeshape.jcr.cache.SiblingCounter; import org.modeshape.jcr.cache.document.DocumentConstants; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.NamespaceRegistry; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.Path.Segment; import org.modeshape.jcr.value.PathFactory; import org.modeshape.jcr.value.Property; import org.modeshape.jcr.value.PropertyFactory; import org.modeshape.jcr.value.Reference; import org.modeshape.jcr.value.ValueFactories; import org.modeshape.jcr.value.ValueFactory; import org.modeshape.jcr.value.basic.NodeKeyReference; /** * The abstract base class for all {@link Node} implementations. */ @ThreadSafe @SuppressWarnings("deprecation") abstract class AbstractJcrNode extends AbstractJcrItem implements Node { enum Type { ROOT, NODE, SYSTEM, VERSION, VERSION_HISTORY; private static final Map<Name, Type> DEFAULT_TYPE_BY_NAME; static { Map<Name, Type> byName = new HashMap<Name, Type>(); // Root node ... byName.put(ModeShapeLexicon.ROOT, Type.ROOT); // System content ... byName.put(ModeShapeLexicon.SYSTEM, Type.SYSTEM); // Versioning byName.put(JcrNtLexicon.VERSION_HISTORY, Type.VERSION_HISTORY); byName.put(JcrNtLexicon.VERSION_LABELS, Type.SYSTEM); byName.put(JcrNtLexicon.VERSION, Type.VERSION); // Node types ... byName.put(ModeShapeLexicon.NODE_TYPES, Type.SYSTEM); byName.put(JcrNtLexicon.NODE_TYPE, Type.SYSTEM); byName.put(JcrNtLexicon.PROPERTY_DEFINITION, Type.SYSTEM); byName.put(JcrNtLexicon.CHILD_NODE_DEFINITION, Type.SYSTEM); // Namespaces ... byName.put(ModeShapeLexicon.NAMESPACES, Type.SYSTEM); byName.put(ModeShapeLexicon.NAMESPACE, Type.SYSTEM); // Locks ... byName.put(ModeShapeLexicon.LOCKS, Type.SYSTEM); byName.put(ModeShapeLexicon.LOCK, Type.SYSTEM); DEFAULT_TYPE_BY_NAME = Collections.unmodifiableMap(byName); } /** * Determine the type given the supplied primary type. * * @param primaryType the primary type * @return the type, or null if the node type could not be determined by the supplied primary type */ public static Type typeForPrimaryType( Name primaryType ) { return DEFAULT_TYPE_BY_NAME.get(primaryType); } } @Immutable private final static class CachedDefinition { protected final NodeDefinitionId nodeDefnId; protected final int nodeTypesVersion; protected CachedDefinition( NodeDefinitionId nodeDefnId, int nodeTypesVersion ) { this.nodeDefnId = nodeDefnId; this.nodeTypesVersion = nodeTypesVersion; } } private static final Set<Name> INTERNAL_NODE_TYPE_NAMES = Collections.singleton(ModeShapeLexicon.SHARE); protected final NodeKey key; private final ConcurrentMap<Name, AbstractJcrProperty> jcrProperties = new ConcurrentHashMap<Name, AbstractJcrProperty>(); private volatile CachedDefinition cachedDefn; protected AbstractJcrNode( JcrSession session, NodeKey key ) { super(session); this.key = key; } abstract boolean isRoot(); abstract Type type(); /** * Check that this type of node can be modified * * @throws RepositoryException */ protected void checkNodeTypeCanBeModified() throws RepositoryException { } protected SessionCache sessionCache() { return session.cache(); } protected final NodeKey key() { return this.key; } /** * Get the cached node. * * @return the cached node * @throws InvalidItemStateException if the node has been removed in this session's transient state * @throws ItemNotFoundException if the node does not exist */ protected final CachedNode node() throws ItemNotFoundException, InvalidItemStateException { CachedNode node = sessionCache().getNode(key); if (node == null) { if (sessionCache().isDestroyed(key)) { throw new InvalidItemStateException("The node with key " + key + " has been removed in this session."); } throw new ItemNotFoundException("The node with key " + key + " no longer exists."); } return node; } protected final MutableCachedNode mutable() { return sessionCache().mutable(key); } protected NodeKey parentKey() throws RepositoryException { return node().getParentKey(sessionCache()); } protected final MutableCachedNode mutableParent() throws RepositoryException { SessionCache cache = sessionCache(); return cache.mutable(parentKey()); } @Override Path path() throws ItemNotFoundException, InvalidItemStateException { return node().getPath(sessionCache()); } /** * Obtain a string identifying this node, usually for error or logging purposes. This method never throws an exception. * * @return the location string; never null */ protected final String location() { try { return getPath(); } catch (Throwable t) { return key.toString(); } } protected Name name() throws RepositoryException { return node().getName(sessionCache()); } protected Segment segment() throws RepositoryException { return node().getSegment(sessionCache()); } /** * Checks if this node is foreign for its current owning session * * @return boolean if this node is considered "foreign" to this session, or false otherwise * @see JcrSession#isForeignKey(org.modeshape.jcr.cache.NodeKey) */ protected final boolean isForeign() { return session().isForeignKey(key()); } /** * Checks if this node belongs to an external source. * * @return true if the node is not repository-local */ protected final boolean isExternal() { return !key().getSourceKey().equals(session().cache().getRootKey().getSourceKey()); } protected final boolean isInTheSameProcessAs( String otherProcessId ) { return session().context().getProcessId().equalsIgnoreCase(otherProcessId); } @Override public final String getIdentifier() { return session().nodeIdentifier(key()); } /** * Get the absolute and normalized identifier path for this node, regardless of whether this node is referenceable. * * @return the node's identifier path; never null * @throws RepositoryException if there is an error accessing the identifier of this node */ final String identifierPath() throws RepositoryException { return "[" + getIdentifier() + "]"; } @Override public final JcrSession getSession() { return session(); } @Override public String getLocalName() throws RepositoryException { return name().getLocalName(); } @Override public String getNamespaceURI() throws RepositoryException { return name().getNamespaceUri(); } @Override public AbstractJcrProperty getProperty( String relativePath ) throws PathNotFoundException, RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); checkSession(); int indexOfFirstSlash = relativePath.indexOf('/'); if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) { // Not a relative path ... throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath")); } Name propertyName = null; if (indexOfFirstSlash != -1) { // We know it's a relative path with more than one segment ... Path path = pathFrom(relativePath).getNormalizedPath(); assert !path.isIdentifier(); if (path.size() > 1) { try { AbstractJcrItem item = session.findItem(key, path); if (item instanceof AbstractJcrProperty) { return (AbstractJcrProperty)item; } } catch (ItemNotFoundException e) { I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode; throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName())); } I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode; throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName())); } propertyName = path.getLastSegment().getName(); } else { propertyName = nameFrom(relativePath); } // It's just a name, so look for it directly ... AbstractJcrProperty result = getProperty(propertyName); if (result != null) return result; I18n msg = JcrI18n.pathNotFoundRelativeTo; throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName())); } /** * Get the {@link AbstractJcrProperty JCR Property} object for the existing property with the supplied name. * * @param propertyName the property name; may not be null * @return the JCR Property object, or null if there is no property with the specified name * @throws RepositoryException if there is a problem accessing the repository */ final AbstractJcrProperty getProperty( Name propertyName ) throws RepositoryException { AbstractJcrProperty prop = jcrProperties.get(propertyName); if (prop == null) { // See if there's a property on the node ... CachedNode node = node(); SessionCache cache = sessionCache(); org.modeshape.jcr.value.Property p = node.getProperty(propertyName, cache); if (p != null) { Name primaryType = node.getPrimaryType(cache); Set<Name> mixinTypes = node.getMixinTypes(cache); prop = createJcrProperty(p, primaryType, mixinTypes); if (prop != null) { AbstractJcrProperty newJcrProperty = jcrProperties.putIfAbsent(propertyName, prop); if (newJcrProperty != null) { // Some other thread snuck in and created it, so use that one ... prop = newJcrProperty; } } } } else { // Make sure the property hasn't been removed by another session ... CachedNode node = node(); SessionCache cache = sessionCache(); if (!node.hasProperty(propertyName, cache)) { jcrProperties.remove(propertyName); prop = null; } } return prop; } /** * Create a new JCR Property instance given the supplied information. Note that this does not alter the node in any way, since * it does not store a reference to this property (the caller must do that if needed). * * @param property the cached node property; may not be null * @param primaryTypeName the name of the node's primary type; may not be null * @param mixinTypeNames the names of the node's mixin types; may be null or empty * @return the JCR Property instance, or null if the property could not be represented with a valid property definition given * the primary type and mixin types * @throws ConstraintViolationException if the property has no valid property definition */ private final AbstractJcrProperty createJcrProperty( Property property, Name primaryTypeName, Set<Name> mixinTypeNames ) throws ConstraintViolationException { NodeTypes nodeTypes = session.nodeTypes(); JcrPropertyDefinition defn = propertyDefinitionFor(property, primaryTypeName, mixinTypeNames, nodeTypes); int jcrPropertyType = defn.getRequiredType(); jcrPropertyType = determineBestPropertyTypeIfUndefined(jcrPropertyType, property); AbstractJcrProperty prop = null; if (defn.isMultiple()) { prop = new JcrMultiValueProperty(this, property.getName(), jcrPropertyType); } else { prop = new JcrSingleValueProperty(this, property.getName(), jcrPropertyType); } prop.setPropertyDefinitionId(defn.getId(), nodeTypes.getVersion()); return prop; } private final int determineBestPropertyTypeIfUndefined( int actualPropertyType, Property property ) { if (actualPropertyType == PropertyType.UNDEFINED) { return PropertyTypeUtil.jcrPropertyTypeFor(property); } return actualPropertyType; } final ValueFactories factories() { return context().getValueFactories(); } final String readable( Object obj ) { return session.stringFactory().create(obj); } final String readable( Collection<?> obj ) { ValueFactory<String> stringFactory = session.stringFactory(); StringBuilder sb = new StringBuilder(); sb.append('['); Iterator<?> iter = obj.iterator(); if (iter.hasNext()) { sb.append(stringFactory.create(iter.next())); while (iter.hasNext()) { sb.append(','); sb.append(stringFactory.create(iter.next())); } } sb.append(']'); return sb.toString(); } /** * Find the property definition for the property, given this node's primary type and mixin types. * * @param property the property owned by this node; may not be null * @param primaryType the name of the node's primary type; may not be null * @param mixinTypes the names of the node's mixin types; may be null or empty * @param nodeTypes the node types cache to use; may not be null * @return the property definition; never null * @throws ConstraintViolationException if the property has no valid property definition */ final JcrPropertyDefinition propertyDefinitionFor( org.modeshape.jcr.value.Property property, Name primaryType, Set<Name> mixinTypes, NodeTypes nodeTypes ) throws ConstraintViolationException { // Figure out the JCR property type ... boolean single = property.isSingle(); boolean skipProtected = false; JcrPropertyDefinition defn = findBestPropertyDefinition(primaryType, mixinTypes, property, single, skipProtected, false, nodeTypes); if (defn != null) return defn; // See if there is a definition that has constraints that were violated ... defn = findBestPropertyDefinition(primaryType, mixinTypes, property, single, skipProtected, true, nodeTypes); String pName = readable(property.getName()); String loc = location(); if (defn != null) { I18n msg = JcrI18n.propertyNoLongerSatisfiesConstraints; throw new ConstraintViolationException(msg.text(pName, loc, defn.getName(), defn.getDeclaringNodeType().getName())); } CachedNode node = sessionCache().getNode(key); String ptype = readable(node.getPrimaryType(sessionCache())); String mixins = readable(node.getMixinTypes(sessionCache())); String pstr = property.getString(session.namespaces()); throw new ConstraintViolationException(JcrI18n.propertyNoLongerHasValidDefinition.text(pstr, loc, ptype, mixins)); } /** * Find the best property definition in this node's primary type and mixin types. * * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins * to include in the search * @param property the property * @param isSingle true if the property definition should be single-valued, or false if the property definition should allow * multiple values * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @param skipConstraints true if any constraints on the potential property definitions should be skipped; usually this is * true for the first attempt but then 'false' for a subsequent attempt when figuring out an appropriate error message * @param nodeTypes the node types cache to use; may not be null * @return the property definition that allows setting this property, or null if there is no such definition */ final JcrPropertyDefinition findBestPropertyDefinition( Name primaryTypeNameOfParent, Collection<Name> mixinTypeNamesOfParent, org.modeshape.jcr.value.Property property, boolean isSingle, boolean skipProtected, boolean skipConstraints, NodeTypes nodeTypes ) { JcrPropertyDefinition definition = null; int propertyType = PropertyTypeUtil.jcrPropertyTypeFor(property); // If single-valued ... ValueFactories factories = context().getValueFactories(); if (isSingle) { // Create a value for the ModeShape property value ... Object value = property.getFirstValue(); Value jcrValue = new JcrValue(factories, propertyType, value); definition = nodeTypes.findPropertyDefinition(session, primaryTypeNameOfParent, mixinTypeNamesOfParent, property.getName(), jcrValue, true, skipProtected); } else { // Create values for the ModeShape property value ... Value[] jcrValues = new Value[property.size()]; int index = 0; for (Object value : property) { jcrValues[index++] = new JcrValue(factories, propertyType, value); } definition = nodeTypes.findPropertyDefinition(session, primaryTypeNameOfParent, mixinTypeNamesOfParent, property.getName(), jcrValues, skipProtected); } if (definition != null) return definition; // No definition that allowed the values ... return null; } final boolean hasProperty( Name name ) throws RepositoryException { if (jcrProperties.containsKey(name)) return true; return node().hasProperty(name, sessionCache()); } boolean removeProperty( AbstractJcrProperty property ) { if (jcrProperties.remove(property.name(), property)) { mutable().removeProperty(sessionCache(), property.name()); return true; } return false; } boolean isReferenceable() throws RepositoryException { SessionCache cache = sessionCache(); NodeTypes nodeTypes = session().nodeTypes(); try { CachedNode node = node(); return nodeTypes.isReferenceable(node.getPrimaryType(cache), node.getMixinTypes(cache)); } catch (ItemNotFoundException e) { // The node has been removed, so do nothing } return false; } boolean isLockable() throws RepositoryException { return isNodeType(JcrMixLexicon.LOCKABLE); } boolean isShareable() throws RepositoryException { return isNodeType(JcrMixLexicon.SHAREABLE); } boolean isShared() { return false; } boolean isSystem() { return false; } final JcrValue valueFrom( int propertyType, Object value ) { return new JcrValue(context().getValueFactories(), propertyType, value); } final JcrValue valueFrom( String value ) { return session.valueFactory().createValue(value); } final JcrValue valueFrom( Calendar value ) { DateTime dateTime = context().getValueFactories().getDateFactory().create(value); return valueFrom(PropertyType.DATE, dateTime); } final JcrValue valueFrom( InputStream value ) { org.modeshape.jcr.value.BinaryValue binary = context().getValueFactories().getBinaryFactory().create(value); return valueFrom(PropertyType.BINARY, binary); } final JcrValue valueFrom( Binary value ) { return valueFrom(PropertyType.BINARY, value); } final JcrValue valueFrom( javax.jcr.Node value ) throws RepositoryException { if (!(value instanceof AbstractJcrNode)) { throw new IllegalArgumentException("Invalid node type (expected a ModeShape node): " + value.getClass().toString()); } AbstractJcrNode node = (AbstractJcrNode)value; if (!this.isInTheSameProcessAs(node.session().context().getProcessId())) { throw new RepositoryException(JcrI18n.nodeNotInTheSameSession.text(node.path())); } NodeKey key = ((AbstractJcrNode)value).key(); Reference ref = session.context().getValueFactories().getReferenceFactory() .create(key, ((AbstractJcrNode)value).isForeign()); return valueFrom(PropertyType.REFERENCE, ref); } final JcrValue[] valuesFrom( int propertyType, Object[] values ) { /* * Null values in the array are "compacted" (read: ignored) as per section 7.1.6 in the JCR 1.0.1 specification. */ int len = values.length; ValueFactories factories = context().getValueFactories(); List<JcrValue> results = new ArrayList<JcrValue>(len); for (int i = 0; i != len; ++i) { if (values[i] != null) results.add(new JcrValue(factories, propertyType, values[i])); } return results.toArray(new JcrValue[results.size()]); } final JcrVersionManager versionManager() { return session.workspace().versionManager(); } /** * Checks that this node is not already locked by another session. If the node is not locked or the node is locked but the * lock is owned by this {@code Session}, this method completes silently. If the node is locked (either directly or as part of * a deep lock from an ancestor), this method throws a {@code LockException}. * * @throws LockException if this node is locked (that is, if {@code isLocked() == true && getLock().getLockToken() == null}). * @throws RepositoryException if any other error occurs * @see Node#isLocked() * @see Lock#getLockToken() */ protected final void checkForLock() throws LockException, RepositoryException { Lock lock = getLockIfExists(); if (lock != null && !lock.isLockOwningSession() && lock.getLockToken() == null) { throw new LockException(JcrI18n.lockTokenNotHeld.text(location())); } } protected final boolean isLockedByAnotherSession() throws RepositoryException { try { checkForLock(); return false; } catch (LockException e) { return true; } } /** * Verifies that this node is either not versionable or that it is versionable but checked out. * * @throws VersionException if the node is versionable but is checked in and cannot be modified * @throws RepositoryException if there is an error accessing the repository */ protected final void checkForCheckedOut() throws VersionException, RepositoryException { if (!isCheckedOut()) { throw new VersionException(JcrI18n.nodeIsCheckedIn.text(location())); } } /** * Get the total number of children. * * @return the total number of children * @throws RepositoryException */ protected final long childCount() throws RepositoryException { return node().getChildReferences(sessionCache()).size(); } /** * Get the JCR node for the named child. * * @param name the child name; may not be null * @param expectedType the expected implementation type for the node, or null if it is not known * @return the JCR node; never null * @throws PathNotFoundException if there is no child with the supplied name * @throws ItemNotFoundException if this node or the referenced child no longer exist or cannot be found * @throws InvalidItemStateException if this node has been removed in this session's transient state */ protected final AbstractJcrNode childNode( Name name, Type expectedType ) throws PathNotFoundException, ItemNotFoundException, InvalidItemStateException { ChildReference ref = node().getChildReferences(sessionCache()).getChild(name); if (ref == null) { String msg = JcrI18n.childNotFoundUnderNode.text(readable(name), location(), session.workspaceName()); throw new PathNotFoundException(msg); } return session().node(ref.getKey(), expectedType, key()); } /** * Get the JCR node for the named child. * * @param segment the child name and SNS index; may not be null * @param expectedType the expected implementation type for the node, or null if it is not known * @return the JCR node; never null * @throws PathNotFoundException if there is no child with the supplied name * @throws ItemNotFoundException if this node or the referenced child cannot be found * @throws InvalidItemStateException if this node has been removed in this session's transient state */ protected final AbstractJcrNode childNode( Segment segment, Type expectedType ) throws PathNotFoundException, ItemNotFoundException, InvalidItemStateException { ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment); if (ref == null) { String msg = JcrI18n.childNotFoundUnderNode.text(readable(segment), location(), session.workspaceName()); throw new PathNotFoundException(msg); } return session().node(ref.getKey(), expectedType, key()); } @Override public boolean hasNode( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); checkSession(); if (relativePath.equals(".")) return true; if (relativePath.equals("..")) { return !isRoot() && session.hasPermission(this.getParent().getPath(), ModeShapePermissions.READ); } int indexOfFirstSlash = relativePath.indexOf('/'); if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) { // Not a relative path ... throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath")); } Path.Segment segment = null; if (indexOfFirstSlash != -1) { // We know it's a relative path with more than one segment ... Path path = pathFrom(relativePath).getNormalizedPath(); if (path.size() == 1) { if (path.getLastSegment().isSelfReference()) return true; if (path.getLastSegment().isParentReference()) { return !isRoot() && session.hasPermission(this.getParent().getPath(), ModeShapePermissions.READ); } } // We know it's a resolved relative path with more than one segment ... if (path.size() > 1) { try { AbstractJcrNode node = session().node(node(), path); return node != null && session().hasPermission(node.getPath(), ModeShapePermissions.READ); } catch (PathNotFoundException e) { return false; } } segment = path.getLastSegment(); } else { segment = segmentFrom(relativePath); } assert !segment.isIdentifier(); // It's just a name, so look for a child ... ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment); if (ref == null) { return false; } Node node = session().node(ref.getKey(), null); return session.hasPermission(node.getPath(), ModeShapePermissions.READ); } @Override public AbstractJcrNode getNode( String relativePath ) throws PathNotFoundException, RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); checkSession(); if (relativePath.equals(".")) { return this; } if (relativePath.equals("..")) { session().checkPermission(this.getParent(), ModeShapePermissions.READ); return this.getParent(); } int indexOfFirstSlash = relativePath.indexOf('/'); if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) { // Not a relative path ... throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath")); } Path.Segment segment = null; if (indexOfFirstSlash != -1) { // We know it's a relative path with more than one segment ... Path path = pathFrom(relativePath).getNormalizedPath(); if (path.size() == 1) { if (path.getLastSegment().isSelfReference()) { return this; } if (path.getLastSegment().isParentReference()) { session().checkPermission(this.getParent(), ModeShapePermissions.READ); return this.getParent(); } } // We know it's a resolved relative path with more than one segment ... if (path.size() > 1) { AbstractJcrNode node = session().node(node(), path); session().checkPermission(node, ModeShapePermissions.READ); return node; } segment = path.getLastSegment(); } else { segment = segmentFrom(relativePath); } assert !segment.isIdentifier(); // It's just a name, so look for a child ... ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment); if (ref == null) { String msg = JcrI18n.childNotFoundUnderNode.text(readable(segment), location(), session.workspaceName()); throw new PathNotFoundException(msg); } try { AbstractJcrNode node = session().node(ref.getKey(), null, key()); session().checkPermission(node, ModeShapePermissions.READ); return node; } catch (ItemNotFoundException e) { // expected by TCK String msg = JcrI18n.pathNotFoundRelativeTo.text(relativePath, location(), workspaceName()); throw new PathNotFoundException(msg); } } AbstractJcrNode getNode( Name childName ) throws PathNotFoundException, RepositoryException { // It's just a name, so look for a child ... ChildReference ref = node().getChildReferences(sessionCache()).getChild(childName); if (ref == null) { String msg = JcrI18n.childNotFoundUnderNode.text(readable(childName), location(), session.workspaceName()); throw new PathNotFoundException(msg); } return session().node(ref.getKey(), null, key()); } protected AbstractJcrNode getNodeIfExists( Name childName ) throws RepositoryException { // It's just a name, so look for a child ... ChildReference ref = node().getChildReferences(sessionCache()).getChild(childName); return ref != null ? session().node(ref.getKey(), null, key()) : null; } @Override public NodeIterator getNodes() throws RepositoryException { checkSession(); ChildReferences childReferences = node().getChildReferences(sessionCache()); if (childReferences.isEmpty()) return JcrEmptyNodeIterator.INSTANCE; // if we don't require permission checking, we should use the child references directly since it may have precomputed the size return session.checkPermissionsWhenIteratingChildren() ? new JcrChildNodeIterator(new ChildNodeResolver(session, key()), childReferences.iterator()) : new JcrChildNodeIterator(new ChildNodeResolver(session, key()), childReferences); } protected NodeIterator getNodesInternal() throws RepositoryException { ChildReferences childReferences = node().getChildReferences(sessionCache()); if (childReferences.isEmpty()) return JcrEmptyNodeIterator.INSTANCE; return new JcrChildNodeIterator(new ChildNodeResolver(session, key(), false), childReferences); } @Override public NodeIterator getNodes( String namePattern ) throws RepositoryException { CheckArg.isNotNull(namePattern, "namePattern"); checkSession(); if (namePattern.length() == 0) return JcrEmptyNodeIterator.INSTANCE; if ("*".equals(namePattern)) return getNodes(); return getNodes(patternStringToGlobArray(namePattern)); } protected NodeIterator getNodesInternal( String namePattern ) throws RepositoryException { CheckArg.isNotNull(namePattern, "namePattern"); checkSession(); if (namePattern.length() == 0) return JcrEmptyNodeIterator.INSTANCE; if ("*".equals(namePattern)) return getNodesInternal(); return getNodesInternal(patternStringToGlobArray(namePattern)); } @Override public NodeIterator getNodes( String[] nameGlobs ) throws RepositoryException { CheckArg.isNotNull(nameGlobs, "nameGlobs"); if (nameGlobs.length == 0) return JcrEmptyNodeIterator.INSTANCE; List<?> patterns = createPatternsFor(nameGlobs); Iterator<ChildReference> iter = null; if (patterns.size() == 1 && patterns.get(0) instanceof String) { // This is a literal, so just look up by name ... Name literal = nameFrom((String)patterns.get(0)); iter = node().getChildReferences(sessionCache()).iterator(literal); } else { NamespaceRegistry registry = session.namespaces(); iter = node().getChildReferences(sessionCache()).iterator(patterns, registry); } return new JcrChildNodeIterator(new ChildNodeResolver(session, key()), iter); } protected NodeIterator getNodesInternal( String... nameGlobs ) throws RepositoryException { CheckArg.isNotNull(nameGlobs, "nameGlobs"); if (nameGlobs.length == 0) return JcrEmptyNodeIterator.INSTANCE; List<?> patterns = createPatternsFor(nameGlobs); Iterator<ChildReference> iter = null; if (patterns.size() == 1 && patterns.get(0) instanceof String) { // This is a literal, so just look up by name ... Name literal = nameFrom((String)patterns.get(0)); iter = node().getChildReferences(sessionCache()).iterator(literal); } else { NamespaceRegistry registry = session.namespaces(); iter = node().getChildReferences(sessionCache()).iterator(patterns, registry); } return new JcrChildNodeIterator(new ChildNodeResolver(session, key(), false), iter); } protected static String[] patternStringToGlobArray( String namePattern ) { List<String> globs = new ArrayList<>(); for (String glob : namePattern.split("\\|")) { String trimmedGlob = glob.trim(); if (trimmedGlob.length() > 0) { globs.add(trimmedGlob); } } return globs.toArray(new String[globs.size()]); } protected static List<?> createPatternsFor( String... namePatterns ) { List<Object> patterns = new LinkedList<Object>(); for (String stringPattern : namePatterns) { int length = stringPattern.length(); if (length == 0) continue; if (!stringPattern.contains("*")) { // Doesn't use wildcard, so use String not Pattern patterns.add(stringPattern); } else { // We need to escape the regular expression characters ... StringBuilder sb = new StringBuilder(length); for (int i = 0; i != length; i++) { char c = stringPattern.charAt(i); switch (c) { // the following characters must be escaped when used in regular expressions ... case '\'': case '|': case '/': case ':': case '[': case ']': case '?': case '!': case '(': case ')': case '$': case '^': case '.': case '{': case '}': case '+': case '\\': sb.append("\\"); sb.append(c); break; case '*': // replace with the regular expression wildcard sb.append(".*"); break; default: // any other characters are added as-is to the regular expression sb.append(c); break; } } String escapedString = sb.toString(); Pattern pattern = Pattern.compile(escapedString, Pattern.DOTALL); patterns.add(pattern); } } return patterns; } @Override public AbstractJcrNode addNode( String relPath ) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { checkSession(); return addNode(relPath, null, null, false); } @Override public AbstractJcrNode addNode( String relPath, String primaryNodeTypeName ) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException { checkSession(); return addNode(relPath, primaryNodeTypeName, null, false); } protected AbstractJcrNode addAclNode( String relPath, String primaryNodeTypeName ) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException { checkSession(); return addNode(relPath, primaryNodeTypeName, null, true); } /** * Adds the a new node with the given primary type (if specified) at the given relative path with the given UUID (if * specified). * * @param relPath the at which the new node should be created * @param primaryNodeTypeName the desired primary type for the new node; null value indicates that the default primary type * from the appropriate definition for this node should be used * @param desiredKey the key for the new node; may be null if the key is to be generated * @param aclScope true if this method is called in access manager scope and false otherwise * @return the newly created node * @throws ItemExistsException if an item at the specified path already exists and same-name siblings are not allowed. * @throws PathNotFoundException if the specified path implies intermediary nodes that do not exist. * @throws VersionException not thrown at this time, but included for compatibility with the specification * @throws ConstraintViolationException if the change would violate a node type or implementation-specific constraint. * @throws LockException not thrown at this time, but included for compatibility with the specification * @throws RepositoryException if another error occurs * @see #addNode(String, String) */ final AbstractJcrNode addNode( String relPath, String primaryNodeTypeName, NodeKey desiredKey, boolean aclScope ) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { // Parse the primary type name ... Name childPrimaryTypeName = null; try { childPrimaryTypeName = session.nameFactory().create(primaryNodeTypeName); } catch (org.modeshape.jcr.value.ValueFormatException e) { throw new RepositoryException(JcrI18n.invalidNodeTypeNameParameter.text(primaryNodeTypeName, "primaryNodeTypeName")); } // Resolve the relative path ... Path path = null; try { path = session.pathFactory().create(relPath); } catch (org.modeshape.jcr.value.ValueFormatException e) { throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath")); } if (path.size() == 0 || path.isIdentifier() || path.getLastSegment().getIndex() > 1 || relPath.endsWith("]")) { throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath")); } if (path.size() > 1) { // The relative path points to another node, so look for it ... Path parentPath = path.getParent(); try { // Find the parent node ... AbstractJcrItem parent = session.findItem(this, parentPath); if (parent instanceof AbstractJcrNode) { // delegate to the parent node ... Name childName = path.getLastSegment().getName(); // MODE-1920: check add_child_node permission on parent node if (!aclScope) { session.checkPermission(absolutePathFor(parent.path(), path.getLastSegment()), ModeShapePermissions.ADD_NODE); } return ((AbstractJcrNode)parent).addChildNode(childName, childPrimaryTypeName, desiredKey, false, aclScope); } else if (parent instanceof AbstractJcrProperty) { // Per the TCK, if relPath references a property, then we have to throw a ConstraintViolationException. throw new ConstraintViolationException(JcrI18n.invalidPathParameter.text(relPath, "relPath")); } } catch (ItemNotFoundException e) { // We have to convert to a path not found ... throw new PathNotFoundException(e.getMessage(), e.getCause()); } catch (RepositoryException e) { throw e; } } // Otherwise, the path has size == 1 and it specifies the child ... if (!aclScope) { session.checkPermission(this, ModeShapePermissions.ADD_NODE); } Name childName = path.getLastSegment().getName(); return addChildNode(childName, childPrimaryTypeName, desiredKey, false, aclScope); } protected final Path absolutePathFor( Path parentPath, Segment childSegment ) { return context().getValueFactories().getPathFactory().create(parentPath, childSegment); } /** * Adds the a new node with the given primary type (if specified) at the given relative path with the given UUID (if * specified). * * @param childName the name for the new node; may not be null * @param childPrimaryNodeTypeName the desired primary type for the new node; null value indicates that the default primary * type from the appropriate definition for this node should be used * @param desiredKey the key for the new node; may be null if the key is to be generated * @param skipVersioningValidation true if the operation can be performed on a checked-in node. * @param aclScope true if this operation performed in the scope of access manager * @return the newly created node * @throws ItemExistsException if an item at the specified path already exists and same-name siblings are not allowed. * @throws PathNotFoundException if the specified path implies intermediary nodes that do not exist. * @throws VersionException not thrown at this time, but included for compatibility with the specification * @throws ConstraintViolationException if the change would violate a node type or implementation-specific constraint. * @throws LockException not thrown at this time, but included for compatibility with the specification * @throws RepositoryException if another error occurs * @see #addNode(String, String) */ final AbstractJcrNode addChildNode( Name childName, Name childPrimaryNodeTypeName, NodeKey desiredKey, boolean skipVersioningValidation, boolean aclScope ) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { checkNodeTypeCanBeModified(); if (!aclScope) { session.checkPermission(this, ModeShapePermissions.ADD_NODE); } if (isLockedByAnotherSession()) { throw new LockException(JcrI18n.lockTokenNotHeld.text(location())); } // Find the best child node definition to use for this new child ... NodeTypes nodeTypes = session.nodeTypes(); // Determine the node type based upon this node's type information ... SessionCache cache = sessionCache(); CachedNode node = node(); // validate there is an appropriate child node definition JcrNodeDefinition childDefn = validateChildNodeDefinition(childName, childPrimaryNodeTypeName, !aclScope); if (childPrimaryNodeTypeName == null) { childPrimaryNodeTypeName = childDefn.getDefaultPrimaryType().getInternalName(); } // See if this node is checked in. If so, then we can only create children if the child // node definition has an OPV of 'ignore'. See Section 15.2.2 of the JSR-283 spec for details ... if (!skipVersioningValidation && !isCheckedOut() && childDefn.getOnParentVersion() != OnParentVersionAction.IGNORE) { // The OPV is not 'ignore', so we can't create the new node ... Path parentPath = path(); String parentPathStr = readable(parentPath); int numExistingSns = node.getChildReferences(cache).getChildCount(childName); int sns = numExistingSns + 1; String segment = readable(session.pathFactory().createSegment(childName, sns)); String opv = OnParentVersionAction.nameFromValue(childDefn.getOnParentVersion()); I18n msg = JcrI18n.cannotCreateChildOnCheckedInNodeSinceOpvOfChildDefinitionIsNotIgnore; throw new VersionException(msg.text(segment, readable(parentPathStr), childDefn.getName(), opv)); } // If there isn't a desired key, check if the document store doesn't require a certain key format (this is especially used // by federation) if (desiredKey == null) { String documentStoreKey = null; try { documentStoreKey = session().repository().documentStore() .newDocumentKey(key().toString(), childName, childPrimaryNodeTypeName); } catch (Exception e) { throw new RepositoryException(e); } if (documentStoreKey != null) { desiredKey = new NodeKey(documentStoreKey); } } // We can create the child, so start by building the required properties ... PropertyFactory propFactory = session.propertyFactory(); Property ptProp = propFactory.create(JcrLexicon.PRIMARY_TYPE, childPrimaryNodeTypeName); if (JcrNtLexicon.UNSTRUCTURED.equals(childPrimaryNodeTypeName)) { // This is very common, and we know they don't have auto-created properties or children ... MutableCachedNode newChild = mutable().createChild(cache, desiredKey, childName, ptProp); // And get or create the JCR node ... AbstractJcrNode jcrNode = session.node(newChild.getKey(), null, key()); // Set the child node definition ... jcrNode.setNodeDefinitionId(childDefn.getId(), nodeTypes.getVersion()); return jcrNode; } // Auto-create the properties ... NodeTypes capabilities = session.repository().nodeTypeManager().getNodeTypes(); LinkedList<Property> props = autoCreatePropertiesFor(childName, childPrimaryNodeTypeName, propFactory, capabilities); // Then create the node ... MutableCachedNode newChild = null; if (props != null) { props.addFirst(ptProp); newChild = mutable().createChild(cache, desiredKey, childName, props); } else { newChild = mutable().createChild(cache, desiredKey, childName, ptProp); } // Check if the child node is referenceable if (capabilities.getNodeType(childPrimaryNodeTypeName).isNodeType(JcrMixLexicon.REFERENCEABLE)) { newChild.setProperty(cache, propFactory.create(JcrLexicon.UUID, session.nodeIdentifier(newChild.getKey()))); } if (nodeTypes.isUnorderedCollection(childPrimaryNodeTypeName, null)) { // we're adding a new unordered collection so we need to set some internal (non JCR) information int bucketIdLength = nodeTypes.getBucketIdLengthForUnorderedCollection(childPrimaryNodeTypeName, null); newChild.addInternalProperty(DocumentConstants.BUCKET_ID_LENGTH, bucketIdLength); } // And get or create the JCR node ... AbstractJcrNode jcrNode = session.node(newChild.getKey(), null, key()); // Set the child node definition ... jcrNode.setNodeDefinitionId(childDefn.getId(), nodeTypes.getVersion()); // Create any mandatory properties or child nodes ... jcrNode.autoCreateChildren(childPrimaryNodeTypeName, capabilities); return jcrNode; } /** * Validates that there is a child node definition on the current node (as parent) which allows a child with the given name * and type. * * @param childName the name of the child * @param childPrimaryNodeTypeName the name of the child's primary type * @param skipProtected true if validation should skip protected definitions * @return a non-null {@link JcrNodeDefinition} * @throws ItemNotFoundException * @throws InvalidItemStateException * @throws ItemExistsException if the parent does not allow same-name-siblings and a child with the given name already exists * @throws ConstraintViolationException if adding the child would violate constraints on the parent * @throws NoSuchNodeTypeException if the named primary type does not exist */ JcrNodeDefinition validateChildNodeDefinition( Name childName, Name childPrimaryNodeTypeName, boolean skipProtected ) throws ItemNotFoundException, InvalidItemStateException, ItemExistsException, ConstraintViolationException, NoSuchNodeTypeException { final SessionCache cache = sessionCache(); final CachedNode node = node(); Name primaryTypeName = node.getPrimaryType(cache); Set<Name> mixins = node.getMixinTypes(cache); NodeTypes nodeTypes = session().nodeTypes(); final SiblingCounter siblingCounter = SiblingCounter.create(node, cache); if (childPrimaryNodeTypeName != null) { if (INTERNAL_NODE_TYPE_NAMES.contains(childPrimaryNodeTypeName)) { int numExistingSns = siblingCounter.countSiblingsNamed(childName); String workspaceName = workspaceName(); String childPath = readable(session.pathFactory().create(path(), childName, numExistingSns + 1)); String msg = JcrI18n.unableToCreateNodeWithInternalPrimaryType.text(childPrimaryNodeTypeName, childPath, workspaceName); throw new ConstraintViolationException(msg); } JcrNodeType primaryType = nodeTypes.getNodeType(childPrimaryNodeTypeName); if (primaryType == null) { int numExistingSns = siblingCounter.countSiblingsNamed(childName); Path pathForChild = session.pathFactory().create(path(), childName, numExistingSns + 1); I18n msg = JcrI18n.unableToCreateNodeWithPrimaryTypeThatDoesNotExist; throw new NoSuchNodeTypeException(msg.text(childPrimaryNodeTypeName, pathForChild, workspaceName())); } if (primaryType.isMixin()) { I18n msg = JcrI18n.cannotUseMixinTypeAsPrimaryType; throw new ConstraintViolationException(msg.text(primaryType.getName())); } if (primaryType.isAbstract()) { I18n msg = JcrI18n.primaryTypeCannotBeAbstract; throw new ConstraintViolationException(msg.text(primaryType.getName())); } } NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(primaryTypeName, mixins); JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(childName, childPrimaryNodeTypeName, skipProtected, siblingCounter); if (childDefn == null) { // Failed to find an appropriate child node definition. Throw an exception with the appropriate message... String repoName = session.repository().repositoryName(); String workspaceName = workspaceName(); childDefns.determineReasonForMismatch(childName, childPrimaryNodeTypeName, skipProtected, siblingCounter, primaryTypeName, mixins, path(), workspaceName, repoName, context()); } assert childDefn != null; if (childPrimaryNodeTypeName == null && childDefn.getDefaultPrimaryType() == null) { // There is no default primary type ... int numExistingSns = siblingCounter.countSiblingsNamed(childName); String childPath = readable(session.pathFactory().create(path(), childName, numExistingSns + 1)); I18n msg = JcrI18n.unableToCreateNodeWithNoDefaultPrimaryTypeOnChildNodeDefinition; String nodeTypeName = childDefn.getDeclaringNodeType().getName(); throw new ConstraintViolationException(msg.text(childDefn.getName(), nodeTypeName, childPath, workspaceName())); } return childDefn; } /** * If there are any auto-created properties, create them and return them in a list. * * @param nodeName the name of the node; may not be null * @param primaryType the name of the primary type; may not be null * @param propertyFactory the factory for properties; may not be null * @param capabilities the node type capabilities cache; may not be null * @return the list of auto-created properties, or null if there are none */ protected LinkedList<Property> autoCreatePropertiesFor( Name nodeName, Name primaryType, PropertyFactory propertyFactory, NodeTypes capabilities ) { Collection<JcrPropertyDefinition> autoPropDefns = capabilities.getAutoCreatedPropertyDefinitions(primaryType); if (autoPropDefns.isEmpty()) { return null; } // There is at least one auto-created property on this node ... LinkedList<Property> props = new LinkedList<Property>(); for (JcrPropertyDefinition defn : autoPropDefns) { Name propName = defn.getInternalName(); if (defn.hasDefaultValues()) { // This may or may not be auto-created; we don't care ... Object[] defaultValues = defn.getRawDefaultValues(); Property prop = null; if (defn.isMultiple()) { prop = propertyFactory.create(propName, defaultValues); } else { prop = propertyFactory.create(propName, defaultValues[0]); } props.add(prop); } } return props; } /** * Create in this node any auto-created child nodes. * * @param primaryType the desired primary type for the new node; null value indicates that the default primary type from the * appropriate definition for this node should be used * @param capabilities the node type capabilities cache; may not be null * @throws ItemExistsException if an item at the specified path already exists and same-name siblings are not allowed. * @throws PathNotFoundException if the specified path implies intermediary nodes that do not exist. * @throws VersionException not thrown at this time, but included for compatibility with the specification * @throws ConstraintViolationException if the change would violate a node type or implementation-specific constraint. * @throws LockException not thrown at this time, but included for compatibility with the specification * @throws RepositoryException if another error occurs */ protected void autoCreateChildren( Name primaryType, NodeTypes capabilities ) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { Collection<JcrNodeDefinition> autoChildDefns = capabilities.getAutoCreatedChildNodeDefinitions(primaryType); if (!autoChildDefns.isEmpty()) { // There is at least one auto-created child under this node ... Set<Name> childNames = new HashSet<Name>(); for (JcrNodeDefinition defn : autoChildDefns) { // Residual definitions cannot be both auto-created and residual; // see Section 3.7.2.3.4 of the JCR 2.0 specfication" assert !defn.isResidual(); if (defn.isProtected()) { // Protected items are created by the implementation, so we'll not do these ... continue; } Name childName = defn.getInternalName(); if (!childNames.contains(childName)) { // We've not already created a child with this name ... JcrNodeType childPrimaryType = defn.getDefaultPrimaryType(); addChildNode(childName, childPrimaryType.getInternalName(), null, false, false); } } } } @Override public void orderBefore( String srcChildRelPath, String destChildRelPath ) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { checkSession(); // Make sure ordering is supported on this node ... if (!getPrimaryNodeType().hasOrderableChildNodes()) { String msg = JcrI18n.notOrderable.text(getPrimaryNodeType().getName(), location()); throw new UnsupportedRepositoryOperationException(msg); } Path srcPath = session.pathFactory().create(srcChildRelPath); if (srcPath.isAbsolute() || srcPath.size() != 1) { throw new ItemNotFoundException(JcrI18n.invalidPathParameter.text(srcChildRelPath, "srcChildRelPath")); } session.checkPermission(this, ModeShapePermissions.ADD_NODE); SessionCache cache = session.cache(); ChildReferences childRefs = node().getChildReferences(cache); ChildReference srcRef = childRefs.getChild(srcPath.getLastSegment()); if (srcRef == null) { String workspaceName = workspaceName(); throw new ItemNotFoundException(JcrI18n.pathNotFound.text(srcChildRelPath, workspaceName)); } NodeKey destKey = null; if (destChildRelPath != null) { Path destPath = session.pathFactory().create(destChildRelPath); if (destPath.isAbsolute() || destPath.size() != 1) { throw new ItemNotFoundException(JcrI18n.invalidPathParameter.text(destChildRelPath, "destChildRelPath")); } if (srcPath.isSameAs(destPath)) { return; } ChildReference destRef = childRefs.getChild(destPath.getLastSegment()); if (destRef == null) { String workspaceName = workspaceName(); throw new ItemNotFoundException(JcrI18n.pathNotFound.text(destChildRelPath, workspaceName)); } destKey = destRef.getKey(); } // Now that we've verified the parameters, perform the move ... mutable().reorderChild(cache, srcRef.getKey(), destKey); } @Override public AbstractJcrProperty setProperty( String name, Value value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); JcrValue jcrValue = (JcrValue)value; if (jcrValue.value() == null) { throw new ValueFormatException(JcrI18n.valueMayNotContainNull.text(name)); } return setProperty(nameFrom(name), jcrValue, false, false, false, false); // don't skip constraint checks or protected // checks } @Override public AbstractJcrProperty setProperty( String name, Value value, int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); JcrValue jcrValue = (JcrValue)value; if (jcrValue.value() == null) { throw new ValueFormatException(JcrI18n.valueMayNotContainNull.text(name)); } // don't skip constraint checks or protected checks return setProperty(nameFrom(name), jcrValue.asType(type), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, Value[] values ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (values == null) return removeExistingProperty(nameFrom(name)); if (values.length == 0) { values = new JcrValue[] {}; } else { // Check for a non-null Value that contains a null reference ... for (Value value : values) { JcrValue jcrValue = (JcrValue)value; if (jcrValue != null && jcrValue.value() == null) { throw new ValueFormatException(JcrI18n.valueMayNotContainNull.text(name)); } } } return setProperty(nameFrom(name), values, PropertyType.UNDEFINED, false); } @Override public AbstractJcrProperty setProperty( String name, Value[] values, int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (values == null) return removeExistingProperty(nameFrom(name)); if (values.length == 0) { values = new JcrValue[] {}; } else { // Check for a non-null Value that contains a null reference ... for (Value value : values) { JcrValue jcrValue = (JcrValue)value; if (jcrValue != null && jcrValue.value() == null) { throw new ValueFormatException(JcrI18n.valueMayNotContainNull.text(name)); } } } // Set the value, perhaps to an empty array ... return setProperty(nameFrom(name), values, type, false); } @Override public AbstractJcrProperty setProperty( String name, String[] values ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (values == null) return removeExistingProperty(nameFrom(name)); return setProperty(nameFrom(name), valuesFrom(PropertyType.STRING, values), PropertyType.UNDEFINED, false, false, false, false); } protected AbstractJcrProperty setPropertyInAccessControlScope( String name, String[] values ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (values == null) return removeExistingProperty(nameFrom(name)); return setProperty(nameFrom(name), valuesFrom(PropertyType.STRING, values), PropertyType.UNDEFINED, true, true, true, false); } @Override public AbstractJcrProperty setProperty( String name, String[] values, int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (values == null) return removeExistingProperty(nameFrom(name)); return setProperty(nameFrom(name), valuesFrom(type, values), PropertyType.UNDEFINED, false); } @Override public AbstractJcrProperty setProperty( String name, String value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); return setProperty(nameFrom(name), valueFrom(PropertyType.STRING, value), false, false, false, false); } protected AbstractJcrProperty setPropertyInAccessControlScope( String name, String value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.STRING, value), false, true, false, true); } @Override public AbstractJcrProperty setProperty( String name, String value, int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(type, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, InputStream value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, Binary value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.BINARY, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, boolean value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.BOOLEAN, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, double value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.DOUBLE, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, BigDecimal value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.DECIMAL, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, long value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(PropertyType.LONG, value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, Calendar value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(value), false, false, false, false); } @Override public AbstractJcrProperty setProperty( String name, Node value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { CheckArg.isNotNull(name, "name"); checkSession(); if (value == null) return removeExistingProperty(nameFrom(name)); // don't skip constraint checks or protected checks return setProperty(nameFrom(name), valueFrom(value), false, false, false, false); } /** * Removes an existing property with the supplied name. Note that if a property with the given name does not exist, then this * method returns null and does not throw an exception. * * @param name the name of the property; may not be null * @return the property that was removed * @throws VersionException if the node is checked out * @throws LockException if the node is locked * @throws RepositoryException if some other error occurred */ final AbstractJcrProperty removeExistingProperty( Name name ) throws VersionException, LockException, RepositoryException { AbstractJcrProperty existing = getProperty(name); if (existing != null) { existing.remove(); return existing; } // Return without throwing an exception to match behavior of the reference implementation. // This is also in conformance with the spec. See MODE-956 for details. return null; } /** * @param name the name of the property; may not be null * @param value the value of the property; may not be null * @param skipReferenceValidation indicates whether constraints on REFERENCE properties should be enforced * @param skipProtectedValidation true if protected properties can be set by the caller of this method, or false if the method * should validate that protected methods are not being called * @param skipVersioningValidation true if the property can be set even if a node is checked in * @param skipPermissionCheck true if this method should be executed without permission validation * @return the new JCR property object * @throws VersionException if the node is checked out * @throws LockException if the node is locked * @throws ConstraintViolationException if the new value would violate the constraints on the property definition * @throws RepositoryException if the named property does not exist, or if some other error occurred */ final AbstractJcrProperty setProperty( Name name, JcrValue value, boolean skipReferenceValidation, boolean skipProtectedValidation, boolean skipVersioningValidation, boolean skipPermissionCheck ) throws VersionException, LockException, ConstraintViolationException, RepositoryException { assert value != null; assert value.value() != null; checkForLock(); // checked-out status is checked later, when we have the property definition ... checkNodeTypeCanBeModified(); if (!skipPermissionCheck) { session.checkPermission(this, ModeShapePermissions.SET_PROPERTY); } // Check for an existing JCR property object; note that this will load the internal property if necessary ... AbstractJcrProperty existing = getProperty(name); if (existing != null) { // Found an existing property ... if (existing.isMultiple()) { // The cardinality of the new values does not match the cardinality of the existing property ... I18n msg = JcrI18n.unableToSetMultiValuedPropertyUsingSingleValue; throw new javax.jcr.ValueFormatException(msg.text(readable(name), location(), workspaceName())); } JcrPropertyDefinition propertyDefinition = existing.getDefinition(); if (!skipProtectedValidation && propertyDefinition.isProtected()) { String text = JcrI18n.cannotSetProtectedPropertyValue.text(value, name, location(), workspaceName()); throw new ConstraintViolationException(text); } if (!propertyDefinition.canCastToTypeAndSatisfyConstraints(value, session)) { String defnName = propertyDefinition.getName(); String nodeTypeName = propertyDefinition.getDeclaringNodeType().getName(); I18n msg = JcrI18n.valueViolatesConstraintsOnDefinition; throw new ConstraintViolationException(msg.text(existing.getName(), value.getString(), location(), defnName, nodeTypeName)); } if (propertyDefinition.getRequiredType() == value.getType()) { // The new value's type and the existing type are the same, so just delegate to the existing JCR property ... try { // set the property via the public method, so that additional checks are performed existing.setValue(value); } catch (VersionException e) { if (skipVersioningValidation) { // the node is checked in, but we should ignore that, so set the property via the protected method ((JcrSingleValueProperty)existing).internalSetValue(value); } else { throw e; } } return existing; } } // Otherwise, we have to create the property, so first find a valid property definition ... SessionCache cache = sessionCache(); MutableCachedNode node = mutable(); Name primaryType = node.getPrimaryType(cache); Set<Name> mixinTypes = node.getMixinTypes(cache); NodeTypes nodeTypes = session.nodeTypes(); JcrPropertyDefinition defn = null; final boolean skipProtected = !skipProtectedValidation; defn = nodeTypes.findPropertyDefinition(session, primaryType, mixinTypes, name, value, true, skipProtected, true); if (defn == null) { // Failed to find a valid property definition, // so figure out if there's a definition that would work if it had no constraints ... defn = nodeTypes.findPropertyDefinition(session, primaryType, mixinTypes, name, value, true, skipProtected, false); String propName = readable(name); if (defn != null) { String defnName = defn.getName(); String nodeTypeName = defn.getDeclaringNodeType().getName(); I18n msg = JcrI18n.valueViolatesConstraintsOnDefinition; throw new ConstraintViolationException(msg.text(propName, value.getString(), location(), defnName, nodeTypeName)); } I18n msg = JcrI18n.noPropertyDefinition; throw new ConstraintViolationException(msg.text(propName, location(), readable(primaryType), readable(mixinTypes))); } // The 'findBestPropertyDefinition' method checks constraints for all definitions exception those with a // require type of REFERENCE. This is because checking such constraints may cause unnecessary loading of nodes. // Therefore, see if this is the case ... int requiredType = defn.getRequiredType(); if (requiredType == PropertyType.REFERENCE || requiredType == PropertyType.WEAKREFERENCE) { // Check that the REFERENCE value satisfies the constraints ... if (!skipReferenceValidation && !defn.canCastToTypeAndSatisfyConstraints(value, session)) { // The REFERENCE value did not satisfy the constraints ... String propName = readable(name); String defnName = defn.getName(); String nodeTypeName = defn.getDeclaringNodeType().getName(); I18n i18n = JcrI18n.weakReferenceValueViolatesConstraintsOnDefinition; if (requiredType == PropertyType.REFERENCE) i18n = JcrI18n.referenceValueViolatesConstraintsOnDefinition; throw new ConstraintViolationException(i18n.text(propName, value.getString(), location(), defnName, nodeTypeName)); } } if (!skipVersioningValidation && !isCheckedOut()) { // Node is not checked out, so changing property is only allowed if OPV of property is 'ignore' ... if (defn.getOnParentVersion() != OnParentVersionAction.IGNORE) { // Can't change this property ... String path = getParent().getPath(); throw new VersionException(JcrI18n.nodeIsCheckedIn.text(path)); } } // Create the JCR Property object ... if (requiredType == PropertyType.UNDEFINED) { requiredType = value.getType(); } if (requiredType != value.getType()) { // Convert the value to the required type ... value = value.asType(requiredType); } AbstractJcrProperty jcrProp = new JcrSingleValueProperty(this, name, requiredType); AbstractJcrProperty otherProp = this.jcrProperties.putIfAbsent(name, jcrProp); if (otherProp != null) { // Someone snuck in and created this property while we created ours, so use that instance instead ... jcrProp = otherProp; } // Set the property on the cached node (even if there was an 'otherProp' instance) ... Property newProperty = session.propertyFactory().create(name, value.value()); node.setProperty(cache, newProperty); return jcrProp; } /** * Sets a multi valued property, skipping over protected ones. * * @param name the name of the property; may not be null * @param values the values of the property; may not be null * @param jcrPropertyType the expected property type; may be {@link PropertyType#UNDEFINED} if the values should not be * converted * @param skipReferenceValidation indicates whether constraints on REFERENCE properties should be enforced * @return the new JCR property object * @throws VersionException if the node is checked out * @throws LockException if the node is locked * @throws ConstraintViolationException if the new value would violate the constraints on the property definition * @throws RepositoryException if the named property does not exist, or if some other error occurred */ final AbstractJcrProperty setProperty( Name name, Value[] values, int jcrPropertyType, boolean skipReferenceValidation ) throws VersionException, LockException, ConstraintViolationException, RepositoryException { return setProperty(name, values, jcrPropertyType, false, skipReferenceValidation, false, false); } /** * @param name the name of the property; may not be null * @param values the values of the property; may not be null * @param jcrPropertyType the expected property type; may be {@link javax.jcr.PropertyType#UNDEFINED} if the values should not * be converted * @param skipProtectedValidation true if protected properties can be set by the caller of this method, or false if the method * should validate that protected methods are not being called * @param skipReferenceValidation indicates whether constraints on REFERENCE properties should be enforced * @param skipPermissionsCheck true if this method should be executed without permission check. * @param skipVersioningValidation true if the property can be set even if a node is checked in * @return the new JCR property object * @throws VersionException if the node is checked out * @throws LockException if the node is locked * @throws ConstraintViolationException if the new value would violate the constraints on the property definition * @throws RepositoryException if the named property does not exist, or if some other error occurred */ final AbstractJcrProperty setProperty( Name name, Value[] values, int jcrPropertyType, boolean skipProtectedValidation, boolean skipReferenceValidation, boolean skipPermissionsCheck, boolean skipVersioningValidation ) throws VersionException, LockException, ConstraintViolationException, RepositoryException { assert values != null; checkForLock(); if (!skipVersioningValidation) { checkForCheckedOut(); } checkNodeTypeCanBeModified(); if (!skipPermissionsCheck) { session.checkPermission(this, ModeShapePermissions.SET_PROPERTY); } values = compactValues(values); checkAllValuesHaveTheSameType(values, name); // Force a conversion to the specified property type (if required) ... if (jcrPropertyType != PropertyType.UNDEFINED) { int len = values.length; JcrValue[] newValues = null; if (len == 0) { newValues = JcrMultiValueProperty.EMPTY_VALUES; } else { List<JcrValue> valuesWithDesiredType = new ArrayList<JcrValue>(len); for (int i = 0; i != len; ++i) { JcrValue value = (JcrValue)values[i]; value = value.asType(jcrPropertyType); valuesWithDesiredType.add(value); } if (valuesWithDesiredType.isEmpty()) { newValues = JcrMultiValueProperty.EMPTY_VALUES; } else { newValues = valuesWithDesiredType.toArray(new JcrValue[valuesWithDesiredType.size()]); } } values = newValues; } // Check for an existing JCR property object; note that this will load the internal property if necessary ... AbstractJcrProperty existing = getProperty(name); if (existing != null) { // Found an existing property, and per the JavaDoc for the multi-valued javax.jcr.Node#setProperty(...), // this method is to throw an exception if there is an existing property and it is not already multi-valued ... if (!existing.isMultiple()) { // The cardinality of the new values does not match the cardinality of the existing property ... I18n msg = JcrI18n.unableToSetSingleValuedPropertyUsingMultipleValues; throw new javax.jcr.ValueFormatException(msg.text(readable(name), location(), workspaceName())); } if (existing.getDefinition().getRequiredType() == jcrPropertyType) { try { // set the property via the public method, so that additional checks are performed existing.setValue(values); } catch (VersionException e) { if (skipVersioningValidation) { // the node is checked in, but we should ignore that, so set the property via the protected method ((JcrMultiValueProperty)existing).internalSetValue(values); } else { throw e; } } return existing; } } // Otherwise, we have to create the property, so first find a valid property definition ... SessionCache cache = sessionCache(); MutableCachedNode node = mutable(); Name primaryType = node.getPrimaryType(cache); Set<Name> mixinTypes = node.getMixinTypes(cache); NodeTypes nodeTypes = session.nodeTypes(); JcrPropertyDefinition defn = null; defn = nodeTypes.findPropertyDefinition(session, primaryType, mixinTypes, name, values, !skipProtectedValidation, skipReferenceValidation); if (defn == null) { // Failed to find a valid property definition, // so figure out if there's a definition that would work if it had no constraints ... defn = nodeTypes.findPropertyDefinition(session, primaryType, mixinTypes, name, values, !skipProtectedValidation, false); String propName = readable(name); if (defn != null) { String defnName = defn.getName(); String nodeTypeName = defn.getDeclaringNodeType().getName(); I18n msg = JcrI18n.valueViolatesConstraintsOnDefinition; throw new ConstraintViolationException(msg.text(propName, readable(values), location(), defnName, nodeTypeName)); } // See if we can find a single-valued property definition ... defn = nodeTypes.findPropertyDefinition(session, primaryType, mixinTypes, name, values[0], true, false); if (defn == null) { // The cardinality of the new values does not match the available property definition ... I18n msg = JcrI18n.unableToSetSingleValuedPropertyUsingMultipleValues; throw new javax.jcr.ValueFormatException(msg.text(readable(name), location(), workspaceName())); } I18n msg = JcrI18n.noPropertyDefinition; throw new ConstraintViolationException(msg.text(propName, location(), readable(primaryType), readable(mixinTypes))); } // The 'findBestPropertyDefintion' method checks constraints for all definitions exception those with a // require type of REFERENCE. This is because checking such constraints may cause unnecessary loading of nodes. // Therefore, see if this is the case ... int requiredType = defn.getRequiredType(); if (!skipReferenceValidation && (requiredType == PropertyType.REFERENCE || requiredType == PropertyType.WEAKREFERENCE)) { // Check that the REFERENCE value satisfies the constraints ... if (!defn.canCastToTypeAndSatisfyConstraints(values, session)) { // The REFERENCE value did not satisfy the constraints ... String propName = readable(name); String defnName = defn.getName(); String nodeTypeName = defn.getDeclaringNodeType().getName(); I18n i18n = JcrI18n.weakReferenceValueViolatesConstraintsOnDefinition; if (requiredType == PropertyType.REFERENCE) i18n = JcrI18n.referenceValueViolatesConstraintsOnDefinition; throw new ConstraintViolationException(i18n.text(propName, readable(values), location(), defnName, nodeTypeName)); } } if (requiredType == PropertyType.UNDEFINED && values.length > 0) { requiredType = values[0].getType(); } // Create the JCR Property object ... AbstractJcrProperty jcrProp = new JcrMultiValueProperty(this, name, requiredType); jcrProp.setPropertyDefinitionId(defn.getId(), nodeTypes.getVersion()); AbstractJcrProperty otherProp = this.jcrProperties.putIfAbsent(name, jcrProp); if (otherProp != null) { // Someone snuck in and created this property while we created ours, so use that instance instead ... // Check that the cardinality is correct ... if (!jcrProp.isMultiple()) { // Overwrite the value anyway ... this.jcrProperties.put(name, jcrProp); } jcrProp = otherProp; } // The values may need to be converted to the definition's required type ... int numValues = values.length; Object[] objValues = new Object[numValues]; int propertyType = defn.getRequiredType(); if (propertyType == PropertyType.UNDEFINED || propertyType == jcrPropertyType) { // Can use the values as is ... for (int i = 0; i != numValues; ++i) { objValues[i] = ((JcrValue)values[i]).value(); } } else { // A conversion is required ... try { org.modeshape.jcr.value.PropertyType msType = PropertyTypeUtil.modePropertyTypeFor(propertyType); org.modeshape.jcr.value.ValueFactory<?> factory = context().getValueFactories().getValueFactory(msType); for (int i = 0; i != numValues; ++i) { objValues[i] = factory.create(((JcrValue)values[i]).value()); } } catch (org.modeshape.jcr.value.ValueFormatException e) { throw new ValueFormatException(e.getMessage()); } } // Set the property on the cached node (even if there was an 'otherProp' instance) ... Property newProperty = session.propertyFactory().create(name, objValues); node.setProperty(cache, newProperty); return jcrProp; } /** * Compacts the given array of input values, by removing all those which are <code>null</code> * * @param inputValues the input values * @return an array without null elements. */ private Value[] compactValues( Value[] inputValues ) { if (inputValues == null) { return null; } List<Value> compactedList = new ArrayList<Value>(); for (Value inputValue : inputValues) { if (inputValue != null) { compactedList.add(inputValue); } } return compactedList.toArray(new Value[0]); } private void checkAllValuesHaveTheSameType( Value[] values, Name name ) throws ValueFormatException { int valueType = -1; for (Value value : values) { if (value == null) { continue; } if (valueType == -1) { valueType = value.getType(); } else if (value.getType() != valueType) { // Make sure the type of each value is the same, as per Javadoc in section 10.4.2.6 of the JCR 2.0 spec String msg = JcrI18n.allPropertyValuesMustHaveSameType.text(readable(name), values, org.modeshape.jcr.api.PropertyType.nameFromValue(valueType), location(), workspaceName()); throw new javax.jcr.ValueFormatException(msg); } } } final Collection<AbstractJcrProperty> findJcrProperties( Iterator<Property> propertyIterator ) throws AccessDeniedException, RepositoryException { try { Collection<AbstractJcrProperty> result = new LinkedList<AbstractJcrProperty>(); while (propertyIterator.hasNext()) { Property property = propertyIterator.next(); Name propertyName = property.getName(); AbstractJcrProperty jcrProp = getProperty(propertyName); if (jcrProp != null) result.add(jcrProp); } return result; } catch (AccessControlException e) { throw new AccessDeniedException(e.getMessage(), e); } catch (Throwable e) { throw new RepositoryException(e.getMessage(), e); } } @Override public PropertyIterator getProperties() throws RepositoryException { checkSession(); Iterator<Property> iter = node().getProperties(sessionCache()); return new JcrPropertyIterator(findJcrProperties(iter)); } @Override public PropertyIterator getProperties( String namePattern ) throws RepositoryException { if (namePattern.length() == 0) return JcrEmptyPropertyIterator.INSTANCE; if ("*".equals(namePattern)) return getProperties(); return getProperties(patternStringToGlobArray(namePattern)); } @Override public PropertyIterator getProperties( String[] nameGlobs ) throws RepositoryException { CheckArg.isNotNull(nameGlobs, "nameGlobs"); if (nameGlobs.length == 0) return JcrEmptyPropertyIterator.INSTANCE; List<?> patterns = createPatternsFor(nameGlobs); if (patterns.size() == 1 && patterns.get(0) instanceof String) { // This is a literal, so just look up by name ... Name literal = nameFrom((String)patterns.get(0)); AbstractJcrProperty prop = getProperty(literal); if (prop == null) return JcrEmptyPropertyIterator.INSTANCE; return new JcrPropertyIterator(Collections.singletonList((javax.jcr.Property)prop)); } Iterator<Property> propIter = node().getProperties(patterns, sessionCache()); return new JcrPropertyIterator(findJcrProperties(propIter)); } @Override public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { checkSession(); // Get the primary item name from this node's type ... NodeType primaryType = getPrimaryNodeType(); String primaryItemNameString = primaryType.getPrimaryItemName(); if (primaryItemNameString == null) { I18n msg = JcrI18n.noPrimaryItemNameDefinedOnPrimaryType; throw new ItemNotFoundException(msg.text(primaryType.getName(), location(), workspaceName())); } try { Path primaryItemPath = context().getValueFactories().getPathFactory().create(primaryItemNameString); if (primaryItemPath.size() == 1 && !primaryItemPath.isAbsolute()) { try { return session.node(node(), primaryItemPath); } catch (PathNotFoundException e) { // Must not be any child by that name, so now look for a property on the parent node ... return getProperty(primaryItemPath.getLastSegment().getName()); } } I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid; throw new ItemNotFoundException(msg.text(primaryType.getName(), primaryItemNameString, location(), workspaceName())); } catch (ValueFormatException error) { I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid; throw new ItemNotFoundException(msg.text(primaryType.getName(), primaryItemNameString, location(), workspaceName())); } catch (PathNotFoundException error) { I18n msg = JcrI18n.primaryItemDoesNotExist; throw new ItemNotFoundException(msg.text(primaryType.getName(), primaryItemNameString, location(), workspaceName())); } } @Deprecated @Override public final String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { if (!isReferenceable()) { throw new UnsupportedRepositoryOperationException(); } return getIdentifier(); } @Override public int getIndex() throws RepositoryException { return node().getSegment(sessionCache()).getIndex(); } @Override public final PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } @Override public final PropertyIterator getReferences( String propertyName ) throws RepositoryException { checkSession(); return propertiesOnOtherNodesReferencingThis(propertyName, PropertyType.REFERENCE); } @Override public PropertyIterator getWeakReferences() throws RepositoryException { return getWeakReferences(null); } @Override public PropertyIterator getWeakReferences( String propertyName ) throws RepositoryException { checkSession(); return propertiesOnOtherNodesReferencingThis(propertyName, PropertyType.WEAKREFERENCE); } /** * Find the properties on other nodes that are REFERENCE or WEAKREFERENCE properties (as dictated by the * <code>referenceType</code> parameter) to this node. * * @param propertyName the name of the referring REFERENCE or WEAKREFERENCE properties on the other nodes, or null if all * referring REFERENCE or WEAKREFERENCE properties should be returned * @param referenceType either {@link PropertyType#REFERENCE} or {@link PropertyType#WEAKREFERENCE} * @return the property iterator; never null by may be {@link JcrEmptyPropertyIterator empty} if there are no references or if * this node is not referenceable * @throws RepositoryException if there is an error finding the referencing properties */ protected PropertyIterator propertiesOnOtherNodesReferencingThis( String propertyName, int referenceType ) throws RepositoryException { assert referenceType == PropertyType.REFERENCE || referenceType == PropertyType.WEAKREFERENCE; if (!this.isReferenceable()) { // This node is not referenceable, so it cannot have any references to it ... return JcrEmptyPropertyIterator.INSTANCE; } ReferenceType refType = referenceType == PropertyType.REFERENCE ? ReferenceType.STRONG : ReferenceType.WEAK; NodeIterator iter = referringNodes(refType); if (!iter.hasNext()) { return JcrEmptyPropertyIterator.INSTANCE; } // Use the identifier, not the UUID (since the getUUID() method just calls the getIdentifier() method) ... String id = getIdentifier(); List<javax.jcr.Property> references = new LinkedList<javax.jcr.Property>(); while (iter.hasNext()) { javax.jcr.Node node = iter.nextNode(); // Go through the properties and look for reference properties that have a value of this node's UUID ... PropertyIterator propIter = node.getProperties(); while (propIter.hasNext()) { javax.jcr.Property prop = propIter.nextProperty(); // Look at the definition's required type ... if (prop.getType() == referenceType) { if (propertyName != null && !propertyName.equals(prop.getName())) continue; if (prop.getDefinition().isMultiple()) { for (Value value : prop.getValues()) { if (id.equals(value.getString())) { references.add(prop); break; } } } else { Value value = prop.getValue(); if (id.equals(value.getString())) { references.add(prop); } } } } } if (references.isEmpty()) return JcrEmptyPropertyIterator.INSTANCE; return new JcrPropertyIterator(references); } /** * Obtain an iterator over the nodes that reference this node. * * @param referenceType specification of the type of references to include; may not be null * @return the iterator over the referencing nodes; never null * @throws RepositoryException if an error occurs while obtaining the information */ protected final NodeIterator referringNodes( ReferenceType referenceType ) throws RepositoryException { if (!this.isReferenceable()) { return JcrEmptyNodeIterator.INSTANCE; } // Get all of the nodes that are referring to this node ... Set<NodeKey> keys = node().getReferrers(sessionCache(), referenceType); if (keys.isEmpty()) return JcrEmptyNodeIterator.INSTANCE; return new JcrNodeIterator(session(), keys.iterator(), keys.size(), null); } @Override public boolean hasProperty( String relPath ) throws RepositoryException { CheckArg.isNotEmpty(relPath, "relPath"); checkSession(); if (relPath.indexOf('/') >= 0 || relPath.startsWith("[")) { try { getProperty(relPath); return true; } catch (PathNotFoundException e) { return false; } } if (relPath.startsWith(".")) { if (relPath.length() == 1) return false; if (relPath.equals("..")) return false; } // Otherwise it should be a property on this node ... return node().hasProperty(nameFrom(relPath), sessionCache()); } @Override public boolean hasNodes() throws RepositoryException { checkSession(); // this should check permissions via the resolver return getNodes().hasNext(); } @Override public boolean hasProperties() throws RepositoryException { checkSession(); return node().hasProperties(sessionCache()); } @Override public JcrNodeType getPrimaryNodeType() throws RepositoryException { checkSession(); return session().nodeTypeManager().getNodeType(node().getPrimaryType(sessionCache())); } /** * Get the name of this node's primary type. * * @return the primary type name * @throws ItemNotFoundException if this node no longer exists in the repository * @throws InvalidItemStateException if the node has been removed in this session's transient state */ Name getPrimaryTypeName() throws ItemNotFoundException, InvalidItemStateException { return node().getPrimaryType(sessionCache()); } @Override public NodeType[] getMixinNodeTypes() throws RepositoryException { checkSession(); JcrNodeTypeManager nodeTypeManager = session().nodeTypeManager(); List<NodeType> mixinNodeTypes = new LinkedList<NodeType>(); for (Name mixinTypeName : node().getMixinTypes(sessionCache())) { NodeType nodeType = nodeTypeManager.getNodeType(mixinTypeName); if (nodeType != null) mixinNodeTypes.add(nodeType); } return mixinNodeTypes.toArray(new NodeType[mixinNodeTypes.size()]); } /** * Get the names of this node's mixin types. * * @return the mixin type names; never null but possibly empty * @throws ItemNotFoundException if this node no longer exists in the repository * @throws InvalidItemStateException if the node has been removed in this session's transient state */ Set<Name> getMixinTypeNames() throws ItemNotFoundException, InvalidItemStateException { return node().getMixinTypes(sessionCache()); } @Override public boolean isNodeType( String nodeTypeName ) throws RepositoryException { return isNodeType(nameFrom(nodeTypeName)); } /** * Determine whether this node's primary type or any of the mixins are or extend the node type with the supplied name. This * method is semantically equivalent to but slightly more efficient than the {@link #isNodeType(String) equivalent in the JCR * API}, especially when the node type name is already a {@link Name} object. * * @param nodeTypeName the name of the node type * @return true if this node is of the node type given by the supplied name, or false otherwise * @throws RepositoryException if there is an exception */ public final boolean isNodeType( Name nodeTypeName ) throws RepositoryException { checkSession(); SessionCache cache = sessionCache(); NodeTypes nodeTypes = session().nodeTypes(); try { CachedNode node = node(); // Check the primary type ... Name primaryTypeName = node.getPrimaryType(cache); if (nodeTypes.isTypeOrSubtype(primaryTypeName, nodeTypeName)) return true; // Check the mixins ... Set<Name> mixinTypes = node.getMixinTypes(cache); if (nodeTypes.isTypeOrSubtype(mixinTypes, nodeTypeName)) return true; } catch (ItemNotFoundException e) { // The node has been removed, so do nothing } return false; } private void autoCreateItemsFor( JcrNodeType nodeType ) throws InvalidItemStateException, ConstraintViolationException, AccessDeniedException, RepositoryException { MutableCachedNode node = mutable(); SessionCache cache = sessionCache(); if (nodeType.hasPropertyDefinitions()) { for (JcrPropertyDefinition propDefn : nodeType.allPropertyDefinitions()) { if (propDefn.isAutoCreated() && !propDefn.isProtected()) { Name propName = propDefn.getInternalName(); Property autoCreatedProp = node.getProperty(propName, cache); if (autoCreatedProp == null) { // We have to 'auto-create' the property ... JcrValue[] defaultValues = propDefn.getDefaultValues(); if (defaultValues != null) { // may be empty if (propDefn.isMultiple()) { setProperty(propDefn.getInternalName(), defaultValues, propDefn.getRequiredType(), true); } else { assert propDefn.getDefaultValues().length == 1; // don't skip constraint checks or protected checks setProperty(propDefn.getInternalName(), defaultValues[0], false, false, false, false); } } // otherwise, we don't care } } } } if (nodeType.hasChildNodeDefinitions()) { ChildReferences refs = null; for (JcrNodeDefinition nodeDefn : nodeType.allChildNodeDefinitions()) { if (nodeDefn.isAutoCreated() && !nodeDefn.isProtected()) { Name nodeName = nodeDefn.getInternalName(); if (refs == null) refs = node.getChildReferences(cache); if (refs.getChildCount(nodeName) == 0) { JcrNodeType defaultPrimaryType = nodeDefn.getDefaultPrimaryType(); assert defaultPrimaryType != null; Name primaryType = defaultPrimaryType.getInternalName(); addChildNode(nodeName, primaryType, null, false, false); } } } } } @Override public void setPrimaryType( String nodeTypeName ) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { CheckArg.isNotZeroLength(nodeTypeName, "nodeTypeName"); checkSession(); checkForLock(); checkForCheckedOut(); session.checkPermission(this, ModeShapePermissions.SET_PROPERTY); if (isRoot()) { throw new ConstraintViolationException(JcrI18n.setPrimaryTypeOnRootNodeIsNotSupported.text()); } Name newPrimaryTypeName = nameFrom(nodeTypeName); NodeTypes nodeTypes = session.nodeTypes(); if (newPrimaryTypeName.equals(getPrimaryTypeName())) return; final JcrNodeType newPrimaryType = nodeTypes.getNodeType(newPrimaryTypeName); // validate the new primary type if (newPrimaryType == null) { throw new NoSuchNodeTypeException(JcrI18n.typeNotFound.text(newPrimaryType)); } if (newPrimaryType.isMixin()) { throw new ConstraintViolationException(JcrI18n.cannotUseMixinTypeAsPrimaryType.text(nodeTypeName)); } if (newPrimaryType.isAbstract()) { throw new ConstraintViolationException(JcrI18n.primaryTypeCannotBeAbstract.text(newPrimaryType)); } // Make sure that all existing properties will have a valid property definition with the new primary type ... SessionCache cache = sessionCache(); CachedNode node = node(); Name oldPrimaryType = node.getPrimaryType(cache); Set<Name> mixinTypeNames = node.getMixinTypes(cache); Iterator<Property> iter = node.getProperties(cache); while (iter.hasNext()) { Property prop = iter.next(); try { createJcrProperty(prop, newPrimaryTypeName, mixinTypeNames); } catch (ConstraintViolationException e) { // Change the message ... String propName = readable(prop.getName()); I18n msg = JcrI18n.unableToChangePrimaryTypeDueToPropertyDefinition; throw new ConstraintViolationException(msg.text(location(), oldPrimaryType, newPrimaryTypeName, propName), e); } } // Check that this would not violate the parent's child node type definitions ... Name nodeName = node.getName(cache); CachedNode parent = getParent().node(); Name primaryType = parent.getPrimaryType(cache); Set<Name> mixins = parent.getMixinTypes(cache); // The node is already a child, so create a counter that returns the count as if it were not a child ... SiblingCounter siblingCounter = SiblingCounter.alter(SiblingCounter.create(parent, cache), -1); boolean skipProtected = true; NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(primaryType, mixins); JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(nodeName, newPrimaryTypeName, skipProtected, siblingCounter); if (childDefn == null) { String ptype = readable(primaryType); String mtypes = readable(parent.getMixinTypes(cache)); I18n msg = JcrI18n.unableToChangePrimaryTypeDueToParentsChildDefinition; throw new ConstraintViolationException(msg.text(location(), oldPrimaryType, newPrimaryTypeName, ptype, mtypes)); } setNodeDefinitionId(childDefn.getId(), nodeTypes.getVersion()); // Change the primary type property ... boolean wasReferenceable = isReferenceable(); MutableCachedNode mutable = mutable(); mutable.setProperty(cache, session.propertyFactory().create(JcrLexicon.PRIMARY_TYPE, newPrimaryTypeName)); if (wasReferenceable && !isReferenceable()) { // Need to remove the 'jcr:uuid' reference ... mutable.removeProperty(cache, JcrLexicon.UUID); } else if (isReferenceable() && !mutable.hasProperty(JcrLexicon.UUID, cache)) { mutable.setProperty(cache, session.propertyFactory().create(JcrLexicon.UUID, getIdentifier())); } // And auto-create any properties that are defined by the new primary type ... autoCreateItemsFor(newPrimaryType); // Since we've changed the primary type, release the cached property definition IDs for the node's properties ... for (AbstractJcrProperty prop : this.jcrProperties.values()) { prop.releasePropertyDefinitionId(); } } @Override public void addMixin( String mixinName ) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { CheckArg.isNotZeroLength(mixinName, "mixinName"); checkSession(); checkForLock(); checkForCheckedOut(); session.checkPermission(this, ModeShapePermissions.SET_PROPERTY); if (!canAddMixin(mixinName)) { throw new ConstraintViolationException(JcrI18n.cannotAddMixin.text(mixinName)); } if (isNodeType(mixinName)) return; boolean wasReferenceable = isReferenceable(); Name mixinTypeName = nameFrom(mixinName); // Change the mixin types property (atomically, even if some other operation snuck in and added the mixin) ... SessionCache cache = sessionCache(); MutableCachedNode mutable = mutable(); mutable.addMixin(cache, mixinTypeName); NodeTypes nodeTypes = session.nodeTypes(); JcrNodeType mixinType = nodeTypes.getNodeType(mixinTypeName); if (!wasReferenceable && mixinType.isNodeType(JcrMixLexicon.REFERENCEABLE)) { // Need to add the 'jcr:uuid' reference ... Property uuidProp = session.propertyFactory().create(JcrLexicon.UUID, getIdentifier()); mutable.setProperty(cache, uuidProp); } if (nodeTypes.isUnorderedCollection(mixinTypeName, null)) { // we're making a node an unordered collection, so we need to set some internal data on the document.. int bucketIdLength = nodeTypes.getBucketIdLengthForUnorderedCollection(mixinTypeName, null); mutable.addInternalProperty(DocumentConstants.BUCKET_ID_LENGTH, bucketIdLength); } // And auto-create any properties that are defined by the new primary type ... autoCreateItemsFor(mixinType); if (mixinType.hasPropertyDefinitions()) { // Since we've changed the mixins, release the cached property definition IDs for the node's properties ... for (AbstractJcrProperty prop : this.jcrProperties.values()) { prop.releasePropertyDefinitionId(); } } // per JCR 2.0 10.10.3.1, the change should be reflected immediately in the property updateMixinsProperty(); } private void updateMixinsProperty() throws RepositoryException { MutableCachedNode mutable = mutable(); // as per JCR, we need to make sure the change is reflected immediately in the jcr property List<Name> currentMixins = new ArrayList<Name>(mutable.getMixinTypes(sessionCache())); Value[] mixinValues = session.valueFactory().createValues(currentMixins, PropertyType.NAME); AbstractJcrProperty mixinProperty = this.jcrProperties.get(JcrLexicon.MIXIN_TYPES); if (mixinProperty == null) { mixinProperty = new JcrMultiValueProperty(this, JcrLexicon.MIXIN_TYPES, PropertyType.NAME); // this will overwrite another property which may've appeared in the meantime this.jcrProperties.put(JcrLexicon.MIXIN_TYPES, mixinProperty); } mixinProperty.setValue(mixinValues); } @Override public void removeMixin( String mixinName ) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { CheckArg.isNotZeroLength(mixinName, "mixinName"); checkSession(); checkForLock(); checkForCheckedOut(); session.checkPermission(this, ModeShapePermissions.SET_PROPERTY); if (getDefinition().isProtected()) { throw new ConstraintViolationException(JcrI18n.cannotRemoveFromProtectedNode.text(getPath())); } NodeTypes nodeTypes = session.nodeTypes(); Name removedMixinName = nameFrom(mixinName); if (!isNodeType(mixinName)) { throw new NoSuchNodeTypeException(JcrI18n.invalidMixinTypeForNode.text(mixinName, location())); } if (JcrMixLexicon.SHAREABLE.equals(removedMixinName) && isShareable()) { // Can only remove the shareable mixin if there are no other shared nodes NodeIterator shared = getSharedSet(); long numShared = shared.getSize(); if (numShared > 1L) { // This is not possible, so build up a useful error message ... StringBuilder paths = new StringBuilder(); if (shared.hasNext()) { Node sharedNode = shared.nextNode(); paths.append(sharedNode.getPath()); } while (shared.hasNext()) { paths.append(", "); Node sharedNode = shared.nextNode(); paths.append(sharedNode.getPath()); } String msg = JcrI18n.cannotRemoveShareableMixinThatIsShared.text(location(), numShared, paths); throw new ConstraintViolationException(msg); } } // TCK expects that if we remove this mixin & the session owns the lock, the node is unlocked // this must be done first, before we modify the node if (JcrMixLexicon.LOCKABLE.equals(removedMixinName)) { Lock lock = getLockIfExists(); if (lock != null) { unlock(); } } // Get the information from the node ... SessionCache cache = sessionCache(); CachedNode cachedNode = node(); Name primaryTypeName = cachedNode.getPrimaryType(cache); // Build up the list of new mixin types ... Set<Name> newMixinNames = new HashSet<Name>(cachedNode.getMixinTypes(cache)); if (!newMixinNames.remove(removedMixinName)) { // Nothing to remove ... return; } JcrNodeType mixinType = nodeTypes.getNodeType(removedMixinName); List<Name> protectedPropertiesToRemove = null; List<AbstractJcrNode> protectedChildrenToRemove = null; if (mixinType.hasPropertyDefinitions()) { protectedPropertiesToRemove = new ArrayList<Name>(); // ------------------------------------------------------------------------------ // Check that any remaining properties that use the mixin type to be removed // match the residual definition for the node. // ------------------------------------------------------------------------------ for (PropertyIterator iter = getProperties(); iter.hasNext();) { javax.jcr.Property property = iter.nextProperty(); PropertyDefinition propertyDefinition = property.getDefinition(); String propertyDeclaredNodeTypeName = propertyDefinition.getDeclaringNodeType().getName(); // if we have a protected property, check if it belongs to the mixin itself or any ancestor of the mixin. // if yes, mark it for removal if (propertyDefinition.isProtected() && mixinType.isNodeType(propertyDeclaredNodeTypeName)) { protectedPropertiesToRemove.add(((AbstractJcrProperty)property).name()); continue; } if (mixinType.isNodeType(propertyDeclaredNodeTypeName)) { JcrPropertyDefinition match; // Only the residual definition would work - if there were any other definition for this name, // the mixin type would not have been added due to the conflict if (propertyDefinition.isMultiple()) { match = nodeTypes.findPropertyDefinition(session, primaryTypeName, newMixinNames, JcrNodeType.RESIDUAL_NAME, property.getValues(), true); } else { match = nodeTypes.findPropertyDefinition(session, primaryTypeName, newMixinNames, JcrNodeType.RESIDUAL_NAME, property.getValue(), true, true); } if (match == null) { throw new ConstraintViolationException(JcrI18n.cannotRemoveMixinNoPropertyDefinition.text( mixinName, property.getName(), location(), readable(primaryTypeName), readable(newMixinNames))); } } } } if (mixinType.hasChildNodeDefinitions()) { protectedChildrenToRemove = new ArrayList<AbstractJcrNode>(); // ------------------------------------------------------------------------------ // Check that any remaining child nodes that use the mixin type to be removed // match the residual definition for the node. // ------------------------------------------------------------------------------ SiblingCounter siblingCounter = SiblingCounter.create(node(), cache()); for (NodeIterator iter = getNodesInternal(); iter.hasNext();) { AbstractJcrNode child = (AbstractJcrNode)iter.nextNode(); NodeDefinition childDefinition = child.getDefinition(); String childDeclaredNodeType = childDefinition.getDeclaringNodeType().getName(); // if we have a protected child, check if it belongs to the mixin itself or any ancestor of the mixin // if yes, mark it for removal if (childDefinition.isProtected() && mixinType.isNodeType(childDeclaredNodeType)) { protectedChildrenToRemove.add(child); continue; } if (mixinType.isNodeType(childDeclaredNodeType)) { // Only the residual definition would work - if there were any other definition for this name, // the mixin type would not have been added due to the conflict boolean skipProtected = true; NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(primaryTypeName, newMixinNames); JcrNodeDefinition match = childDefns.findBestDefinitionForChild(JcrNodeType.RESIDUAL_NAME, child.getPrimaryNodeType().getInternalName(), skipProtected, siblingCounter); if (match == null) { throw new ConstraintViolationException(JcrI18n.cannotRemoveMixinNoChildNodeDefinition.text( mixinName, child.getName(), location(), readable(primaryTypeName), readable(newMixinNames))); } } } } boolean wasReferenceable = isReferenceable(); // Change the mixin types property (atomically, even if some other operation snuck in and added the mixin) ... MutableCachedNode mutable = mutable(); if (nodeTypes.isUnorderedCollection(removedMixinName, null)) { if (hasNodes()) { // we're removing a large unordered collection mixin which is not possible if this node has children throw new ConstraintViolationException(JcrI18n.cannotRemoveUnorderedCollectionMixin.text(removedMixinName, getPath())); } else { // we're removing a large collection mixin from an empty node, so we need to remove some internal data from the document... mutable.removeInternalProperty(DocumentConstants.BUCKET_ID_LENGTH); } } mutable.removeMixin(cache, removedMixinName); // If there were protected properties or children, remove them if (protectedPropertiesToRemove != null) { for (Name protectedPropertyName : protectedPropertiesToRemove) { mutable.removeProperty(cache, protectedPropertyName); } } if (protectedChildrenToRemove != null) { for (AbstractJcrNode protectedChild : protectedChildrenToRemove) { protectedChild.remove(); } } if (wasReferenceable && !isReferenceable()) { // Need to remove the 'jcr:uuid' reference ... mutable.removeProperty(cache, JcrLexicon.UUID); } // Since we've changed the mixins, release the cached property definition IDs for the node's properties ... for (AbstractJcrProperty prop : this.jcrProperties.values()) { prop.releasePropertyDefinitionId(); } for (NodeIterator iter = getNodesInternal(); iter.hasNext();) { AbstractJcrNode child = (AbstractJcrNode)iter.nextNode(); child.releaseNodeDefinitionId(); } // per JCR 2.0 10.10.3.1, the change should be reflected immediately in the property updateMixinsProperty(); } @Override public boolean canAddMixin( String mixinName ) throws NoSuchNodeTypeException, RepositoryException { CheckArg.isNotEmpty(mixinName, "mixinName"); JcrNodeTypeManager jcrNodeTypeManager = session().nodeTypeManager(); NodeTypes nodeTypes = session().nodeTypes(); JcrNodeType mixinType = jcrNodeTypeManager.getNodeType(mixinName); if (!mixinType.isMixin()) return false; if (isLockedByAnotherSession()) return false; if (!isCheckedOut()) return false; if (getDefinition().isProtected()) return false; if (mixinType.isAbstract()) return false; final Name mixinNameObj = mixinType.getInternalName(); if (isNodeType(mixinNameObj)) return true; // do not allow versionable mixin on external nodes or large collections boolean isNodeUnorderedCollection = nodeTypes.isUnorderedCollection(getPrimaryTypeName(), getMixinTypeNames()); if ((isExternal() || isNodeUnorderedCollection) && session().nodeTypes().isVersionable(mixinNameObj)) { return false; } boolean isMixinUnorderedCollection = nodeTypes.isUnorderedCollection(mixinNameObj, null); if (isMixinUnorderedCollection) { if (isNodeUnorderedCollection) { // do not allow 2 unordered large collections return false; } else if (hasNodes()){ // we're trying to make a new node an unordered collection. // this is possible only if the node doesn't have any children return false; } } // ------------------------------------------------------------------------------ // Check for any existing properties based on residual definitions that conflict // ------------------------------------------------------------------------------ if (mixinType.hasPropertyDefinitions()) { for (JcrPropertyDefinition propDefn : mixinType.allPropertyDefinitions()) { Name propName = propDefn.getInternalName(); AbstractJcrProperty existingProp = getProperty(propName); if (existingProp == null) continue; if (propDefn.isMultiple()) { if (!propDefn.canCastToTypeAndSatisfyConstraints(existingProp.getValues(), session())) { return false; } } else { if (!propDefn.canCastToTypeAndSatisfyConstraints(existingProp.getValue(), session())) { return false; } } } } // ------------------------------------------------------------------------------ // Check for any existing child nodes based on residual definitions that conflict // ------------------------------------------------------------------------------ if (mixinType.hasChildNodeDefinitions()) { // There is at least one child node definition (unusual for mixins!) .... Set<Name> mixinChildNodeNames = new HashSet<Name>(); for (JcrNodeDefinition nodeDefinition : mixinType.allChildNodeDefinitions()) { mixinChildNodeNames.add(nodeDefinition.getInternalName()); } CachedNode node = node(); NodeCache cache = cache(); // Need to figure out if the child node requires an SNS definition ChildReferences refs = node.getChildReferences(cache()); // Create a sibling counter that reduces the count by 1, since we're always dealing with existing children // but the 'findBestDefinitionForChild' logic is looking to *add* a child ... SiblingCounter siblingCounter = SiblingCounter.alter(SiblingCounter.create(refs), -1); for (Name nodeName : mixinChildNodeNames) { int snsCount = siblingCounter.countSiblingsNamed(nodeName); if (snsCount == 0) continue; Iterator<ChildReference> iter = refs.iterator(nodeName); while (iter.hasNext()) { ChildReference ref = iter.next(); CachedNode child = cache.getNode(ref); Name childPrimaryType = child.getPrimaryType(cache); boolean skipProtected = true; NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(mixinType.getInternalName(), null); JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(nodeName, childPrimaryType, skipProtected, siblingCounter); if (childDefn == null) { return false; } } } } return true; } @Override public NodeDefinition getDefinition() throws RepositoryException { checkSession(); return nodeDefinition(); } final void setNodeDefinitionId( NodeDefinitionId nodeDefnId, int nodeTypeVersion ) { this.cachedDefn = new CachedDefinition(nodeDefnId, nodeTypeVersion); } final void releaseNodeDefinitionId() { this.cachedDefn = null; } /** * Get the property definition ID. * * @return the cached property definition ID; never null * @throws ItemNotFoundException if the node that contains this property doesn't exist anymore * @throws ConstraintViolationException if no valid property definition could be found * @throws RepositoryException if there is a problem with this repository */ NodeDefinitionId nodeDefinitionId() throws ItemNotFoundException, ConstraintViolationException, RepositoryException { CachedDefinition defn = cachedDefn; NodeTypes nodeTypes = session().nodeTypes(); if (defn == null || nodeTypes.getVersion() > defn.nodeTypesVersion) { assert !this.isRoot(); // Determine the node type based upon this node's type information ... CachedNode parent = getParent().node(); SessionCache cache = sessionCache(); Name nodeName = name(); Name primaryType = node().getPrimaryType(cache); Name parentPrimaryType = parent.getPrimaryType(cache); Set<Name> parentMixins = parent.getMixinTypes(cache); SiblingCounter siblingCounter = SiblingCounter.create(parent, cache); boolean skipProtected = true; NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(parentPrimaryType, parentMixins); JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(nodeName, primaryType, skipProtected, siblingCounter); if (childDefn == null) { throw new ConstraintViolationException(JcrI18n.noChildNodeDefinition.text(nodeName, getParent().location(), readable(parentPrimaryType), readable(parentMixins))); } NodeDefinitionId id = childDefn.getId(); setNodeDefinitionId(id, nodeTypes.getVersion()); return id; } return defn.nodeDefnId; } /** * Get the definition for this property. * * @return the cached property definition ID; never null * @throws ItemNotFoundException if the node that contains this property doesn't exist anymore * @throws ConstraintViolationException if no valid property definition could be found * @throws RepositoryException if there is a problem with this repository */ final NodeDefinition nodeDefinition() throws ItemNotFoundException, ConstraintViolationException, RepositoryException { CachedDefinition defn = cachedDefn; NodeTypes nodeTypes = session().nodeTypes(); if (defn == null || nodeTypes.getVersion() > defn.nodeTypesVersion) { assert !this.isRoot(); // Determine the node type based upon this node's type information ... CachedNode parent = getParent().node(); SessionCache cache = sessionCache(); Name nodeName = name(); Name primaryType = node().getPrimaryType(cache); Name parentPrimaryType = parent.getPrimaryType(cache); Set<Name> parentMixins = parent.getMixinTypes(cache); // The node is already a child, so create a counter that returns the count as if it were not a child ... SiblingCounter siblingCounter = SiblingCounter.alter(SiblingCounter.create(parent, cache), -1); boolean skipProtected = false; NodeDefinitionSet childDefns = nodeTypes.findChildNodeDefinitions(parentPrimaryType, parentMixins); JcrNodeDefinition childDefn = childDefns.findBestDefinitionForChild(nodeName, primaryType, skipProtected, siblingCounter); if (childDefn == null) { throw new ConstraintViolationException(JcrI18n.noChildNodeDefinition.text(nodeName, getParent().location(), readable(parentPrimaryType), readable(parentMixins))); } NodeDefinitionId id = childDefn.getId(); setNodeDefinitionId(id, nodeTypes.getVersion()); return childDefn; } return nodeTypes.getChildNodeDefinition(defn.nodeDefnId); } /** * Throw a {@link ConstraintViolationException} if this node is protected (based on the its node definition). * * @throws ConstraintViolationException if this node's definition indicates that the node is protected * @throws RepositoryException if an error occurs retrieving the definition for this node */ private void checkNotProtected() throws ConstraintViolationException, RepositoryException { if (getDefinition().isProtected()) { throw new ConstraintViolationException(JcrI18n.cannotRemoveItemWithProtectedDefinition.text(getPath())); } } @Override public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException { return versionManager().checkin(this); } @Override public void checkout() throws UnsupportedRepositoryOperationException, LockException, ActivityViolationException, RepositoryException { versionManager().checkout(this); } @Override public void doneMerge( Version version ) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { versionManager().doneMerge(this, version); } @Override public void cancelMerge( Version version ) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { versionManager().cancelMerge(this, version); } @Override public void update( String srcWorkspace ) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException { CheckArg.isNotNull(srcWorkspace, "srcWorkspace"); session().getWorkspace().validateCrossWorkspaceAction(srcWorkspace); if (workspaceName().equalsIgnoreCase(srcWorkspace)) { return; } if (session().hasPendingChanges()) { throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text()); } checkNotProtected(); Path srcPath = null; try { srcPath = correspondingNodePath(srcWorkspace); } catch (ItemNotFoundException infe) { return; } /* * Check if the source is locked */ JcrSession sourceSession = session.spawnSession(srcWorkspace, true); AbstractJcrNode sourceNode = sourceSession.node(srcPath); if (session.lockManager().isLocked(sourceNode) && !session.lockManager().hasLockToken(sourceNode.getLock().getLockToken())) { throw new LockException(srcPath.toString()); } /** * Check if the destination is locked */ Path dstPath = path(); if (isLockedByAnotherSession()) { throw new LockException(dstPath.toString()); } // create an inner session for cloning JcrSession cloneSession = session.spawnSession(false); getSession().getWorkspace().deepClone(sourceSession, sourceNode.key(), cloneSession, key()); session().refresh(false); } @Override public NodeIterator merge( String srcWorkspace, boolean bestEffort ) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException { CheckArg.isNotNull(srcWorkspace, "srcWorkspace"); checkNotProtected(); return versionManager().merge(this, srcWorkspace, bestEffort, false); } @Override public String getCorrespondingNodePath( String workspaceName ) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { CheckArg.isNotNull(workspaceName, "workspaceName"); checkSession(); NamespaceRegistry namespaces = this.context().getNamespaceRegistry(); return correspondingNodePath(workspaceName).getString(namespaces); } protected final Path correspondingNodePath( String workspaceName ) throws NoSuchWorkspaceException, ItemNotFoundException, RepositoryException { assert workspaceName != null; NamespaceRegistry namespaces = this.context().getNamespaceRegistry(); // Find the closest ancestor (including this node) that is referenceable ... AbstractJcrNode referenceableRoot = this; while (!referenceableRoot.isNodeType(JcrMixLexicon.REFERENCEABLE.getString(namespaces))) { referenceableRoot = referenceableRoot.getParent(); } // Find the relative path from the nearest referenceable node to this node (or null if this node is referenceable) ... Path relativePath = path().equals(referenceableRoot.path()) ? null : path().relativeTo(referenceableRoot.path()); NodeKey key = referenceableRoot.key(); // if the we're looking for a system node, we need to use the system ws name, which is repository-wide String systemWsKey = session.getRepository().systemWorkspaceKey(); String workspaceKey = systemWsKey.equals(key.getWorkspaceKey()) ? systemWsKey : NodeKey.keyForWorkspaceName(workspaceName); NodeKey nodeKey = new NodeKey(key.getSourceKey(), workspaceKey, key.getIdentifier()); return session.getPathForCorrespondingNode(workspaceName, nodeKey, relativePath); } @Override public NodeIterator getSharedSet() throws RepositoryException { if (isShareable()) { // Find the nodes that make up this shared set ... return sharedSet().getSharedNodes(); } // Otherwise, the shared set is just this node ... return new JcrSingleNodeIterator(this); } /** * Find all of the {@link javax.jcr.Node}s that make up the shared set. * * @return the iterator over the nodes in the node set; never null, but possibly empty if this is not shareable, or of size 1 * if the node is shareable but hasn't been shared */ SharedSet sharedSet() { return session.shareableNodeCache().getSharedSet(this); } void addSharedNode( AbstractJcrNode shareableNode, Name newNodeName ) throws RepositoryException { assert session == shareableNode.session; session.checkPermission(this, ModeShapePermissions.ADD_NODE); if (isLockedByAnotherSession()) { throw new LockException(JcrI18n.lockTokenNotHeld.text(location())); } // Determine the node type based upon this node's type information ... SessionCache cache = sessionCache(); MutableCachedNode node = mutable(); // validate there is an appropriate child node definition JcrNodeDefinition childDefn = validateChildNodeDefinition(newNodeName, shareableNode.getPrimaryTypeName(), true); // See if this node is checked in. If so, then we can only create children if the child // node definition has an OPV of 'ignore'. See Section 15.2.2 of the JSR-283 spec for details ... if (!isCheckedOut() && childDefn.getOnParentVersion() != OnParentVersionAction.IGNORE) { // The OPV is not 'ignore', so we can't create the new node ... Path parentPath = path(); String parentPathStr = readable(parentPath); int numExistingSns = node.getChildReferences(cache).getChildCount(newNodeName); int sns = numExistingSns + 1; String segment = readable(session.pathFactory().createSegment(newNodeName, sns)); String opv = OnParentVersionAction.nameFromValue(childDefn.getOnParentVersion()); I18n msg = JcrI18n.cannotCreateChildOnCheckedInNodeSinceOpvOfChildDefinitionIsNotIgnore; throw new VersionException(msg.text(segment, readable(parentPathStr), childDefn.getName(), opv)); } // We can create the shared node ... NodeKey childKey = shareableNode.key(); node.linkChild(cache, childKey, newNodeName); } @Override public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException { if (isShareable()) { // Remove all of the node is the shared set ... SharedSet sharedSet = sharedSet(); if (sharedSet.getSize() != 1) { // There's more than one shared NodeIterator sharedSetNodes = sharedSet.getSharedNodes(); List<AbstractJcrNode> shared = new ArrayList<AbstractJcrNode>(); while (sharedSetNodes.hasNext()) { AbstractJcrNode nodeInSharedSet = (AbstractJcrNode)sharedSetNodes.nextNode(); // Check whether the parent is locked or if the session has permissions ... try { AbstractJcrNode parent = nodeInSharedSet.getParent(); parent.checkForLock(); session.checkPermission(nodeInSharedSet, ModeShapePermissions.REMOVE); if (nodeInSharedSet != this) { // Okay to remove ... shared.add(nodeInSharedSet); } } catch (ItemNotFoundException e) { // expected by the TCK throw new InvalidItemStateException(e); } } // It's okay to remove all of the nodes in the shared set ... for (AbstractJcrNode nodeInSharedSet : shared) { nodeInSharedSet.doRemove(); } // continue by removing the shareable node ... } } // Per section 14.2 of the JCR 2.0 specification: // "In cases where the shared set consists of a single node, or when these methods are // called on a non-shareable node, their behavior is identical to Node.remove()." doRemove(); } @Override public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException { internalRemove(false); } void internalRemove( boolean skipVersioningValidation ) throws VersionException, LockException, ConstraintViolationException, RepositoryException { checkSession(); // A node that is locked by one session can be removed by another session as long as there is no lock // on the parent node. See Section 17.7 of the JCR 2.0 specification: // // "Removing a node is considered an alteration of its parent. This means that a node within the scope of // a lock may be removed by a session that is not an owner of that lock, assuming no other restriction // prevents the removal." AbstractJcrNode parent = null; try { parent = getParent(); } catch (ItemNotFoundException e) { // expected by the TCK throw new InvalidItemStateException(e); } try { parent.checkForLock(); session.checkPermission(this, ModeShapePermissions.REMOVE); // MODE-1920: check permission to remove child nodes session.checkPermission(parent.path(), ModeShapePermissions.REMOVE_CHILD_NODES); } catch (NodeNotFoundInParentException e) { // expected by the TCK throw new InvalidItemStateException(e); } if (!skipVersioningValidation && !parent.isCheckedOut()) { // The parent node is checked in, so we can only remove this node if this node has an OPV of 'ignore'. // This is probably rarely the case, so the extra work is acceptable // See Section 15.2.2 of JSR-283 spec for details ... NodeDefinition defn = getDefinition(); int opv = defn.getOnParentVersion(); if (opv != OnParentVersionAction.IGNORE) { // The OPV is not 'ignore', so we can't create the new node ... String opvStr = OnParentVersionAction.nameFromValue(opv); I18n msg = JcrI18n.cannotRemoveChildOnCheckedInNodeSinceOpvOfChildDefinitionIsNotIgnore; throw new VersionException(msg.text(path(), defn.getName(), opvStr)); } // Otherwise, child node definition is 'ignore', so okay to remove ... } // Even if this is shareable, we remove the shareable nodes the same way // (per section 14.2 of the JCR 2.0 specification) ... doRemove(); } /** * Perform a real remove of this node. * * @throws VersionException * @throws LockException * @throws ConstraintViolationException * @throws AccessDeniedException * @throws RepositoryException */ protected abstract void doRemove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException; @Override public boolean isCheckedOut() throws RepositoryException { AbstractJcrNode node = this; SessionCache cache = sessionCache(); ValueFactory<Boolean> booleanFactory = session.context().getValueFactories().getBooleanFactory(); while (node != null) { // If the node has an OPV of 'ignore' ... NodeDefinition defn = node.getDefinition(); if (defn.getOnParentVersion() == OnParentVersionAction.IGNORE) { // This node is not or cannot be checked in ... return true; } if (node.isNodeType(JcrMixLexicon.VERSIONABLE)) { Property prop = node.node().getProperty(JcrLexicon.IS_CHECKED_OUT, cache); // This prop can only be null if the node has not been saved since it was made versionable. return prop == null || booleanFactory.create(prop.getFirstValue()); } if (node.isRoot()) break; node = node.getParent(); } return true; } @Override public void restore( String versionName, boolean removeExisting ) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { restore(getVersionHistory().getVersion(versionName), removeExisting); } @Override public void restore( Version version, boolean removeExisting ) throws VersionException, ItemExistsException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException { try { checkNotProtected(); } catch (ConstraintViolationException cve) { throw new UnsupportedRepositoryOperationException(cve); } versionManager().restoreAtAbsPath(getPath(), version, removeExisting, false); } @Override public void restore( Version version, String relPath, boolean removeExisting ) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { checkNotProtected(); PathFactory pathFactory = session.pathFactory(); Path relPathAsPath = pathFactory.create(relPath); if (relPathAsPath.isAbsolute()) throw new RepositoryException(JcrI18n.invalidRelativePath.text(relPath)); Path actualPath = pathFactory.create(path(), relPathAsPath).getCanonicalPath(); versionManager().restoreAtAbsPath(session.stringFactory().create(actualPath), version, removeExisting, false); } @Override public void restoreByLabel( String versionLabel, boolean removeExisting ) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { restore(getVersionHistory().getVersionByLabel(versionLabel), removeExisting); } @Override public JcrVersionHistoryNode getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException { return versionManager().getVersionHistory(this); } @Override public JcrVersionNode getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException { checkSession(); if (!hasProperty(JcrLexicon.BASE_VERSION)) { throw new UnsupportedRepositoryOperationException(JcrI18n.requiresVersionable.text()); } NodeKey baseVersionKey = ((NodeKeyReference)getProperty(JcrLexicon.BASE_VERSION).getValue().value()).getNodeKey(); try { return (JcrVersionNode)session().node(baseVersionKey, null); } catch (NodeNotFoundInParentException | ItemNotFoundException e) { // this should only happen if multiple threads are performing non-atomic checkin/checkout flows on the same node // see MODE-2418 throw new VersionException(JcrI18n.cannotLocateBaseVersion.text(baseVersionKey, getPath()), e); } } @Override public Lock lock( boolean isDeep, boolean isSessionScoped ) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // Session's liveness will be checked in 'lockManager()' ... return session.lockManager().lock(this, isDeep, isSessionScoped, Long.MAX_VALUE, null); } @Override public final Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // Session's liveness will be checked in 'lockManager()' ... return session.lockManager().getLock(this); } protected final Lock getLockIfExists() throws UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { return session.lockManager().getLockIfExists(this); } @Override public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { // Session's liveness will be checked in 'lockManager()' ... session.lockManager().unlock(this); } @Override public boolean holdsLock() { // Session's liveness will be checked in 'lockManager()' ... return session.lockManager().holdsLock(this); } @Override public boolean isLocked() throws RepositoryException { // Session's liveness will be checked in 'lockManager()' ... return session.lockManager().isLocked(this); } @Override public void followLifecycleTransition( String transition ) throws UnsupportedRepositoryOperationException /*, InvalidLifecycleTransitionException, RepositoryException*/{ throw new UnsupportedRepositoryOperationException(); } @Override public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException { throw new UnsupportedRepositoryOperationException(); } @Override public boolean isNode() { return true; } @Override public boolean isNew() { try { CachedNode node = node(); return node instanceof MutableCachedNode && ((MutableCachedNode)node).isNew(); } catch (RepositoryException e) { // continue by returning false, since the node probably doesn't exist anymore return false; } } @Override public boolean isModified() { try { CachedNode node = node(); if (node instanceof MutableCachedNode) { MutableCachedNode mutable = (MutableCachedNode)node; return !mutable.isNew() && mutable.hasChanges(); } } catch (RepositoryException e) { // continue by returning false, since the node probably doesn't exist anymore } return false; } @Override public boolean isSame( Item otherItem ) throws RepositoryException { if (otherItem == this) return true; if (otherItem instanceof AbstractJcrNode) { NodeKey thatKey = ((AbstractJcrNode)otherItem).key(); if (!this.key.equals(thatKey)) return false; // Make sure they are the same repository ... return super.isSameRepository(otherItem); } return false; } @Override public void accept( ItemVisitor visitor ) throws RepositoryException { CheckArg.isNotNull(visitor, "visitor"); checkSession(); visitor.visit(this); } @Deprecated @Override public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException { if (this.isRoot()) { // Just call save on the session (it's more efficient) ... session.save(); } else { session.save(this); } } @Override public void refresh( boolean keepChanges ) throws RepositoryException { CachedNode node = node(); // TCK: this should throw an exception if the node has been removed if (!keepChanges) { session.cache().clear(node); } } /** * {@inheritDoc} * <p> * According to Section 14.3 of the JCR 2.0 specification, an implementation may choose whether the {@link Item#remove()} * method (and the {@link Session#removeItem(String)} method) behaves as a {@link javax.jcr.Node#removeShare()} or * {@link javax.jcr.Node#removeSharedSet()}. {@link javax.jcr.Node#removeShare()} just removes this node from the shared set, * whereas {@link javax.jcr.Node#removeSharedSet()} removes all nodes in the shared set, including the original shared node. * </p> * <p> * ModeShape implements {@link Item#remove()} of a shared node as simply removing this node from the shared set. In other * words, this method is equivalent to calling {@link #removeShare()}. * </p> * * @see javax.jcr.Item#remove() */ @Override public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException { // Since this node might be shareable, we want to implement 'remove()' by calling 'removeShare()', // which will behave correctly even if it is not shareable ... removeShare(); } /** * Performs a "best effort" check on whether a node can be added at the given relative path from this node with the given * primary node type (if one is specified). * <p> * Note that a result of {@code true} from this method does not guarantee that a call to {@code #addNode(String, String)} with * the same arguments will succeed, but a result of {@code false} guarantees that it would fail (assuming that the current * repository state does not change). * </p> * * @param relPath the relative path at which the node would be added; may not be null * @param primaryNodeTypeName the primary type that would be used for the node; null indicates that a default primary type * should be used if possible * @return false if the node could not be added for any reason; true if the node <i>might</i> be able to be added * @throws RepositoryException if an error occurs accessing the repository */ final boolean canAddNode( String relPath, String primaryNodeTypeName ) throws RepositoryException { CheckArg.isNotEmpty(relPath, relPath); checkSession(); // Determine the path ... Path path = null; try { path = session().pathFactory().create(relPath); } catch (org.modeshape.jcr.value.ValueFormatException e) { return false; } if (path.size() == 0) { return false; } if (path.isIdentifier()) { return false; } if (path.getLastSegment().getIndex() > 1 || relPath.endsWith("]")) { return false; } if (path.size() > 1) { // The only segment in the path is the child name ... Path parentPath = path.getParent(); try { // Find the parent node ... AbstractJcrNode other = session.node(node(), parentPath); return other.canAddNode(primaryNodeTypeName); } catch (RepositoryException e) { return false; } } return this.canAddNode(primaryNodeTypeName); } private boolean canAddNode( String primaryNodeTypeName ) throws RepositoryException { if (isLockedByAnotherSession()) { return false; } // Determine the name for the primary node type if (primaryNodeTypeName != null) { if (!session().nodeTypeManager().hasNodeType(primaryNodeTypeName)) return false; JcrNodeType nodeType = session().nodeTypeManager().getNodeType(primaryNodeTypeName); if (nodeType.isAbstract()) return false; if (nodeType.isMixin()) return false; if (INTERNAL_NODE_TYPE_NAMES.contains(nodeType.getInternalName())) return false; } return true; } @Override public String toString() { try { PropertyIterator iter = this.getProperties(); StringBuilder propertyBuff = new StringBuilder(); while (iter.hasNext()) { AbstractJcrProperty prop = (AbstractJcrProperty)iter.nextProperty(); propertyBuff.append(prop); if (iter.hasNext()) { propertyBuff.append(", "); } } return this.getPath() + " {" + propertyBuff.toString() + "}"; } catch (Exception e) { return e.getMessage(); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AbstractJcrNode)) { return false; } AbstractJcrNode that = (AbstractJcrNode) o; return Objects.equals(key, that.key); } @Override public int hashCode() { return Objects.hash(key); } /** * Determines whether this node, or any nodes below it, contain changes that depend on nodes that are outside of this node's * hierarchy. * * @param affectedNodeKeys the reference that should be assigned to the set of node keys that are at or below this node; may * be null if not needed * @return true if this node's hierarchy has nodes with changes dependent on nodes from outside the hierarchy * @throws InvalidItemStateException * @throws ItemNotFoundException * @throws RepositoryException */ protected boolean containsChangesWithExternalDependencies( AtomicReference<Set<NodeKey>> affectedNodeKeys ) throws RepositoryException { Set<NodeKey> allChanges = sessionCache().getChangedNodeKeys(); Set<NodeKey> changesAtOrBelowThis = sessionCache().getChangedNodeKeysAtOrBelow(this.node()); removeReferrerChanges(allChanges, changesAtOrBelowThis); if (affectedNodeKeys != null) affectedNodeKeys.set(changesAtOrBelowThis); return !changesAtOrBelowThis.containsAll(allChanges); } /** * Removes all the keys from the first set which represent referrer node keys to any of the nodes in the second set. * * @param allChanges the set of keys to the referrer nodes * @param changesAtOrBelowThis the set of referrers that were changed at or below this node * @throws RepositoryException if there is a problem */ private void removeReferrerChanges( Set<NodeKey> allChanges, Set<NodeKey> changesAtOrBelowThis ) throws RepositoryException { // check if there are any nodes in the overall list of changes (and outside the branch) due to reference changes for (Iterator<NodeKey> allChangesIt = allChanges.iterator(); allChangesIt.hasNext();) { NodeKey changedNodeKey = allChangesIt.next(); if (changesAtOrBelowThis.contains(changedNodeKey)) { continue; } MutableCachedNode changedNodeOutsideBranch = session().cache().mutable(changedNodeKey); AbstractJcrNode changedNode = null; try { changedNode = session().node(changedNodeKey, null); } catch (ItemNotFoundException e) { // node was deleted allChangesIt.remove(); continue; } boolean isShareable = changedNode.isShareable(); if (isShareable /* && changedNodeOutsideBranch.hasOnlyChangesToAdditionalParents() */) { // assume that a shared node was added/removed and is to be included ... allChangesIt.remove(); continue; } boolean isReferenceable = changedNode.isReferenceable(); if (!isReferenceable) { continue; } Set<NodeKey> changedReferrers = changedNodeOutsideBranch.getChangedReferrerNodes(); for (NodeKey changedNodeInBranchKey : changesAtOrBelowThis) { if (changedReferrers.contains(changedNodeInBranchKey)) { // one of the changes in the branch is a referrer of the node outside the branch so we won't take the outside // node into account allChangesIt.remove(); } } } } protected static final class ChildNodeResolver implements JcrChildNodeIterator.NodeResolver { private final JcrSession session; private final NodeKey parentKey; private final boolean checkPermission; public ChildNodeResolver( JcrSession session, NodeKey parentKey, boolean checkPermission ) { this.session = session; this.parentKey = parentKey; this.checkPermission = checkPermission; } protected ChildNodeResolver( JcrSession session, NodeKey parentKey ) { this(session, parentKey, session.checkPermissionsWhenIteratingChildren()); } @Override public Node nodeFrom( ChildReference ref ) { try { AbstractJcrNode node = session.node(ref.getKey(), null, parentKey); if (checkPermission && !node.isExternal() && !session.hasPermission(node.getPath(), ModeShapePermissions.READ)) { return null; } return node; } catch (RepositoryException e) { return null; } } } }