diff --git a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/FileCache.java b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/FileCache.java index 84f28d1317e..09149c66525 100644 --- a/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/FileCache.java +++ b/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/FileCache.java @@ -176,6 +176,9 @@ public static FileCache build(long maxSize /* bytes */, File root, return new FileCache(maxSize, root, loader, executor); } return new FileCache() { + + private final Cache cache = new CacheLIRS<>(0); + @Override public void put(String key, File file) { } @@ -195,7 +198,7 @@ public static FileCache build(long maxSize /* bytes */, File root, } @Override public DataStoreCacheStatsMBean getStats() { - return new FileCacheStats(this, weigher, memWeigher, 0); + return new FileCacheStats(cache, weigher, memWeigher, 0); } @Override public void close() { diff --git a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/FileCacheTest.java b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/FileCacheTest.java index a94eddc79cc..d23834d1bb8 100644 --- a/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/FileCacheTest.java +++ b/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/FileCacheTest.java @@ -108,6 +108,19 @@ public void zeroCache() throws Exception { assertNull(cache.getIfPresent(ID_PREFIX + 0)); assertNull(cache.get(ID_PREFIX + 0)); assertEquals(0, cache.getStats().getMaxTotalWeight()); + assertEquals(0, cache.getStats().getElementCount()); + assertEquals(0, cache.getStats().getEvictionCount()); + assertEquals(0, cache.getStats().getTotalLoadTime()); + assertEquals(0, cache.getStats().getLoadSuccessCount()); + assertEquals(0.0, cache.getStats().getMissRate(), 0.0); + assertEquals(0, cache.getStats().getMissCount()); + assertEquals(0, cache.getStats().getHitCount()); + assertEquals(1.0, cache.getStats().getHitRate(), 0.0); + assertEquals(0.0, cache.getStats().getAverageLoadPenalty(), 0.0); + assertEquals(0, cache.getStats().getLoadCount()); + assertEquals(0, cache.getStats().getLoadExceptionCount()); + assertEquals(0.0, cache.getStats().getLoadExceptionRate(), 0.0); + assertEquals(0, cache.getStats().getRequestCount()); cache.invalidate(ID_PREFIX + 0); assertFalse(cache.containsKey(ID_PREFIX + 0)); cache.close(); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java index 5f47a95997c..adc30fbcccd 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java @@ -16,11 +16,6 @@ */ package org.apache.jackrabbit.oak.security.authentication.token; -import java.security.Principal; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -29,6 +24,7 @@ import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider; import org.apache.jackrabbit.oak.spi.security.ConfigurationBase; import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; +import org.apache.jackrabbit.oak.spi.security.Context; import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration; import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.security.authentication.credentials.CompositeCredentialsSupport; @@ -48,6 +44,12 @@ import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import java.security.Principal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + import static org.apache.jackrabbit.oak.spi.security.RegistrationConstants.OAK_SECURITY_NAME; /** @@ -148,6 +150,11 @@ public List getValidators(@NotNull String workspace return ImmutableList.of(vp); } + @Override + public @NotNull Context getContext() { + return TokenContext.getInstance(); + } + //-------------------------------------------------< TokenConfiguration >--- /** * Returns a new instance of {@link org.apache.jackrabbit.oak.spi.security.authentication.token.TokenProvider}. diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContext.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContext.java new file mode 100644 index 00000000000..4c1f76c8691 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContext.java @@ -0,0 +1,74 @@ +/* + * 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.security.authentication.token; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.tree.TreeLocation; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.security.Context; +import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConstants; +import org.jetbrains.annotations.NotNull; + +import static org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConstants.TOKEN_NT_NAME; + +final class TokenContext implements Context { + + private static final Context INSTANCE = new TokenContext(); + + private TokenContext() {} + + static Context getInstance() { + return INSTANCE; + } + + @Override + public boolean definesProperty(@NotNull Tree parent, @NotNull PropertyState property) { + return isTokenNode(parent); + } + + @Override + public boolean definesContextRoot(@NotNull Tree tree) { + return TokenConstants.TOKENS_NODE_NAME.equals(tree.getName()); + } + + @Override + public boolean definesTree(@NotNull Tree tree) { + return definesContextRoot(tree) || isTokenNode(tree); + } + + @Override + public boolean definesLocation(@NotNull TreeLocation location) { + PropertyState ps = location.getProperty(); + TreeLocation l = (ps != null) ? location.getParent() : location; + Tree t = l.getTree(); + if (t == null) { + return false; + } else { + return (ps == null) ? definesTree(t) : definesProperty(t, ps); + } + } + + @Override + public boolean definesInternal(@NotNull Tree tree) { + return false; + } + + private static boolean isTokenNode(@NotNull Tree tree) { + return TOKEN_NT_NAME.equals(TreeUtil.getPrimaryTypeName(tree)); + } +} \ No newline at end of file diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProvider.java index 20fe4643623..73ef193aae2 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProvider.java @@ -38,7 +38,6 @@ import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Set; import java.util.function.Function; /** diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOr.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOr.java index e31819e6be7..ff69a4d51df 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOr.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOr.java @@ -117,7 +117,7 @@ public boolean isGranted(@NotNull Tree parent, @Nullable PropertyState property, for (AggregatedPermissionProvider aggregatedPermissionProvider : getPermissionProviders()) { long supportedPermissions = aggregatedPermissionProvider.supportedPermissions(immParent, property, permissions); if (Util.doEvaluate(supportedPermissions)) { - for (long p : Permissions.aggregates(permissions)) { + for (long p : Permissions.aggregates(supportedPermissions)) { if (aggregatedPermissionProvider.isGranted(immParent, property, p)) { coveredPermissions |= p; isGranted = true; @@ -144,7 +144,7 @@ public boolean isGranted(@NotNull TreeLocation location, long permissions) { for (AggregatedPermissionProvider aggregatedPermissionProvider : getPermissionProviders()) { long supportedPermissions = aggregatedPermissionProvider.supportedPermissions(location, permissions); if (Util.doEvaluate(supportedPermissions)) { - for (long p : Permissions.aggregates(permissions)) { + for (long p : Permissions.aggregates(supportedPermissions)) { if (aggregatedPermissionProvider.isGranted(location, p)) { coveredPermissions |= p; isGranted = true; @@ -171,7 +171,7 @@ public boolean isGranted(long repositoryPermissions) { long supportedPermissions = aggregatedPermissionProvider.supportedPermissions((Tree) null, null, repositoryPermissions); if (Util.doEvaluate(supportedPermissions)) { RepositoryPermission rp = aggregatedPermissionProvider.getRepositoryPermission(); - for (long p : Permissions.aggregates(repositoryPermissions)) { + for (long p : Permissions.aggregates(supportedPermissions)) { if (rp.isGranted(p)) { coveredPermissions |= p; isGranted = true; diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImplTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImplTest.java index 44d07b17c70..c08f5ab8862 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImplTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImplTest.java @@ -22,6 +22,7 @@ import org.apache.jackrabbit.oak.spi.commit.MoveTracker; import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider; import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; +import org.apache.jackrabbit.oak.spi.security.Context; import org.apache.jackrabbit.oak.spi.security.authentication.credentials.CredentialsSupport; import org.apache.jackrabbit.oak.spi.security.authentication.credentials.SimpleCredentialsSupport; import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration; @@ -30,7 +31,6 @@ import javax.jcr.Credentials; import javax.jcr.SimpleCredentials; -import java.security.Principal; import java.util.Collections; import java.util.List; import java.util.Map; @@ -160,6 +160,11 @@ public void testBindMultipleCredentialsSupport() { verify(cs, times(4)).getCredentialClasses(); verify(cs, times(2)).getAttributes(creds); - + } + + @Test + public void testGetContext() { + Context ctx = tc.getContext(); + assertTrue(ctx instanceof TokenContext); } } \ No newline at end of file diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContextTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContextTest.java new file mode 100644 index 00000000000..f963a84d16d --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenContextTest.java @@ -0,0 +1,184 @@ +/* + * 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.security.authentication.token; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; +import org.apache.jackrabbit.oak.plugins.tree.TreeLocation; +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; +import org.apache.jackrabbit.oak.spi.security.Context; +import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConstants; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConstants.TOKENS_NODE_NAME; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class TokenContextTest { + + private final Context ctx = TokenContext.getInstance(); + + @Test + public void testDefinesProperty() { + PropertyState ps = mock(PropertyState.class); + Tree t = when(mock(Tree.class).getName()).thenReturn(TOKENS_NODE_NAME).getMock(); + + assertFalse(ctx.definesProperty(t, ps)); + + when(t.getName()).thenReturn("anyName"); + PropertyState primaryType = PropertyStates.createProperty(JCR_PRIMARYTYPE, TokenConstants.TOKEN_NT_NAME, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(primaryType); + + assertTrue(ctx.definesProperty(t, ps)); + + PropertyState primaryType2 = PropertyStates.createProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(primaryType2); + + assertFalse(ctx.definesProperty(t, primaryType2)); + + verifyNoInteractions(ps); + verify(t, never()).getName(); + verify(t, times(3)).getProperty(JCR_PRIMARYTYPE); + verifyNoMoreInteractions(t); + } + + @Test + public void testDefinesContextRoot() { + Tree t = when(mock(Tree.class).getName()).thenReturn(TOKENS_NODE_NAME).getMock(); + assertTrue(ctx.definesContextRoot(t)); + + when(t.getName()).thenReturn("anyName", TokenConstants.TOKEN_ATTRIBUTE, TokenConstants.TOKEN_ATTRIBUTE_KEY); + assertFalse(ctx.definesContextRoot(t)); + assertFalse(ctx.definesContextRoot(t)); + assertFalse(ctx.definesContextRoot(t)); + + verify(t, times(4)).getName(); + verifyNoMoreInteractions(t); + } + + @Test + public void testDefinesTree() { + Tree t = when(mock(Tree.class).getName()).thenReturn(TOKENS_NODE_NAME).getMock(); + assertTrue(ctx.definesTree(t)); + + when(t.getName()).thenReturn("anyName"); + PropertyState ps = PropertyStates.createProperty(JCR_PRIMARYTYPE, TokenConstants.TOKEN_NT_NAME, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(ps); + + assertTrue(ctx.definesTree(t)); + + PropertyState ps2 = PropertyStates.createProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(ps2); + + assertFalse(ctx.definesTree(t)); + + verify(t, times(3)).getName(); + verify(t, times(2)).getProperty(anyString()); + + verifyNoMoreInteractions(t); + } + + @Test + public void testDefinesLocation() { + PropertyState ps = mock(PropertyState.class); + + TreeLocation tl = mock(TreeLocation.class); + when(tl.getName()).thenReturn(TOKENS_NODE_NAME); + + assertFalse(ctx.definesLocation(tl)); + verifyTL(tl, false); + + when(tl.getName()).thenReturn("any"); + + assertFalse(ctx.definesLocation(tl)); + verifyTL(tl, false); + + Tree t = when(mock(Tree.class).getName()).thenReturn(TOKENS_NODE_NAME).getMock(); + when(tl.getTree()).thenReturn(t); + + assertTrue(ctx.definesLocation(tl)); + verifyTL(tl, false); + verify(t).getName(); + verifyNoMoreInteractions(t); + reset(t); + + when(t.getName()).thenReturn("any").getMock(); + PropertyState primary = PropertyStates.createProperty(JCR_PRIMARYTYPE, TokenConstants.TOKEN_NT_NAME, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(primary); + when(tl.getTree()).thenReturn(t); + + assertTrue(ctx.definesLocation(tl)); + verifyTL(tl, false); + verify(t).getName(); + verify(t).getProperty(JCR_PRIMARYTYPE); + verifyNoMoreInteractions(t); + + when(tl.getProperty()).thenReturn(ps); + when(tl.getTree()).thenReturn(t); + when(tl.getParent()).thenReturn(tl); + + assertTrue(ctx.definesLocation(tl)); + verifyTL(tl, true); + + PropertyState primary2 = PropertyStates.createProperty(JCR_PRIMARYTYPE, NodeTypeConstants.NT_OAK_UNSTRUCTURED, Type.NAME); + when(t.getProperty(JCR_PRIMARYTYPE)).thenReturn(primary2); + + when(tl.getProperty()).thenReturn(ps); + when(tl.getTree()).thenReturn(t); + when(tl.getParent()).thenReturn(tl); + + assertFalse(ctx.definesLocation(tl)); + verifyTL(tl, true); + + verifyNoInteractions(ps); + } + + private static void verifyTL(@NotNull TreeLocation tl, boolean isProperty) { + verify(tl).getProperty(); + verify(tl).getTree(); + if (isProperty) { + verify(tl).getParent(); + } + verifyNoMoreInteractions(tl); + reset(tl); + } + + @Test + public void testDefinesInternal() { + Tree t = when(mock(Tree.class).getName()).thenReturn(TOKENS_NODE_NAME).getMock(); + assertFalse(ctx.definesInternal(t)); + + when(t.getName()).thenReturn("anyName"); + assertFalse(ctx.definesInternal(t)); + + verifyNoInteractions(t); + } +} \ No newline at end of file diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/AbstractCompositeProviderTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/AbstractCompositeProviderTest.java index 980100b2f5e..10def7d131d 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/AbstractCompositeProviderTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/AbstractCompositeProviderTest.java @@ -101,16 +101,7 @@ public abstract class AbstractCompositeProviderTest extends AbstractSecurityTest public void before() throws Exception { super.before(); - Tree rootNode = root.getTree("/"); - - Tree test = TreeUtil.addChild(rootNode, "test", NT_OAK_UNSTRUCTURED); - TreeUtil.addChild(test, "child", NT_OAK_UNSTRUCTURED); - Tree a = TreeUtil.addChild(test, "a", NT_OAK_UNSTRUCTURED); - TreeUtil.addChild(a, "b2", NT_OAK_UNSTRUCTURED); - Tree b = TreeUtil.addChild(a, "b", NT_OAK_UNSTRUCTURED); - TreeUtil.addChild(b, "c", NT_OAK_UNSTRUCTURED); - - TreeUtil.addChild(rootNode, "test2", NT_OAK_UNSTRUCTURED); + setupTestContent(root); AccessControlManager acMgr = getAccessControlManager(root); Principal everyone = EveryonePrincipal.getInstance(); @@ -156,8 +147,8 @@ public void before() throws Exception { Permissions.VERSION_MANAGEMENT). build(); defPrivileges = ImmutableMap.>builder(). - put(ROOT_PATH, ImmutableSet.of()). - put(TEST_PATH_2, ImmutableSet.of()). + put(ROOT_PATH, ImmutableSet.of()). + put(TEST_PATH_2, ImmutableSet.of()). put(TEST_PATH, ImmutableSet.of(JCR_READ)). put(TEST_CHILD_PATH, ImmutableSet.of(JCR_READ, JCR_READ_ACCESS_CONTROL)). put(TEST_A_PATH, ImmutableSet.of(JCR_READ, JCR_WRITE, JCR_VERSION_MANAGEMENT)). @@ -193,6 +184,20 @@ public void after() throws Exception { super.after(); } } + + static void setupTestContent(@NotNull Root root) throws Exception { + Tree rootNode = root.getTree("/"); + + Tree test = TreeUtil.addChild(rootNode, "test", NT_OAK_UNSTRUCTURED); + TreeUtil.addChild(test, "child", NT_OAK_UNSTRUCTURED); + Tree a = TreeUtil.addChild(test, "a", NT_OAK_UNSTRUCTURED); + TreeUtil.addChild(a, "b2", NT_OAK_UNSTRUCTURED); + Tree b = TreeUtil.addChild(a, "b", NT_OAK_UNSTRUCTURED); + TreeUtil.addChild(b, "c", NT_OAK_UNSTRUCTURED); + + TreeUtil.addChild(rootNode, "test2", NT_OAK_UNSTRUCTURED); + root.commit(); + } private static void allow(@NotNull AccessControlManager acMgr, @NotNull Principal principal, @@ -267,13 +272,13 @@ CompositePermissionProvider createPermissionProviderOR(Set principals } @Test - public void testRefresh() throws Exception { + public void testRefresh() { createPermissionProvider().refresh(); createPermissionProviderOR().refresh(); } @Test - public void testHasPrivilegesJcrAll() throws Exception { + public void testHasPrivilegesJcrAll() { PermissionProvider pp = createPermissionProvider(); for (String p : NODE_PATHS) { Tree tree = readOnlyRoot.getTree(p); @@ -291,7 +296,7 @@ public void testHasPrivilegesJcrAllOR() throws Exception { } @Test - public void testHasPrivilegesNone() throws Exception { + public void testHasPrivilegesNone() { PermissionProvider pp = createPermissionProvider(); PermissionProvider ppo = createPermissionProviderOR(); for (String p : NODE_PATHS) { @@ -310,7 +315,7 @@ public void testHasPrivilegesOnRepoJcrAll() throws Exception { } @Test - public void testHasPrivilegesOnRepoNone() throws Exception { + public void testHasPrivilegesOnRepoNone() { PermissionProvider pp = createPermissionProvider(); assertTrue(pp.hasPrivileges(null)); PermissionProvider ppo = createPermissionProviderOR(); @@ -334,7 +339,7 @@ public void testIsGrantedAll() throws Exception { } @Test - public void testIsGrantedNone() throws Exception { + public void testIsGrantedNone() { PermissionProvider pp = createPermissionProvider(); PermissionProvider ppo = createPermissionProviderOR(); @@ -366,7 +371,7 @@ public void testIsNotGranted() throws Exception { } @Test - public void testIsGrantedActionNone() throws Exception { + public void testIsGrantedActionNone() { PermissionProvider pp = createPermissionProvider(); PermissionProvider ppo = createPermissionProviderOR(); String actions = ""; @@ -407,7 +412,7 @@ public void testIsNotGrantedAction() throws Exception { } @Test - public void testGetTreePermissionAllParent() throws Exception { + public void testGetTreePermissionAllParent() { TreePermission tp = createPermissionProvider().getTreePermission(readOnlyRoot.getTree(TEST_PATH), TreePermission.ALL); assertSame(TreePermission.ALL, tp); TreePermission tpo = createPermissionProviderOR().getTreePermission(readOnlyRoot.getTree(TEST_PATH), TreePermission.ALL); @@ -415,7 +420,7 @@ public void testGetTreePermissionAllParent() throws Exception { } @Test - public void testGetTreePermissionEmptyParent() throws Exception { + public void testGetTreePermissionEmptyParent() { TreePermission tp = createPermissionProvider().getTreePermission(readOnlyRoot.getTree(TEST_PATH), TreePermission.EMPTY); assertSame(TreePermission.EMPTY, tp); TreePermission tpo = createPermissionProviderOR().getTreePermission(readOnlyRoot.getTree(TEST_PATH), TreePermission.EMPTY); @@ -423,7 +428,7 @@ public void testGetTreePermissionEmptyParent() throws Exception { } @Test - public void testTreePermissionIsGrantedAll() throws Exception { + public void testTreePermissionIsGrantedAll() { PermissionProvider pp = createPermissionProvider(); TreePermission parentPermission = TreePermission.EMPTY; @@ -459,7 +464,7 @@ public void testTreePermissionIsGrantedAllOR() throws Exception { } @Test - public void testTreePermissionIsNotGranted() throws Exception { + public void testTreePermissionIsNotGranted() { PermissionProvider pp = createPermissionProvider(); TreePermission parentPermission = TreePermission.EMPTY; @@ -499,7 +504,7 @@ public void testTreePermissionIsNotGrantedOR() throws Exception { } @Test - public void testTreePermissionCanReadAll() throws Exception { + public void testTreePermissionCanReadAll() { PermissionProvider pp = createPermissionProvider(); TreePermission parentPermission = TreePermission.EMPTY; PermissionProvider ppO = createPermissionProviderOR(); @@ -517,7 +522,7 @@ public void testTreePermissionCanReadAll() throws Exception { } @Test - public void testTreePermissionCanReadProperties() throws Exception { + public void testTreePermissionCanReadProperties() { PermissionProvider pp = createPermissionProvider(); TreePermission parentPermission = TreePermission.EMPTY; diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOrTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOrTest.java new file mode 100644 index 00000000000..e8350006a69 --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositePermissionProviderOrTest.java @@ -0,0 +1,266 @@ +/* + * 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.security.authorization.composite; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.apache.jackrabbit.oak.AbstractSecurityTest; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.tree.TreeLocation; +import org.apache.jackrabbit.oak.plugins.tree.TreeType; +import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.AggregatedPermissionProvider; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.RepositoryPermission; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission; +import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; +import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits; +import org.apache.jackrabbit.util.Text; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; +import static org.apache.jackrabbit.oak.security.authorization.composite.AbstractCompositeProviderTest.TEST_A_B_C_PATH; +import static org.apache.jackrabbit.oak.security.authorization.composite.AbstractCompositeProviderTest.TEST_A_PATH; +import static org.apache.jackrabbit.oak.security.authorization.composite.AbstractCompositeProviderTest.TEST_CHILD_PATH; +import static org.apache.jackrabbit.oak.security.authorization.composite.AbstractCompositeProviderTest.TEST_PATH; +import static org.apache.jackrabbit.oak.security.authorization.composite.AbstractCompositeProviderTest.TEST_PATH_2; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.ADD_NODE; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.ALL; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.MODIFY_ACCESS_CONTROL; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.NODE_TYPE_DEFINITION_MANAGEMENT; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.NO_PERMISSION; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.READ; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.READ_NODE; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.SET_PROPERTY; +import static org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions.WORKSPACE_MANAGEMENT; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for OAK-9798 + * + * @see CompositeTreePermissionOrTest#testIsGrantedUncoveredPermissions() for a similar test covering the behavior of {@code CompositeTreePermissionOr}. + */ +@RunWith(Parameterized.class) +public class CompositePermissionProviderOrTest extends AbstractSecurityTest { + + private final boolean extraVerification; + + private CompositePermissionProvider pp; + private Root readOnlyRoot; + + @Parameterized.Parameters(name = "name={1}") + public static Collection parameters() { + return Lists.newArrayList( + new Object[] { true, "Extra verification of supported permissions in 'isGranted'" }, + new Object[] { false, "No verification of supported permissions in 'isGranted'" }); + } + public CompositePermissionProviderOrTest(boolean extraVerification, @NotNull String name) { + this.extraVerification = extraVerification; + } + + @Before() + public void before() throws Exception { + super.before(); + AbstractCompositeProviderTest.setupTestContent(root); + this.pp = createPermissionProviderOR(Collections.singleton(EveryonePrincipal.getInstance())); + + readOnlyRoot = getRootProvider().createReadOnlyRoot(root); + } + + private CompositePermissionProvider createPermissionProviderOR(Set principals) { + String workspaceName = root.getContentSession().getWorkspaceName(); + AuthorizationConfiguration config = getConfig(AuthorizationConfiguration.class); + + ImmutableList l = ImmutableList.of( + (AggregatedPermissionProvider) config.getPermissionProvider(root, workspaceName, principals), + new ReadNodeGrantedInSupportedTree()); + + return new CompositePermissionProviderOr(root, l, config.getContext(), getRootProvider(), getTreeProvider()); + } + + private Tree getTree(@NotNull String path) { + return readOnlyRoot.getTree(path); + } + + private TreeLocation getTreeLocation(@NotNull String path) { + return TreeLocation.create(readOnlyRoot, path); + } + + @Test + public void testIsGrantedTreeUnsupportedPath() { + for (long permissions : new long[]{READ_NODE, READ, SET_PROPERTY, ADD_NODE, MODIFY_ACCESS_CONTROL, ALL}) { + assertFalse(pp.isGranted(getTree(TEST_PATH), null, permissions)); + assertFalse(pp.isGranted(getTree(ROOT_PATH), null, permissions)); + assertFalse(pp.isGranted(getTree(TEST_CHILD_PATH), null, permissions)); + assertFalse(pp.isGranted(getTree(TEST_PATH_2), null, permissions)); + } + } + + @Test + public void testIsGrantedTreeSupportedPath() { + assertTrue(pp.isGranted(getTree(TEST_A_PATH), null, READ_NODE)); + assertTrue(pp.isGranted(getTree(TEST_A_B_C_PATH), null, READ_NODE)); + + for (long permissions : new long[]{READ, SET_PROPERTY, ADD_NODE, MODIFY_ACCESS_CONTROL, ALL}) { + assertFalse(pp.isGranted(getTree(TEST_A_PATH), null, permissions)); + assertFalse(pp.isGranted(getTree(TEST_A_B_C_PATH), null, permissions)); + } + } + + @Test + public void testIsGrantedTreeLocationUnsupportedPath() { + for (long permissions : new long[]{READ_NODE, READ, SET_PROPERTY, ADD_NODE, MODIFY_ACCESS_CONTROL, ALL}) { + assertFalse(pp.isGranted(getTreeLocation(TEST_PATH), permissions)); + assertFalse(pp.isGranted(getTreeLocation(ROOT_PATH), permissions)); + assertFalse(pp.isGranted(getTreeLocation(TEST_CHILD_PATH), permissions)); + assertFalse(pp.isGranted(getTreeLocation(TEST_PATH_2), permissions)); + } + } + + @Test + public void testIsGrantedTreeLocationSupportedPath() { + assertTrue(pp.isGranted(getTreeLocation(TEST_A_PATH), READ_NODE)); + assertTrue(pp.isGranted(getTreeLocation(TEST_A_B_C_PATH), READ_NODE)); + + for (long permissions : new long[]{READ, SET_PROPERTY, ADD_NODE, MODIFY_ACCESS_CONTROL, ALL}) { + assertFalse(pp.isGranted(getTreeLocation(TEST_A_PATH), permissions)); + assertFalse(pp.isGranted(getTreeLocation(TEST_A_B_C_PATH), permissions)); + } + } + + @Test + public void testRepositoryPermissions() { + RepositoryPermission rp = pp.getRepositoryPermission(); + + assertTrue(rp.isGranted(WORKSPACE_MANAGEMENT)); + + assertFalse(rp.isGranted(ALL)); + assertFalse(rp.isGranted(WORKSPACE_MANAGEMENT|NODE_TYPE_DEFINITION_MANAGEMENT)); + } + + private final class ReadNodeGrantedInSupportedTree implements AggregatedPermissionProvider { + + private boolean isSupportedPath(@NotNull String path) { + return Text.isDescendantOrEqual(TEST_A_PATH, path); + } + + private boolean includesSupportedPermission(long permissions) { + return Permissions.includes(permissions, READ_NODE); + } + + private boolean isGranted(@NotNull String path, long permissions) { + if (extraVerification) { + return isSupportedPath(path) && permissions == READ_NODE; + } else { + return isSupportedPath(path); + } + } + + @Override + public @NotNull PrivilegeBits supportedPrivileges(@Nullable Tree tree, @Nullable PrivilegeBits privilegeBits) { + throw new UnsupportedOperationException(); + } + + @Override + public long supportedPermissions(@Nullable Tree tree, @Nullable PropertyState property, long permissions) { + if (tree == null) { + return (Permissions.includes(permissions, WORKSPACE_MANAGEMENT)) ? WORKSPACE_MANAGEMENT : NO_PERMISSION; + } + if (isSupportedPath(tree.getPath()) && includesSupportedPermission(permissions)) { + return READ_NODE; + } + return NO_PERMISSION; + } + + @Override + public long supportedPermissions(@NotNull TreeLocation location, long permissions) { + if (isSupportedPath(location.getPath()) && includesSupportedPermission(permissions)) { + return READ_NODE; + } + return NO_PERMISSION; + } + + @Override + public long supportedPermissions(@NotNull TreePermission treePermission, @Nullable PropertyState property, long permissions) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isGranted(@NotNull TreeLocation location, long permissions) { + return isGranted(location.getPath(), permissions); + } + + @Override + public @NotNull TreePermission getTreePermission(@NotNull Tree tree, @NotNull TreeType type, @NotNull TreePermission parentPermission) { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Set getPrivileges(@Nullable Tree tree) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasPrivileges(@Nullable Tree tree, @NotNull String... privilegeNames) { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull RepositoryPermission getRepositoryPermission() { + return repositoryPermissions -> { + if (extraVerification) { + return repositoryPermissions == WORKSPACE_MANAGEMENT; + } else { + return true; + } + }; + } + + @Override + public @NotNull TreePermission getTreePermission(@NotNull Tree tree, @NotNull TreePermission parentPermission) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isGranted(@NotNull Tree tree, @Nullable PropertyState property, long permissions) { + return isGranted(tree.getPath(), permissions); + } + + @Override + public boolean isGranted(@NotNull String oakPath, @NotNull String jcrActions) { + return isGranted(oakPath, Permissions.getPermissions(jcrActions, TreeLocation.create(readOnlyRoot, oakPath), false)); + } + } +} \ No newline at end of file diff --git a/oak-doc/src/site/site.xml b/oak-doc/src/site/site.xml index 5c10e842776..3fd28aff167 100644 --- a/oak-doc/src/site/site.xml +++ b/oak-doc/src/site/site.xml @@ -19,11 +19,16 @@ specific language governing permissions and limitations under the License. --> - + + + + + + @@ -159,37 +164,17 @@ under the License. - - - - - ]]> - + ]]> - + https://github.com/apache/jackrabbit-oak/edit/trunk/oak-doc org.apache.maven.skins maven-fluido-skin - 1.6 + 1.11.0 @@ -207,11 +192,16 @@ under the License. apache/jackrabbit-oak right - - jackrabbit-oak - thin-badge - - + + + 4 + https://analytics.apache.org + + + + + + diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java index 52d96d9c0bc..72b783cfbf4 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java @@ -16,10 +16,10 @@ */ package org.apache.jackrabbit.oak.jcr.session; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Sets.newTreeSet; import static org.apache.jackrabbit.api.stats.RepositoryStatistics.Type.SESSION_COUNT; import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; +import static java.util.Objects.requireNonNull; import java.io.IOException; import java.io.InputStream; @@ -147,8 +147,7 @@ private void checkAlive() throws RepositoryException { } @NotNull - private String getOakPathOrThrow(@NotNull String absPath) - throws RepositoryException { + private String getOakPathOrThrow(@NotNull String absPath) throws RepositoryException { String p = sessionContext.getOakPathOrThrow(absPath); if (!PathUtils.isAbsolute(p)) { throw new RepositoryException("Not an absolute path: " + absPath); @@ -157,14 +156,12 @@ private String getOakPathOrThrow(@NotNull String absPath) } @NotNull - private String getOakPathOrThrowNotFound(@NotNull String absPath) - throws PathNotFoundException { + private String getOakPathOrThrowNotFound(@NotNull String absPath) throws PathNotFoundException { return sessionContext.getOakPathOrThrowNotFound(absPath); } @Nullable - private ItemImpl getItemInternal(@NotNull String oakPath) - throws RepositoryException { + private ItemImpl getItemInternal(@NotNull String oakPath) throws RepositoryException { checkAlive(); ItemDelegate item = sd.getItem(oakPath); if (item instanceof NodeDelegate) { @@ -179,7 +176,7 @@ private ItemImpl getItemInternal(@NotNull String oakPath) @Override @Nullable public Node getNodeOrNull(final String absPath) throws RepositoryException { - checkNotNull(absPath); + requireNonNull(absPath, "parameter 'absPath' must not be null"); checkAlive(); return sd.performNullable(new ReadOperation("getNodeOrNull") { @Override @@ -197,7 +194,7 @@ public Node performNullable() throws RepositoryException { @Nullable public Property getPropertyOrNull(final String absPath) throws RepositoryException { checkAlive(); - if (checkNotNull(absPath).equals("/")) { + if (requireNonNull(absPath, "parameter 'absPath' must not be null").equals("/")) { return null; } else { final String oakPath; @@ -223,7 +220,7 @@ public Property performNullable() { @Override @Nullable public Item getItemOrNull(final String absPath) throws RepositoryException { - checkNotNull(absPath); + requireNonNull(absPath, "parameter 'absPath' must not be null"); checkAlive(); return sd.performNullable(new ReadOperation("getItemOrNull") { @Override @@ -246,23 +243,25 @@ public Node performNullable() throws RepositoryException { } }); } - - private static void checkContext(@NotNull ItemImpl item, @NotNull SessionContext context) throws RepositoryException { + + private static void checkContext(@NotNull ItemImpl item, @NotNull SessionContext context) + throws RepositoryException { if (item.sessionContext != context) { throw new RepositoryException("Item '" + item + "' has been obtained through a different Session."); } } - + @NotNull private static ItemImpl checkItemImpl(@NotNull Item item) throws RepositoryException { if (item instanceof ItemImpl) { return (ItemImpl) item; } else { - throw new RepositoryException("Invalid item implementation '" + item.getClass().getName() + "'. Expected " + ItemImpl.class.getName()); + throw new RepositoryException("Invalid item implementation '" + item.getClass().getName() + "'. Expected " + + ItemImpl.class.getName()); } } - //------------------------------------------------------------< Session >--- + // ------------------------------------------------------------< Session >--- @Override @NotNull @@ -295,7 +294,8 @@ public Object getAttribute(String name) { return attribute; } - @Override @NotNull + @Override + @NotNull public Workspace getWorkspace() { return sessionContext.getWorkspace(); } @@ -306,7 +306,7 @@ public Session impersonate(Credentials credentials) throws RepositoryException { checkAlive(); ImpersonationCredentials impCreds = new ImpersonationCredentials( - checkNotNull(credentials), sd.getAuthInfo()); + requireNonNull(credentials, "parameter 'credentials' must not be null"), sd.getAuthInfo()); return getRepository().login(impCreds, sd.getWorkspaceName()); } @@ -336,7 +336,7 @@ public Node perform() throws RepositoryException { @Override public Node getNode(String absPath) throws RepositoryException { - Node node = getNodeOrNull(checkNotNull(absPath)); + Node node = getNodeOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")); if (node == null) { throw new PathNotFoundException("Node with path " + absPath + " does not exist."); } @@ -345,7 +345,7 @@ public Node getNode(String absPath) throws RepositoryException { @Override public boolean nodeExists(String absPath) throws RepositoryException { - return getNodeOrNull(checkNotNull(absPath)) != null; + return getNodeOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")) != null; } @NotNull @@ -371,18 +371,18 @@ public Node perform() throws RepositoryException { @Override @NotNull public Node getNodeByUUID(String uuid) throws RepositoryException { - return getNodeById(checkNotNull(uuid)); + return getNodeById(requireNonNull(uuid, "parameter 'uuid' must not be null")); } @Override @NotNull public Node getNodeByIdentifier(String id) throws RepositoryException { - return getNodeById(checkNotNull(id)); + return getNodeById(requireNonNull(id, "parameter 'id' must not be null")); } @Override public Property getProperty(String absPath) throws RepositoryException { - Property property = getPropertyOrNull(checkNotNull(absPath)); + Property property = getPropertyOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")); if (property == null) { throw new PathNotFoundException(absPath); } @@ -391,12 +391,12 @@ public Property getProperty(String absPath) throws RepositoryException { @Override public boolean propertyExists(String absPath) throws RepositoryException { - return getPropertyOrNull(checkNotNull(absPath)) != null; + return getPropertyOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")) != null; } @Override public Item getItem(String absPath) throws RepositoryException { - Item item = getItemOrNull(checkNotNull(absPath)); + Item item = getItemOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")); if (item == null) { throw new PathNotFoundException(absPath); } @@ -405,14 +405,14 @@ public Item getItem(String absPath) throws RepositoryException { @Override public boolean itemExists(String absPath) throws RepositoryException { - return getItemOrNull(checkNotNull(absPath)) != null; + return getItemOrNull(requireNonNull(absPath, "parameter 'absPath' must not be null")) != null; } @Override public void move(String srcAbsPath, final String destAbsPath) throws RepositoryException { checkAlive(); - checkIndexOnName(checkNotNull(destAbsPath)); - final String srcOakPath = getOakPathOrThrowNotFound(checkNotNull(srcAbsPath)); + checkIndexOnName(requireNonNull(destAbsPath, "parameter 'destAbsPath' must not be null")); + final String srcOakPath = getOakPathOrThrowNotFound(requireNonNull(srcAbsPath, "parameter 'srcAbsPath' must not be null")); final String destOakPath = getOakPathOrThrowNotFound(destAbsPath); sd.performVoid(new WriteOperation("move") { @Override @@ -432,7 +432,7 @@ public void performVoid() throws RepositoryException { @Override public void removeItem(final String absPath) throws RepositoryException { checkAlive(); - final String oakPath = getOakPathOrThrowNotFound(checkNotNull(absPath)); + final String oakPath = getOakPathOrThrowNotFound(requireNonNull(absPath, "parameter 'absPath' must not be null")); sd.performVoid(new WriteOperation("removeItem") { @Override public void performVoid() throws RepositoryException { @@ -440,11 +440,9 @@ public void performVoid() throws RepositoryException { if (item == null) { throw new PathNotFoundException(absPath); } else if (item.isProtected()) { - throw new ConstraintViolationException( - item.getPath() + " is protected"); + throw new ConstraintViolationException(item.getPath() + " is protected"); } else if (!item.remove()) { - throw new RepositoryException( - item.getPath() + " could not be removed"); + throw new RepositoryException(item.getPath() + " could not be removed"); } } }); @@ -523,9 +521,8 @@ public boolean isLogout() { @Override @NotNull - public ContentHandler getImportContentHandler(String parentAbsPath, - int uuidBehavior) throws RepositoryException { - return new ImportHandler(checkNotNull(parentAbsPath), sessionContext, + public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws RepositoryException { + return new ImportHandler(requireNonNull(parentAbsPath, "parameter 'parentAbsPath' must not be null"), sessionContext, uuidBehavior, false); } @@ -534,7 +531,7 @@ public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, RepositoryException { try { ContentHandler handler = getImportContentHandler( - checkNotNull(parentAbsPath), uuidBehavior); + requireNonNull(parentAbsPath, "parameter 'parentAbsPath' must not be null"), uuidBehavior); new ParsingContentHandler(handler).parse(in); } catch (SAXException e) { Throwable exception = e.getException(); @@ -564,8 +561,7 @@ public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) * @throws SAXException if the SAX event handler failed * @throws RepositoryException if another error occurs */ - private synchronized void export(String path, Exporter exporter) - throws SAXException, RepositoryException { + private synchronized void export(String path, Exporter exporter) throws SAXException, RepositoryException { Item item = getItem(path); if (item.isNode()) { exporter.export((Node) item); @@ -575,21 +571,19 @@ private synchronized void export(String path, Exporter exporter) } @Override - public void exportSystemView(String absPath, ContentHandler contentHandler, - boolean skipBinary, boolean noRecurse) throws SAXException, - RepositoryException { - export(checkNotNull(absPath), new SystemViewExporter(this, - checkNotNull(contentHandler), !noRecurse, !skipBinary)); + public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) + throws SAXException, RepositoryException { + export(requireNonNull(absPath, "parameter 'absPath' must not be null"), new SystemViewExporter(this, + requireNonNull(contentHandler, "parameter 'contentHandler' must not be null"), !noRecurse, !skipBinary)); } @Override - public void exportSystemView(String absPath, OutputStream out, - boolean skipBinary, boolean noRecurse) throws IOException, - RepositoryException { + public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) + throws IOException, RepositoryException { try { - ContentHandler handler = new ToXmlContentHandler(checkNotNull(out)); - export(checkNotNull(absPath), new SystemViewExporter(this, handler, - !noRecurse, !skipBinary)); + ContentHandler handler = new ToXmlContentHandler(requireNonNull(out, "parameter 'out' must not be null")); + export(requireNonNull(absPath, "parameter 'absPath' must not be null"), + new SystemViewExporter(this, handler, !noRecurse, !skipBinary)); } catch (SAXException e) { Exception exception = e.getException(); if (exception instanceof RepositoryException) { @@ -597,28 +591,25 @@ public void exportSystemView(String absPath, OutputStream out, } else if (exception instanceof IOException) { throw (IOException) exception; } else { - throw new RepositoryException( - "Error serializing system view XML", e); + throw new RepositoryException("Error serializing system view XML", e); } } } @Override - public void exportDocumentView(String absPath, - ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) + public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws SAXException, RepositoryException { - export(checkNotNull(absPath), new DocumentViewExporter(this, - checkNotNull(contentHandler), !noRecurse, !skipBinary)); + export(requireNonNull(absPath, "parameter 'absPath' must not be null"), new DocumentViewExporter(this, + requireNonNull(contentHandler, "parameter 'contentHandler' must not be null"), !noRecurse, !skipBinary)); } @Override - public void exportDocumentView(String absPath, OutputStream out, - boolean skipBinary, boolean noRecurse) throws IOException, - RepositoryException { + public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) + throws IOException, RepositoryException { try { - ContentHandler handler = new ToXmlContentHandler(checkNotNull(out)); - export(checkNotNull(absPath), new DocumentViewExporter(this, - handler, !noRecurse, !skipBinary)); + ContentHandler handler = new ToXmlContentHandler(requireNonNull(out, "parameter 'out' must not be null")); + export(requireNonNull(absPath, "parameter 'absPath' must not be null"), + new DocumentViewExporter(this, handler, !noRecurse, !skipBinary)); } catch (SAXException e) { Exception exception = e.getException(); if (exception instanceof RepositoryException) { @@ -626,8 +617,7 @@ public void exportDocumentView(String absPath, OutputStream out, } else if (exception instanceof IOException) { throw (IOException) exception; } else { - throw new RepositoryException( - "Error serializing document view XML", e); + throw new RepositoryException("Error serializing document view XML", e); } } } @@ -635,13 +625,14 @@ public void exportDocumentView(String absPath, OutputStream out, @Override public void addLockToken(String lt) { try { - getWorkspace().getLockManager().addLockToken(checkNotNull(lt)); + getWorkspace().getLockManager().addLockToken(requireNonNull(lt, "parameter 'lt' must not be null")); } catch (RepositoryException e) { log.warn("Unable to add lock token " + lt + " to session", e); } } - @Override @NotNull + @Override + @NotNull public String[] getLockTokens() { try { return getWorkspace().getLockManager().getLockTokens(); @@ -654,7 +645,7 @@ public String[] getLockTokens() { @Override public void removeLockToken(String lt) { try { - getWorkspace().getLockManager().removeLockToken(checkNotNull(lt)); + getWorkspace().getLockManager().removeLockToken(requireNonNull(lt, "parameter 'lt' must not be null")); } catch (RepositoryException e) { log.warn("Unable to remove lock token " + lt + " from session", e); } @@ -663,8 +654,8 @@ public void removeLockToken(String lt) { @Override public boolean hasPermission(String absPath, final String actions) throws RepositoryException { checkAlive(); - final String oakPath = getOakPathOrThrow(checkNotNull(absPath)); - checkNotNull(actions); + final String oakPath = getOakPathOrThrow(requireNonNull(absPath, "parameter 'absPath' must not be null")); + requireNonNull(actions, "parameter 'actions' must not be null"); return sd.perform(new ReadOperation("hasPermission") { @NotNull @Override @@ -676,15 +667,16 @@ public Boolean perform() throws RepositoryException { @Override public void checkPermission(String absPath, String actions) throws RepositoryException { - if (!hasPermission(checkNotNull(absPath), checkNotNull(actions))) { + if (!hasPermission(requireNonNull(absPath, "parameter 'absPath' must not be null"), + requireNonNull(actions, "parameter 'actions' must not be null"))) { throw new AccessControlException("Access denied."); } } @Override public boolean hasCapability(String methodName, Object target, Object[] arguments) throws RepositoryException { - checkNotNull(methodName); - checkNotNull(target); + requireNonNull(methodName, "parameter 'methodName' must not be null"); + requireNonNull(target, "parameter 'target' must not be null"); checkAlive(); if (target instanceof ItemImpl) { @@ -698,7 +690,8 @@ public boolean hasCapability(String methodName, Object target, Object[] argument if (!parent.isCheckedOut()) { return false; } - boolean hasLocking = sessionContext.getRepository().getDescriptorValue(Repository.OPTION_LOCKING_SUPPORTED).getBoolean(); + boolean hasLocking = sessionContext.getRepository().getDescriptorValue(Repository.OPTION_LOCKING_SUPPORTED) + .getBoolean(); if (hasLocking && parent.isLocked()) { return false; } @@ -711,10 +704,12 @@ public boolean hasCapability(String methodName, Object target, Object[] argument if (arguments != null && arguments.length > 0) { // add-node needs to be checked on the (path of) the // new node that has/will be added - String path = PathUtils.concat(tree.getPath(), sessionContext.getOakName(arguments[0].toString())); - return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE) && !isMountedReadOnly(path); + String path = PathUtils.concat(tree.getPath(), + sessionContext.getOakName(arguments[0].toString())); + return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE) && !isMountedReadOnly(path); } - } else if ("setPrimaryType".equals(methodName) || "addMixin".equals(methodName) || "removeMixin".equals(methodName)) { + } else if ("setPrimaryType".equals(methodName) || "addMixin".equals(methodName) + || "removeMixin".equals(methodName)) { permission = Permissions.NODE_TYPE_MANAGEMENT; } else if ("orderBefore".equals(methodName)) { if (tree.isRoot()) { @@ -737,10 +732,13 @@ public boolean hasCapability(String methodName, Object target, Object[] argument } NodeDelegate parentDelegate = dlg.getParent(); if (parentDelegate != null) { - return accessMgr.hasPermissions(parentDelegate.getTree(), ((PropertyDelegate) dlg).getPropertyState(), permission) + return accessMgr.hasPermissions(parentDelegate.getTree(), + ((PropertyDelegate) dlg).getPropertyState(), permission) && !isMountedReadOnly(parentDelegate.getPath()); } else { - return accessMgr.hasPermissions(dlg.getPath(), (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY : Session.ACTION_REMOVE) + return accessMgr.hasPermissions(dlg.getPath(), + (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY + : Session.ACTION_REMOVE) && !isMountedReadOnly(dlg.getPath()); } } @@ -769,13 +767,12 @@ public RetentionManager getRetentionManager() throws RepositoryException { throw new UnsupportedRepositoryOperationException("Retention Management is not supported."); } - //---------------------------------------------------------< Namespaces >--- + // ---------------------------------------------------------< Namespaces >--- @Override - public void setNamespacePrefix(String prefix, String uri) - throws RepositoryException { - sessionContext.getNamespaces().setNamespacePrefix(checkNotNull(prefix), - checkNotNull(uri)); + public void setNamespacePrefix(String prefix, String uri) throws RepositoryException { + sessionContext.getNamespaces().setNamespacePrefix(requireNonNull(prefix, "parameter 'prefix' must not be null"), + requireNonNull(uri, "parameter 'uri' must not be null")); } @Override @@ -785,17 +782,15 @@ public String[] getNamespacePrefixes() throws RepositoryException { @Override public String getNamespaceURI(String prefix) throws RepositoryException { - return sessionContext.getNamespaces().getNamespaceURI( - checkNotNull(prefix)); + return sessionContext.getNamespaces().getNamespaceURI(requireNonNull(prefix, "parameter 'prefix' must not be null")); } @Override public String getNamespacePrefix(String uri) throws RepositoryException { - return sessionContext.getNamespaces().getNamespacePrefix( - checkNotNull(uri)); + return sessionContext.getNamespaces().getNamespacePrefix(requireNonNull(uri, "parameter 'uri' must not be null")); } - //--------------------------------------------------< JackrabbitSession >--- + // --------------------------------------------------< JackrabbitSession >--- @Override public boolean hasPermission(@NotNull String absPath, @NotNull String... actions) throws RepositoryException { diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java index b0713b155f2..fdd445d1621 100644 --- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java +++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java @@ -170,5 +170,4 @@ public interface LuceneIndexConstants extends FulltextIndexConstants { */ @Deprecated String INDEX_PATH = "indexPath"; - } diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneIndexDefinitionBuilder.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneIndexDefinitionBuilder.java index 48b55b32831..ee16e391003 100644 --- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneIndexDefinitionBuilder.java +++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneIndexDefinitionBuilder.java @@ -19,9 +19,14 @@ package org.apache.jackrabbit.oak.plugins.index.lucene.util; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import java.util.Arrays; + import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE; public final class LuceneIndexDefinitionBuilder extends IndexDefinitionBuilder { @@ -41,5 +46,4 @@ public LuceneIndexDefinitionBuilder(NodeBuilder nodeBuilder, boolean autoManageR protected String getIndexType() { return TYPE_LUCENE; } - } diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java index a9f29694522..930512e48c1 100644 --- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java +++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java @@ -2988,6 +2988,122 @@ public void testRepSimilarWithBinaryFeatureVectors() throws Exception { } } + @Test + public void testRepSimilarWithBinaryFeatureVectorsWithIndexSimilarityBinariesDefinedAsLucene() throws Exception { + + IndexDefinitionBuilder idxb = new LuceneIndexDefinitionBuilder().noAsync().indexSimilarityBinaries("lucene"); + idxb.indexRule("nt:base").property("fv").useInSimilarity().nodeScopeIndex().propertyIndex(); + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + + URI uri = getClass().getResource("/org/apache/jackrabbit/oak/query/fvs.csv").toURI(); + File file = new File(uri); + + Collection children = new LinkedList<>(); + for (String line : IOUtils.readLines(new FileInputStream(file), Charset.defaultCharset())) { + String[] split = line.split(","); + List values = new LinkedList<>(); + int i = 0; + for (String s : split) { + if (i > 0) { + values.add(Double.parseDouble(s)); + } + i++; + } + + byte[] bytes = SimSearchUtils.toByteArray(values); + List actual = SimSearchUtils.toDoubles(bytes); + assertEquals(values, actual); + + Blob blob = root.createBlob(new ByteArrayInputStream(bytes)); + String name = split[0]; + Tree child = test.addChild(name); + child.setProperty("fv", blob, Type.BINARY); + children.add(child.getPath()); + } + root.commit(); + + // check that similarity changes across different feature vectors + List baseline = new LinkedList<>(); + for (String similarPath : children) { + String query = "select [jcr:path] from [nt:base] where similar(., '" + similarPath + "')"; + + Iterator result = executeQuery(query, "JCR-SQL2").iterator(); + List current = new LinkedList<>(); + while (result.hasNext()) { + String next = result.next(); + current.add(next); + } + assertNotEquals(baseline, current); + baseline.clear(); + baseline.addAll(current); + } + } + + /** + * To disable similarity for binaries the index type should not be in present as value for FulltextIndexConstants.INDEX_SIMILARITY_BINARIES. + * In this case index type is lucene but indexSimilarityBinaries is set to elasticsearch + * + * @throws Exception + */ + @Test + public void testRepSimilarWithBinaryFeatureVectorsWithIndexSimilarityBinariesDefinedAsElasticsearch() throws Exception { + + IndexDefinitionBuilder idxb = new LuceneIndexDefinitionBuilder().noAsync().indexSimilarityBinaries("elasticsearch"); + idxb.indexRule("nt:base").property("fv").useInSimilarity().nodeScopeIndex().propertyIndex(); + + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + + URI uri = getClass().getResource("/org/apache/jackrabbit/oak/query/fvs.csv").toURI(); + File file = new File(uri); + + Collection children = new LinkedList<>(); + for (String line : IOUtils.readLines(new FileInputStream(file), Charset.defaultCharset())) { + String[] split = line.split(","); + List values = new LinkedList<>(); + int i = 0; + for (String s : split) { + if (i > 0) { + values.add(Double.parseDouble(s)); + } + i++; + } + + byte[] bytes = SimSearchUtils.toByteArray(values); + List actual = SimSearchUtils.toDoubles(bytes); + assertEquals(values, actual); + + Blob blob = root.createBlob(new ByteArrayInputStream(bytes)); + String name = split[0]; + Tree child = test.addChild(name); + child.setProperty("fv", blob, Type.BINARY); + children.add(child.getPath()); + } + root.commit(); + + // check that similarity changes across different feature vectors + for (String similarPath : children) { + String query = "select [jcr:path] from [nt:base] where similar(., '" + similarPath + "')"; + + Iterator result = executeQuery(query, "JCR-SQL2").iterator(); + List current = new LinkedList<>(); + while (result.hasNext()) { + String next = result.next(); + current.add(next); + } + assertEquals("binary data for similarity should not be indexed", 0, current.size()); + } + } + @Test public void testRepSimilarWithStringFeatureVectors() throws Exception { @@ -3032,6 +3148,97 @@ public void testRepSimilarWithStringFeatureVectors() throws Exception { } } + @Test + public void testRepSimilarWithStringFeatureVectorsWithIndexSimilarityStringsDefinedAsLucene() throws Exception { + + IndexDefinitionBuilder idxb = new LuceneIndexDefinitionBuilder().noAsync().indexSimilarityStrings("lucene"); + idxb.indexRule("nt:base").property("fv").useInSimilarity().nodeScopeIndex().propertyIndex(); + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + + URI uri = getClass().getResource("/org/apache/jackrabbit/oak/query/fvs.csv").toURI(); + File file = new File(uri); + + Collection children = new LinkedList<>(); + + for (String line : IOUtils.readLines(new FileInputStream(file), Charset.defaultCharset())) { + int i1 = line.indexOf(','); + String name = line.substring(0, i1); + String value = line.substring(i1 + 1); + Tree child = test.addChild(name); + child.setProperty("fv", value, Type.STRING); + children.add(child.getPath()); + } + root.commit(); + + // check that similarity changes across different feature vectors + List baseline = new LinkedList<>(); + for (String similarPath : children) { + String query = "select [jcr:path] from [nt:base] where similar(., '" + similarPath + "')"; + + Iterator result = executeQuery(query, "JCR-SQL2").iterator(); + List current = new LinkedList<>(); + while (result.hasNext()) { + String next = result.next(); + current.add(next); + } + assertNotEquals(baseline, current); + baseline.clear(); + baseline.addAll(current); + } + } + + /** + * To disable similarity for strings the index type should not be in present as value for FulltextIndexConstants.INDEX_SIMILARITY_STRINGS. + * In this case index type is lucene but indexSimilarityStrings is set to elasticsearch + * + * @throws Exception + */ + @Test + public void testRepSimilarWithStringFeatureVectorsWithIndexSimilarityStringsDefinedAsElasticsearch() throws Exception { + + IndexDefinitionBuilder idxb = new LuceneIndexDefinitionBuilder().noAsync().indexSimilarityStrings("elasticsearch"); + idxb.indexRule("nt:base").property("fv").useInSimilarity().nodeScopeIndex().propertyIndex(); + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + + URI uri = getClass().getResource("/org/apache/jackrabbit/oak/query/fvs.csv").toURI(); + File file = new File(uri); + + Collection children = new LinkedList<>(); + + for (String line : IOUtils.readLines(new FileInputStream(file), Charset.defaultCharset())) { + int i1 = line.indexOf(','); + String name = line.substring(0, i1); + String value = line.substring(i1 + 1); + Tree child = test.addChild(name); + child.setProperty("fv", value, Type.STRING); + children.add(child.getPath()); + } + root.commit(); + + // check that similarity changes across different feature vectors + for (String similarPath : children) { + String query = "select [jcr:path] from [nt:base] where similar(., '" + similarPath + "')"; + + Iterator result = executeQuery(query, "JCR-SQL2").iterator(); + List current = new LinkedList<>(); + while (result.hasNext()) { + String next = result.next(); + current.add(next); + } + assertEquals("String data for similarity should not be indexed", 0, current.size()); + } + } + @Test public void testRepSimilarWithBinaryFeatureVectorsAndRerank() throws Exception { diff --git a/oak-parent/pom.xml b/oak-parent/pom.xml index 67846307109..de1557c0300 100644 --- a/oak-parent/pom.xml +++ b/oak-parent/pom.xml @@ -393,11 +393,12 @@ org.apache.maven.plugins maven-site-plugin - 3.7.1 + 3.12.0 false true true + false diff --git a/oak-run-commons/src/test/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/FlatFileStoreTest.java b/oak-run-commons/src/test/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/FlatFileStoreTest.java index 18bc33e75de..eb7f1a26728 100644 --- a/oak-run-commons/src/test/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/FlatFileStoreTest.java +++ b/oak-run-commons/src/test/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/FlatFileStoreTest.java @@ -23,6 +23,7 @@ import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.oak.index.indexer.document.CompositeException; import org.apache.jackrabbit.oak.index.indexer.document.NodeStateEntry; +import org.apache.jackrabbit.oak.index.indexer.document.NodeStateEntry.NodeStateEntryBuilder; import org.apache.jackrabbit.oak.index.indexer.document.NodeStateEntryTraverser; import org.apache.jackrabbit.oak.index.indexer.document.NodeStateEntryTraverserFactory; import org.apache.jackrabbit.oak.plugins.document.mongo.DocumentStoreSplitter; @@ -397,7 +398,11 @@ public void resumePreviousUnfinishedDownloadAndMerge() throws Exception { assertContainsMergeFolder(spyBuilder.getFlatFileStoreDir(), true); List sortedPaths = TestUtils.sortPaths(mongoDocs.stream().map(md -> md.path).collect(Collectors.toList())); - assertEquals(mongoDocs.size(), nsetf.getTotalProvidedDocCount()); + // with multi-threaded download and multiple threads, + // we can't expect that each entry is downloaded exactly once, + // as there could be some overlap (the range check happends + // _after_ retrieving the entry) + assertTrue(mongoDocs.size() <= nsetf.getTotalProvidedDocCount()); assertEquals(sortedPaths, entryPaths); } finally { System.clearProperty(OAK_INDEXER_SORT_STRATEGY_TYPE); @@ -479,7 +484,8 @@ public NodeStateEntryTraverser create(MongoDocumentTraverser.TraversingRange ran String traverserId = getId(); return new Iterator() { - NodeStateEntry lastReturnedDoc; + // ensure we don't get a NullPointerException in logger.debug below + NodeStateEntry lastReturnedDoc = new NodeStateEntryBuilder(null, "/").build(); @Override public boolean hasNext() { @@ -488,7 +494,8 @@ public boolean hasNext() { @Override public NodeStateEntry next() { - if (providedDocuments.get() == breakAfterDelivering.get()) { + // multiple threads could fail at the same time, that's fine + if (providedDocuments.get() >= breakAfterDelivering.get()) { logger.debug("{} Breaking after getting docs with id {}", traverserId, lastReturnedDoc.getId()); throw new IllegalStateException(EXCEPTION_MESSAGE); } diff --git a/oak-run/pom.xml b/oak-run/pom.xml index 252a2eda924..b1997972909 100644 --- a/oak-run/pom.xml +++ b/oak-run/pom.xml @@ -31,7 +31,7 @@ Oak Runnable Jar - 8.2.0.v20160908 + 9.4.46.v20220331 2.4.17