This is an automated email from the ASF dual-hosted git repository.

angela pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 62a3f776cb OAK-10471: Implement ConflictHandler for 
UserPrincipalProvider Cache
62a3f776cb is described below

commit 62a3f776cb4febe17003f8d550c749846d561324
Author: Nicola Scendoni <[email protected]>
AuthorDate: Thu Oct 12 14:07:18 2023 +0200

    OAK-10471: Implement ConflictHandler for UserPrincipalProvider Cache
---
 .../oak/security/user/CacheConflictHandler.java    |  95 +++++++++++
 .../oak/security/user/UserConfigurationImpl.java   |   2 +-
 .../security/user/CacheConflictHandlerTest.java    | 181 +++++++++++++++++++++
 .../security/user/UserConfigurationImplTest.java   |   2 +-
 4 files changed, 278 insertions(+), 2 deletions(-)

diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandler.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandler.java
new file mode 100644
index 0000000000..4122c1b070
--- /dev/null
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandler.java
@@ -0,0 +1,95 @@
+/*
+ * 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.user;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.commit.DefaultThreeWayConflictHandler;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@code CacheConflictHandler} takes care of merging the {@code 
rep:expiration} property
+ * during parallel updates.
+ *<p>
+ * The conflict handler deals with the following conflicts:
+ * <ul>
+ *     <li>{@code addExistingProperty}  : {@code Resolution.IGNORED}, We 
should not have add conflints, since the {@code rep:{@code rep:expiration}} 
node is created with the user</li>
+ *     <li>{@code changeDeletedProperty}: {@code Resolution.IGNORED},</li>
+ *     <li>{@code changeChangedProperty}: {@code Resolution.MERGED}, the 
properties with higher {@code rep:expiration} get merged</li>
+ *     <li>{@code deleteChangedProperty}: {@code Resolution.IGNORED} .</li>
+ *     <li>{@code deleteDeletedProperty}: {@code Resolution.IGNORED}.</li>
+ *     <li>{@code changeDeletedNode}    : {@code Resolution.IGNORED}, .</li>
+ *     <li>{@code deleteChangedNode}    : {@code Resolution.IGNORED}, </li>
+ *     <li>{@code deleteDeletedNode}    : {@code Resolution.IGNORED}.</li>
+ * </ul>
+ */
+
+class CacheConflictHandler extends DefaultThreeWayConflictHandler {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CacheConflictHandler.class);
+
+    protected CacheConflictHandler() {
+        super(Resolution.IGNORED);
+
+    }
+
+    private Resolution resolveRepExpirationConflict(@NotNull NodeBuilder 
parent, @NotNull PropertyState ours, @NotNull PropertyState theirs,
+                                         PropertyState base) {
+        if (CacheConstants.REP_EXPIRATION.equals(ours.getName()) && 
CacheConstants.REP_EXPIRATION.equals(theirs.getName())){
+
+            PropertyBuilder<Long> merged = PropertyBuilder.scalar(Type.LONG);
+            merged.setName(CacheConstants.REP_EXPIRATION);
+
+            //if base is bigger than ours and theirs, then use base. This 
should never happens
+            if (base != null &&
+                    base.getValue(Type.LONG) > ours.getValue(Type.LONG)  &&
+                    base.getValue(Type.LONG) > theirs.getValue(Type.LONG)){
+                merged.setValue(base.getValue(Type.LONG));
+                LOG.warn("base is bigger than ours and theirs. This was 
supposed to never happens");
+                return Resolution.MERGED;
+            }
+
+            //if ours is bigger than theirs, then use ours
+            //otherwise use theirs
+            if (ours.getValue(Type.LONG) > theirs.getValue(Type.LONG)){
+                merged.setValue(ours.getValue(Type.LONG));
+            } else {
+                merged.setValue(theirs.getValue(Type.LONG));
+            }
+            parent.setProperty(merged.getPropertyState());
+            LOG.debug("Resolved conflict for property {} our value: {}, their 
value {}, merged value: {}", CacheConstants.REP_EXPIRATION, 
ours.getValue(Type.LONG), theirs.getValue(Type.LONG), merged.getValue(0));
+            return Resolution.MERGED;
+        }
+        return Resolution.IGNORED;
+
+    }
+
+    @Override
+    public Resolution changeChangedProperty(@NotNull NodeBuilder parent, 
@NotNull PropertyState ours, @NotNull PropertyState theirs,
+                                            @NotNull PropertyState base) {
+
+        return resolveRepExpirationConflict(parent, ours, theirs, base);
+    }
+
+
+}
diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
index 06696dc4a6..84e3826ff2 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java
@@ -263,7 +263,7 @@ public class UserConfigurationImpl extends 
ConfigurationBase implements UserConf
     @NotNull
     @Override
     public List<ThreeWayConflictHandler> getConflictHandlers() {
-        return ImmutableList.of(new RepMembersConflictHandler());
+        return ImmutableList.of(new RepMembersConflictHandler(), new 
CacheConflictHandler());
     }
 
     @NotNull
