Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.modeshape.jcr.AbstractJcrNode Maven / Gradle / Ivy
/*
* 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.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.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
abstract class AbstractJcrNode extends AbstractJcrItem implements Node {
enum Type {
ROOT,
NODE,
SYSTEM,
VERSION,
VERSION_HISTORY;
private static final Map DEFAULT_TYPE_BY_NAME;
static {
Map byName = new HashMap();
// 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 INTERNAL_NODE_TYPE_NAMES = Collections.singleton(ModeShapeLexicon.SHARE);
protected final NodeKey key;
private final ConcurrentMap jcrProperties = new ConcurrentHashMap();
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 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 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 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 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 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 results = new ArrayList(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()));
}
}
/**
* 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() ? false : true;
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() ? false : true;
}
// We know it's a resolved relative path with more than one segment ...
if (path.size() > 1) {
try {
return session().node(node(), path) != null;
} 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);
return ref != null;
}
@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());
}
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 {
ChildReferences childReferences = node().getChildReferences(sessionCache());
if (childReferences.isEmpty()) return JcrEmptyNodeIterator.INSTANCE;
return new JcrChildNodeIterator(new ChildNodeResolver(session, key()), childReferences.iterator());
}
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 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 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 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 patterns = new LinkedList();
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 '\\':
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 (isLocked() && !getLock().isLockOwningSession()) {
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 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())));
}
// 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 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 autoCreatePropertiesFor( Name nodeName,
Name primaryType,
PropertyFactory propertyFactory,
NodeTypes capabilities ) {
Collection autoPropDefns = capabilities.getAutoCreatedPropertyDefinitions(primaryType);
if (autoPropDefns.isEmpty()) {
return null;
}
// There is at least one auto-created property on this node ...
LinkedList props = new LinkedList();
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 autoChildDefns = capabilities.getAutoCreatedChildNodeDefinitions(primaryType);
if (!autoChildDefns.isEmpty()) {
// There is at least one auto-created child under this node ...
Set childNames = new HashSet();
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 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 valuesWithDesiredType = new ArrayList(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 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 null
*
* @param inputValues the input values
* @return an array without null elements.
*/
private Value[] compactValues( Value[] inputValues ) {
if (inputValues == null) {
return null;
}
List compactedList = new ArrayList();
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 findJcrProperties( Iterator propertyIterator )
throws AccessDeniedException, RepositoryException {
try {
Collection result = new LinkedList();
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 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 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
* referenceType
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 references = new LinkedList();
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 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 {
return !node().getChildReferences(sessionCache()).isEmpty();
}
@Override
public boolean hasProperties() throws RepositoryException {
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 mixinNodeTypes = new LinkedList();
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 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 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 mixinTypeNames = node.getMixinTypes(cache);
Iterator 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 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);
}
// 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 currentMixins = new ArrayList(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);
}
}
// 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 newMixinNames = new HashSet(cachedNode.getMixinTypes(cache));
if (!newMixinNames.remove(removedMixinName)) {
// Nothing to remove ...
return;
}
JcrNodeType mixinType = nodeTypes.getNodeType(removedMixinName);
List protectedPropertiesToRemove = null;
List protectedChildrenToRemove = null;
if (mixinType.hasPropertyDefinitions()) {
protectedPropertiesToRemove = new ArrayList();
// ------------------------------------------------------------------------------
// 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.noPropertyDefinition.text(property.getName(), location(),
readable(primaryTypeName),
readable(newMixinNames)));
}
}
}
}
if (mixinType.hasChildNodeDefinitions()) {
protectedChildrenToRemove = new ArrayList();
// ------------------------------------------------------------------------------
// 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.noChildNodeDefinition.text(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();
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");
JcrNodeType mixinType = session().nodeTypeManager().getNodeType(mixinName);
if (!mixinType.isMixin()) return false;
if (isLocked()) 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
if (isExternal() && session().nodeTypes().isVersionable(mixinNameObj)) {
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 mixinChildNodeNames = new HashSet();
for (JcrNodeDefinition nodeDefinition : mixinType.allChildNodeDefinitions()) {
mixinChildNodeNames.add(nodeDefinition.getInternalName());
}
CachedNode node = node();
NodeCache cache = cache();
NodeTypes nodeTypes = session.nodeTypes();
// 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 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 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 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 (isLocked() && !session.lockManager().hasLockToken(this.getLock().getLockToken())) {
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 (isLocked() && !getLock().isLockOwningSession()) {
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 shared = new ArrayList();
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 {
if (!session().repository().versioningUsed()) {
//we can bypass this altogether is versioning is not being used....
return true;
}
AbstractJcrNode node = this;
SessionCache cache = sessionCache();
ValueFactory 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();
return (JcrVersionNode)session().node(baseVersionKey, null);
}
@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}
*
* 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.
*
*
* 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()}.
*
*
* @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).
*
* 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).
*
*
* @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 might 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 final boolean canAddNode( String primaryNodeTypeName ) throws RepositoryException {
if (isLocked() && !getLock().isLockOwningSession()) {
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();
}
}
/**
* 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> affectedNodeKeys )
throws RepositoryException {
Set allChanges = sessionCache().getChangedNodeKeys();
Set 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 allChanges,
Set 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 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 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;
}
}
}
}