blob: aac474eb13ee0715712f7f312b3bdc272ce03337 [file] [log] [blame]
/*
* 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 java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.VersionManager;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.delegate.ItemDelegate;
import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
import org.apache.jackrabbit.oak.jcr.session.operation.ItemOperation;
import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
import org.apache.jackrabbit.oak.util.PropertyBuilder;
import org.apache.jackrabbit.oak.plugins.nodetype.write.ReadWriteNodeTypeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.collect.Lists.newArrayListWithCapacity;
import static org.apache.jackrabbit.oak.api.Type.NAME;
import static org.apache.jackrabbit.oak.api.Type.NAMES;
import static org.apache.jackrabbit.oak.api.Type.PATH;
import static org.apache.jackrabbit.oak.api.Type.PATHS;
import static org.apache.jackrabbit.oak.api.Type.STRING;
import static org.apache.jackrabbit.oak.api.Type.UNDEFINED;
import static org.apache.jackrabbit.oak.api.Type.UNDEFINEDS;
import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
/**
* TODO document
*/
abstract class ItemImpl<T extends ItemDelegate> implements Item {
private static final Logger log = LoggerFactory.getLogger(ItemImpl.class);
public static final String ITEM_SAVE_DOES_SESSION_SAVE = "item-save-does-session-save";
/**
* The value of this flag determines the behaviour of {@link #save()}. If {@code false},
* save will throw a {@link javax.jcr.UnsupportedRepositoryOperationException} if the
* sub tree rooted at this item does not contain <em>all</em> transient changes. If
* {@code true}, save will delegate to {@link Session#save()}.
*/
public static final boolean SAVE_SESSION;
static {
String property = System.getProperty(ITEM_SAVE_DOES_SESSION_SAVE);
SAVE_SESSION = property == null || Boolean.parseBoolean(property);
}
protected final SessionContext sessionContext;
protected final T dlg;
protected final SessionDelegate sessionDelegate;
protected ItemImpl(T itemDelegate, SessionContext sessionContext) {
this.sessionContext = sessionContext;
this.dlg = itemDelegate;
this.sessionDelegate = sessionContext.getSessionDelegate();
}
protected abstract class ItemWriteOperation<U> extends SessionOperation<U> {
protected ItemWriteOperation() {
super(true);
}
@Override
public void checkPreconditions() throws RepositoryException {
dlg.checkAlive();
if (dlg.isProtected()) {
throw new ConstraintViolationException("Item is protected.");
}
}
}
/**
* Perform the passed {@link SessionOperation}.
* @param op operation to perform
* @param <U> return type of the operation
* @return the result of {@code op.perform()}
* @throws RepositoryException as thrown by {@code op.perform()}.
*/
@CheckForNull
protected final <U> U perform(@Nonnull SessionOperation<U> op) throws RepositoryException {
return sessionDelegate.perform(op);
}
/**
* Perform the passed {@link SessionOperation} assuming it does not throw an
* {@code RepositoryException}. If it does, wrap it into and throw it as a
* {@code RuntimeException}.
* @param op operation to perform
* @param <U> return type of the operation
* @return the result of {@code op.perform()}
*/
@CheckForNull
protected final <U> U safePerform(@Nonnull SessionOperation<U> op) {
return sessionDelegate.safePerform(op);
}
//---------------------------------------------------------------< Item >---
/**
* @see javax.jcr.Item#getName()
*/
@Override
@Nonnull
public String getName() throws RepositoryException {
String oakName = perform(new ItemOperation<String>(dlg) {
@Override
public String perform() {
return item.getName();
}
});
// special case name of root node
return oakName.isEmpty() ? "" : toJcrPath(dlg.getName());
}
/**
* @see javax.jcr.Property#getPath()
*/
@Override
@Nonnull
public String getPath() throws RepositoryException {
return toJcrPath(perform(new ItemOperation<String>(dlg) {
@Override
public String perform() {
return item.getPath();
}
}));
}
@Override @Nonnull
public Session getSession() {
return sessionContext.getSession();
}
@Override
public Item getAncestor(final int depth) throws RepositoryException {
if (depth < 0) {
throw new ItemNotFoundException(
getPath() + "Invalid ancestor depth " + depth);
} else if (depth == 0) {
return sessionContext.getSession().getRootNode();
}
ItemDelegate ancestor = perform(new ItemOperation<ItemDelegate>(dlg) {
@Override
public ItemDelegate perform() throws RepositoryException {
String path = item.getPath();
int slash = 0;
for (int i = 0; i < depth - 1; i++) {
slash = PathUtils.getNextSlash(path, slash + 1);
if (slash == -1) {
throw new ItemNotFoundException(
path + ": Invalid ancestor depth " + depth);
}
}
slash = PathUtils.getNextSlash(path, slash + 1);
if (slash == -1) {
return item;
}
return sessionDelegate.getNode(path.substring(0, slash));
}
});
if (ancestor == dlg) {
return this;
} else if (ancestor instanceof NodeDelegate) {
return NodeImpl.createNode((NodeDelegate) ancestor, sessionContext);
} else {
throw new AccessDeniedException(
getPath() + ": Access denied to ancestor at depth " + depth);
}
}
@Override
public int getDepth() throws RepositoryException {
return PathUtils.getDepth(getPath());
}
/**
* @see Item#isSame(javax.jcr.Item)
*/
@Override
public boolean isSame(Item otherItem) throws RepositoryException {
if (this == otherItem) {
return true;
}
// The objects are either both Node objects or both Property objects.
if (isNode() != otherItem.isNode()) {
return false;
}
// Test if both items belong to the same repository
// created by the same Repository object
if (!getSession().getRepository().equals(otherItem.getSession().getRepository())) {
return false;
}
// Both objects were acquired through Session objects bound to the same
// repository workspace.
if (!getSession().getWorkspace().getName().equals(otherItem.getSession().getWorkspace().getName())) {
return false;
}
if (isNode()) {
return ((Node) this).getIdentifier().equals(((Node) otherItem).getIdentifier());
} else {
return getName().equals(otherItem.getName()) && getParent().isSame(otherItem.getParent());
}
}
/**
* This implementation delegates to {@link Session#save()} if {@link #SAVE_SESSION} is
* {@code true}. Otherwise it only performs the save if the subtree rooted at this item contains
* all transient changes. That is, if calling {@link Session#save()} would have the same effect
* as calling this method. In all other cases this method will throw an
* {@link javax.jcr.UnsupportedRepositoryOperationException}
*
* @see javax.jcr.Item#save()
*
*
*/
@Override
public void save() throws RepositoryException {
try {
perform(new ItemWriteOperation<Void>() {
@Override
public Void perform() throws RepositoryException {
dlg.save();
return null;
}
@Override
public boolean isSave() {
return true;
}
});
} catch (UnsupportedRepositoryOperationException e) {
if (SAVE_SESSION) {
if (isNew()) {
throw new RepositoryException("Item.save() not allowed on new item");
}
log.warn("Item#save is only supported when the subtree rooted at that item " +
"contains all transient changes. Falling back to Session#save since " +
"system property " + ITEM_SAVE_DOES_SESSION_SAVE + " is true.");
getSession().save();
} else {
throw e;
}
}
}
/**
* @see Item#refresh(boolean)
*/
@Override
public void refresh(final boolean keepChanges) throws RepositoryException {
if (!keepChanges) {
log.warn("Item#refresh invokes Session#refresh!");
}
perform(new SessionOperation<Void>() {
@Override
public Void perform() throws InvalidItemStateException {
sessionDelegate.refresh(keepChanges);
if (!dlg.exists()) {
throw new InvalidItemStateException(
"This item no longer exists");
}
return null;
}
@Override
public boolean isUpdate() {
return true;
}
@Override
public boolean isRefresh() {
return true;
}
});
}
@Override
public String toString() {
return (isNode() ? "Node[" : "Property[") + dlg + ']';
}
//-----------------------------------------------------------< internal >---
@Nonnull
String getOakName(String name) throws RepositoryException {
return sessionContext.getOakName(name);
}
@Nonnull
String getOakPathOrThrow(String jcrPath) throws RepositoryException {
return sessionContext.getOakPathOrThrow(jcrPath);
}
@Nonnull
String getOakPathOrThrowNotFound(String relPath) throws PathNotFoundException {
return sessionContext.getOakPathOrThrowNotFound(relPath);
}
@Nonnull
String toJcrPath(String oakPath) {
return sessionContext.getJcrPath(oakPath);
}
/**
* Returns the value factory associated with the editing session.
*
* @return the value factory
*/
@Nonnull
ValueFactory getValueFactory() {
return sessionContext.getValueFactory();
}
@Nonnull
ReadWriteNodeTypeManager getNodeTypeManager() {
return sessionContext.getWorkspace().getNodeTypeManager();
}
@Nonnull
VersionManager getVersionManager() throws RepositoryException {
return sessionContext.getWorkspace().getVersionManager();
}
protected PropertyState createSingleState(
String oakName, Value value, Type<?> type)
throws RepositoryException {
if (type == UNDEFINED) {
type = Type.fromTag(value.getType(), false);
}
if (type == NAME || type == PATH) {
return createProperty(oakName, getOakValue(value, type), type);
} else {
return createProperty(oakName, value);
}
}
protected PropertyState createMultiState(
String oakName, List<Value> values, Type<?> type)
throws RepositoryException {
if (values.isEmpty()) {
Type<?> base = type.getBaseType();
if (base == UNDEFINED) {
base = STRING;
}
return PropertyBuilder.array(base)
.setName(oakName).getPropertyState();
}
if (type == UNDEFINEDS) {
type = Type.fromTag(values.get(0).getType(), true);
}
if (type == NAMES || type == PATHS) {
Type<?> base = type.getBaseType();
List<String> strings = newArrayListWithCapacity(values.size());
for (Value value : values) {
strings.add(getOakValue(value, base));
}
return createProperty(oakName, strings, type);
} else {
return createProperty(oakName, values, type.tag());
}
}
private String getOakValue(Value value, Type<?> type)
throws RepositoryException {
if (type == NAME) {
return getOakName(value.getString());
} else if (type == PATH) {
String path = value.getString();
if (!path.startsWith("[")) { // leave identifiers unmodified
path = getOakPathOrThrow(path);
}
return path;
} else {
throw new IllegalArgumentException();
}
}
}