diff --git 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandlerTest.java
 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandlerTest.java
new file mode 100644
index 0000000000..d32bf4813d
--- /dev/null
+++ 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/CacheConflictHandlerTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.user;
+
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.ContentSession;
+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.api.Type;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.PrivilegedExceptionAction;
+import java.util.Set;
+import java.util.UUID;
+
+import static 
org.apache.jackrabbit.oak.security.user.CacheConstants.REP_EXPIRATION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CacheConflictHandlerTest extends AbstractSecurityTest {
+
+    static final String PARAM_CACHE_EXPIRATION = "cacheExpiration";
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        String groupId = "testGroup" + UUID.randomUUID();
+        @NotNull Group testGroup = getUserManager(root).createGroup(groupId);
+        testGroup.addMember(getTestUser());
+
+        String groupId2 = "testGroup" + UUID.randomUUID() + "2";
+        @NotNull Group testGroup2 = getUserManager(root).createGroup(groupId2);
+        testGroup.addMember(testGroup2);
+
+        String groupId3 = "testGroup" + UUID.randomUUID() + "3";
+        @NotNull Group testGroup3 = getUserManager(root).createGroup(groupId3);
+
+        root.commit();
+    }
+
+    private Tree getCacheTree(Root root) throws Exception {
+        return getCacheTree(root, getTestUser().getPath());
+    }
+
+    private Tree getCacheTree(Root root, String authorizablePath) {
+        return root.getTree(authorizablePath + '/' + CacheConstants.REP_CACHE);
+    }
+
+    @Override
+    protected ConfigurationParameters getSecurityConfigParameters() {
+        return ConfigurationParameters.of(
+                UserConfiguration.NAME,
+                ConfigurationParameters.of(PARAM_CACHE_EXPIRATION, 3600 * 1000)
+        );
+    }
+
+    @Test
+    public void testChangeChangedPropertyLower() throws Exception {
+
+        PrincipalConfiguration pc = getConfig(PrincipalConfiguration.class);
+
+        Root oursRoot = Subject.doAs(SystemSubject.INSTANCE, 
(PrivilegedExceptionAction<ContentSession>) () -> login(null)).getLatestRoot();
+        Root theirsRoot = Subject.doAs(SystemSubject.INSTANCE, 
(PrivilegedExceptionAction<ContentSession>) () -> login(null)).getLatestRoot();
+
+        PrincipalProvider oursPP = pc.getPrincipalProvider(oursRoot, 
namePathMapper);
+        PrincipalProvider theirsPP = pc.getPrincipalProvider(theirsRoot, 
namePathMapper);
+
+        // set of principals that read from user + membership-provider -> 
cache being filled
+        oursPP.getPrincipals(getTestUser().getID());
+        assertTrue(getCacheTree(oursRoot).exists());
+        
getCacheTree(oursRoot).getProperty("rep:expiration").getValue(Type.LONG).longValue();
+
+        theirsPP.getPrincipals(getTestUser().getID());
+        assertTrue(getCacheTree(theirsRoot).exists());
+        long theirExpiration = 
getCacheTree(theirsRoot).getProperty("rep:expiration").getValue(Type.LONG).longValue();
+
+
+        Tree ourCache = getCacheTree(oursRoot);
+        ourCache.setProperty(REP_EXPIRATION, 2);
+        oursRoot.commit(CacheValidatorProvider.asCommitAttributes());
+
+        root.commit();
+        
assertEquals(getCacheTree(root).getProperty(REP_EXPIRATION).getValue(Type.LONG).longValue(),
 theirExpiration);
+
+    }
+
+    @Test
+    public void testChangeChangedPropertyHigher() throws Exception {
+
+        PrincipalConfiguration pc = getConfig(PrincipalConfiguration.class);
+
+        Root oursRoot = Subject.doAs(SystemSubject.INSTANCE, 
(PrivilegedExceptionAction<ContentSession>) () -> login(null)).getLatestRoot();
+        Root theirsRoot = Subject.doAs(SystemSubject.INSTANCE, 
(PrivilegedExceptionAction<ContentSession>) () -> login(null)).getLatestRoot();
+
+        PrincipalProvider oursPP = pc.getPrincipalProvider(oursRoot, 
namePathMapper);
+        PrincipalProvider theirsPP = pc.getPrincipalProvider(theirsRoot, 
namePathMapper);
+
+        // set of principals that read from user + membership-provider -> 
cache being filled
+        Set<? extends Principal> ourPrincipals = 
oursPP.getPrincipals(getTestUser().getID());
+        assertTrue(getCacheTree(oursRoot).exists());
+        
getCacheTree(oursRoot).getProperty("rep:expiration").getValue(Type.LONG).longValue();
+
+        Set<? extends Principal> theirPrincipals = 
theirsPP.getPrincipals(getTestUser().getID());
+        assertTrue(getCacheTree(theirsRoot).exists());
+        long theirExpiration = 
getCacheTree(theirsRoot).getProperty("rep:expiration").getValue(Type.LONG).longValue();
+
+
+        Tree ourCache = getCacheTree(oursRoot);
+        ourCache.setProperty(REP_EXPIRATION, theirExpiration + 1000);
+        oursRoot.commit(CacheValidatorProvider.asCommitAttributes());
+
+        root.commit();
+        
assertEquals(getCacheTree(root).getProperty(REP_EXPIRATION).getValue(Type.LONG).longValue(),
 theirExpiration + 1000);
+
+    }
+
+    @Test
+    public void testChangeChangedPropertyBaseHigher() {
+        NodeBuilder parent = mock(NodeBuilder.class);
+
+        PropertyState ours = mock(PropertyState.class);
+        PropertyState base = mock(PropertyState.class);
+        PropertyState theirs = mock(PropertyState.class);
+
+        when(ours.getName()).thenReturn(REP_EXPIRATION);
+        when(base.getName()).thenReturn(REP_EXPIRATION);
+        when(theirs.getName()).thenReturn(REP_EXPIRATION);
+
+        when(ours.getValue(Type.LONG)).thenReturn(1000L);
+        when(base.getValue(Type.LONG)).thenReturn(2000L);
+        when(theirs.getValue(Type.LONG)).thenReturn(900L);
+
+        CacheConflictHandler handler = new CacheConflictHandler();
+        assertEquals(CacheConflictHandler.Resolution.MERGED, 
handler.changeChangedProperty(parent, ours, theirs, base));
+
+    }
+
+    @Test
+    public void testChangeChangedPropertyIgnore() {
+        NodeBuilder parent = mock(NodeBuilder.class);
+
+        PropertyState ours = mock(PropertyState.class);
+        PropertyState base = mock(PropertyState.class);
+        PropertyState theirs = mock(PropertyState.class);
+
+        CacheConflictHandler handler = new CacheConflictHandler();
+        assertEquals(CacheConflictHandler.Resolution.IGNORED, 
handler.changeChangedProperty(parent, ours, theirs, base));
+
+    }
+}
\ No newline at end of file
diff --git 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
index d026cf5440..f08789c99d 100644
--- 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
+++ 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImplTest.java
@@ -83,7 +83,7 @@ public class UserConfigurationImplTest extends 
AbstractSecurityTest {
         UserConfigurationImpl configuration = new UserConfigurationImpl();
 
         List<ThreeWayConflictHandler> conflictHandlers = 
configuration.getConflictHandlers();
-        assertEquals(1, conflictHandlers.size());
+        assertEquals(2, conflictHandlers.size());
         assertTrue(conflictHandlers.get(0) instanceof 
RepMembersConflictHandler);
     }
 

Reply via email to