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 an...@apache.org on 2016/06/06 09:10:48 UTC

svn commit: r1746975 - in /jackrabbit/oak/trunk/oak-auth-external/src: main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/ test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/

Author: angela
Date: Mon Jun  6 09:10:48 2016
New Revision: 1746975

URL: http://svn.apache.org/viewvc?rev=1746975&view=rev
Log:
OAK-4379 : Batch mode for SyncMBeanImpl

Added:
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/AbstractJmxTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchOneTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchTwoTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeTest.java
Modified:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/Delegatee.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImplTest.java

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/Delegatee.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/Delegatee.java?rev=1746975&r1=1746974&r2=1746975&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/Delegatee.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/Delegatee.java Mon Jun  6 09:10:48 2016
@@ -21,6 +21,7 @@ import java.security.PrivilegedException
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.Repository;
@@ -58,26 +59,41 @@ final class Delegatee {
     private static final String ERROR_CREATE_DELEGATEE = "Unable to create delegatee";
     private static final String ERROR_SYNC_USER = "Error while syncing user {}";
 
+    private static final int NO_BATCH_SIZE = 0;
+    private static final int DEFAULT_BATCH_SIZE = 100;
+
     private final SyncHandler handler;
     private final ExternalIdentityProvider idp;
     private final UserManager userMgr;
     private final Session systemSession;
 
+    private final int batchSize;
+
     private SyncContext context;
 
     private Delegatee(@Nonnull SyncHandler handler, @Nonnull ExternalIdentityProvider idp,
-                      @Nonnull JackrabbitSession systemSession) throws SyncException, RepositoryException {
+                      @Nonnull JackrabbitSession systemSession, int batchSize) throws SyncException, RepositoryException {
         this.handler = handler;
         this.idp = idp;
 
         this.systemSession = systemSession;
         this.userMgr = systemSession.getUserManager();
         this.context = handler.createContext(idp, userMgr, systemSession.getValueFactory());
+        this.batchSize = batchSize;
 
         log.info("Created delegatee for SyncMBean with session: {} {}", systemSession, systemSession.getUserID());
     }
 
-    static Delegatee createInstance(@Nonnull final Repository repository, @Nonnull SyncHandler handler, @Nonnull ExternalIdentityProvider idp) {
+    static Delegatee createInstance(@Nonnull final Repository repository,
+                                    @Nonnull SyncHandler handler,
+                                    @Nonnull ExternalIdentityProvider idp) {
+        return createInstance(repository, handler, idp, DEFAULT_BATCH_SIZE);
+    }
+
+    static Delegatee createInstance(@Nonnull final Repository repository,
+                                    @Nonnull SyncHandler handler,
+                                    @Nonnull ExternalIdentityProvider idp,
+                                    int batchSize) {
         Session systemSession;
         try {
             systemSession = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<Session>() {
@@ -100,7 +116,7 @@ final class Delegatee {
             throw new SyncRuntimeException("Unable to create SyncContext: JackrabbitSession required.");
         }
         try {
-            return new Delegatee(handler, idp, (JackrabbitSession) systemSession);
+            return new Delegatee(handler, idp, (JackrabbitSession) systemSession, batchSize);
         } catch (RepositoryException e) {
             systemSession.logout();
             throw new SyncRuntimeException(ERROR_CREATE_DELEGATEE, e);
@@ -129,14 +145,12 @@ final class Delegatee {
                 .setForceGroupSync(true)
                 .setForceUserSync(true);
         List<String> list = new ArrayList<String>();
+
+        List<SyncResult> results = new ArrayList<SyncResult>(batchSize);
         for (String userId : userIds) {
-            try {
-                append(list, syncUser(userId));
-            } catch (SyncException e) {
-                log.warn(ERROR_SYNC_USER, userId, e);
-                append(list, new DefaultSyncedIdentity(userId, null, false, -1), e);
-            }
+            results = syncUser(userId, false, results, list);
         }
+        commit(list, results, NO_BATCH_SIZE);
         return list.toArray(new String[list.size()]);
     }
 
@@ -150,18 +164,16 @@ final class Delegatee {
             context.setKeepMissing(!purge)
                     .setForceGroupSync(true)
                     .setForceUserSync(true);
-            Iterator<SyncedIdentity> iter = handler.listIdentities(userMgr);
-            while (iter.hasNext()) {
-                SyncedIdentity id = iter.next();
+            Iterator<SyncedIdentity> it = handler.listIdentities(userMgr);
+
+            List<SyncResult> results = new ArrayList<SyncResult>(batchSize);
+            while (it.hasNext()) {
+                SyncedIdentity id = it.next();
                 if (isMyIDP(id)) {
-                    try {
-                        append(list, syncUser(id.getId()));
-                    } catch (SyncException e) {
-                        log.error(ERROR_SYNC_USER, id, e);
-                        append(list, id, e);
-                    }
+                    results = syncUser(id.getId(), false, results, list);
                 }
             }
+            commit(list, results, NO_BATCH_SIZE);
             return list.toArray(new String[list.size()]);
         } catch (RepositoryException e) {
             throw new IllegalStateException("Error retrieving users for syncing", e);
@@ -175,32 +187,30 @@ final class Delegatee {
     String[] syncExternalUsers(@Nonnull String[] externalIds) {
         List<String> list = new ArrayList<String>();
         context.setForceGroupSync(true).setForceUserSync(true);
+
+        List<SyncResult> results = new ArrayList<SyncResult>(batchSize);
         for (String externalId : externalIds) {
             ExternalIdentityRef ref = ExternalIdentityRef.fromString(externalId);
             if (!idp.getName().equals(ref.getProviderName())) {
-                append(list, new DefaultSyncResultImpl(new DefaultSyncedIdentity(ref.getId(), ref, false, -1), SyncResult.Status.FOREIGN));
+                results.add(new DefaultSyncResultImpl(new DefaultSyncedIdentity(ref.getId(), ref, false, -1), SyncResult.Status.FOREIGN));
             } else {
                 try {
                     ExternalIdentity id = idp.getIdentity(ref);
-                    SyncResult r;
                     if (id != null) {
-                        r = syncUser(id);
+                        results = syncUser(id, results, list);
                     } else {
-                        r = new DefaultSyncResultImpl(
+                        results.add(new DefaultSyncResultImpl(
                                 new DefaultSyncedIdentity("", ref, false, -1),
                                 SyncResult.Status.NO_SUCH_IDENTITY
-                        );
+                        ));
                     }
-                    append(list, r);
                 } catch (ExternalIdentityException e) {
                     log.warn("error while fetching the external identity {}", externalId, e);
-                    append(list, ref, e);
-                } catch (SyncException e) {
-                    log.error(ERROR_SYNC_USER, ref, e);
-                    append(list, ref, e);
+                    results.add(new ErrorSyncResult(ref, e));
                 }
             }
         }
+        commit(list, results, NO_BATCH_SIZE);
         return list.toArray(new String[list.size()]);
     }
 
@@ -212,26 +222,13 @@ final class Delegatee {
         List<String> list = new ArrayList<String>();
         context.setForceGroupSync(true).setForceUserSync(true);
         try {
-            Iterator<ExternalUser> iter = idp.listUsers();
-            while (iter.hasNext()) {
-                ExternalUser user = iter.next();
-                try {
-                    SyncResult r = syncUser(user);
-                    if (r.getIdentity() == null) {
-                        r = new DefaultSyncResultImpl(
-                                new DefaultSyncedIdentity(user.getId(), user.getExternalId(), false, -1),
-                                SyncResult.Status.NO_SUCH_IDENTITY
-                        );
-                        log.warn("sync failed. {}", r.getIdentity());
-                    } else {
-                        log.info("synced {}", r.getIdentity());
-                    }
-                    append(list, r);
-                } catch (SyncException e) {
-                    log.error(ERROR_SYNC_USER, user, e);
-                    append(list, user.getExternalId(), e);
-                }
+            List<SyncResult> results = new ArrayList<SyncResult>(batchSize);
+            Iterator<ExternalUser> it = idp.listUsers();
+            while (it.hasNext()) {
+                ExternalUser user = it.next();
+                results = syncUser(user, results, list);
             }
+            commit(list, results, NO_BATCH_SIZE);
             return list.toArray(new String[list.size()]);
         } catch (ExternalIdentityException e) {
             throw new SyncRuntimeException("Unable to retrieve external users", e);
@@ -254,15 +251,13 @@ final class Delegatee {
         context.setKeepMissing(false);
         List<String> list = new ArrayList<String>();
         Iterator<String> orphanedIdentities = internalListOrphanedIdentities();
+
+        List<SyncResult> results = new ArrayList<SyncResult>(batchSize);
         while (orphanedIdentities.hasNext()) {
             String userId = orphanedIdentities.next();
-            try {
-                append(list, syncUser(userId));
-            } catch (SyncException e) {
-                log.warn(ERROR_SYNC_USER, userId, e);
-                append(list, new DefaultSyncedIdentity(userId, new ExternalIdentityRef(userId, idp.getName()), false, -1), e);
-            }
+            results = syncUser(userId, true, results, list);
         }
+        commit(list, results, NO_BATCH_SIZE);
         return list.toArray(new String[list.size()]);
     }
 
@@ -274,34 +269,64 @@ final class Delegatee {
         return providerName != null && (providerName.isEmpty() || providerName.equals(idp.getName()));
     }
 
-
     @Nonnull
-    private SyncResult syncUser(@Nonnull ExternalIdentity id) throws SyncException {
+    private List<SyncResult> syncUser(@Nonnull ExternalIdentity id, @Nonnull List<SyncResult> results, @Nonnull List<String> list) {
         try {
             SyncResult r = context.sync(id);
-            systemSession.save();
-            return r;
-        } catch (RepositoryException e) {
-            throw new SyncException(e);
+            if (r.getIdentity() == null) {
+                r = new DefaultSyncResultImpl(
+                        new DefaultSyncedIdentity(id.getId(), id.getExternalId(), false, -1),
+                        SyncResult.Status.NO_SUCH_IDENTITY
+                );
+                log.warn("sync failed. {}", r.getIdentity());
+            } else {
+                log.info("synced {}", r.getIdentity());
+            }
+            results.add(r);
+        } catch (SyncException e) {
+            log.error(ERROR_SYNC_USER, id, e);
+            results.add(new ErrorSyncResult(id.getExternalId(), e));
         }
+        return commit(list, results, batchSize);
     }
 
-    @Nonnull
-    private SyncResult syncUser(@Nonnull String userId) throws SyncException {
+    private List<SyncResult> syncUser(@Nonnull String userId, boolean includeIdpName,
+                                      @Nonnull List<SyncResult> results, @Nonnull List<String> list) {
         try {
-            SyncResult r = context.sync(userId);
-            systemSession.save();
-            return r;
-        } catch (RepositoryException e) {
-            throw new SyncException(e);
+            results.add(context.sync(userId));
+        } catch (SyncException e) {
+            log.warn(ERROR_SYNC_USER, userId, e);
+            results.add(new ErrorSyncResult(userId, ((includeIdpName) ? idp.getName() : null), e));
+        }
+        return commit(list, results, batchSize);
+    }
+
+    private List<SyncResult> commit(@Nonnull List<String> list, @Nonnull List<SyncResult> resultList, int size) {
+        if (resultList.isEmpty() || resultList.size() < size) {
+            return resultList;
+        } else {
+            try {
+                systemSession.save();
+                append(list, resultList);
+            } catch (RepositoryException e) {
+                append(list, resultList, e);
+            } finally {
+                // make sure there are not pending changes that would fail the next batches
+                try {
+                    systemSession.refresh(false);
+                } catch (RepositoryException e) {
+                    log.warn(e.getMessage());
+                }
+            }
+            return new ArrayList<SyncResult>(size);
         }
     }
 
     @Nonnull
     private Iterator<String> internalListOrphanedIdentities() {
         try {
-            Iterator<SyncedIdentity> iter = handler.listIdentities(userMgr);
-            return Iterators.filter(Iterators.transform(iter, new Function<SyncedIdentity, String>() {
+            Iterator<SyncedIdentity> it = handler.listIdentities(userMgr);
+            return Iterators.filter(Iterators.transform(it, new Function<SyncedIdentity, String>() {
                 @Nullable
                 @Override
                 public String apply(@Nullable SyncedIdentity syncedIdentity) {
@@ -326,28 +351,52 @@ final class Delegatee {
     }
 
     private static void append(@Nonnull List<String> list, @Nonnull SyncResult r) {
-        SyncedIdentity syncedIdentity = r.getIdentity();
+        if (r instanceof ErrorSyncResult) {
+            ((ErrorSyncResult) r).append(list);
+        } else {
+            append(list, r.getIdentity(), getOperationFromStatus(r.getStatus()), null);
+        }
+    }
+
+    private static void append(@Nonnull List<String> list, @CheckForNull SyncedIdentity syncedIdentity, @Nonnull Exception e) {
+        append(list, syncedIdentity, "ERR", e.toString());
+    }
+
+    private static void append(@Nonnull List<String> list, @CheckForNull SyncedIdentity syncedIdentity, @Nonnull String op, @CheckForNull String msg) {
         String uid = JsonUtil.getJsonString((syncedIdentity == null ? null : syncedIdentity.getId()));
         ExternalIdentityRef externalIdentityRef = (syncedIdentity == null) ? null : syncedIdentity.getExternalIdRef();
         String eid = (externalIdentityRef == null) ? "\"\"" : JsonUtil.getJsonString(externalIdentityRef.getString());
-        String jsonStr = String.format("{op:\"%s\",uid:%s,eid:%s}", getOperationFromStatus(r.getStatus()), uid, eid);
-        list.add(jsonStr);
+
+        if (msg == null) {
+            list.add(String.format("{op:\"%s\",uid:%s,eid:%s}", op, uid, eid));
+        } else {
+            list.add(String.format("{op:\"%s\",uid:%s,eid:%s,msg:%s}", op, uid, eid, JsonUtil.getJsonString(msg)));
+        }
     }
 
-    private static void append(@Nonnull List<String> list, @Nonnull ExternalIdentityRef idRef, @Nonnull Exception e) {
-        String uid = JsonUtil.getJsonString(idRef.getId());
-        String eid = JsonUtil.getJsonString(idRef.getString());
-        String msg = JsonUtil.getJsonString(e.toString());
-        String jsonStr = String.format("{op:\"ERR\",uid:%s,eid:%s,msg:%s}", uid, eid, msg);
-        list.add(jsonStr);
+    private static void append(@Nonnull List<String> list, @Nonnull List<SyncResult> results) {
+        for (SyncResult result : results) {
+            append(list, result);
+        }
     }
 
-    private static void append(@Nonnull List<String> list, @Nonnull SyncedIdentity id, @Nonnull Exception e) {
-        String uid = JsonUtil.getJsonString(id.getId());
-        ExternalIdentityRef ref = id.getExternalIdRef();
-        String eid = (ref == null) ? "\"\"" : JsonUtil.getJsonString(ref.getString());
-        String msg = JsonUtil.getJsonString(e.toString());
-        list.add(String.format("{op:\"ERR\",uid:%s,eid:%s,msg:%s}", uid, eid, msg));
+    private static void append(@Nonnull List<String> list, @Nonnull List<SyncResult> results, @Nonnull Exception e) {
+        for (SyncResult result : results) {
+            if (result instanceof ErrorSyncResult) {
+                ((ErrorSyncResult) result).append(list);
+            } else {
+                SyncResult.Status st = result.getStatus();
+                switch (st) {
+                    case ADD:
+                    case DELETE:
+                    case UPDATE:
+                        append(list, result.getIdentity(), e);
+                        break;
+                    default:
+                        append(list, result);
+                }
+            }
+        }
     }
 
     private static String getOperationFromStatus(SyncResult.Status syncStatus) {
@@ -382,4 +431,37 @@ final class Delegatee {
         }
         return op;
     }
+
+    private static final class ErrorSyncResult implements SyncResult {
+
+        private final SyncedIdentity syncedIdentity;
+        private final Exception error;
+
+        private ErrorSyncResult(@Nonnull String userId, @CheckForNull String idpName, @Nonnull Exception error) {
+            ExternalIdentityRef ref = (idpName != null) ? new ExternalIdentityRef(userId, idpName) : null;
+            this.syncedIdentity = new DefaultSyncedIdentity(userId, ref, false, -1);
+            this.error = error;
+        }
+
+        private ErrorSyncResult(@Nonnull ExternalIdentityRef ref, @Nonnull Exception error) {
+            this.syncedIdentity = new DefaultSyncedIdentity(ref.getId(), ref, false, -1);
+            this.error = error;
+        }
+
+        @Nonnull
+        @Override
+        public SyncedIdentity getIdentity() {
+            return syncedIdentity;
+        }
+
+        @Nonnull
+        @Override
+        public Status getStatus() {
+            return Status.NOP;
+        }
+
+        private void append(@Nonnull List<String> list) {
+            Delegatee.append(list, syncedIdentity, error);
+        }
+    }
 }

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/AbstractJmxTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/AbstractJmxTest.java?rev=1746975&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/AbstractJmxTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/AbstractJmxTest.java Mon Jun  6 09:10:48 2016
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public abstract class AbstractJmxTest {
+
+    static Repository REPOSITORY;
+
+    Session session;
+    UserManager userManager;
+
+    DefaultSyncConfig syncConfig;
+
+    ExternalIdentityProvider idp;
+    ExternalIdentityProvider foreignIDP;
+
+    private Set<String> ids;
+
+    @BeforeClass
+    public static void beforeClass() {
+        REPOSITORY = new Jcr().createRepository();
+    }
+
+    @Before
+    public void before() throws Exception {
+        session = REPOSITORY.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        if (!(session instanceof JackrabbitSession)) {
+            throw new IllegalStateException();
+        } else {
+            userManager = ((JackrabbitSession) session).getUserManager();
+        }
+        ids = Sets.newHashSet(getAllAuthorizableIds(userManager));
+
+        syncConfig = new DefaultSyncConfig();
+        syncConfig.user().setMembershipNestingDepth(1);
+
+        idp = new TestIdentityProvider();
+        foreignIDP = new TestIdentityProvider("anotherIDP");
+    }
+
+    @After
+    public void after() throws Exception {
+        try {
+            session.refresh(false);
+            Iterator<String> iter = getAllAuthorizableIds(userManager);
+            while (iter.hasNext()) {
+                String id = iter.next();
+                if (!ids.remove(id)) {
+                    Authorizable a = userManager.getAuthorizable(id);
+                    if (a != null) {
+                        a.remove();
+                    }
+                }
+            }
+            session.save();
+        } finally {
+            session.logout();
+        }
+    }
+
+    private static Iterator<String> getAllAuthorizableIds(@Nonnull UserManager userManager) throws Exception {
+        Iterator<Authorizable> it = userManager.findAuthorizables("jcr:primaryType", null);
+        return Iterators.filter(Iterators.transform(it, new Function<Authorizable, String>() {
+            @Nullable
+            @Override
+            public String apply(Authorizable input) {
+                try {
+                    if (input != null) {
+                        return input.getID();
+                    }
+                } catch (RepositoryException e) {
+                    // failed to retrieve ID
+                }
+                return null;
+            }
+        }), Predicates.notNull());
+    }
+
+    static void assertResultMessages(@Nonnull String[] resultMessages, String uid, @Nonnull String expectedOperation) {
+        assertResultMessages(resultMessages, ImmutableMap.of(uid, expectedOperation));
+    }
+
+    static void assertResultMessages(@Nonnull String[] resultMessages, @Nonnull Map<String, String> expected) {
+        assertEquals(expected.size(), resultMessages.length);
+        for (int i = 0; i < resultMessages.length; i++) {
+            String rm = resultMessages[i];
+            String op = rm.substring(rm.indexOf(":") + 2, rm.indexOf("\","));
+
+            int index = rm.indexOf("uid:\"") + 5;
+            String uid = rm.substring(index, rm.indexOf("\",", index));
+
+            assertTrue(expected.containsKey(uid));
+            assertEquals(expected.get(uid), op);
+        }
+    }
+
+    static void assertSync(@Nonnull ExternalIdentity ei, @Nonnull UserManager userManager) throws Exception {
+        Authorizable authorizable;
+        if (ei instanceof ExternalUser) {
+            authorizable = userManager.getAuthorizable(ei.getId(), User.class);
+        } else {
+            authorizable = userManager.getAuthorizable(ei.getId(), Group.class);
+        }
+        assertNotNull(ei.getId(), authorizable);
+        assertEquals(ei.getId(), authorizable.getID());
+        assertEquals(ei.getExternalId(), ExternalIdentityRef.fromString(authorizable.getProperty(DefaultSyncContext.REP_EXTERNAL_ID)[0].getString()));
+    }
+
+    SyncResult sync(@Nonnull ExternalIdentityProvider idp, @Nonnull String id, boolean isGroup) throws Exception {
+        return sync((isGroup) ? idp.getGroup(id) : idp.getUser(id), idp);
+    }
+
+    SyncResult sync(@Nonnull ExternalIdentity externalIdentity, @Nonnull ExternalIdentityProvider idp) throws Exception {
+        SyncContext ctx = new DefaultSyncContext(syncConfig, idp, userManager, session.getValueFactory());
+        SyncResult res = ctx.sync(externalIdentity);
+        session.save();
+        return res;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchOneTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchOneTest.java?rev=1746975&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchOneTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchOneTest.java Mon Jun  6 09:10:48 2016
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx;
+
+public class DelegateeBatchOneTest extends DelegateeTest {
+
+    @Override
+    int getBatchSize() {
+        return 1;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchTwoTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchTwoTest.java?rev=1746975&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchTwoTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeBatchTwoTest.java Mon Jun  6 09:10:48 2016
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx;
+
+public class DelegateeBatchTwoTest extends DelegateeTest {
+
+    @Override
+    int getBatchSize() {
+        // test-idp-provider has 3 users => forcing save in the final commit
+        return 2;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeTest.java?rev=1746975&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/DelegateeTest.java Mon Jun  6 09:10:48 2016
@@ -0,0 +1,524 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.jmx;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.jcr.Credentials;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
+import javax.jcr.Workspace;
+import javax.jcr.retention.RetentionManager;
+import javax.jcr.security.AccessControlManager;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import static org.junit.Assert.assertFalse;
+
+public class DelegateeTest extends AbstractJmxTest {
+
+    private Delegatee delegatee;
+
+    private static final String[] TEST_IDS = new String[] {
+            TestIdentityProvider.ID_TEST_USER,
+            TestIdentityProvider.ID_SECOND_USER,
+            TestIdentityProvider.ID_WILDCARD_USER};
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        delegatee = createDelegatee(new TestIdentityProvider());
+    }
+
+    @Override
+    public void after() throws Exception {
+        try {
+            if (delegatee != null) {
+                delegatee.close();
+            }
+        } finally {
+            super.after();
+        }
+    }
+
+    int getBatchSize() {
+        return 100;
+    }
+
+    private Delegatee createDelegatee(@Nonnull ExternalIdentityProvider idp) {
+        return Delegatee.createInstance(REPOSITORY, new DefaultSyncHandler(syncConfig), idp, getBatchSize());
+    }
+
+    private static Session preventSessionSave(@Nonnull Delegatee delegatee) throws Exception {
+        Field sessionField = Delegatee.class.getDeclaredField("systemSession");
+        sessionField.setAccessible(true);
+
+        JackrabbitSession s = (JackrabbitSession) sessionField.get(delegatee);
+        s.refresh(false);
+        sessionField.set(delegatee, new ThrowingSession(s));
+        return s;
+    }
+
+    @Test
+    public void testDoubleClose() {
+        delegatee.close();
+        delegatee.close();
+    }
+
+    @Test
+    public void testSyncUsersBeforeSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncUsers(TEST_IDS, false);
+        assertResultMessages(result, ImmutableMap.of(
+                TestIdentityProvider.ID_TEST_USER, "nsa",
+                TestIdentityProvider.ID_SECOND_USER, "nsa",
+                TestIdentityProvider.ID_WILDCARD_USER, "nsa"));
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncUsersSaveError() throws Exception {
+        sync(idp, TestIdentityProvider.ID_TEST_USER, false);
+        sync(foreignIDP, TestIdentityProvider.ID_SECOND_USER, false);
+        // don't sync ID_WILDCARD_USER
+
+        Session s = preventSessionSave(delegatee);
+
+        String[] result = delegatee.syncUsers(new String[] {
+                TestIdentityProvider.ID_TEST_USER,
+                TestIdentityProvider.ID_SECOND_USER,
+                TestIdentityProvider.ID_WILDCARD_USER}, false);
+        assertResultMessages(result, ImmutableMap.of(
+                TestIdentityProvider.ID_TEST_USER, "ERR",
+                TestIdentityProvider.ID_SECOND_USER, "for",
+                TestIdentityProvider.ID_WILDCARD_USER, "nsa"));
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncAllUsersBeforeSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncAllUsers(false);
+        assertResultMessages(result, ImmutableMap.<String,String>of());
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncAllUsersSaveError() throws Exception {
+        sync(idp, TestIdentityProvider.ID_TEST_USER, false);
+        sync(idp, TestIdentityProvider.ID_SECOND_USER, false);
+        sync(new TestIdentityProvider.TestUser("third", idp.getName()), idp);
+        sync(foreignIDP, TestIdentityProvider.ID_WILDCARD_USER, false);
+
+        Session s = preventSessionSave(delegatee);;
+
+        ImmutableMap<String, String> expected = ImmutableMap.<String, String>builder()
+                .put(TestIdentityProvider.ID_TEST_USER, "ERR")
+                .put("a", "ERR")
+                .put("b", "ERR")
+                .put("c", "ERR")
+                .put(TestIdentityProvider.ID_SECOND_USER, "ERR")
+                .put("secondGroup", "ERR")
+                .put("third", "mis").build();
+
+        String[] result = delegatee.syncAllUsers(false);
+        assertResultMessages(result, expected);
+        // NOTE: foreign user is not included in the results
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncAllUsersPurgeSaveError() throws Exception {
+        sync(idp, TestIdentityProvider.ID_TEST_USER, false);
+        sync(idp, TestIdentityProvider.ID_SECOND_USER, false);
+        sync(new TestIdentityProvider.TestUser("third", idp.getName()), idp);
+        sync(foreignIDP, TestIdentityProvider.ID_WILDCARD_USER, false);
+
+        Session s = preventSessionSave(delegatee);;
+
+        ImmutableMap<String, String> expected = ImmutableMap.<String, String>builder()
+                .put(TestIdentityProvider.ID_TEST_USER, "ERR")
+                .put("a", "ERR")
+                .put("b", "ERR")
+                .put("c", "ERR")
+                .put(TestIdentityProvider.ID_SECOND_USER, "ERR")
+                .put("secondGroup", "ERR")
+                .put("third", "ERR").build();
+
+        String[] result = delegatee.syncAllUsers(true);
+        assertResultMessages(result, expected);
+        // NOTE: foreign user is not included in the results
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncNonExistingExternalUserSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncExternalUsers(new String[] {new ExternalIdentityRef("nonExisting", idp.getName()).getString()});
+        assertResultMessages(result, "", "nsi");
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncForeignExternalUserSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncExternalUsers(new String[] {new ExternalIdentityRef(TestIdentityProvider.ID_TEST_USER, foreignIDP.getName()).getString()});
+        assertResultMessages(result, TestIdentityProvider.ID_TEST_USER, "for");
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncThrowingExternalUserSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncExternalUsers(new String[] {new ExternalIdentityRef(TestIdentityProvider.ID_EXCEPTION, idp.getName()).getString()});
+        assertResultMessages(result, TestIdentityProvider.ID_EXCEPTION, "ERR");
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncExternalUsersSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        List<String> externalIds = new ArrayList();
+        for (String id : TEST_IDS) {
+                externalIds.add(new ExternalIdentityRef(id, idp.getName()).getString());
+        }
+        String[] result = delegatee.syncExternalUsers(externalIds.toArray(new String[externalIds.size()]));
+        assertResultMessages(result, ImmutableMap.of(
+                TestIdentityProvider.ID_TEST_USER, "ERR",
+                TestIdentityProvider.ID_SECOND_USER, "ERR",
+                TestIdentityProvider.ID_WILDCARD_USER, "ERR"));
+        assertFalse(s.hasPendingChanges());
+    }
+
+    @Test
+    public void testSyncAllExternalUsersSaveError() throws Exception {
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.syncAllExternalUsers();
+        assertResultMessages(result, ImmutableMap.of(
+                TestIdentityProvider.ID_TEST_USER, "ERR",
+                TestIdentityProvider.ID_SECOND_USER, "ERR",
+                TestIdentityProvider.ID_WILDCARD_USER, "ERR"));
+        assertFalse(s.hasPendingChanges());
+    }
+
+
+    @Test(expected = SyncRuntimeException.class)
+    public void testSyncAllExternalUsersThrowingIDP() {
+        Delegatee dg = Delegatee.createInstance(REPOSITORY, new DefaultSyncHandler(syncConfig), new TestIdentityProvider("throwing") {
+
+            @Nonnull
+            @Override
+            public Iterator<ExternalUser> listUsers() throws ExternalIdentityException {
+                throw new ExternalIdentityException();
+            }
+        }, getBatchSize());
+
+        dg.syncAllExternalUsers();
+    }
+
+    @Test
+    public void testPurgeOrphanedSaveError() throws Exception {
+        sync(new TestIdentityProvider.TestUser("third", idp.getName()), idp);
+        sync(new TestIdentityProvider.TestUser("forth", idp.getName()), idp);
+        sync(idp, TestIdentityProvider.ID_TEST_USER, false);
+
+        Session s = preventSessionSave(delegatee);;
+
+        String[] result = delegatee.purgeOrphanedUsers();
+        assertResultMessages(result, ImmutableMap.of(
+                "third", "ERR",
+                "forth", "ERR"));
+        assertFalse(s.hasPendingChanges());
+    }
+
+    private static final class ThrowingSession implements JackrabbitSession {
+
+        private JackrabbitSession base;
+
+        private ThrowingSession(@Nonnull JackrabbitSession session) {
+            this.base = session;
+        }
+        @Override
+        public boolean hasPermission(@Nonnull String s, @Nonnull String... strings) throws RepositoryException {
+            return base.hasPermission(s, strings);
+        }
+
+        @Override
+        public PrincipalManager getPrincipalManager() throws RepositoryException {
+            return base.getPrincipalManager();
+        }
+
+        @Override
+        public UserManager getUserManager() throws RepositoryException {
+            return base.getUserManager();
+        }
+
+        @Override
+        public Item getItemOrNull(String s) throws RepositoryException {
+            return base.getItemOrNull(s);
+        }
+
+        @Override
+        public Property getPropertyOrNull(String s) throws RepositoryException {
+            return base.getPropertyOrNull(s);
+        }
+
+        @Override
+        public Node getNodeOrNull(String s) throws RepositoryException {
+            return getNodeOrNull(s);
+        }
+
+        @Override
+        public Repository getRepository() {
+            return base.getRepository();
+        }
+
+        @Override
+        public String getUserID() {
+            return base.getUserID();
+        }
+
+        @Override
+        public String[] getAttributeNames() {
+            return base.getAttributeNames();
+        }
+
+        @Override
+        public Object getAttribute(String name) {
+            return base.getAttribute(name);
+        }
+
+        @Override
+        public Workspace getWorkspace() {
+            return base.getWorkspace();
+        }
+
+        @Override
+        public Node getRootNode() throws RepositoryException {
+            return base.getRootNode();
+        }
+
+        @Override
+        public Session impersonate(Credentials credentials) throws RepositoryException {
+            return base.impersonate(credentials);
+        }
+
+        @Override
+        public Node getNodeByUUID(String uuid) throws RepositoryException {
+            return base.getNodeByUUID(uuid);
+        }
+
+        @Override
+        public Node getNodeByIdentifier(String id) throws RepositoryException {
+            return base.getNodeByIdentifier(id);
+        }
+
+        @Override
+        public Item getItem(String absPath) throws RepositoryException {
+            return base.getItem(absPath);
+        }
+
+        @Override
+        public Node getNode(String absPath) throws RepositoryException {
+            return base.getNode(absPath);
+        }
+
+        @Override
+        public Property getProperty(String absPath) throws RepositoryException {
+            return base.getProperty(absPath);
+        }
+
+        @Override
+        public boolean itemExists(String absPath) throws RepositoryException {
+            return base.itemExists(absPath);
+        }
+
+        @Override
+        public boolean nodeExists(String absPath) throws RepositoryException {
+            return base.nodeExists(absPath);
+        }
+
+        @Override
+        public boolean propertyExists(String absPath) throws RepositoryException {
+            return base.propertyExists(absPath);
+        }
+
+        @Override
+        public void move(String srcAbsPath, String destAbsPath) throws RepositoryException {
+            base.move(srcAbsPath, destAbsPath);
+        }
+
+        @Override
+        public void removeItem(String absPath) throws RepositoryException {
+            base.removeItem(absPath);
+        }
+
+        @Override
+        public void save() throws RepositoryException {
+            throw new RepositoryException();
+
+        }
+
+        @Override
+        public void refresh(boolean keepChanges) throws RepositoryException {
+            base.refresh(keepChanges);
+        }
+
+        @Override
+        public boolean hasPendingChanges() throws RepositoryException {
+            return base.hasPendingChanges();
+        }
+
+        @Override
+        public ValueFactory getValueFactory() throws RepositoryException {
+            return base.getValueFactory();
+        }
+
+        @Override
+        public boolean hasPermission(String absPath, String actions) throws RepositoryException {
+            return base.hasPermission(absPath, actions);
+        }
+
+        @Override
+        public void checkPermission(String absPath, String actions) throws AccessControlException, RepositoryException {
+            base.checkPermission(absPath, actions);
+        }
+
+        @Override
+        public boolean hasCapability(String methodName, Object target, Object[] arguments) throws RepositoryException {
+            return base.hasCapability(methodName, target, arguments);
+        }
+
+        @Override
+        public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws RepositoryException {
+            return base.getImportContentHandler(parentAbsPath, uuidBehavior);
+        }
+
+        @Override
+        public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, RepositoryException {
+            base.importXML(parentAbsPath, in, uuidBehavior);
+        }
+
+        @Override
+        public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws SAXException, RepositoryException {
+            base.exportSystemView(absPath, contentHandler, skipBinary, noRecurse);
+        }
+
+        @Override
+        public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException, RepositoryException {
+            base.exportSystemView(absPath, out, skipBinary, noRecurse);
+        }
+
+        @Override
+        public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws SAXException, RepositoryException {
+            base.exportDocumentView(absPath, contentHandler, skipBinary, noRecurse);
+        }
+
+        @Override
+        public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException, RepositoryException {
+            base.exportDocumentView(absPath, out, skipBinary, noRecurse);
+        }
+
+        @Override
+        public void setNamespacePrefix(String prefix, String uri) throws RepositoryException {
+            base.setNamespacePrefix(prefix, uri);
+        }
+
+        @Override
+        public String[] getNamespacePrefixes() throws RepositoryException {
+            return base.getNamespacePrefixes();
+        }
+
+        @Override
+        public String getNamespaceURI(String prefix) throws RepositoryException {
+            return base.getNamespaceURI(prefix);
+        }
+
+        @Override
+        public String getNamespacePrefix(String uri) throws RepositoryException {
+            return base.getNamespacePrefix(uri);
+        }
+
+        @Override
+        public void logout() {
+            base.logout();
+        }
+
+        @Override
+        public boolean isLive() {
+            return base.isLive();
+        }
+
+        @Override
+        public void addLockToken(String lt) {
+            base.addLockToken(lt);
+        }
+
+        @Override
+        public String[] getLockTokens() {
+            return base.getLockTokens();
+        }
+
+        @Override
+        public void removeLockToken(String lt) {
+            base.removeLockToken(lt);
+        }
+
+        @Override
+        public AccessControlManager getAccessControlManager() throws RepositoryException {
+            return base.getAccessControlManager();
+        }
+
+        @Override
+        public RetentionManager getRetentionManager() throws RepositoryException {
+            return base.getRetentionManager();
+        }
+    }
+}
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImplTest.java?rev=1746975&r1=1746974&r2=1746975&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImplTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SyncMBeanImplTest.java Mon Jun  6 09:10:48 2016
@@ -19,28 +19,17 @@ package org.apache.jackrabbit.oak.spi.se
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
 import javax.jcr.ValueFactory;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Sets;
-import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.oak.jcr.Jcr;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
@@ -55,13 +44,10 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
-import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
@@ -70,41 +56,18 @@ import static org.junit.Assert.assertNul
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-public class SyncMBeanImplTest {
+public class SyncMBeanImplTest extends AbstractJmxTest {
 
     private static final String SYNC_NAME = "testSyncName";
 
-    private static Repository REPOSITORY;
-
-    private ExternalIdentityProvider idp;
-    private ExternalIdentityProvider foreignIDP;
-    private DefaultSyncConfig syncConfig;
     private SyncMBeanImpl syncMBean;
 
     private SyncManager syncMgr;
     private ExternalIdentityProviderManager idpMgr;
 
-    private Session session;
-    private UserManager userManager;
-    private Set<String> ids;
-
-    @BeforeClass
-    public static void beforeClass() {
-        REPOSITORY = new Jcr().createRepository();
-    }
-
     @Before
     public void before() throws Exception {
-        idp = new TestIdentityProvider();
-        foreignIDP = new TestIdentityProvider() {
-            @Nonnull
-            public String getName() {
-                return "anotherIDP";
-            }
-
-        };
-        syncConfig = new DefaultSyncConfig();
-        syncConfig.user().setMembershipNestingDepth(1);
+        super.before();
 
         syncMgr = new SyncManager() {
             @CheckForNull
@@ -133,93 +96,6 @@ public class SyncMBeanImplTest {
             }
         };
         syncMBean = new SyncMBeanImpl(REPOSITORY, syncMgr, SYNC_NAME, idpMgr, idp.getName());
-
-        session = REPOSITORY.login(new SimpleCredentials("admin", "admin".toCharArray()));
-        if (!(session instanceof JackrabbitSession)) {
-            throw new IllegalStateException();
-        } else {
-            userManager = ((JackrabbitSession) session).getUserManager();
-        }
-        ids = Sets.newHashSet(getAllAuthorizableIds(userManager));
-    }
-
-    @After
-    public void after() throws Exception {
-        try {
-            session.refresh(false);
-            Iterator<String> iter = getAllAuthorizableIds(userManager);
-            while (iter.hasNext()) {
-                String id = iter.next();
-                if (!ids.remove(id)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    if (a != null) {
-                        a.remove();
-                    }
-                }
-            }
-            session.save();
-        } finally {
-            session.logout();
-        }
-    }
-
-    private static Iterator<String> getAllAuthorizableIds(@Nonnull UserManager userManager) throws Exception {
-        Iterator<Authorizable> iter = userManager.findAuthorizables("jcr:primaryType", null);
-        return Iterators.filter(Iterators.transform(iter, new Function<Authorizable, String>() {
-            @Nullable
-            @Override
-            public String apply(Authorizable input) {
-                try {
-                    if (input != null) {
-                        return input.getID();
-                    }
-                } catch (RepositoryException e) {
-                    // failed to retrieve ID
-                }
-                return null;
-            }
-        }), Predicates.notNull());
-    }
-
-    private static void assertResultMessages(@Nonnull String[] resultMessages, String uid, @Nonnull String expectedOperation) {
-        assertResultMessages(resultMessages, ImmutableMap.of(uid, expectedOperation));
-    }
-
-    private static void assertResultMessages(@Nonnull String[] resultMessages, @Nonnull Map<String, String> expected) {
-        assertEquals(expected.size(), resultMessages.length);
-        for (int i = 0; i < resultMessages.length; i++) {
-            String rm = resultMessages[i];
-            String op = rm.substring(rm.indexOf(":") + 2, rm.indexOf("\","));
-
-            int index = rm.indexOf("uid:\"") + 5;
-            String uid = rm.substring(index, rm.indexOf("\",", index));
-
-            assertTrue(expected.containsKey(uid));
-            assertEquals(expected.get(uid), op);
-        }
-    }
-
-    private static void assertSync(@Nonnull ExternalIdentity ei, @Nonnull UserManager userManager) throws Exception {
-        Authorizable authorizable;
-        if (ei instanceof ExternalUser) {
-            authorizable = userManager.getAuthorizable(ei.getId(), User.class);
-        } else {
-            authorizable = userManager.getAuthorizable(ei.getId(), Group.class);
-        }
-        assertNotNull(ei.getId(), authorizable);
-        assertEquals(ei.getId(), authorizable.getID());
-        assertEquals(ei.getExternalId(), ExternalIdentityRef.fromString(authorizable.getProperty(DefaultSyncContext.REP_EXTERNAL_ID)[0].getString()));
-    }
-
-    private SyncResult sync(@Nonnull ExternalIdentityProvider idp, @Nonnull String id, boolean isGroup) throws Exception {
-        return sync((isGroup) ? idp.getGroup(id) : idp.getUser(id), idp);
-    }
-
-    private SyncResult sync(@Nonnull ExternalIdentity externalIdentity, @Nonnull ExternalIdentityProvider idp) throws Exception {
-        SyncContext ctx = new DefaultSyncContext(syncConfig, idp, userManager, session.getValueFactory());
-        SyncResult res = ctx.sync(externalIdentity);
-        session.save();
-        return res;
     }
 
     private Map<String, String> getExpectedUserResult(String expectedOp, boolean includeGroups) throws ExternalIdentityException {