| /* |
| * 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.oak.jcr.session; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.Iterators.transform; |
| import static com.google.common.collect.Sets.newLinkedHashSet; |
| import static java.lang.String.format; |
| import static java.util.Arrays.asList; |
| import static java.util.Collections.singleton; |
| import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; |
| import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; |
| import static org.apache.jackrabbit.oak.api.Type.NAME; |
| import static org.apache.jackrabbit.oak.api.Type.NAMES; |
| import static org.apache.jackrabbit.oak.jcr.session.SessionImpl.checkIndexOnName; |
| import static org.apache.jackrabbit.oak.plugins.tree.TreeUtil.getNames; |
| |
| import java.io.InputStream; |
| import java.math.BigDecimal; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| 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.RepositoryException; |
| import javax.jcr.UnsupportedRepositoryOperationException; |
| import javax.jcr.Value; |
| import javax.jcr.lock.Lock; |
| import javax.jcr.lock.LockManager; |
| import javax.jcr.nodetype.ConstraintViolationException; |
| import javax.jcr.nodetype.NodeDefinition; |
| import javax.jcr.nodetype.NodeType; |
| import javax.jcr.nodetype.NodeTypeManager; |
| import javax.jcr.version.OnParentVersionAction; |
| import javax.jcr.version.Version; |
| import javax.jcr.version.VersionException; |
| import javax.jcr.version.VersionHistory; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| import org.apache.jackrabbit.JcrConstants; |
| import org.apache.jackrabbit.api.JackrabbitNode; |
| import org.apache.jackrabbit.commons.ItemNameMatcher; |
| import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; |
| import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.api.Tree.Status; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate; |
| import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate; |
| import org.apache.jackrabbit.oak.jcr.delegate.VersionManagerDelegate; |
| import org.apache.jackrabbit.oak.jcr.lock.LockDeprecation; |
| import org.apache.jackrabbit.oak.jcr.session.operation.ItemOperation; |
| import org.apache.jackrabbit.oak.jcr.session.operation.NodeOperation; |
| import org.apache.jackrabbit.oak.jcr.version.VersionHistoryImpl; |
| import org.apache.jackrabbit.oak.jcr.version.VersionImpl; |
| import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; |
| import org.apache.jackrabbit.oak.spi.nodetype.EffectiveNodeType; |
| import org.apache.jackrabbit.oak.plugins.tree.factories.RootFactory; |
| import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions; |
| import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; |
| import org.apache.jackrabbit.value.ValueHelper; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * TODO document |
| * |
| * @param <T> the delegate type |
| */ |
| public class NodeImpl<T extends NodeDelegate> extends ItemImpl<T> implements Node, JackrabbitNode { |
| |
| /** |
| * The maximum returned value for {@link NodeIterator#getSize()}. If there |
| * are more nodes, the method returns -1. |
| */ |
| private static final long NODE_ITERATOR_MAX_SIZE = Long.MAX_VALUE; |
| |
| /** |
| * logger instance |
| */ |
| private static final Logger LOG = LoggerFactory.getLogger(NodeImpl.class); |
| |
| @Nullable |
| public static NodeImpl<? extends NodeDelegate> createNodeOrNull( |
| @Nullable NodeDelegate delegate, @NotNull SessionContext context) |
| throws RepositoryException { |
| if (delegate != null) { |
| return createNode(delegate, context); |
| } else { |
| return null; |
| } |
| } |
| |
| @NotNull |
| public static NodeImpl<? extends NodeDelegate> createNode( |
| @NotNull NodeDelegate delegate, @NotNull SessionContext context) |
| throws RepositoryException { |
| PropertyDelegate pd = delegate.getPropertyOrNull(JCR_PRIMARYTYPE); |
| String type = pd != null ? pd.getString() : null; |
| if (JcrConstants.NT_VERSION.equals(type)) { |
| VersionManagerDelegate vmd = |
| VersionManagerDelegate.create(context.getSessionDelegate()); |
| return new VersionImpl(vmd.createVersion(delegate), context); |
| } else if (JcrConstants.NT_VERSIONHISTORY.equals(type)) { |
| VersionManagerDelegate vmd = |
| VersionManagerDelegate.create(context.getSessionDelegate()); |
| return new VersionHistoryImpl(vmd.createVersionHistory(delegate), context); |
| } else { |
| return new NodeImpl<NodeDelegate>(delegate, context); |
| } |
| } |
| |
| public NodeImpl(T dlg, SessionContext sessionContext) { |
| super(dlg, sessionContext); |
| } |
| |
| //---------------------------------------------------------------< Item >--- |
| |
| /** |
| * @see javax.jcr.Item#isNode() |
| */ |
| @Override |
| public boolean isNode() { |
| return true; |
| } |
| |
| /** |
| * @see javax.jcr.Item#getParent() |
| */ |
| @Override |
| @NotNull |
| public Node getParent() throws RepositoryException { |
| return perform(new NodeOperation<Node>(dlg, "getParent") { |
| @NotNull |
| @Override |
| public Node perform() throws RepositoryException { |
| if (node.isRoot()) { |
| throw new ItemNotFoundException("Root has no parent"); |
| } else { |
| NodeDelegate parent = node.getParent(); |
| if (parent == null) { |
| throw new AccessDeniedException(); |
| } |
| return createNode(parent, sessionContext); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Item#isNew() |
| */ |
| @Override |
| public boolean isNew() { |
| return sessionDelegate.safePerform(new NodeOperation<Boolean>(dlg, "isNew") { |
| @NotNull |
| @Override |
| public Boolean perform() { |
| return node.exists() && node.getStatus() == Status.NEW; |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Item#isModified() |
| */ |
| @Override |
| public boolean isModified() { |
| return sessionDelegate.safePerform(new NodeOperation<Boolean>(dlg, "isModified") { |
| @NotNull |
| @Override |
| public Boolean perform() { |
| return node.exists() && node.getStatus() == Status.MODIFIED; |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Item#remove() |
| */ |
| @Override |
| public void remove() throws RepositoryException { |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("remove") { |
| @Override |
| public void performVoid() throws RepositoryException { |
| if (dlg.isRoot()) { |
| throw new RepositoryException("Cannot remove the root node"); |
| } |
| |
| dlg.remove(); |
| } |
| |
| @Override |
| public String toString() { |
| return format("Removing node [%s]", dlg.getPath()); |
| } |
| }); |
| } |
| |
| @Override |
| public void accept(ItemVisitor visitor) throws RepositoryException { |
| visitor.visit(this); |
| } |
| |
| //---------------------------------------------------------------< Node >--- |
| |
| /** |
| * @see Node#addNode(String) |
| */ |
| @Override |
| @NotNull |
| public Node addNode(String relPath) throws RepositoryException { |
| return addNode(relPath, null); |
| } |
| |
| @Override @NotNull |
| public Node addNode(final String relPath, String primaryNodeTypeName) |
| throws RepositoryException { |
| final String oakPath = getOakPathOrThrowNotFound(relPath); |
| final String oakTypeName; |
| if (primaryNodeTypeName != null) { |
| oakTypeName = getOakName(primaryNodeTypeName); |
| } else { |
| oakTypeName = null; |
| } |
| |
| checkIndexOnName(relPath); |
| return perform(new ItemWriteOperation<Node>("addNode") { |
| @NotNull |
| @Override |
| public Node perform() throws RepositoryException { |
| String oakName = PathUtils.getName(oakPath); |
| String parentPath = PathUtils.getParentPath(oakPath); |
| |
| NodeDelegate parent = dlg.getChild(parentPath); |
| if (parent == null) { |
| // is it a property? |
| String grandParentPath = PathUtils.getParentPath(parentPath); |
| NodeDelegate grandParent = dlg.getChild(grandParentPath); |
| if (grandParent != null) { |
| String propName = PathUtils.getName(parentPath); |
| if (grandParent.getPropertyOrNull(propName) != null) { |
| throw new ConstraintViolationException("Can't add new node to property."); |
| } |
| } |
| |
| throw new PathNotFoundException(relPath); |
| } |
| |
| if (parent.getChild(oakName) != null) { |
| throw new ItemExistsException(relPath); |
| } |
| |
| // check for NODE_TYPE_MANAGEMENT permission here as we cannot |
| // distinguish between user-supplied and system-generated |
| // modification of that property in the PermissionValidator |
| if (oakTypeName != null) { |
| PropertyState prop = PropertyStates.createProperty(JCR_PRIMARYTYPE, oakTypeName, NAME); |
| sessionContext.getAccessManager().checkPermissions(parent.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT); |
| } |
| |
| NodeDelegate added = parent.addChild(oakName, oakTypeName); |
| if (added == null) { |
| throw new ItemExistsException(format("Node [%s/%s] exists", getNodePath(),oakName)); |
| } |
| return createNode(added, sessionContext); |
| } |
| |
| @Override |
| public String toString() { |
| return format("Adding node [%s/%s]", dlg.getPath(), relPath); |
| } |
| }); |
| } |
| |
| @Override |
| public void orderBefore(final String srcChildRelPath, final String destChildRelPath) throws RepositoryException { |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("orderBefore") { |
| @Override |
| public void performVoid() throws RepositoryException { |
| getEffectiveNodeType().checkOrderableChildNodes(); |
| String oakSrcChildRelPath = getOakPathOrThrowNotFound(srcChildRelPath); |
| String oakDestChildRelPath = null; |
| if (destChildRelPath != null) { |
| oakDestChildRelPath = getOakPathOrThrowNotFound(destChildRelPath); |
| } |
| dlg.orderBefore(oakSrcChildRelPath, oakDestChildRelPath); |
| |
| } |
| }); |
| } |
| |
| //-------------------------------------------------------< setProperty >-- |
| // |
| // The setProperty() variants below follow the same pattern: |
| // |
| // if (value != null) { |
| // return internalSetProperty(name, ...); |
| // } else { |
| // return internalRemoveProperty(name); |
| // } |
| // |
| // In addition the value and value type information is pre-processed |
| // according to the method signature before being passed to |
| // internalSetProperty(). The methods that take a non-nullable |
| // primitive value as an argument can skip the if clause. |
| // |
| // Note that due to backwards compatibility reasons (OAK-395) none |
| // of the methods will ever return null, even if asked to remove |
| // a non-existing property! See internalRemoveProperty() for details. |
| |
| @Override @NotNull |
| public Property setProperty(String name, Value value) |
| throws RepositoryException { |
| if (value != null) { |
| return internalSetProperty(name, value, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, Value value, int type) |
| throws RepositoryException { |
| if (value != null) { |
| boolean exactTypeMatch = true; |
| if (type == PropertyType.UNDEFINED) { |
| exactTypeMatch = false; |
| } else { |
| value = ValueHelper.convert(value, type, getValueFactory()); |
| } |
| return internalSetProperty(name, value, exactTypeMatch); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, Value[] values) |
| throws RepositoryException { |
| if (values != null) { |
| // TODO: type |
| return internalSetProperty(name, values, ValueHelper.getType(values), false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String jcrName, Value[] values, int type) |
| throws RepositoryException { |
| if (values != null) { |
| boolean exactTypeMatch = true; |
| if (type == PropertyType.UNDEFINED) { |
| type = PropertyType.STRING; |
| exactTypeMatch = false; |
| } |
| values = ValueHelper.convert(values, type, getValueFactory()); |
| return internalSetProperty(jcrName, values, type, exactTypeMatch); |
| } else { |
| return internalRemoveProperty(jcrName); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, String[] values) |
| throws RepositoryException { |
| if (values != null) { |
| int type = PropertyType.STRING; |
| Value[] vs = ValueHelper.convert(values, type, getValueFactory()); |
| return internalSetProperty(name, vs, type, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, String[] values, int type) |
| throws RepositoryException { |
| if (values != null) { |
| boolean exactTypeMatch = true; |
| if (type == PropertyType.UNDEFINED) { |
| type = PropertyType.STRING; |
| exactTypeMatch = false; |
| } |
| Value[] vs = ValueHelper.convert(values, type, getValueFactory()); |
| return internalSetProperty(name, vs, type, exactTypeMatch); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, String value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, String value, int type) |
| throws RepositoryException { |
| if (value != null) { |
| boolean exactTypeMatch = true; |
| if (type == PropertyType.UNDEFINED) { |
| type = PropertyType.STRING; |
| exactTypeMatch = false; |
| } |
| Value v = getValueFactory().createValue(value, type); |
| return internalSetProperty(name, v, exactTypeMatch); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull @SuppressWarnings("deprecation") |
| public Property setProperty(String name, InputStream value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, Binary value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, boolean value) |
| throws RepositoryException { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, double value) |
| throws RepositoryException { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, BigDecimal value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, long value) |
| throws RepositoryException { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, Calendar value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override @NotNull |
| public Property setProperty(String name, Node value) |
| throws RepositoryException { |
| if (value != null) { |
| Value v = getValueFactory().createValue(value); |
| return internalSetProperty(name, v, false); |
| } else { |
| return internalRemoveProperty(name); |
| } |
| } |
| |
| @Override |
| @NotNull |
| public Node getNode(String relPath) throws RepositoryException { |
| final String oakPath = getOakPathOrThrowNotFound(relPath); |
| return perform(new NodeOperation<Node>(dlg, "getNode") { |
| @NotNull |
| @Override |
| public Node perform() throws RepositoryException { |
| NodeDelegate nd = node.getChild(oakPath); |
| if (nd == null) { |
| throw new PathNotFoundException(oakPath); |
| } else { |
| return createNode(nd, sessionContext); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public NodeIterator getNodes() throws RepositoryException { |
| return perform(new NodeOperation<NodeIterator>(dlg, "getNodes") { |
| @NotNull |
| @Override |
| public NodeIterator perform() throws RepositoryException { |
| Iterator<NodeDelegate> children = node.getChildren(); |
| return new NodeIteratorAdapter(nodeIterator(children)) { |
| private long size = -2; |
| @Override |
| public long getSize() { |
| if (size == -2) { |
| try { |
| size = node.getChildCount(NODE_ITERATOR_MAX_SIZE); // TODO: perform() |
| if (size == Long.MAX_VALUE) { |
| size = -1; |
| } |
| } catch (InvalidItemStateException e) { |
| throw new IllegalStateException( |
| "This iterator is no longer valid", e); |
| } |
| } |
| return size; |
| } |
| }; |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public NodeIterator getNodes(final String namePattern) |
| throws RepositoryException { |
| return perform(new NodeOperation<NodeIterator>(dlg, "getNodes") { |
| @NotNull |
| @Override |
| public NodeIterator perform() throws RepositoryException { |
| Iterator<NodeDelegate> children = Iterators.filter( |
| node.getChildren(), |
| new Predicate<NodeDelegate>() { |
| @Override |
| public boolean apply(NodeDelegate state) { |
| // TODO: use Oak names |
| return ItemNameMatcher.matches(toJcrPath(state.getName()), namePattern); |
| } |
| }); |
| return new NodeIteratorAdapter(nodeIterator(children)); |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public NodeIterator getNodes(final String[] nameGlobs) throws RepositoryException { |
| return perform(new NodeOperation<NodeIterator>(dlg, "getNodes") { |
| @NotNull |
| @Override |
| public NodeIterator perform() throws RepositoryException { |
| Iterator<NodeDelegate> children = Iterators.filter( |
| node.getChildren(), |
| new Predicate<NodeDelegate>() { |
| @Override |
| public boolean apply(NodeDelegate state) { |
| // TODO: use Oak names |
| return ItemNameMatcher.matches(toJcrPath(state.getName()), nameGlobs); |
| } |
| }); |
| return new NodeIteratorAdapter(nodeIterator(children)); |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public Property getProperty(String relPath) throws RepositoryException { |
| final String oakPath = getOakPathOrThrowNotFound(relPath); |
| return perform(new NodeOperation<PropertyImpl>(dlg, "getProperty") { |
| @NotNull |
| @Override |
| public PropertyImpl perform() throws RepositoryException { |
| PropertyDelegate pd = node.getPropertyOrNull(oakPath); |
| if (pd == null) { |
| throw new PathNotFoundException( |
| oakPath + " not found on " + node.getPath()); |
| } else { |
| return new PropertyImpl(pd, sessionContext); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public PropertyIterator getProperties() throws RepositoryException { |
| return perform(new NodeOperation<PropertyIterator>(dlg, "getProperties") { |
| @NotNull |
| @Override |
| public PropertyIterator perform() throws RepositoryException { |
| Iterator<PropertyDelegate> properties = node.getProperties(); |
| long size = node.getPropertyCount(); |
| return new PropertyIteratorAdapter( |
| propertyIterator(properties), size); |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public PropertyIterator getProperties(final String namePattern) throws RepositoryException { |
| return perform(new NodeOperation<PropertyIterator>(dlg, "getProperties") { |
| @NotNull |
| @Override |
| public PropertyIterator perform() throws RepositoryException { |
| final PropertyIteratorDelegate delegate = new PropertyIteratorDelegate(node, new Predicate<PropertyDelegate>() { |
| @Override |
| public boolean apply(PropertyDelegate entry) { |
| // TODO: use Oak names |
| return ItemNameMatcher.matches(toJcrPath(entry.getName()), namePattern); |
| } |
| }); |
| return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())){ |
| @Override |
| public long getSize() { |
| return delegate.getSize(); |
| } |
| }; |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public PropertyIterator getProperties(final String[] nameGlobs) throws RepositoryException { |
| return perform(new NodeOperation<PropertyIterator>(dlg, "getProperties") { |
| @NotNull |
| @Override |
| public PropertyIterator perform() throws RepositoryException { |
| final PropertyIteratorDelegate delegate = new PropertyIteratorDelegate(node, new Predicate<PropertyDelegate>() { |
| @Override |
| public boolean apply(PropertyDelegate entry) { |
| // TODO: use Oak names |
| return ItemNameMatcher.matches(toJcrPath(entry.getName()), nameGlobs); |
| } |
| }); |
| return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())){ |
| @Override |
| public long getSize() { |
| return delegate.getSize(); |
| } |
| }; |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getPrimaryItem() |
| */ |
| @Override |
| @NotNull |
| public Item getPrimaryItem() throws RepositoryException { |
| return perform(new NodeOperation<Item>(dlg, "getPrimaryItem") { |
| @NotNull |
| @Override |
| public Item perform() throws RepositoryException { |
| // TODO: avoid nested calls |
| String name = getPrimaryNodeType().getPrimaryItemName(); |
| if (name == null) { |
| throw new ItemNotFoundException( |
| "No primary item present on node " + NodeImpl.this); |
| } |
| 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 " + NodeImpl.this); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getUUID() |
| */ |
| @Override |
| @NotNull |
| public String getUUID() throws RepositoryException { |
| return perform(new NodeOperation<String>(dlg, "getUUID") { |
| @NotNull |
| @Override |
| public String perform() throws RepositoryException { |
| // TODO: avoid nested calls |
| if (isNodeType(NodeType.MIX_REFERENCEABLE)) { |
| return getIdentifier(); |
| } |
| throw new UnsupportedRepositoryOperationException(format("Node [%s] is not referenceable.", getNodePath())); |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public String getIdentifier() throws RepositoryException { |
| // TODO: name mapping for path identifiers |
| return perform(new NodeOperation<String>(dlg, "getIdentifier") { |
| @NotNull |
| @Override |
| public String perform() throws RepositoryException { |
| return node.getIdentifier(); |
| } |
| }); |
| } |
| |
| @Override |
| public int getIndex() throws RepositoryException { |
| // as long as we do not support same name siblings, index always is 1 |
| return 1; // TODO |
| } |
| |
| private PropertyIterator internalGetReferences(final String name, final boolean weak) throws RepositoryException { |
| return perform(new NodeOperation<PropertyIterator>(dlg, "internalGetReferences") { |
| @NotNull |
| @Override |
| public PropertyIterator perform() throws InvalidItemStateException { |
| IdentifierManager idManager = sessionDelegate.getIdManager(); |
| |
| Iterable<String> propertyOakPaths = idManager.getReferences(weak, node.getTree(), name); // TODO: oak name? |
| Iterable<Property> properties = Iterables.transform( |
| propertyOakPaths, |
| new Function<String, Property>() { |
| @Override |
| public Property apply(String oakPath) { |
| PropertyDelegate pd = sessionDelegate.getProperty(oakPath); |
| return pd == null ? null : new PropertyImpl(pd, sessionContext); |
| } |
| } |
| ); |
| |
| return new PropertyIteratorAdapter(sessionDelegate.sync(properties.iterator())); |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getReferences() |
| */ |
| @Override |
| @NotNull |
| public PropertyIterator getReferences() throws RepositoryException { |
| return internalGetReferences(null, false); |
| } |
| |
| @Override |
| @NotNull |
| public PropertyIterator getReferences(final String name) throws RepositoryException { |
| return internalGetReferences(name, false); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getWeakReferences() |
| */ |
| @Override |
| @NotNull |
| public PropertyIterator getWeakReferences() throws RepositoryException { |
| return internalGetReferences(null, true); |
| } |
| |
| @Override |
| @NotNull |
| public PropertyIterator getWeakReferences(String name) throws RepositoryException { |
| return internalGetReferences(name, true); |
| } |
| |
| @Override |
| public boolean hasNode(String relPath) throws RepositoryException { |
| try { |
| final String oakPath = getOakPathOrThrow(relPath); |
| return perform(new NodeOperation<Boolean>(dlg, "hasNode") { |
| @NotNull |
| @Override |
| public Boolean perform() throws RepositoryException { |
| return node.getChild(oakPath) != null; |
| } |
| }); |
| } catch (PathNotFoundException e) { |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean hasProperty(String relPath) throws RepositoryException { |
| try { |
| final String oakPath = getOakPathOrThrow(relPath); |
| return perform(new NodeOperation<Boolean>(dlg, "hasProperty") { |
| @NotNull |
| @Override |
| public Boolean perform() throws RepositoryException { |
| return node.getPropertyOrNull(oakPath) != null; |
| } |
| }); |
| } catch (PathNotFoundException e) { |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean hasNodes() throws RepositoryException { |
| return getNodes().hasNext(); |
| } |
| |
| @Override |
| public boolean hasProperties() throws RepositoryException { |
| return perform(new NodeOperation<Boolean>(dlg, "hasProperties") { |
| @NotNull |
| @Override |
| public Boolean perform() throws RepositoryException { |
| return node.getPropertyCount() != 0; |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getPrimaryNodeType() |
| */ |
| @Override |
| @NotNull |
| public NodeType getPrimaryNodeType() throws RepositoryException { |
| return perform(new NodeOperation<NodeType>(dlg, "getPrimaryNodeType") { |
| @NotNull |
| @Override |
| public NodeType perform() throws RepositoryException { |
| Tree tree = node.getTree(); |
| String primaryTypeName = getPrimaryTypeName(tree); |
| if (primaryTypeName != null) { |
| return getNodeTypeManager().getNodeType(sessionContext.getJcrName(primaryTypeName)); |
| } else { |
| throw new RepositoryException("Unable to retrieve primary type for Node " + getNodePath()); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getMixinNodeTypes() |
| */ |
| @Override |
| @NotNull |
| public NodeType[] getMixinNodeTypes() throws RepositoryException { |
| return perform(new NodeOperation<NodeType[]>(dlg, "getMixinNodeTypes") { |
| @NotNull |
| @Override |
| public NodeType[] perform() throws RepositoryException { |
| Tree tree = node.getTree(); |
| |
| Iterator<String> mixinNames = getMixinTypeNames(tree); |
| if (mixinNames.hasNext()) { |
| NodeTypeManager ntMgr = getNodeTypeManager(); |
| List<NodeType> mixinTypes = Lists.newArrayList(); |
| while (mixinNames.hasNext()) { |
| mixinTypes.add(ntMgr.getNodeType(sessionContext.getJcrName(mixinNames.next()))); |
| } |
| return mixinTypes.toArray(new NodeType[mixinTypes.size()]); |
| } else { |
| return new NodeType[0]; |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public boolean isNodeType(String nodeTypeName) throws RepositoryException { |
| final String oakName = getOakName(nodeTypeName); |
| return perform(new NodeOperation<Boolean>(dlg, "isNodeType") { |
| @NotNull |
| @Override |
| public Boolean perform() throws RepositoryException { |
| Tree tree = node.getTree(); |
| return getNodeTypeManager().isNodeType(getPrimaryTypeName(tree), getMixinTypeNames(tree), oakName); |
| } |
| }); |
| } |
| |
| @Override |
| public void setPrimaryType(final String nodeTypeName) throws RepositoryException { |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("setPrimaryType") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut()) { |
| throw new VersionException(format("Cannot set primary type. Node [%s] is checked in.", getNodePath())); |
| } |
| } |
| |
| @Override |
| public void performVoid() throws RepositoryException { |
| internalSetPrimaryType(nodeTypeName); |
| } |
| }); |
| } |
| |
| @Override |
| public void addMixin(String mixinName) throws RepositoryException { |
| final String oakTypeName = getOakName(checkNotNull(mixinName)); |
| if (JcrConstants.MIX_LOCKABLE.equals(oakTypeName)) { |
| LockDeprecation.handleCall("addMixin " + JcrConstants.MIX_LOCKABLE); |
| } |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("addMixin") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut()) { |
| throw new VersionException(format( |
| "Cannot add mixin type. Node [%s] is checked in.", getNodePath())); |
| } |
| } |
| @Override |
| public void performVoid() throws RepositoryException { |
| dlg.addMixin(oakTypeName); |
| } |
| }); |
| } |
| |
| @Override |
| public void removeMixin(final String mixinName) throws RepositoryException { |
| final String oakTypeName = getOakName(checkNotNull(mixinName)); |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("removeMixin") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut()) { |
| throw new VersionException(format( |
| "Cannot remove mixin type. Node [%s] is checked in.", getNodePath())); |
| } |
| |
| // check for NODE_TYPE_MANAGEMENT permission here as we cannot |
| // distinguish between a combination of removeMixin and addMixin |
| // and Node#remove plus subsequent addNode when it comes to |
| // autocreated properties like jcr:create, jcr:uuid and so forth. |
| Set<String> mixins = newLinkedHashSet(getNames(dlg.getTree(), JCR_MIXINTYPES)); |
| if (!mixins.isEmpty() && mixins.remove(getOakName(mixinName))) { |
| PropertyState prop = PropertyStates.createProperty(JCR_MIXINTYPES, mixins, NAMES); |
| sessionContext.getAccessManager().checkPermissions(dlg.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT); |
| } |
| } |
| @Override |
| public void performVoid() throws RepositoryException { |
| dlg.removeMixin(oakTypeName); |
| } |
| }); |
| } |
| |
| @Override |
| public boolean canAddMixin(String mixinName) throws RepositoryException { |
| final String oakTypeName = getOakName(mixinName); |
| return perform(new NodeOperation<Boolean>(dlg, "canAddMixin") { |
| @NotNull |
| @Override |
| public Boolean perform() throws RepositoryException { |
| PropertyState prop = PropertyStates.createProperty(JCR_MIXINTYPES, singleton(oakTypeName), NAMES); |
| return sessionContext.getAccessManager().hasPermissions( |
| node.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT) |
| && !node.isProtected() |
| && getVersionManager().isCheckedOut(toJcrPath(dlg.getPath())) // TODO: avoid nested calls |
| && node.canAddMixin(oakTypeName); |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public NodeDefinition getDefinition() throws RepositoryException { |
| return perform(new NodeOperation<NodeDefinition>(dlg, "getDefinition") { |
| @NotNull |
| @Override |
| public NodeDefinition perform() throws RepositoryException { |
| NodeDelegate parent = node.getParent(); |
| if (parent == null) { |
| return getNodeTypeManager().getRootDefinition(); |
| } else { |
| return getNodeTypeManager().getDefinition( |
| parent.getTree(), node.getTree()); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| @NotNull |
| public String getCorrespondingNodePath(final String workspaceName) throws RepositoryException { |
| return toJcrPath(perform(new ItemOperation<String>(dlg, "getCorrespondingNodePath") { |
| @NotNull |
| @Override |
| public String perform() throws RepositoryException { |
| checkValidWorkspace(workspaceName); |
| if (workspaceName.equals(sessionDelegate.getWorkspaceName())) { |
| return item.getPath(); |
| } else { |
| throw new UnsupportedRepositoryOperationException("OAK-118: Node.getCorrespondingNodePath at " + getNodePath()); |
| } |
| } |
| })); |
| } |
| |
| |
| @Override |
| public void update(final String srcWorkspace) throws RepositoryException { |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("update") { |
| @Override |
| public void performVoid() throws RepositoryException { |
| checkValidWorkspace(srcWorkspace); |
| |
| // check for pending changes |
| if (sessionDelegate.hasPendingChanges()) { |
| String msg = format("Unable to perform operation. Session has pending changes. Node [%s]", getNodePath()); |
| LOG.debug(msg); |
| throw new InvalidItemStateException(msg); |
| } |
| |
| if (!srcWorkspace.equals(sessionDelegate.getWorkspaceName())) { |
| throw new UnsupportedRepositoryOperationException("OAK-118: Node.update at " + getNodePath()); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * @see javax.jcr.Node#checkin() |
| */ |
| @Override |
| @NotNull |
| public Version checkin() throws RepositoryException { |
| return getVersionManager().checkin(getPath()); |
| } |
| |
| /** |
| * @see javax.jcr.Node#checkout() |
| */ |
| @Override |
| public void checkout() throws RepositoryException { |
| getVersionManager().checkout(getPath()); |
| } |
| |
| /** |
| * @see javax.jcr.Node#doneMerge(javax.jcr.version.Version) |
| */ |
| @Override |
| public void doneMerge(Version version) throws RepositoryException { |
| getVersionManager().doneMerge(getPath(), version); |
| } |
| |
| /** |
| * @see javax.jcr.Node#cancelMerge(javax.jcr.version.Version) |
| */ |
| @Override |
| public void cancelMerge(Version version) throws RepositoryException { |
| getVersionManager().cancelMerge(getPath(), version); |
| } |
| |
| /** |
| * @see javax.jcr.Node#merge(String, boolean) |
| */ |
| @Override |
| @NotNull |
| public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws RepositoryException { |
| return getVersionManager().merge(getPath(), srcWorkspace, bestEffort); |
| } |
| |
| /** |
| * @see javax.jcr.Node#isCheckedOut() |
| */ |
| @Override |
| public boolean isCheckedOut() throws RepositoryException { |
| try { |
| return getVersionManager().isCheckedOut(getPath()); |
| } catch (UnsupportedRepositoryOperationException ex) { |
| // when versioning is not supported all nodes are considered to be |
| // checked out |
| return true; |
| } |
| } |
| |
| /** |
| * @see javax.jcr.Node#restore(String, boolean) |
| */ |
| @Override |
| public void restore(String versionName, boolean removeExisting) throws RepositoryException { |
| if (!isNodeType(NodeType.MIX_VERSIONABLE)) { |
| throw new UnsupportedRepositoryOperationException(format("Node [%s] is not mix:versionable", getNodePath())); |
| } |
| getVersionManager().restore(getPath(), versionName, removeExisting); |
| } |
| |
| /** |
| * @see javax.jcr.Node#restore(javax.jcr.version.Version, boolean) |
| */ |
| @Override |
| public void restore(Version version, boolean removeExisting) throws RepositoryException { |
| if (!isNodeType(NodeType.MIX_VERSIONABLE)) { |
| throw new UnsupportedRepositoryOperationException(format("Node [%s] is not mix:versionable", getNodePath())); |
| } |
| String id = version.getContainingHistory().getVersionableIdentifier(); |
| if (getIdentifier().equals(id)) { |
| getVersionManager().restore(version, removeExisting); |
| } else { |
| throw new VersionException(format("Version does not belong to the " + |
| "VersionHistory of this node [%s].", getNodePath())); |
| } |
| } |
| |
| /** |
| * @see javax.jcr.Node#restore(Version, String, boolean) |
| */ |
| @Override |
| public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException { |
| // 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 { |
| String absPath = PathUtils.concat(getPath(), relPath); |
| getVersionManager().restore(absPath, version, removeExisting); |
| } |
| } |
| |
| /** |
| * @see javax.jcr.Node#restoreByLabel(String, boolean) |
| */ |
| @Override |
| public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException { |
| getVersionManager().restoreByLabel(getPath(), versionLabel, removeExisting); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getVersionHistory() |
| */ |
| @Override |
| @NotNull |
| public VersionHistory getVersionHistory() throws RepositoryException { |
| return getVersionManager().getVersionHistory(getPath()); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getBaseVersion() |
| */ |
| @Override |
| @NotNull |
| public Version getBaseVersion() throws RepositoryException { |
| return getVersionManager().getBaseVersion(getPath()); |
| } |
| |
| private LockManager getLockManager() throws RepositoryException { |
| return getSession().getWorkspace().getLockManager(); |
| } |
| |
| @Override |
| public boolean isLocked() throws RepositoryException { |
| if (!LockDeprecation.isLockingSupported()) { |
| return false; |
| } |
| return getLockManager().isLocked(getPath()); |
| } |
| |
| @Override |
| public boolean holdsLock() throws RepositoryException { |
| return getLockManager().holdsLock(getPath()); |
| } |
| |
| @Override @NotNull |
| public Lock getLock() throws RepositoryException { |
| return getLockManager().getLock(getPath()); |
| } |
| |
| @Override @NotNull |
| public Lock lock(boolean isDeep, boolean isSessionScoped) |
| throws RepositoryException { |
| return getLockManager().lock( |
| getPath(), isDeep, isSessionScoped, Long.MAX_VALUE, null); |
| } |
| |
| @Override |
| public void unlock() throws RepositoryException { |
| getLockManager().unlock(getPath()); |
| } |
| |
| @Override @NotNull |
| public NodeIterator getSharedSet() { |
| return new NodeIteratorAdapter(singleton(this)); |
| } |
| |
| @Override |
| public void removeSharedSet() throws RepositoryException { |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("removeSharedSet") { |
| @Override |
| public void performVoid() throws RepositoryException { |
| // TODO: avoid nested calls |
| NodeIterator sharedSet = getSharedSet(); |
| while (sharedSet.hasNext()) { |
| sharedSet.nextNode().removeShare(); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void removeShare() throws RepositoryException { |
| remove(); |
| } |
| |
| /** |
| * @see javax.jcr.Node#followLifecycleTransition(String) |
| */ |
| @Override |
| public void followLifecycleTransition(String transition) throws RepositoryException { |
| throw new UnsupportedRepositoryOperationException("Lifecycle Management is not supported"); |
| } |
| |
| /** |
| * @see javax.jcr.Node#getAllowedLifecycleTransistions() |
| */ |
| @Override |
| @NotNull |
| public String[] getAllowedLifecycleTransistions() throws RepositoryException { |
| throw new UnsupportedRepositoryOperationException("Lifecycle Management is not supported"); |
| |
| } |
| |
| //------------------------------------------------------------< internal >--- |
| @Nullable |
| private String getPrimaryTypeName(@NotNull Tree tree) { |
| String primaryTypeName = null; |
| if (tree.hasProperty(JcrConstants.JCR_PRIMARYTYPE)) { |
| primaryTypeName = TreeUtil.getPrimaryTypeName(tree); |
| } else if (tree.getStatus() != Status.NEW) { |
| // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to |
| // read the primary type from the underlying node state. |
| primaryTypeName = TreeUtil.getPrimaryTypeName(RootFactory.createReadOnlyRoot(sessionDelegate.getRoot()).getTree(tree.getPath())); |
| } |
| return primaryTypeName; |
| } |
| |
| @NotNull |
| private Iterator<String> getMixinTypeNames(@NotNull Tree tree) throws RepositoryException { |
| Iterator<String> mixinNames = Collections.emptyIterator(); |
| if (tree.hasProperty(JcrConstants.JCR_MIXINTYPES) || canReadMixinTypes(tree)) { |
| mixinNames = TreeUtil.getNames(tree, JcrConstants.JCR_MIXINTYPES).iterator(); |
| } else if (tree.getStatus() != Status.NEW) { |
| // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to |
| // read the primary type from the underlying node state. |
| mixinNames = TreeUtil.getNames( |
| RootFactory.createReadOnlyRoot(sessionDelegate.getRoot()).getTree(tree.getPath()), |
| JcrConstants.JCR_MIXINTYPES).iterator(); |
| } |
| return mixinNames; |
| } |
| |
| private boolean canReadMixinTypes(@NotNull Tree tree) throws RepositoryException { |
| // OAK-7652: use an zero length MVP to check read permission on jcr:mixinTypes |
| PropertyState mixinTypes = PropertyStates.createProperty( |
| JcrConstants.JCR_MIXINTYPES, Collections.emptyList(), Type.NAMES); |
| return sessionContext.getAccessManager().hasPermissions( |
| tree, mixinTypes, Permissions.READ_PROPERTY); |
| } |
| |
| private EffectiveNodeType getEffectiveNodeType() throws RepositoryException { |
| return getNodeTypeManager().getEffectiveNodeType(dlg.getTree()); |
| } |
| |
| private Iterator<Node> nodeIterator(Iterator<NodeDelegate> childNodes) { |
| return sessionDelegate.sync(transform( |
| childNodes, |
| new Function<NodeDelegate, Node>() { |
| @Override |
| public Node apply(NodeDelegate nodeDelegate) { |
| return new NodeImpl<NodeDelegate>(nodeDelegate, sessionContext); |
| } |
| })); |
| } |
| |
| private Iterator<Property> propertyIterator(Iterator<PropertyDelegate> properties) { |
| return sessionDelegate.sync(transform( |
| properties, |
| new Function<PropertyDelegate, Property>() { |
| @Override |
| public Property apply(PropertyDelegate propertyDelegate) { |
| return new PropertyImpl(propertyDelegate, sessionContext); |
| } |
| })); |
| } |
| |
| private void checkValidWorkspace(String workspaceName) |
| throws RepositoryException { |
| String[] workspaceNames = |
| getSession().getWorkspace().getAccessibleWorkspaceNames(); |
| if (!asList(workspaceNames).contains(workspaceName)) { |
| throw new NoSuchWorkspaceException( |
| "Workspace " + workspaceName + " does not exist"); |
| } |
| } |
| |
| private void internalSetPrimaryType(final String nodeTypeName) throws RepositoryException { |
| // TODO: figure out the right place for this check |
| NodeType nt = getNodeTypeManager().getNodeType(nodeTypeName); // throws on not found |
| if (nt.isAbstract() || nt.isMixin()) { |
| throw new ConstraintViolationException(getNodePath()); |
| } |
| // TODO: END |
| |
| PropertyState state = PropertyStates.createProperty( |
| JCR_PRIMARYTYPE, getOakName(nodeTypeName), NAME); |
| dlg.setProperty(state, true, true); |
| dlg.setOrderableChildren(nt.hasOrderableChildNodes()); |
| } |
| |
| private Property internalSetProperty( |
| final String jcrName, final Value value, final boolean exactTypeMatch) |
| throws RepositoryException { |
| final String oakName = getOakPathOrThrow(checkNotNull(jcrName)); |
| final PropertyState state = createSingleState( |
| oakName, value, Type.fromTag(value.getType(), false)); |
| return perform(new ItemWriteOperation<Property>("internalSetProperty") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut() && getOPV(dlg.getTree(), state) != OnParentVersionAction.IGNORE) { |
| throw new VersionException(format( |
| "Cannot set property. Node [%s] is checked in.", getNodePath())); |
| } |
| } |
| @NotNull |
| @Override |
| public Property perform() throws RepositoryException { |
| return new PropertyImpl( |
| dlg.setProperty(state, exactTypeMatch, false), |
| sessionContext); |
| } |
| |
| @Override |
| public String toString() { |
| return format("Setting property [%s/%s]", dlg.getPath(), jcrName); |
| } |
| }); |
| } |
| |
| private Property internalSetProperty( |
| final String jcrName, final Value[] values, |
| final int type, final boolean exactTypeMatch) |
| throws RepositoryException { |
| final String oakName = getOakPathOrThrow(checkNotNull(jcrName)); |
| final PropertyState state = createMultiState( |
| oakName, compact(values), Type.fromTag(type, true)); |
| |
| if (values.length > MV_PROPERTY_WARN_THRESHOLD) { |
| LOG.warn("Large multi valued property [{}/{}] detected ({} values).",dlg.getPath(), jcrName, values.length); |
| } |
| |
| return perform(new ItemWriteOperation<Property>("internalSetProperty") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut() && getOPV(dlg.getTree(), state) != OnParentVersionAction.IGNORE) { |
| throw new VersionException(format( |
| "Cannot set property. Node [%s] is checked in.", getNodePath())); |
| } |
| } |
| @NotNull |
| @Override |
| public Property perform() throws RepositoryException { |
| return new PropertyImpl( |
| dlg.setProperty(state, exactTypeMatch, false), |
| sessionContext); |
| } |
| |
| @Override |
| public String toString() { |
| return format("Setting property [%s/%s]", dlg.getPath(), jcrName); |
| } |
| }); |
| } |
| |
| /** |
| * Removes all {@code null} values from the given array. |
| * |
| * @param values value array |
| * @return value list without {@code null} entries |
| */ |
| private static List<Value> compact(Value[] values) { |
| List<Value> list = Lists.newArrayListWithCapacity(values.length); |
| for (Value value : values) { |
| if (value != null) { |
| list.add(value); |
| } |
| } |
| return list; |
| } |
| |
| |
| private Property internalRemoveProperty(final String jcrName) |
| throws RepositoryException { |
| final String oakName = getOakName(checkNotNull(jcrName)); |
| return perform(new ItemWriteOperation<Property>("internalRemoveProperty") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| PropertyDelegate property = dlg.getPropertyOrNull(oakName); |
| if (property != null && |
| !isCheckedOut() && |
| getOPV(dlg.getTree(), property.getPropertyState()) != OnParentVersionAction.IGNORE) { |
| throw new VersionException(format( |
| "Cannot remove property. Node [%s] is checked in.", getNodePath())); |
| } |
| } |
| @NotNull |
| @Override |
| public Property perform() throws RepositoryException { |
| PropertyDelegate property = dlg.getPropertyOrNull(oakName); |
| if (property != null) { |
| property.remove(); |
| } else { |
| // Return an instance which throws on access; see OAK-395 |
| property = dlg.getProperty(oakName); |
| } |
| return new PropertyImpl(property, sessionContext); |
| } |
| |
| @Override |
| public String toString() { |
| return format("Removing property [%s]", jcrName); |
| } |
| }); |
| } |
| |
| //-----------------------------------------------------< JackrabbitNode >--- |
| |
| /** |
| * Simplified implementation of {@link JackrabbitNode#rename(String)}. In |
| * contrast to the implementation in Jackrabbit 2.x which was operating on |
| * the NodeState level directly, this implementation does a move plus |
| * subsequent reorder on the JCR API due to a missing support for renaming |
| * on the OAK API. |
| * |
| * Note, that this also has an impact on how permissions are enforced: In |
| * Jackrabbit 2.x the rename just required permission to modify the child |
| * collection on the parent, whereas a move did the full permission check. |
| * With this simplified implementation that (somewhat inconsistent) difference |
| * has been removed. |
| * |
| * @param newName The new name of this node. |
| * @throws RepositoryException If an error occurs. |
| */ |
| @Override |
| public void rename(final String newName) throws RepositoryException { |
| if (dlg.isRoot()) { |
| throw new RepositoryException("Cannot rename the root node"); |
| } |
| |
| final String name = getName(); |
| if (newName.equals(name)) { |
| // nothing to do |
| return; |
| } |
| |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("rename") { |
| @Override |
| public void performVoid() throws RepositoryException { |
| Node parent = getParent(); |
| String beforeName = null; |
| |
| if (isOrderable(parent)) { |
| // remember position amongst siblings |
| NodeIterator nit = parent.getNodes(); |
| while (nit.hasNext()) { |
| Node child = nit.nextNode(); |
| if (name.equals(child.getName())) { |
| if (nit.hasNext()) { |
| beforeName = nit.nextNode().getName(); |
| } |
| break; |
| } |
| } |
| } |
| |
| String srcPath = getPath(); |
| String destPath = '/' + newName; |
| String parentPath = parent.getPath(); |
| if (!"/".equals(parentPath)) { |
| destPath = parentPath + destPath; |
| } |
| sessionContext.getSession().move(srcPath, destPath); |
| |
| if (beforeName != null) { |
| // restore position within siblings |
| parent.orderBefore(newName, beforeName); |
| } |
| } |
| }); |
| } |
| |
| private static boolean isOrderable(Node node) throws RepositoryException { |
| if (node.getPrimaryNodeType().hasOrderableChildNodes()) { |
| return true; |
| } |
| |
| NodeType[] types = node.getMixinNodeTypes(); |
| for (NodeType type : types) { |
| if (type.hasOrderableChildNodes()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Simplified implementation of the {@link org.apache.jackrabbit.api.JackrabbitNode#setMixins(String[])} |
| * method that adds all mixin types that are not yet present on this node |
| * and removes all mixins that are no longer contained in the specified |
| * array. Note, that this implementation will not work exactly like the |
| * variant in Jackrabbit 2.x which first created the effective node type |
| * and adjusted the set of child items accordingly. |
| * |
| * @param mixinNames |
| * @throws RepositoryException |
| */ |
| @Override |
| public void setMixins(String[] mixinNames) throws RepositoryException { |
| final Set<String> oakTypeNames = newLinkedHashSet(); |
| for (String mixinName : mixinNames) { |
| oakTypeNames.add(getOakName(checkNotNull(mixinName))); |
| } |
| sessionDelegate.performVoid(new ItemWriteOperation<Void>("setMixins") { |
| @Override |
| public void checkPreconditions() throws RepositoryException { |
| super.checkPreconditions(); |
| if (!isCheckedOut()) { |
| throw new VersionException(format("Cannot set mixin types. Node [%s] is checked in.", getNodePath())); |
| } |
| |
| // check for NODE_TYPE_MANAGEMENT permission here as we cannot |
| // distinguish between a combination of removeMixin and addMixin |
| // and Node#remove plus subsequent addNode when it comes to |
| // autocreated properties like jcr:create, jcr:uuid and so forth. |
| PropertyDelegate mixinProp = dlg.getPropertyOrNull(JCR_MIXINTYPES); |
| if (mixinProp != null) { |
| sessionContext.getAccessManager().checkPermissions(dlg.getTree(), mixinProp.getPropertyState(), Permissions.NODE_TYPE_MANAGEMENT); |
| } |
| } |
| @Override |
| public void performVoid() throws RepositoryException { |
| dlg.setMixins(oakTypeNames); |
| } |
| }); |
| } |
| |
| /** |
| * Provide current node path. Should be invoked from within |
| * the SessionDelegate#perform and preferred instead of getPath |
| * as it provides direct access to path |
| */ |
| private String getNodePath(){ |
| return dlg.getPath(); |
| } |
| |
| private int getOPV(Tree nodeTree, PropertyState property) |
| throws RepositoryException { |
| return getNodeTypeManager().getDefinition(nodeTree, |
| property, false).getOnParentVersion(); |
| |
| } |
| |
| private static class PropertyIteratorDelegate { |
| private final NodeDelegate node; |
| private final Predicate<PropertyDelegate> predicate; |
| |
| PropertyIteratorDelegate(NodeDelegate node, Predicate<PropertyDelegate> predicate) { |
| this.node = node; |
| this.predicate = predicate; |
| } |
| |
| public Iterator<PropertyDelegate> iterator() throws InvalidItemStateException { |
| return Iterators.filter(node.getProperties(), predicate); |
| } |
| |
| public long getSize() { |
| try { |
| return Iterators.size(iterator()); |
| } catch (InvalidItemStateException e) { |
| throw new IllegalStateException( |
| "This iterator is no longer valid", e); |
| } |
| } |
| |
| } |
| } |