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 jo...@apache.org on 2026/07/21 13:32:36 UTC
(jackrabbit-oak) 01/01: OAK-12017 augment exceptions with path of affected node
This is an automated email from the ASF dual-hosted git repository.
joerghoh pushed a commit to branch OAK-12017
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit ccaca0093bbfae154bf765aef88c8e37403c1cfe
Author: Joerg Hoh <jh...@adobe.com>
AuthorDate: Tue Jul 21 15:32:10 2026 +0200
OAK-12017 augment exceptions with path of affected node
---
.../oak/plugins/version/VersionEditor.java | 21 +++-
.../oak/plugins/version/VersionEditorTest.java | 136 +++++++++++++++++++++
2 files changed, 153 insertions(+), 4 deletions(-)
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java
index f83fd50fdf..97657a5b8c 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java
@@ -303,16 +303,29 @@ class VersionEditor implements Editor {
return identifier;
}
- private static void throwCheckedIn(String msg)
+ private void throwCheckedIn(String msg)
throws CommitFailedException {
throw new CommitFailedException(CommitFailedException.VERSION,
- VersionExceptionCode.NODE_CHECKED_IN.ordinal(), msg);
+ VersionExceptionCode.NODE_CHECKED_IN.ordinal(),
+ msg + " at " + getPath());
}
- private static void throwProtected(String name)
+ /**
+ * @return the absolute path of the node this editor is processing,
+ * reconstructed from the parent chain.
+ */
+ private String getPath() {
+ if (parent == null) {
+ return "/";
+ }
+ String parentPath = parent.getPath();
+ return parentPath.equals("/") ? "/" + name : parentPath + "/" + name;
+ }
+
+ private void throwProtected(String name)
throws CommitFailedException {
throw new CommitFailedException(CommitFailedException.CONSTRAINT, 100,
- "Property is protected: " + name);
+ "Property is protected: " + name + " at " + getPath());
}
private boolean isIgnoreOnOPV() throws CommitFailedException {
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/version/VersionEditorTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/version/VersionEditorTest.java
new file mode 100644
index 0000000000..08c204c3f5
--- /dev/null
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/version/VersionEditorTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.plugins.version;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
+import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_ISCHECKEDOUT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class VersionEditorTest extends AbstractSecurityTest {
+
+ private Tree versionable;
+ private Tree child;
+
+ @Override
+ @Before
+ public void before() throws Exception {
+ super.before();
+
+ Tree a = TreeUtil.addChild(root.getTree("/"), "a", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+ Tree b = TreeUtil.addChild(a, "b", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+ b.setProperty("prop", "value");
+ // a non-versionable child that inherits the checked-in state from /a/b
+ Tree c = TreeUtil.addChild(b, "c", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+ c.setProperty("prop", "value");
+
+ TreeUtil.addMixin(b, JcrConstants.MIX_VERSIONABLE,
+ root.getTree(NodeTypeConstants.NODE_TYPES_PATH), null);
+ root.commit();
+
+ versionable = root.getTree("/a/b");
+ child = root.getTree("/a/b/c");
+
+ // check the versionable node in
+ versionable.setProperty(JCR_ISCHECKEDOUT, Boolean.FALSE, Type.BOOLEAN);
+ root.commit();
+ }
+
+ @Override
+ @After
+ public void after() throws Exception {
+ try {
+ root.refresh();
+ Tree a = root.getTree("/a");
+ if (a.exists()) {
+ a.remove();
+ root.commit();
+ }
+ } finally {
+ super.after();
+ }
+ }
+
+ @Test
+ public void changePropertyOnCheckedInNodeReportsPath() {
+ versionable.setProperty("prop", "changed");
+ assertCheckedInFailure("/a/b");
+ }
+
+ @Test
+ public void addPropertyOnCheckedInNodeReportsPath() {
+ versionable.setProperty("newProp", "value");
+ assertCheckedInFailure("/a/b");
+ }
+
+ @Test
+ public void deletePropertyOnCheckedInNodeReportsPath() {
+ versionable.removeProperty("prop");
+ assertCheckedInFailure("/a/b");
+ }
+
+ @Test
+ public void changePropertyOnCheckedInChildReportsChildPath() {
+ // the child inherits the read-only state; the reported path must be the
+ // child's path, which exercises getPath() across multiple parent levels
+ child.setProperty("prop", "changed");
+ assertCheckedInFailure("/a/b/c");
+ }
+
+ @Test
+ public void changeProtectedVersionPropertyReportsPath() {
+ // changing a protected version property (other than jcr:isCheckedOut /
+ // jcr:baseVersion) must fail; use the node's own uuid as the new
+ // reference value so referential integrity is not violated first
+ String uuid = versionable.getProperty(JcrConstants.JCR_UUID).getValue(Type.STRING);
+ versionable.setProperty(JcrConstants.JCR_VERSIONHISTORY, uuid, Type.REFERENCE);
+ try {
+ root.commit();
+ fail("Changing a protected version property must fail");
+ } catch (CommitFailedException e) {
+ assertEquals(CommitFailedException.CONSTRAINT, e.getType());
+ assertEquals(100, e.getCode());
+ assertTrue("Message should mention the protected property and the node path, but was: "
+ + e.getMessage(),
+ e.getMessage().endsWith("Property is protected: "
+ + JcrConstants.JCR_VERSIONHISTORY + " at /a/b"));
+ }
+ }
+
+ private void assertCheckedInFailure(String expectedPath) {
+ try {
+ root.commit();
+ fail("Modifying a checked-in node must fail");
+ } catch (CommitFailedException e) {
+ assertEquals(CommitFailedException.VERSION, e.getType());
+ assertEquals(VersionExceptionCode.NODE_CHECKED_IN.ordinal(), e.getCode());
+ assertTrue("Message should end with the node path, but was: " + e.getMessage(),
+ e.getMessage().endsWith(" at " + expectedPath));
+ }
+ }
+}