You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2013/09/09 13:35:14 UTC
svn commit: r1521055 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/api/
oak-core/src/main/java/org/apache/jackrabbit/oak/core/
oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/
oak-jcr/src/main/java/org/apache/...
Author: mduerig
Date: Mon Sep 9 11:35:14 2013
New Revision: 1521055
URL: http://svn.apache.org/r1521055
Log:
OAK-993 Improve backward compatibility for Item.save and Item.refresh
Item.save throws UnsupportedRepositoryException when its effect is not equivalent to calling Session.save
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ItemSaveTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/CommitFailedException.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractRoot.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/CommitFailedException.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/CommitFailedException.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/CommitFailedException.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/CommitFailedException.java Mon Sep 9 11:35:14 2013
@@ -16,12 +16,15 @@
*/
package org.apache.jackrabbit.oak.api;
+import static java.lang.String.format;
+
import javax.annotation.Nonnull;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.NamespaceException;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
@@ -29,8 +32,6 @@ import javax.jcr.security.AccessControlE
import javax.jcr.version.LabelExistsVersionException;
import javax.jcr.version.VersionException;
-import static java.lang.String.format;
-
/**
* Main exception thrown by methods defined on the {@code ContentSession}
* interface indicating that committing a given set of changes failed.
@@ -58,7 +59,7 @@ public class CommitFailedException exten
public static final String CONSTRAINT = "Constraint";
/**
- * Type name for referencial integrity violation errors.
+ * Type name for referential integrity violation errors.
*/
public static final String INTEGRITY = "Integrity";
@@ -98,6 +99,11 @@ public class CommitFailedException exten
public static final String LABEL_EXISTS = "LabelExists";
/**
+ * Unsupported operation or feature
+ */
+ public static final String UNSUPPORTED = "Unsupported";
+
+ /**
* Serial version UID
*/
private static final long serialVersionUID = 2727602333350620918L;
@@ -230,6 +236,8 @@ public class CommitFailedException exten
return new LabelExistsVersionException(message, this);
} else if (isOfType(LOCK)) {
return new LockException(message, this);
+ } else if (isOfType(UNSUPPORTED)) {
+ return new UnsupportedRepositoryOperationException(message, this);
} else {
return new RepositoryException(message, this);
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java Mon Sep 9 11:35:14 2013
@@ -20,6 +20,8 @@ package org.apache.jackrabbit.oak.api;
import javax.annotation.Nonnull;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+
/**
* A {@code Root} instance serves as a container for a {@link Tree}. It is
* obtained from a {@link ContentSession}, which governs accessibility and
@@ -30,8 +32,8 @@ import javax.annotation.Nonnull;
* will throw an {@code InvalidStateException}.
* <p>
* {@link Tree} instances may become non existing after a call to
- * {@link #refresh()}, {@link #rebase()} or {@link #commit()}. Any write
- * access to non existing {@code Tree} instances will cause an
+ * {@link #refresh()}, {@link #rebase()} or {@link #commit(CommitHook... hooks)}.
+ * Any write access to non existing {@code Tree} instances will cause an
* {@code InvalidStateException}.
* @see Tree Existence and iterability of trees
*/
@@ -50,7 +52,7 @@ public interface Root {
* </ul>
* If a tree at {@code destinationPath} exists but is not accessible to the
* editing content session this method succeeds but a subsequent
- * {@link #commit()} will detect the violation and fail.
+ * {@link #commit(CommitHook... hooks)} will detect the violation and fail.
*
* @param sourcePath The source path
* @param destPath The destination path
@@ -70,7 +72,7 @@ public interface Root {
* </ul>
* If a tree at {@code destinationPath} exists but is not accessible to the
* editing content session this method succeeds but a subsequent
- * {@link #commit()} will detect the violation and fail.
+ * {@link #commit(CommitHook... hooks)} will detect the violation and fail.
*
* @param sourcePath source path
* @param destPath destination path
@@ -102,13 +104,20 @@ public interface Root {
void refresh();
/**
- * Atomically apply all changes made to the tree contained in this root to the
- * underlying store and refreshes this root. After a call to this method,
- * trees obtained through {@link #getTree(String)} may become non existing.
+ * Atomically persists all changes made to the tree contained in this root to the underlying
+ * store.
+ * <p>
+ * Before any changes are actually persisted the passed commit hooks are run and may fail the
+ * commit by throwing a {@code CommitFailedException}. The commit hooks are run in the order as
+ * passed and <em>before</em> any other commit hook that might be present in this root.
+ * <p>
+ * After a successful operation the root is automatically {@link #refresh() refreshed}, such
+ * that trees obtained through {@link #getTree(String)} may become non existing.
*
+ * @param hooks commit hooks to run before any changes are persisted.
* @throws CommitFailedException
*/
- void commit() throws CommitFailedException;
+ void commit(CommitHook... hooks) throws CommitFailedException;
/**
* Determine whether there are changes on this tree
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractRoot.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractRoot.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractRoot.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractRoot.java Mon Sep 9 11:35:14 2013
@@ -18,15 +18,21 @@
*/
package org.apache.jackrabbit.oak.core;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+
import javax.annotation.Nonnull;
import javax.security.auth.Subject;
+import com.google.common.collect.Lists;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.BlobFactory;
import org.apache.jackrabbit.oak.api.CommitFailedException;
@@ -56,10 +62,6 @@ import org.apache.jackrabbit.oak.spi.sta
import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch;
import org.apache.jackrabbit.oak.util.LazyValue;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
-import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
-
public abstract class AbstractRoot implements Root {
/**
@@ -234,7 +236,7 @@ public abstract class AbstractRoot imple
}
@Override
- public void commit() throws CommitFailedException {
+ public void commit(final CommitHook... hooks) throws CommitFailedException {
checkLive();
purgePendingChanges();
CommitFailedException exception = Subject.doAs(
@@ -242,7 +244,7 @@ public abstract class AbstractRoot imple
@Override
public CommitFailedException run() {
try {
- branch.merge(getCommitHook(), postHook);
+ branch.merge(getCommitHook(hooks), postHook);
return null;
} catch (CommitFailedException e) {
return e;
@@ -256,14 +258,15 @@ public abstract class AbstractRoot imple
}
/**
- * Combine the globally defined commit hook(s) with the hooks and
+ * Combine the passed {@code hooks}, the globally defined commit hook(s) and the hooks and
* validators defined by the various security related configurations.
*
- * @return A commit hook combining repository global commit hook(s) with
- * the pluggable hooks defined with the security modules.
+ * @return A commit hook combining repository global commit hook(s) with the pluggable hooks
+ * defined with the security modules and the padded {@code hooks}.
+ * @param hooks
*/
- private CommitHook getCommitHook() {
- List<CommitHook> commitHooks = new ArrayList<CommitHook>();
+ private CommitHook getCommitHook(CommitHook[] hooks) {
+ List<CommitHook> commitHooks = Lists.newArrayList(hooks);
commitHooks.add(hook);
List<CommitHook> postValidationHooks = new ArrayList<CommitHook>();
for (SecurityConfiguration sc : securityProvider.getConfigurations()) {
@@ -285,7 +288,7 @@ public abstract class AbstractRoot imple
/**
* TODO: review again once the permission validation is completed.
- * Build a read only subject for the {@link #commit()} call that makes the
+ * Build a read only subject for the {@link #commit(CommitHook...)} call that makes the
* principals and the permission provider available to the commit hooks.
*
* @return a new read only subject.
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java Mon Sep 9 11:35:14 2013
@@ -31,6 +31,7 @@ import org.apache.jackrabbit.oak.api.Tre
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
import org.apache.jackrabbit.oak.query.QueryEngineImpl;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
import org.apache.jackrabbit.oak.spi.state.NodeState;
/**
@@ -91,7 +92,7 @@ public final class ImmutableRoot impleme
}
@Override
- public void commit() {
+ public void commit(CommitHook... hooks) {
throw new UnsupportedOperationException();
}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java?rev=1521055&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java Mon Sep 9 11:35:14 2013
@@ -0,0 +1,108 @@
+/*
+ * 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.spi.commit;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Validator that excludes a subtree from the validation process and delegates
+ * validation of other changes to another given validator.
+ *
+ * @see SubtreeValidator
+ * @since Oak 0.9
+ */
+public class SubtreeExcludingValidator extends DefaultValidator {
+
+ private final Validator validator;
+
+ private final String head;
+
+ private final List<String> tail;
+
+ public SubtreeExcludingValidator(Validator validator, String... path) {
+ this(validator, Arrays.asList(path));
+ }
+
+ protected SubtreeExcludingValidator(Validator validator, List<String> path) {
+ this.validator = checkNotNull(validator);
+ checkNotNull(path);
+ checkArgument(!path.isEmpty());
+ this.head = path.get(0);
+ this.tail = path.subList(1, path.size());
+ }
+
+ @Override
+ public void propertyAdded(PropertyState after) throws CommitFailedException {
+ validator.propertyAdded(after);
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after)
+ throws CommitFailedException {
+ validator.propertyChanged(before, after);
+ }
+
+ @Override
+ public void propertyDeleted(PropertyState before) throws CommitFailedException {
+ validator.propertyDeleted(before);
+ }
+
+ @Override
+ public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
+ validator.childNodeAdded(name, after);
+ return descend(name);
+ }
+
+ @Override
+ public Validator childNodeChanged(String name, NodeState before, NodeState after)
+ throws CommitFailedException {
+ validator.childNodeChanged(name, before, after);
+ return descend(name);
+ }
+
+ @Override
+ public Validator childNodeDeleted(String name, NodeState before)
+ throws CommitFailedException {
+ validator.childNodeDeleted(name, before);
+ return descend(name);
+ }
+
+ private Validator descend(String name) {
+ if (!head.equals(name)) {
+ return validator;
+ } else if (tail.isEmpty()) {
+ return null;
+ } else {
+ return createValidator(validator, tail);
+ }
+ }
+
+ protected SubtreeExcludingValidator createValidator(Validator validator, List<String> path) {
+ return new SubtreeExcludingValidator(validator, path);
+ }
+
+}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeValidator.java Mon Sep 9 11:35:14 2013
@@ -16,18 +16,19 @@
*/
package org.apache.jackrabbit.oak.spi.commit;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import java.util.Arrays;
import java.util.List;
import org.apache.jackrabbit.oak.spi.state.NodeState;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
/**
* Validator that detects changes to a specified subtree and delegates the
* validation of such changes to another given validator.
*
+ * @see SubtreeExcludingValidator
* @since Oak 0.3
*/
public class SubtreeValidator extends DefaultValidator {
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java Mon Sep 9 11:35:14 2013
@@ -39,7 +39,6 @@ import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
-import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.ItemDefinition;
import javax.jcr.version.VersionManager;
@@ -63,6 +62,14 @@ import org.slf4j.LoggerFactory;
abstract class ItemImpl<T extends ItemDelegate> implements Item {
private static final Logger log = LoggerFactory.getLogger(ItemImpl.class);
+ /**
+ * 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 = Boolean.getBoolean("item-safe-does-session-safe");
+
protected final SessionContext sessionContext;
protected final T dlg;
protected final SessionDelegate sessionDelegate;
@@ -121,7 +128,7 @@ abstract class ItemImpl<T extends ItemDe
public String getName() throws RepositoryException {
String oakName = perform(new ItemOperation<String>(dlg) {
@Override
- public String perform() throws RepositoryException {
+ public String perform() {
return item.getName();
}
});
@@ -137,7 +144,7 @@ abstract class ItemImpl<T extends ItemDe
public String getPath() throws RepositoryException {
return toJcrPath(perform(new ItemOperation<String>(dlg) {
@Override
- public String perform() throws RepositoryException {
+ public String perform() {
return item.getPath();
}
}));
@@ -228,17 +235,32 @@ abstract class ItemImpl<T extends ItemDe
}
/**
+ * 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 {
- log.warn("Item#save is no longer supported. Please use Session#save instead.");
-
- if (isNew()) {
- throw new RepositoryException("Item.save() not allowed on new item");
+ if (SAVE_SESSION) {
+ getSession().save();
+ } else {
+ perform(new ItemWriteOperation<Void>() {
+ @Override
+ public Void perform() throws RepositoryException {
+ dlg.save();
+ return null;
+ }
+
+ @Override
+ public boolean isSave() {
+ return true;
+ }
+ });
}
-
- getSession().save();
}
/**
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java Mon Sep 9 11:35:14 2013
@@ -110,4 +110,12 @@ public abstract class ItemDelegate {
*/
public abstract boolean remove() throws InvalidItemStateException;
+ /**
+ * Save the subtree rooted at this item.
+ *
+ * @throws RepositoryException
+ */
+ public void save() throws RepositoryException {
+ sessionDelegate.save(getPath());
+ }
}
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java?rev=1521055&r1=1521054&r2=1521055&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java Mon Sep 9 11:35:14 2013
@@ -17,8 +17,12 @@
package org.apache.jackrabbit.oak.jcr.delegate;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
import java.io.IOException;
+import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -30,6 +34,7 @@ import javax.jcr.nodetype.ConstraintViol
import org.apache.jackrabbit.oak.api.AuthInfo;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.QueryEngine;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
@@ -38,10 +43,18 @@ import org.apache.jackrabbit.oak.core.Id
import org.apache.jackrabbit.oak.jcr.RefreshStrategy;
import org.apache.jackrabbit.oak.jcr.operation.SessionOperation;
import org.apache.jackrabbit.oak.jcr.security.AccessManager;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.FailingValidator;
+import org.apache.jackrabbit.oak.spi.commit.SubtreeExcludingValidator;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -291,6 +304,34 @@ public class SessionDelegate {
permissionProvider.refresh();
}
+ /**
+ * Save the subtree rooted at the given {@code path}.
+ * <p>
+ * This implementation only performs the save if the subtree rooted at {@code path} contains
+ * all transient changes and will throw an
+ * {@link javax.jcr.UnsupportedRepositoryOperationException} otherwise.
+ *
+ * @param path
+ * @throws RepositoryException
+ */
+ public void save(final String path) throws RepositoryException {
+ if (denotesRoot(path)) {
+ save();
+ } else {
+ try {
+ root.commit(new EditorHook(new EditorProvider() {
+ @Override
+ public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder) {
+ return new ItemSaveValidator(path);
+ }
+ }));
+ } catch (CommitFailedException e) {
+ throw newRepositoryException(e);
+ }
+ }
+ permissionProvider.refresh();
+ }
+
public void refresh(boolean keepChanges) {
if (keepChanges && hasPendingChanges()) {
root.rebase();
@@ -434,4 +475,50 @@ public class SessionDelegate {
private static RepositoryException newRepositoryException(CommitFailedException exception) {
return exception.asRepositoryException();
}
+
+ /**
+ * This validator checks that all changes are contained within the subtree
+ * rooted at a given path.
+ */
+ private static class ItemSaveValidator extends SubtreeExcludingValidator {
+
+ /**
+ * Name of the property whose {@link #propertyChanged(PropertyState, PropertyState)} to
+ * ignore or {@code null} if no property should be ignored.
+ */
+ private final String ignorePropertyChange;
+
+ /**
+ * Create a new validator that only throws a {@link CommitFailedException} whenever
+ * there are changes not contained in the subtree rooted at {@code path}.
+ * @param path
+ */
+ public ItemSaveValidator(String path) {
+ this(new FailingValidator(CommitFailedException.UNSUPPORTED, 0,
+ "Failed to save subtree at " + path + ". There are " +
+ "transient modifications outside that subtree."),
+ newArrayList(elements(path)));
+ }
+
+ private ItemSaveValidator(Validator validator, List<String> path) {
+ super(validator, path);
+ // Ignore property changes if this is the head of the path.
+ // This allows for calling save on a changed property.
+ ignorePropertyChange = path.size() == 1 ? path.get(0) : null;
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after)
+ throws CommitFailedException {
+ if (!before.getName().equals(ignorePropertyChange)) {
+ super.propertyChanged(before, after);
+ }
+ }
+
+ @Override
+ protected SubtreeExcludingValidator createValidator(
+ Validator validator, final List<String> path) {
+ return new ItemSaveValidator(validator, path);
+ }
+ }
}
Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ItemSaveTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ItemSaveTest.java?rev=1521055&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ItemSaveTest.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ItemSaveTest.java Mon Sep 9 11:35:14 2013
@@ -0,0 +1,139 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * See OAK-993
+ */
+public class ItemSaveTest extends AbstractRepositoryTest {
+
+ public ItemSaveTest(NodeStoreFixture fixture) {
+ super(fixture);
+ }
+
+ private Session session;
+ private Node root;
+ private Node foo;
+ private Property prop0;
+ private Property prop1;
+ private Property prop2;
+
+ @Before
+ public void setup() throws RepositoryException {
+ session = getAdminSession();
+ root = session.getRootNode();
+ foo = root.addNode("foo").addNode("child0");
+ prop0 = root.setProperty("p0", "v0");
+ prop1 = foo.setProperty("p1", "v1");
+ prop2 = foo.setProperty("p2", "v2");
+ session.save();
+ }
+
+ @Test
+ public void noChangesAtAll() throws RepositoryException {
+ foo.save();
+ }
+
+ @Test
+ public void saveContainsAllChanges() throws RepositoryException {
+ foo.addNode("child");
+ foo.save();
+ }
+
+ @Test
+ public void saveOnRoot() throws RepositoryException {
+ root.addNode("child");
+ root.save();
+ }
+
+ @Test
+ public void saveMissesNode() throws RepositoryException {
+ try {
+ root.addNode("child1");
+ foo.addNode("child2");
+ foo.save();
+ fail("Expected UnsupportedRepositoryOperationException");
+ } catch (UnsupportedRepositoryOperationException e) {
+ assertTrue(e.getCause() instanceof CommitFailedException);
+ }
+ }
+
+ @Test
+ public void saveOnNewNode() throws RepositoryException {
+ try {
+ foo.addNode("child").save();
+ fail("Expected UnsupportedRepositoryOperationException");
+ } catch (UnsupportedRepositoryOperationException e) {
+ assertTrue(e.getCause() instanceof CommitFailedException);
+ }
+ }
+
+ @Test
+ public void saveOnChangedProperty() throws RepositoryException {
+ // Property on root
+ prop0.setValue("changed");
+ prop0.save();
+
+ // Property on child node
+ prop1.setValue("changed");
+ prop1.save();
+ }
+
+ @Test
+ public void saveMissesProperty() throws RepositoryException {
+ try {
+ prop1.setValue("changed");
+ prop2.setValue("changed");
+ prop1.save();
+ fail("Expected UnsupportedRepositoryOperationException");
+ } catch (UnsupportedRepositoryOperationException e) {
+ assertTrue(e.getCause() instanceof CommitFailedException);
+ } finally {
+ session.refresh(false);
+ }
+ }
+
+ @Test
+ public void saveOnNewProperty() throws RepositoryException {
+ try {
+ foo.setProperty("p3", "v3").save();
+ fail("Expected UnsupportedRepositoryOperationException");
+ } catch (UnsupportedRepositoryOperationException e) {
+ assertTrue(e.getCause() instanceof CommitFailedException);
+ } finally {
+ session.refresh(false);
+ }
+
+ }
+
+}