/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.jackrabbit.jcr2spi; import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry; import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry; import org.apache.jackrabbit.jcr2spi.lock.LockStateManager; import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType; import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeImpl; import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.jcr2spi.operation.AddNode; import org.apache.jackrabbit.jcr2spi.operation.AddProperty; import org.apache.jackrabbit.jcr2spi.operation.Operation; import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes; import org.apache.jackrabbit.jcr2spi.operation.SetMixin; import org.apache.jackrabbit.jcr2spi.operation.SetPrimaryType; import org.apache.jackrabbit.jcr2spi.operation.Update; import org.apache.jackrabbit.jcr2spi.state.ItemState; import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator; import org.apache.jackrabbit.jcr2spi.state.NodeState; import org.apache.jackrabbit.jcr2spi.state.Status; import org.apache.jackrabbit.jcr2spi.util.LogUtil; import org.apache.jackrabbit.jcr2spi.util.StateUtility; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.NodeId; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.PropertyId; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QValue; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.value.ValueFormat; import org.apache.jackrabbit.util.ChildrenCollectorFilter; import org.apache.jackrabbit.value.ValueHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; 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.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Iterator; import java.util.List; /** * <code>NodeImpl</code>... */ public class NodeImpl extends ItemImpl implements Node { private static Logger log = LoggerFactory.getLogger(NodeImpl.class); protected NodeImpl(SessionImpl session, NodeState state, ItemLifeCycleListener[] listeners) { super(session, state, listeners); Name nodeTypeName = state.getNodeTypeName(); // make sure the nodetype name is valid if (!session.getNodeTypeManager().hasNodeType(nodeTypeName)) { // should not occur. Since nodetypes are defined by the 'server' // its not possible to determine a fallback nodetype that is // always available. throw new IllegalArgumentException("Unknown nodetype " + LogUtil.saveGetJCRName(nodeTypeName, session.getNameResolver())); } } //---------------------------------------------------------------< Item >--- /** * @see Item#getName() */ @Override public String getName() throws RepositoryException { checkStatus(); return session.getNameResolver().getJCRName(getQName()); } /** * Implementation of {@link Item#accept(javax.jcr.ItemVisitor)} for nodes. * * @param visitor * @throws RepositoryException * @see Item#accept(javax.jcr.ItemVisitor) */ @Override public void accept(ItemVisitor visitor) throws RepositoryException { checkStatus(); visitor.visit(this); } /** * Returns true * * @return true * @see Item#isNode() */ @Override public boolean isNode() { return true; } //---------------------------------------------------------------< Node >--- /** * @see Node#addNode(String) */ public Node addNode(String relPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { // validation performed in subsequent method return addNode(relPath, null); } /** * @see Node#addNode(String, String) */ public Node addNode(String relPath, String primaryNodeTypeName) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException { checkIsWritable(); // build path object and retrieve parent node Path nodePath = getPath(relPath).getNormalizedPath(); if (nodePath.getIndex() != Path.INDEX_UNDEFINED) { String msg = "Illegal subscript specified: " + relPath; log.debug(msg); throw new RepositoryException(msg); } NodeImpl parentNode; if (nodePath.getLength() == 1) { parentNode = this; } else { Path parentPath = nodePath.getAncestor(1); ItemManager itemMgr = getItemManager(); if (itemMgr.nodeExists(parentPath)) { parentNode = (NodeImpl) itemMgr.getNode(parentPath); } else if (itemMgr.propertyExists(parentPath)) { String msg = "Cannot add a node to property " + LogUtil.safeGetJCRPath(parentPath, session.getPathResolver()); log.debug(msg); throw new ConstraintViolationException(msg); } else { throw new PathNotFoundException("Cannot add a new node to a non-existing parent at " + LogUtil.safeGetJCRPath(parentPath, session.getPathResolver())); } } // get names objects for node and nt Name nodeName = nodePath.getName(); Name ntName = (primaryNodeTypeName == null) ? null : getQName(primaryNodeTypeName); // create new node (including validation checks) return parentNode.createNode(nodeName, ntName); } /** * @see Node#orderBefore(String, String) */ public synchronized void orderBefore(String srcChildRelPath, String destChildRelPath) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException { checkIsWritable(); if (!getPrimaryNodeType().hasOrderableChildNodes()) { throw new UnsupportedRepositoryOperationException("Child node ordering not supported on node " + safeGetJCRPath()); } // check arguments if (srcChildRelPath.equals(destChildRelPath)) { // there's nothing to do return; } // check existence if (!hasNode(srcChildRelPath)) { throw new ItemNotFoundException("Node " + safeGetJCRPath() + " has no child node with name " + srcChildRelPath); } if (destChildRelPath != null && !hasNode(destChildRelPath)) { throw new ItemNotFoundException("Node " + safeGetJCRPath() + " has no child node with name " + destChildRelPath); } Path srcPath = getReorderPath(srcChildRelPath); Path beforePath = null; if (destChildRelPath != null) { beforePath = getReorderPath(destChildRelPath); } Operation op = ReorderNodes.create(getNodeState(), srcPath, beforePath); session.getSessionItemStateManager().execute(op); } /** * @see Node#setProperty(String, Value) */ public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method int type = PropertyType.UNDEFINED; if (value != null) { type = value.getType(); } return setProperty(name, value, type); } /** * @see Node#setProperty(String, javax.jcr.Value, int) */ public Property setProperty(String name, Value value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { checkIsWritable(); Name propName = getQName(name); Property prop; if (hasProperty(propName)) { // property already exists: pass call to property prop = getProperty(propName); Value v = (type == PropertyType.UNDEFINED) ? value : ValueHelper.convert(value, type, session.getValueFactory()); prop.setValue(v); } else { if (value == null) { return new StaleProperty(); } else { // new property to be added prop = createProperty(propName, value, type); } } return prop; } /** * @see Node#setProperty(String, Value[]) */ public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method int type; if (values == null || values.length == 0 || values[0] == null) { type = PropertyType.UNDEFINED; } else { type = values[0].getType(); } return setProperty(name, values, type); } /** * @see Node#setProperty(String, Value[], int) */ public Property setProperty(String name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { checkIsWritable(); Name propName = getQName(name); Property prop; if (hasProperty(propName)) { // property already exists: pass call to property prop = getProperty(propName); Value[] vs = (type == PropertyType.UNDEFINED) ? values : ValueHelper.convert(values, type, session.getValueFactory()); prop.setValue(vs); } else { if (values == null) { // create and remove property is a nop. throw new ItemNotFoundException("Cannot remove a non-existing property."); } else { // new property to be added prop = createProperty(propName, values, type); } } return prop; } /** * @see Node#setProperty(String, String[]) */ public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method return setProperty(name, values, PropertyType.UNDEFINED); } /** * @see Node#setProperty(String, String[], int) */ public Property setProperty(String name, String[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method Value[] vs; if (type == PropertyType.UNDEFINED) { vs = ValueHelper.convert(values, PropertyType.STRING, session.getValueFactory()); } else { vs = ValueHelper.convert(values, type, session.getValueFactory()); } return setProperty(name, vs, type); } /** * @see Node#setProperty(String, String) */ public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method Value v = (value == null) ? null : session.getValueFactory().createValue(value, PropertyType.STRING); return setProperty(name, v, PropertyType.UNDEFINED); } /** * @see Node#setProperty(String, String, int) */ public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method Value v = (value == null) ? null : session.getValueFactory().createValue(value, type); return setProperty(name, v, type); } /** * @see Node#setProperty(String, InputStream) */ public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method Value v = (value == null ? null : session.getValueFactory().createValue(value)); return setProperty(name, v, PropertyType.BINARY); } /** * @see javax.jcr.Node#setProperty(String, Binary) */ public Property setProperty(String name, Binary value) throws RepositoryException { // validation performed in subsequent method Value v = (value == null ? null : session.getValueFactory().createValue(value)); return setProperty(name, v, PropertyType.BINARY); } /** * @see Node#setProperty(String, boolean) */ public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method return setProperty(name, session.getValueFactory().createValue(value), PropertyType.BOOLEAN); } /** * @see Node#setProperty(String, double) */ public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method return setProperty(name, session.getValueFactory().createValue(value), PropertyType.DOUBLE); } /** * @see javax.jcr.Node#setProperty(String, BigDecimal) */ public Property setProperty(String name, BigDecimal value) throws RepositoryException { // validation performed in subsequent method Value v = (value == null ? null : session.getValueFactory().createValue(value)); return setProperty(name, v, PropertyType.DECIMAL); } /** * @see Node#setProperty(String, long) */ public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method return setProperty(name, session.getValueFactory().createValue(value), PropertyType.LONG); } /** * @see Node#setProperty(String, Calendar) */ public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // validation performed in subsequent method Value v = (value == null ? null : session.getValueFactory().createValue(value)); return setProperty(name, v, PropertyType.DATE); } /** * @see Node#setProperty(String, Node) */ public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { // duplicate check to make sure, property can be written before value // validation below. checkIsWritable(); Value v; if (value == null) { v = null; } else { PropertyImpl.checkValidReference(value, PropertyType.REFERENCE, session.getNameResolver()); v = session.getValueFactory().createValue(value); } return setProperty(name, v, PropertyType.REFERENCE); } /** * @see Node#getNode(String) */ public Node getNode(String relPath) throws PathNotFoundException, RepositoryException { checkStatus(); NodeEntry nodeEntry = resolveRelativeNodePath(relPath); if (nodeEntry == null) { throw new PathNotFoundException(relPath); } try { return (Node) getItemManager().getItem(nodeEntry); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath, e); } } /** * @see Node#getNodes() */ public NodeIterator getNodes() throws RepositoryException { checkStatus(); // NOTE: Don't use a class derived from TraversingElementVisitor to traverse // the child nodes because this would lead to an infinite recursion. try { return getItemManager().getChildNodes(getNodeEntry()); } catch (ItemNotFoundException infe) { String msg = "Failed to list the child nodes of " + safeGetJCRPath(); log.debug(msg); throw new RepositoryException(msg, infe); } catch (AccessDeniedException ade) { String msg = "Failed to list the child nodes of " + safeGetJCRPath(); log.debug(msg); throw new RepositoryException(msg, ade); } } /** * @see Node#getNodes(String) */ public NodeIterator getNodes(String namePattern) throws RepositoryException { checkStatus(); return ChildrenCollectorFilter.collectChildNodes(this, namePattern); } /** * @see javax.jcr.Node#getNodes(String[]) */ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException { checkStatus(); return ChildrenCollectorFilter.collectChildNodes(this, nameGlobs); } /** * @see Node#getProperty(String) */ public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException { checkStatus(); PropertyEntry entry = resolveRelativePropertyPath(relPath); if (entry == null) { throw new PathNotFoundException(relPath); } try { return (Property) getItemManager().getItem(entry); } catch (AccessDeniedException e) { throw new PathNotFoundException(relPath); } catch (ItemNotFoundException e) { throw new PathNotFoundException(relPath); } } /** * @see Node#getProperties() */ public PropertyIterator getProperties() throws RepositoryException { checkStatus(); try { return getItemManager().getChildProperties(getNodeEntry()); } catch (ItemNotFoundException infe) { String msg = "Failed to list the child properties of " + getPath(); log.debug(msg); throw new RepositoryException(msg, infe); } catch (AccessDeniedException ade) { String msg = "Failed to list the child properties of " + getPath(); log.debug(msg); throw new RepositoryException(msg, ade); } } /** * @see Node#getProperties(String) */ public PropertyIterator getProperties(String namePattern) throws RepositoryException { checkStatus(); return ChildrenCollectorFilter.collectProperties(this, namePattern); } /** * @see javax.jcr.Node#getProperties(String) */ public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException { checkStatus(); return ChildrenCollectorFilter.collectProperties(this, nameGlobs); } /** * @see Node#getPrimaryItem() */ public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException { checkStatus(); String name = getPrimaryNodeType().getPrimaryItemName(); if (name == null) { throw new ItemNotFoundException("No primary item present on Node " + safeGetJCRPath()); } if (hasProperty(name)) { return getProperty(name); } else if (hasNode(name)) { return getNode(name); } else { throw new ItemNotFoundException("Primary item " + name + " does not exist on Node " + safeGetJCRPath()); } } /** * @see Node#getUUID() */ public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException { checkStatus(); String uuid = getNodeState().getUniqueID(); if (uuid == null || !isNodeType(NameConstants.MIX_REFERENCEABLE)) { throw new UnsupportedRepositoryOperationException(); } // Node is referenceable -> NodeId must contain a UUID part return uuid; } /** * @see Node#getIdentifier() */ public String getIdentifier() throws RepositoryException { checkStatus(); return session.getIdFactory().toJcrIdentifier(getNodeEntry().getId()); } /** * @see Node#getIndex() */ public int getIndex() throws RepositoryException { checkStatus(); int index = getNodeEntry().getIndex(); if (index == Path.INDEX_UNDEFINED) { throw new RepositoryException("Error while retrieving index."); } return index; } /** * @see Node#getReferences() */ public PropertyIterator getReferences() throws RepositoryException { return getReferences(null); } /** * @see javax.jcr.Node#getReferences(String) */ public PropertyIterator getReferences(String name) throws RepositoryException { return getReferences(name, false); } /** * @see javax.jcr.Node#getWeakReferences() */ public PropertyIterator getWeakReferences() throws RepositoryException { return getWeakReferences(null); } /** * @see javax.jcr.Node#getWeakReferences() */ public PropertyIterator getWeakReferences(String name) throws RepositoryException { return getReferences(name, true); } /** * @see Node#hasNode(String) */ public boolean hasNode(String relPath) throws RepositoryException { checkStatus(); NodeEntry nodeEntry = resolveRelativeNodePath(relPath); return (nodeEntry != null) && getItemManager().itemExists(nodeEntry); } /** * @see Node#hasProperty(String) */ public boolean hasProperty(String relPath) throws RepositoryException { checkStatus(); PropertyEntry childEntry = resolveRelativePropertyPath(relPath); return (childEntry != null) && getItemManager().itemExists(childEntry); } /** * Returns true, if this <code>Node</code> has a property with the given name. * * @param propertyName * @return <code>true</code>, if this <code>Node</code> has a property with * the given name. */ private boolean hasProperty(Name propertyName) { return getNodeEntry().hasPropertyEntry(propertyName); } /** * @see Node#hasNodes() */ public boolean hasNodes() throws RepositoryException { checkStatus(); return getItemManager().hasChildNodes(getNodeEntry()); } /** * @see Node#hasProperties() */ public boolean hasProperties() throws RepositoryException { checkStatus(); return getItemManager().hasChildProperties(getNodeEntry()); } /** * @see Node#getPrimaryNodeType() */ public NodeType getPrimaryNodeType() throws RepositoryException { checkStatus(); return session.getNodeTypeManager().getNodeType(getPrimaryNodeTypeName()); } /** * @see javax.jcr.Node#setPrimaryType(String) */ public void setPrimaryType(String nodeTypeName) throws RepositoryException { checkStatus(); if (getNodeState().isRoot()) { String msg = "The primary type of the root node may not be changed."; log.debug(msg); throw new RepositoryException(msg); } Name ntName = getQName(nodeTypeName); if (ntName.equals(getPrimaryNodeTypeName())) { log.debug("Changing the primary type has no effect: '" + nodeTypeName + "' already is the primary node type."); return; } NodeTypeManagerImpl ntMgr = session.getNodeTypeManager(); NodeType nt = ntMgr.getNodeType(ntName); if (nt.isMixin() || nt.isAbstract()) { throw new ConstraintViolationException("Cannot change the primary type: '" + nodeTypeName + "' is a mixin type or abstract."); } // perform the operation Operation op = SetPrimaryType.create(getNodeState(), ntName); session.getSessionItemStateManager().execute(op); } /** * @see Node#getMixinNodeTypes() */ public NodeType[] getMixinNodeTypes() throws RepositoryException { checkStatus(); Name[] mixinNames = getNodeState().getMixinTypeNames(); NodeType[] nta = new NodeType[mixinNames.length]; for (int i = 0; i < mixinNames.length; i++) { nta[i] = session.getNodeTypeManager().getNodeType(mixinNames[i]); } return nta; } /** * @see Node#isNodeType(String) */ public boolean isNodeType(String nodeTypeName) throws RepositoryException { checkStatus(); // try shortcut first (avoids parsing of name) if (session.getNameResolver().getJCRName(getPrimaryNodeTypeName()).equals(nodeTypeName)) { return true; } // parse to Name and check against effective nodetype return isNodeType(getQName(nodeTypeName)); } /** * @see Node#addMixin(String) */ public void addMixin(String mixinName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { checkIsWritable(); Name mixinQName = getQName(mixinName); // get mixin types present in the jcr:mixinTypes property without // modifying the NodeState. List<Name> mixinValue = getMixinTypes(); if (!mixinValue.contains(mixinQName) && !isNodeType(mixinQName)) { if (!canAddMixin(mixinQName)) { throw new ConstraintViolationException("Cannot add '" + mixinName + "' mixin type."); } mixinValue.add(mixinQName); // perform the operation Operation op = SetMixin.create(getNodeState(), mixinValue.toArray(new Name[mixinValue.size()])); session.getSessionItemStateManager().execute(op); } } /** * @see Node#removeMixin(String) */ public void removeMixin(String mixinName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { checkIsWritable(); Name ntName = getQName(mixinName); List<Name> mixinValue = getMixinTypes(); // remove name of target mixin if (!mixinValue.remove(ntName)) { throw new NoSuchNodeTypeException("Cannot remove mixin '" + mixinName + "': Nodetype is not present on this node."); } // mix:referenceable needs additional assertion: the mixin cannot be // removed, if any references are left to this node. NodeTypeImpl mixin = session.getNodeTypeManager().getNodeType(ntName); if (mixin.isNodeType(NameConstants.MIX_REFERENCEABLE)) { EffectiveNodeType entRemaining = getRemainingENT(mixinValue); if (!entRemaining.includesNodeType(NameConstants.MIX_REFERENCEABLE)) { PropertyIterator iter = getReferences(); if (iter.hasNext()) { throw new ConstraintViolationException("Mixin type " + mixinName + " can not be removed: the node is being referenced through at least one property of type REFERENCE"); } } } /* * mix:lockable: the mixin cannot be removed if the node is currently * locked even if the editing session is the lock holder. */ if (mixin.isNodeType((NameConstants.MIX_LOCKABLE))) { EffectiveNodeType entRemaining = getRemainingENT(mixinValue); if (!entRemaining.includesNodeType(NameConstants.MIX_LOCKABLE) && isLocked()) { throw new ConstraintViolationException(mixinName + " can not be removed: the node is locked."); } } // delegate to operation Name[] mixins = mixinValue.toArray(new Name[mixinValue.size()]); Operation op = SetMixin.create(getNodeState(), mixins); session.getSessionItemStateManager().execute(op); } /** * Retrieves the value of the jcr:mixinTypes property present with this * Node including those that have been transiently added and excluding * those, that have been transiently removed.<br> * NOTE, that the result of this method, does NOT represent the list of * mixin-types that currently affect this node. * * @return mixin names present with the jcr:mixinTypes property. */ private List<Name> getMixinTypes() { Name[] mixinValue; if (getNodeState().getStatus() == Status.EXISTING) { // jcr:mixinTypes must correspond to the mixins present on the nodestate. mixinValue = getNodeState().getMixinTypeNames(); } else { try { PropertyEntry pe = getNodeEntry().getPropertyEntry(NameConstants.JCR_MIXINTYPES); if (pe != null) { // prop entry exists (and ev. has been transiently mod.) // -> retrieve mixin types from prop mixinValue = StateUtility.getMixinNames(pe.getPropertyState()); } else { // prop entry has not been loaded yet -> not modified mixinValue = getNodeState().getMixinTypeNames(); } } catch (RepositoryException e) { // should never occur log.warn("Internal error", e); mixinValue = Name.EMPTY_ARRAY; } } List<Name> l = new ArrayList<Name>(); l.addAll(Arrays.asList(mixinValue)); return l; } /** * Build the effective node type of remaining mixin's & primary type * * @param remainingMixins * @return effective node type * @throws ConstraintViolationException * @throws NoSuchNodeTypeException */ private EffectiveNodeType getRemainingENT(List<Name> remainingMixins) throws ConstraintViolationException, NoSuchNodeTypeException { Name[] allRemaining = remainingMixins.toArray(new Name[remainingMixins.size() + 1]); allRemaining[remainingMixins.size()] = getPrimaryNodeTypeName(); return session.getEffectiveNodeTypeProvider().getEffectiveNodeType(allRemaining); } /** * @see Node#canAddMixin(String) */ public boolean canAddMixin(String mixinName) throws RepositoryException { if (!isWritable()) { // shortcut: repository does not support writing anyway. return false; } try { // first check if node is writable regarding protection status, // locks, versioning, access restriction. session.getValidator().checkIsWritable(getNodeState(), ItemStateValidator.CHECK_ALL); // then make sure the new mixin would not conflict. return canAddMixin(getQName(mixinName)); } catch (LockException e) { log.debug("Cannot add mixin '" + mixinName + "': " + e.getMessage()); return false; } catch (VersionException e) { log.debug("Cannot add mixin '" + mixinName + "': " + e.getMessage()); return false; } catch (ConstraintViolationException e) { log.debug("Cannot add mixin '" + mixinName + "': " + e.getMessage()); return false; } } /** * @see Node#getDefinition() */ public NodeDefinition getDefinition() throws RepositoryException { checkStatus(); QNodeDefinition qnd = getNodeState().getDefinition(); return session.getNodeTypeManager().getNodeDefinition(qnd); } /** * @see Node#checkin() */ public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException { checkIsVersionable(); checkHasPendingChanges(); checkIsLocked(); if (isCheckedOut()) { NodeEntry newVersion = session.getVersionStateManager().checkin(getNodeState()); return (Version) getItemManager().getItem(newVersion); } else { // nothing to do log.debug("Node " + safeGetJCRPath() + " is already checked in."); return getBaseVersion(); } } /** * @see Node#checkout() */ public void checkout() throws UnsupportedRepositoryOperationException, LockException, RepositoryException { checkIsVersionable(); checkIsLocked(); if (!isCheckedOut()) { if (session.isSupportedOption(Repository.OPTION_ACTIVITIES_SUPPORTED)) { NodeImpl activity = (NodeImpl) session.getWorkspace().getVersionManager().getActivity(); NodeId activityId = (activity == null) ? null : activity.getNodeState().getNodeId(); session.getVersionStateManager().checkout(getNodeState(), activityId); } else { session.getVersionStateManager().checkout(getNodeState()); } } else { // nothing to do log.debug("Node " + safeGetJCRPath() + " is already checked out."); } } Version checkpoint() throws RepositoryException { checkIsVersionable(); checkHasPendingChanges(); checkIsLocked(); if (!isCheckedOut()) { checkout(); return getBaseVersion(); } else { NodeEntry newVersion; if (session.isSupportedOption(Repository.OPTION_ACTIVITIES_SUPPORTED)) { NodeImpl activity = (NodeImpl) session.getWorkspace().getVersionManager().getActivity(); NodeId activityId = (activity == null) ? null : activity.getNodeState().getNodeId(); newVersion = session.getVersionStateManager().checkpoint(getNodeState(), activityId); } else { newVersion = session.getVersionStateManager().checkpoint(getNodeState()); } return (Version) getItemManager().getItem(newVersion); } } /** * @see Node#doneMerge(Version) */ public void doneMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { resolveMergeConflict(version, true); } /** * @see Node#cancelMerge(Version) */ public void cancelMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { resolveMergeConflict(version, false); } /** * Internal method covering both {@link #doneMerge(Version)} and {@link #cancelMerge(Version)}. * * @param version * @param done * @throws VersionException * @throws InvalidItemStateException * @throws UnsupportedRepositoryOperationException * @throws RepositoryException */ private void resolveMergeConflict(Version version, boolean done) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException { checkIsVersionable(); checkHasPendingChanges(); checkIsLocked(); // check if checked out if (!isCheckedOut()) { String msg = "Unable to resolve merge conflict. Node is checked-in: " + safeGetJCRPath(); log.error(msg); throw new VersionException(msg); } // check if version is in mergeFailed list boolean isConflicting = false; if (hasProperty(NameConstants.JCR_MERGEFAILED)) { Value[] vals = getProperty(NameConstants.JCR_MERGEFAILED).getValues(); for (int i = 0; i < vals.length && !isConflicting; i++) { isConflicting = vals[i].getString().equals(version.getUUID()); } } if (!isConflicting) { String msg = "Unable to resolve merge conflict. Specified version is not in jcr:mergeFailed property: " + safeGetJCRPath(); log.error(msg); throw new VersionException(msg); } NodeState versionState = session.getVersionState(version); session.getVersionStateManager().resolveMergeConflict(getNodeState(), versionState, done); } /** * @see Node#update(String) */ public void update(String srcWorkspaceName) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException { checkIsWritable(); checkSessionHasPendingChanges(); // if same workspace, ignore if (session.getWorkspace().getName().equals(srcWorkspaceName)) { return; } // test if the corresponding node exists in the src-workspace which includes // a check if the specified source workspace is accessible try { getCorrespondingNodePath(srcWorkspaceName); } catch (ItemNotFoundException e) { // no corresponding node exists -> method has not effect return; } Operation op = Update.create(getNodeState(), srcWorkspaceName); ((WorkspaceImpl)session.getWorkspace()).getUpdatableItemStateManager().execute(op); } /** * @see Node#merge(String, boolean) */ public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws NoSuchWorkspaceException, AccessDeniedException, VersionException, LockException, InvalidItemStateException, RepositoryException { return session.getWorkspace().getVersionManager().merge(getPath(), srcWorkspace, bestEffort); } /** * TODO: Issue 728 of the pfd... this method is a leftover and will be removed in the final version. * -> change to package protected then */ public NodeIterator merge(String srcWorkspace, boolean bestEffort, boolean isShallow) throws RepositoryException { return session.getWorkspace().getVersionManager().merge(getPath(), srcWorkspace, bestEffort, isShallow); } /** * @see Node#getCorrespondingNodePath(String) */ public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException { checkStatus(); SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) srcSession = session.switchWorkspace(workspaceName); // search nearest ancestor that is referenceable NodeImpl referenceableNode = this; while (referenceableNode.getDepth() != Path.ROOT_DEPTH && !referenceableNode.isNodeType(NameConstants.MIX_REFERENCEABLE)) { referenceableNode = (NodeImpl) referenceableNode.getParent(); } // if root is common ancestor, corresponding path is same as ours // otherwise access referenceable ancestor and calculate correspond. path. String correspondingPath; if (referenceableNode.getDepth() == Path.ROOT_DEPTH) { if (!srcSession.getItemManager().nodeExists(getQPath())) { throw new ItemNotFoundException("No corresponding path found in workspace " + workspaceName + "(" + safeGetJCRPath() + ")"); } else { correspondingPath = getPath(); } } else { // get corresponding ancestor Node correspNode = srcSession.getNodeByUUID(referenceableNode.getUUID()); // path of m2 found, if m1 == n1 if (referenceableNode == this) { correspondingPath = correspNode.getPath(); } else { Path p = referenceableNode.getQPath().computeRelativePath(getQPath()); // use prefix mappings of srcSession String relPath = session.getPathResolver().getJCRPath(p); if (!correspNode.hasNode(relPath)) { throw new ItemNotFoundException("No corresponding path found in workspace " + workspaceName + "(" + safeGetJCRPath() + ")"); } else { correspondingPath = correspNode.getNode(relPath).getPath(); } } } return correspondingPath; } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * @see Node#isCheckedOut() */ public boolean isCheckedOut() throws RepositoryException { checkStatus(); // shortcut: if state is new, its ancestor must be checkout if (isNew()) { return true; } return session.getVersionStateManager().isCheckedOut(getNodeState()); } /** * @see Node#restore(String, boolean) */ public void restore(String versionName, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { checkSessionHasPendingChanges(); // check for version-enabled and lock are performed with subsequent calls. Version v = getVersionHistory().getVersion(versionName); restore(this, null, v, removeExisting); } /** * @see Node#restore(Version, boolean) */ public void restore(Version version, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, RepositoryException { checkSessionHasPendingChanges(); restore(this, null, version, removeExisting); } /** * @see Node#restore(Version, String, boolean) */ public void restore(Version version, String relPath, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { checkSessionHasPendingChanges(); // additional checks are performed with subsequent calls. if (hasNode(relPath)) { // node at 'relPath' exists -> call restore on the target Node getNode(relPath).restore(version, removeExisting); } else { // node at 'relPath' does not yet exist -> build the NodeId Path nPath = getPath(relPath); Path parentPath = nPath.getAncestor(1); ItemManager itemMgr = getItemManager(); if (itemMgr.nodeExists(parentPath)) { Node parent = itemMgr.getNode(parentPath); Path relQPath = parentPath.computeRelativePath(nPath); NodeImpl parentNode = ((NodeImpl)parent); // call the restore restore(parentNode, relQPath, version, removeExisting); } else if (itemMgr.propertyExists(parentPath)) { // the item at parentParentPath is Property throw new ConstraintViolationException("Cannot restore to a parent presenting a property (relative path = '" + relPath + "'"); } else { // although the node itself must not exist, is direct ancestor must. throw new PathNotFoundException("Cannot restore to relative path '" + relPath + ": Ancestor does not exist."); } } } /** * @see Node#restoreByLabel(String, boolean) */ public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { checkSessionHasPendingChanges(); // check for version-enabled and lock are performed with subsequent calls. Version v = getVersionHistory().getVersionByLabel(versionLabel); if (v == null) { throw new VersionException("No version for label " + versionLabel + " found."); } restore(this, null, v, removeExisting); } /** * Common internal restore method for the various Node#restore calls. * * @param targetNode * @param relQPath * @param version * @param removeExisting * @throws PathNotFoundException * @throws ItemExistsException * @throws VersionException * @throws ConstraintViolationException * @throws UnsupportedRepositoryOperationException * @throws LockException * @throws InvalidItemStateException * @throws RepositoryException */ private void restore(NodeImpl targetNode, Path relQPath, Version version, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException { if (relQPath == null) { /* restore target already exists. */ // target must be versionable targetNode.checkIsVersionable(); VersionHistory vH = targetNode.getVersionHistory(); // version must be a version of the target node if (!vH.isSame(version.getContainingHistory())) { throw new VersionException("Version " + version + " does not correspond to the restore target."); } // version must not be the root version if (vH.getRootVersion().isSame(version)) { throw new VersionException("Attempt to restore root version."); } targetNode.checkIsWritable(); targetNode.checkIsLocked(); } else { /* If no node exists at relPath then a VersionException is thrown if the parent node is not checked out. */ if (!targetNode.isCheckedOut()) { throw new VersionException("Parent " + targetNode.safeGetJCRPath() + " for non-existing restore target '" + LogUtil.safeGetJCRPath(relQPath, session.getPathResolver()) + "' must be checked out."); } targetNode.checkIsLocked(); // NOTE: check for nodetype constraint violation is left to the 'server' } NodeState versionState = session.getVersionState(version); session.getVersionStateManager().restore(targetNode.getNodeState(), relQPath, versionState, removeExisting); } /** * @see Node#getVersionHistory() */ public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException { checkIsVersionable(); return (VersionHistory) getProperty(NameConstants.JCR_VERSIONHISTORY).getNode(); } /** * @see Node#getBaseVersion() */ public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException { checkIsVersionable(); return (Version) getProperty(NameConstants.JCR_BASEVERSION).getNode(); } /** * @see Node#lock(boolean, boolean) */ public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { return lock(isDeep, isSessionScoped, Long.MAX_VALUE, null); } public Lock lock(boolean isDeep, boolean isSessionScoped, long timeoutHint, String ownerHint) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { checkIsLockable(); checkHasPendingChanges(); return session.getLockStateManager().lock(getNodeState(), isDeep, isSessionScoped, timeoutHint, ownerHint); } /** * @see Node#getLock() */ public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException { // lock can be inherited from a parent > do not check for node being lockable. checkStatus(); return session.getLockStateManager().getLock(getNodeState()); } /** * @see javax.jcr.Node#unlock() */ public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException { checkIsLockable(); checkHasPendingChanges(); session.getLockStateManager().unlock(getNodeState()); } /** * @see javax.jcr.Node#holdsLock() */ public boolean holdsLock() throws RepositoryException { // lock can be inherited from a parent > do not check for node being lockable. checkStatus(); if (isNew() || !isNodeType(NameConstants.MIX_LOCKABLE)) { // a node that is new or not lockable never holds a lock return false; } else { LockStateManager lMgr = session.getLockStateManager(); return (lMgr.isLocked(getNodeState()) && lMgr.getLock(getNodeState()).getNode().isSame(this)); } } /** * @see javax.jcr.Node#isLocked() */ public boolean isLocked() throws RepositoryException { // lock can be inherited from a parent > do not check for node being lockable. checkStatus(); return session.getLockStateManager().isLocked(getNodeState()); } /** * @see javax.jcr.Node#followLifecycleTransition(String) */ public void followLifecycleTransition(String transition) throws RepositoryException { session.checkSupportedOption(Repository.OPTION_LIFECYCLE_SUPPORTED); // TODO: implementation missing throw new UnsupportedRepositoryOperationException("JCR-1104"); } /** * @see javax.jcr.Node#getAllowedLifecycleTransistions() */ public String[] getAllowedLifecycleTransistions() throws RepositoryException { session.checkSupportedOption(Repository.OPTION_LIFECYCLE_SUPPORTED); // TODO: implementation missing throw new UnsupportedRepositoryOperationException("JCR-1104"); } /** * @see javax.jcr.Node#getSharedSet() */ public NodeIterator getSharedSet() throws RepositoryException { // TODO: implementation missing throw new UnsupportedRepositoryOperationException("JCR-1104"); } /** * @see javax.jcr.Node#removeShare() */ public void removeShare() throws RepositoryException { // TODO: implementation missing throw new UnsupportedRepositoryOperationException("JCR-1104"); } /** * @see javax.jcr.Node#removeSharedSet() */ public void removeSharedSet() throws RepositoryException { throw new UnsupportedRepositoryOperationException("JCR-1104"); } //--------------------------------------------------------< public impl >--- /** * * @param qName * @return * @throws RepositoryException */ boolean isNodeType(Name qName) throws RepositoryException { // first do trivial checks without using type hierarchy if (qName.equals(getPrimaryNodeTypeName())) { return true; } // check if contained in mixin types for (Name mixin : getNodeState().getMixinTypeNames()) { if (mixin.equals(qName)) { return true; } } // NEW nodes with inherited-mixins -> mixin not yet active if (getNodeState().getStatus() == Status.NEW && session.getNodeTypeManager().getNodeType(qName).isMixin()) { return false; } // check effective node type EffectiveNodeType effnt = session.getEffectiveNodeTypeProvider().getEffectiveNodeType(getNodeState().getNodeTypeNames()); return effnt.includesNodeType(qName); } //-----------------------------------------------------------< ItemImpl >--- /** * @see ItemImpl#getName() */ @Override Name getQName() throws RepositoryException { if (getNodeState().isRoot()) { // shortcut. the given state represents the root or an orphaned node return NameConstants.ROOT; } return getNodeState().getName(); } //------------------------------------------------------< check methods >--- /** * Checks if this nodes session has pending changes. * * @throws InvalidItemStateException if this nodes session has pending changes * @throws RepositoryException */ private void checkSessionHasPendingChanges() throws RepositoryException { session.checkHasPendingChanges(); } /** * * @throws InvalidItemStateException * @throws RepositoryException */ private void checkHasPendingChanges() throws InvalidItemStateException, RepositoryException { if (hasPendingChanges()) { String msg = "Node has pending changes: " + getPath(); log.debug(msg); throw new InvalidItemStateException(msg); } } /** * @return true if this <code>Node</code> is modified or new. */ private boolean hasPendingChanges() { return isModified() || isNew(); } /** * Checks if this node is lockable, i.e. has 'mix:lockable'. * * @throws UnsupportedRepositoryOperationException if this node is not lockable. * @throws RepositoryException if another error occurs. */ private void checkIsLockable() throws UnsupportedRepositoryOperationException, RepositoryException { checkStatus(); if (!isNodeType(NameConstants.MIX_LOCKABLE)) { String msg = "Unable to perform locking operation on non-lockable node: " + getPath(); log.debug(msg); throw new LockException(msg); } } /** * Check whether this node is locked by somebody else. * * @throws LockException if this node is locked by somebody else. * @throws RepositoryException if some other error occurs. */ void checkIsLocked() throws LockException, RepositoryException { if (isNew()) { // if this node is new, no checks must be performed. return; } // perform check session.getLockStateManager().checkLock(getNodeState()); } /** * Check if this node is versionable. * * @throws UnsupportedRepositoryOperationException * @throws RepositoryException */ private void checkIsVersionable() throws UnsupportedRepositoryOperationException, RepositoryException { checkStatus(); if (!isNodeType(NameConstants.MIX_VERSIONABLE)) { String msg = "Unable to perform versioning operation on non versionable node: " + getPath(); log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } } //---------------------------------------------< private implementation >--- /** * Create a new <code>NodeState</code> and subsequently retrieves the * corresponding <code>Node</code> object. * * @param nodeName name of the new node * @param nodeTypeName name of the new node's node type or <code>null</code> * if it should be determined automatically * @return the newly added node * @throws ItemExistsException * @throws NoSuchNodeTypeException * @throws VersionException * @throws ConstraintViolationException * @throws LockException * @throws RepositoryException */ private synchronized Node createNode(Name nodeName, Name nodeTypeName) throws ItemExistsException, NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException { QNodeDefinition definition = session.getItemDefinitionProvider().getQNodeDefinition(getNodeState().getAllNodeTypeNames(), nodeName, nodeTypeName); if (nodeTypeName == null) { // use default node type nodeTypeName = definition.getDefaultPrimaryType(); } // validation check are performed by item state manager // NOTE: uuid is generated while creating new state. Operation an = AddNode.create(getNodeState(), nodeName, nodeTypeName, null); session.getSessionItemStateManager().execute(an); // finally retrieve the new node List<ItemState> addedStates = ((AddNode) an).getAddedStates(); ItemState nState = addedStates.get(0); return (Node) getItemManager().getItem(nState.getHierarchyEntry()); } // TODO: protected due to usage within VersionImpl, VersionHistoryImpl (check for alternatives) /** * * @param nodeName * @param index * @return * @throws PathNotFoundException * @throws RepositoryException */ protected Node getNode(Name nodeName, int index) throws PathNotFoundException, RepositoryException { checkStatus(); try { NodeEntry nEntry = getNodeEntry().getNodeEntry(nodeName, index); if (nEntry == null) { throw new PathNotFoundException(LogUtil.saveGetJCRName(nodeName, session.getNameResolver())); } return (Node) getItemManager().getItem(nEntry); } catch (AccessDeniedException e) { throw new PathNotFoundException(LogUtil.saveGetJCRName(nodeName, session.getNameResolver())); } } /** * * @param qName * @return * @throws PathNotFoundException * @throws RepositoryException */ // TODO: protected due to usage within VersionImpl, VersionHistoryImpl (check for alternatives) protected Property getProperty(Name qName) throws PathNotFoundException, RepositoryException { checkStatus(); try { PropertyEntry pEntry = getNodeEntry().getPropertyEntry(qName, true); if (pEntry == null) { throw new PathNotFoundException(LogUtil.saveGetJCRName(qName, session.getNameResolver())); } return (Property) getItemManager().getItem(pEntry); } catch (AccessDeniedException e) { throw new PathNotFoundException(LogUtil.saveGetJCRName(qName, session.getNameResolver())); } } /** * Create a new single valued property * * @param qName * @param type * @param value * @return * @throws ConstraintViolationException if no applicable property definition * could be found. * @throws RepositoryException if another error occurs. */ private Property createProperty(Name qName, Value value, int type) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = getApplicablePropertyDefinition(qName, type, false); int targetType = def.getRequiredType(); if (targetType == PropertyType.UNDEFINED) { targetType = type; } QValue qvs; if (targetType == PropertyType.UNDEFINED) { qvs = ValueFormat.getQValue(value, session.getNamePathResolver(), session.getQValueFactory()); targetType = qvs.getType(); } else { Value targetValue = ValueHelper.convert(value, targetType, session.getValueFactory()); qvs = ValueFormat.getQValue(targetValue, session.getNamePathResolver(), session.getQValueFactory()); } return createProperty(qName, targetType, def, new QValue[] {qvs}); } /** * Create a new multi valued property * * @param qName * @param type * @param values * @return * @throws ConstraintViolationException * @throws RepositoryException */ private Property createProperty(Name qName, Value[] values, int type) throws ConstraintViolationException, RepositoryException { QPropertyDefinition def = getApplicablePropertyDefinition(qName, type, true); int targetType = def.getRequiredType(); // make sure, the final type is not set to undefined if (targetType == PropertyType.UNDEFINED) { if (type == PropertyType.UNDEFINED) { // try to retrieve type from the values array if (values.length > 0) { for (Value value : values) { if (value != null) { targetType = value.getType(); break; } } } if (targetType == PropertyType.UNDEFINED) { // fallback targetType = PropertyType.STRING; } } else { targetType = type; } } Value[] targetValues = ValueHelper.convert(values, targetType, session.getValueFactory()); QValue[] qvs = ValueFormat.getQValues(targetValues, session.getNamePathResolver(), session.getQValueFactory()); return createProperty(qName, targetType, def, qvs); } /** * * @param qName * @param type * @param def * @param qvs * @return * @throws PathNotFoundException * @throws ConstraintViolationException * @throws RepositoryException */ private Property createProperty(Name qName, int type, QPropertyDefinition def, QValue[] qvs) throws ConstraintViolationException, RepositoryException { Operation op = AddProperty.create(getNodeState(), qName, type, def, qvs); session.getSessionItemStateManager().execute(op); return getProperty(qName); } /** * * @param jcrName * @return * @throws RepositoryException */ private Name getQName(String jcrName) throws RepositoryException { Name qName; try { qName = session.getNameResolver().getQName(jcrName); } catch (NameException upe) { throw new RepositoryException("invalid name: "+ jcrName, upe); } return qName; } /** * @return the primary node type name. */ private Name getPrimaryNodeTypeName() { return getNodeState().getNodeTypeName(); } /** * * @param name * @param weak * @return * @throws RepositoryException */ private LazyItemIterator getReferences(String name, boolean weak) throws RepositoryException { checkStatus(); Name propName = (name == null) ? null : getQName(name); Iterator<PropertyId> itr = getNodeState().getNodeReferences(propName, weak); return new LazyItemIterator(getItemManager(), session.getHierarchyManager(), itr); } /** * * @param mixinName * @return * @throws NoSuchNodeTypeException * @throws ConstraintViolationException */ private boolean canAddMixin(Name mixinName) throws NoSuchNodeTypeException, ConstraintViolationException { NodeTypeManagerImpl ntMgr = session.getNodeTypeManager(); // first check characteristics of each mixin NodeType mixin = ntMgr.getNodeType(mixinName); if (!mixin.isMixin()) { log.error(mixin.getName() + ": not a mixin node type"); return false; } // get list of existing nodetypes Name[] existingNts = getNodeState().getNodeTypeNames(); // build effective node type representing primary type including existing mixins EffectiveNodeType entExisting = session.getEffectiveNodeTypeProvider().getEffectiveNodeType(existingNts); // check if the base type supports adding this mixin if (!entExisting.supportsMixin(mixinName)) { log.debug(mixin.getName() + ": not supported on node type " + getPrimaryNodeTypeName()); return false; } // second, build new effective node type for nts including the new mixin // types, detecting eventual incompatibilities Name[] resultingNts = new Name[existingNts.length + 1]; System.arraycopy(existingNts, 0, resultingNts, 0, existingNts.length); resultingNts[existingNts.length] = mixinName; session.getEffectiveNodeTypeProvider().getEffectiveNodeType(resultingNts); // all validations succeeded: return true return true; } /** * @return <code>NodeState</code> of this <code>Node</code> */ private NodeState getNodeState() { return (NodeState) getItemState(); } /** * @return <code>NodeEntry</code> of this <code>Node</code> */ private NodeEntry getNodeEntry() { return (NodeEntry) getItemState().getHierarchyEntry(); } /** * * @param relativePath * @return * @throws RepositoryException */ private Path getReorderPath(String relativePath) throws RepositoryException { try { Path p = session.getPathResolver().getQPath(relativePath); if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) { throw new RepositoryException("Invalid relative path: " + relativePath); } return p; } catch (NameException e) { String msg = "Invalid relative path: " + relativePath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @param relativeJcrPath * @return Path object for the specified relative JCR path string. * @throws RepositoryException */ private Path getPath(String relativeJcrPath) throws RepositoryException { try { Path p = session.getPathResolver().getQPath(relativeJcrPath); return getPath(p); } catch (NameException e) { String msg = "Invalid relative path: " + relativeJcrPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @param relativePath * @return normalized absolute path calculated from the given relative * path and the path of this node. * @throws RepositoryException */ private Path getPath(Path relativePath) throws RepositoryException { // shortcut if (relativePath.getLength() == 1 && relativePath.denotesCurrent()) { return getQPath(); } return session.getPathFactory().create(getQPath(), relativePath, true); } /** * Returns the <code>NodeEntry</code> at <code>relPath</code> or * <code>null</code> if no node exists at <code>relPath</code>. * <p> * Note that access rights are not checked. * * @param relPath relative path of a (possible) node. * @return the HierarchyEntry of the node at <code>relPath</code> or * <code>null</code> if no node exists at <code>relPath</code>. * @throws RepositoryException if <code>relPath</code> is not a valid * relative path. */ private NodeEntry resolveRelativeNodePath(String relPath) throws RepositoryException { NodeEntry targetEntry = null; try { Path rp = session.getPathResolver().getQPath(relPath); // shortcut if (rp.getLength() == 1) { if (rp.denotesCurrent()) { targetEntry = getNodeEntry(); } else if (rp.denotesParent()) { targetEntry = getNodeEntry().getParent(); } else { // try to get child entry + force loading of not known yet targetEntry = getNodeEntry().getNodeEntry( rp.getName(), rp.getNormalizedIndex(), true); } } else { // rp length > 1 Path p = getPath(rp); targetEntry = session.getHierarchyManager().getNodeEntry(p.getCanonicalPath()); } } catch (PathNotFoundException e) { // item does not exist -> ignore and return null } catch (NameException e) { String msg = "Invalid relative path: " + relPath; log.debug(msg); throw new RepositoryException(msg, e); } return targetEntry; } /** * Returns the id of the property at <code>relPath</code> or <code>null</code> * if no property exists at <code>relPath</code>. * <p> * Note that access rights are not checked. * * @param relPath relative path of a (possible) property * @return the PropertyEntry of the property at <code>relPath</code> or * <code>null</code> if no property exists at <code>relPath</code> * @throws RepositoryException if <code>relPath</code> is not a valid * relative path */ private PropertyEntry resolveRelativePropertyPath(String relPath) throws RepositoryException { PropertyEntry targetEntry = null; try { Path rp = session.getPathResolver().getQPath(relPath); if (rp.getLength() == 1 && rp.denotesName()) { // a single path element must always denote a name. '.' and '..' // will never point to a property. If the NodeEntry does not // contain such a property entry, the targetEntry is 'null; Name propName = rp.getName(); // check if property entry exists targetEntry = getNodeEntry().getPropertyEntry(propName, true); } else { // build and resolve absolute path Path p = getPath(rp).getCanonicalPath(); try { targetEntry = session.getHierarchyManager().getPropertyEntry(p); } catch (PathNotFoundException e) { // ignore -> return null; } } } catch (NameException e) { String msg = "failed to resolve property path " + relPath + " relative to " + safeGetJCRPath(); log.debug(msg); throw new RepositoryException(msg, e); } return targetEntry; } /** * Returns the applicable property definition for a property with the * specified name and type. * * @param propertyName * @param type * @param multiValued * @return * @throws ConstraintViolationException if no applicable property definition * could be found * @throws RepositoryException if another error occurs */ private QPropertyDefinition getApplicablePropertyDefinition(Name propertyName, int type, boolean multiValued) throws ConstraintViolationException, RepositoryException { return session.getItemDefinitionProvider().getQPropertyDefinition(getNodeState().getAllNodeTypeNames(), propertyName, type, multiValued); } }