"port" tools from master

Project: http://git-wip-us.apache.org/repos/asf/usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/usergrid/commit/e5ebb375
Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/e5ebb375
Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/e5ebb375

Branch: refs/heads/USERGRID-1047
Commit: e5ebb375386f34b2ad8883c28701500b59208a81
Parents: 4e048f3
Author: Dave Johnson <snoopd...@apache.org>
Authored: Fri Jan 22 09:31:06 2016 -0500
Committer: Dave Johnson <snoopd...@apache.org>
Committed: Fri Jan 22 09:31:06 2016 -0500

----------------------------------------------------------------------
 .../usergrid/management/ManagementService.java  |   4 +-
 .../cassandra/ManagementServiceImpl.java        |  10 +-
 .../usergrid/tools/DryRunUserOrgManager.java    |  62 +++
 .../usergrid/tools/DuplicateAdminRepair.java    | 293 +++++++++++++
 .../usergrid/tools/DuplicateOrgRepair.java      | 421 +++++++++++++++++++
 .../org/apache/usergrid/tools/ExportAdmins.java |  38 +-
 .../apache/usergrid/tools/UserOrgInterface.java | 173 ++++++++
 .../apache/usergrid/tools/UserOrgManager.java   | 389 +++++++++++++++++
 .../apache/usergrid/tools/ExportAppTest.java    |   4 -
 9 files changed, 1372 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
 
b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
index cf2924b..0851838 100644
--- 
a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
+++ 
b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
@@ -229,7 +229,9 @@ public interface ManagementService {
                                           String objectType, String 
objectName, String title, String content )
             throws Exception;
 
-    public void removeAdminUserFromOrganization( UUID userId, UUID 
organizationId ) throws Exception;
+    public void removeAdminUserFromOrganization(UUID userId, UUID 
organizationId ) throws Exception;
+
+    public void removeAdminUserFromOrganization(UUID userId, UUID 
organizationId, boolean force ) throws Exception;
 
     public void removeOrganizationApplication( UUID organizationId, UUID 
applicationId ) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
 
b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
index f252705..fe8152d 100644
--- 
a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
+++ 
b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
@@ -1585,7 +1585,13 @@ public class ManagementServiceImpl implements 
ManagementService {
 
 
     @Override
-    public void removeAdminUserFromOrganization( UUID userId, UUID 
organizationId ) throws Exception {
+    public void removeAdminUserFromOrganization(UUID userId, UUID 
organizationId ) throws Exception {
+        removeAdminUserFromOrganization( userId, organizationId, false );
+    }
+
+
+    @Override
+    public void removeAdminUserFromOrganization(UUID userId, UUID 
organizationId, boolean force) throws Exception {
 
         if ( ( userId == null ) || ( organizationId == null ) ) {
             return;
@@ -1594,7 +1600,7 @@ public class ManagementServiceImpl implements 
ManagementService {
         EntityManager em = emf.getEntityManager( smf.getManagementAppId() );
 
         try {
-            if ( em.getCollection( new SimpleEntityRef( Group.ENTITY_TYPE, 
organizationId ), "users", null, 2,
+            if ( !force && em.getCollection( new SimpleEntityRef( 
Group.ENTITY_TYPE, organizationId ), "users", null, 2,
                     Level.IDS, false ).size() <= 1 ) {
                 throw new Exception();
             }

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/DryRunUserOrgManager.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/DryRunUserOrgManager.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DryRunUserOrgManager.java
new file mode 100644
index 0000000..966b18c
--- /dev/null
+++ 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DryRunUserOrgManager.java
@@ -0,0 +1,62 @@
+/*
+ * 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.usergrid.tools;
+
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.persistence.EntityManagerFactory;
+
+import java.util.UUID;
+
+class DryRunUserOrgManager extends UserOrgManager {
+
+    public DryRunUserOrgManager(EntityManagerFactory emf, ManagementService 
managementService) {
+        super( emf, managementService );
+    }
+
+    @Override
+    public void removeUserFromOrg(OrgUser user, Org org) throws Exception {
+    }
+
+    @Override
+    public void addUserToOrg(OrgUser user, Org org) throws Exception {
+    }
+
+    @Override
+    public void addAppToOrg(UUID appId, Org org) throws Exception {
+    }
+
+    @Override
+    public void removeOrgUser(OrgUser orgUser) {
+    }
+
+    @Override
+    public void updateOrgUser(OrgUser targetUserEntity) {
+    }
+
+    @Override
+    public void setOrgUserName(OrgUser other, String newUserName) {
+
+    }
+
+    @Override
+    public void removeAppFromOrg(UUID appId, Org org) throws Exception {
+    }
+
+    @Override
+    public void removeOrg(Org keeper, Org duplicate) throws Exception {
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateAdminRepair.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateAdminRepair.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateAdminRepair.java
new file mode 100644
index 0000000..172199c
--- /dev/null
+++ 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateAdminRepair.java
@@ -0,0 +1,293 @@
+/*
+ * 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.usergrid.tools;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.persistence.EntityManagerFactory;
+import rx.functions.Action1;
+
+import java.util.*;
+
+import static org.apache.usergrid.tools.UserOrgInterface.Org;
+import static org.apache.usergrid.tools.UserOrgInterface.OrgUser;
+
+
+/**
+ * Find duplicate admin users, delete the one that is not indexed.
+ */
+public class DuplicateAdminRepair extends ToolBase {
+
+    UserOrgInterface           manager = null;
+
+    static final String        THREADS_ARG_NAME = "threads";
+
+    int                        threadCount = 5;
+
+    static final String        DRYRUN_ARG_NAME = "dryrun";
+
+    boolean                    dryRun = false;
+
+    Multimap<String, OrgUser>  emails = HashMultimap.create();
+
+    Multimap<String, OrgUser>  usernames = HashMultimap.create();
+
+    boolean                    testing = false;
+
+
+    DuplicateAdminRepair() {
+        super();
+    }
+
+    DuplicateAdminRepair(EntityManagerFactory emf, ManagementService 
managementService ) {
+        this();
+        this.emf = emf;
+        this.managementService = managementService;
+    }
+
+
+    @Override
+    @SuppressWarnings("static-access")
+    public Options createOptions() {
+
+        Options options = super.createOptions();
+
+        Option dryRunOption = OptionBuilder.hasArg()
+            .withType(Boolean.TRUE)
+            .withDescription( "-" + DRYRUN_ARG_NAME + " true to print what 
tool would do and do not alter data.")
+            .create( DRYRUN_ARG_NAME );
+        options.addOption( dryRunOption );
+
+        Option writeThreadsOption = OptionBuilder.hasArg()
+            .withType(0)
+            .withDescription( "Write Threads -" + THREADS_ARG_NAME )
+            .create(THREADS_ARG_NAME);
+        options.addOption( writeThreadsOption );
+
+        return options;
+    }
+
+
+    public UserOrgManager createNewRepairManager() {
+        return new UserOrgManager( emf, managementService );
+    }
+
+
+    @Override
+    public void runTool(CommandLine line) throws Exception {
+
+        startSpring();
+        setVerbose( line );
+
+        if (StringUtils.isNotEmpty( line.getOptionValue( THREADS_ARG_NAME ) )) 
{
+            try {
+                threadCount = Integer.parseInt( line.getOptionValue( 
THREADS_ARG_NAME ) );
+            } catch (NumberFormatException nfe) {
+                logger.error( "-" + THREADS_ARG_NAME + " must be specified as 
an integer. Aborting..." );
+                return;
+            }
+        }
+
+        if ( StringUtils.isNotEmpty( line.getOptionValue( DRYRUN_ARG_NAME ) )) 
{
+            dryRun = Boolean.parseBoolean( line.getOptionValue( 
DRYRUN_ARG_NAME ));
+        }
+
+        if ( manager == null ) { // we use a special manager when mockTesting
+            if (dryRun) {
+                manager = new DryRunUserOrgManager( emf, managementService );
+            } else {
+                manager = new UserOrgManager( emf, managementService );
+            }
+        }
+
+        logger.info( "DuplicateAdminUserRepair tool starting up... manager: " 
+ manager.getClass().getSimpleName() );
+
+        // build multi-map of users by email and users by name
+
+        manager.getUsers().doOnNext( new Action1<OrgUser>() {
+            @Override
+            public void call( OrgUser user ) {
+
+                if (user.getUsername() == null) {
+                    logger.warn( "User {} has no username", user.getId() );
+                    return;
+                }
+                if (user.getEmail() == null) {
+                    logger.warn( "User {} has no email", user.getId() );
+                    return;
+                }
+                emails.put( user.getEmail().toLowerCase(), user );
+                usernames.put( user.getUsername().toLowerCase(), user );
+
+            }
+        } ).toBlocking().lastOrDefault( null );
+
+        for ( String username : usernames.keySet() ) {
+            Collection<OrgUser> users = usernames.get( username );
+
+            if ( users.size() > 1 ) {
+                logger.info( "Found multiple users with the username {}", 
username );
+
+                // force the username to be reset to the user's email
+                resolveUsernameConflicts( username, users );
+            }
+        }
+
+        for ( String email : emails.keySet() ) {
+
+            // go through a set of users with duplicate emails
+            Collection<OrgUser> usersWithDupEmails = emails.get( email );
+
+            if ( usersWithDupEmails.size() > 1 ) {
+
+                // get the user that is in the users-by-email index, it's the 
keeper
+                OrgUser indexedUser = manager.lookupOrgUserByEmail( email );
+
+                if ( indexedUser == null ) {
+
+                    // no user is indexed with that email, pick oldest as 
keeper
+                    List<OrgUser> tempUsers = new ArrayList<OrgUser>( 
usersWithDupEmails );
+                    Collections.sort( tempUsers );
+                    OrgUser oldestOfLot = tempUsers.get( 0 );
+
+                    logger.warn( "Could not load target user by email {}, 
loading by UUID {} instead", email, oldestOfLot );
+                    indexedUser = oldestOfLot;
+                }
+
+                logger.warn( "Found multiple admins with the email {}.  
Retaining uuid {}", email, indexedUser.getId() );
+
+                for ( OrgUser orgUser : usersWithDupEmails ) {
+                    if ( !orgUser.getId().equals( indexedUser.getId() )) {
+                        mergeAdmins( orgUser, indexedUser );
+                    }
+                }
+
+                // force the index update after all other admins have been 
merged
+                if ( dryRun ) {
+                    logger.info("Would force re-index of 'keeper' user {}:{}",
+                        indexedUser.getUsername(), indexedUser.getId());
+                } else {
+                    logger.info( "Forcing re-index of admin with email {} and 
id {}", email, indexedUser.getId());
+                    manager.updateOrgUser( indexedUser );
+                }
+            }
+        }
+
+        logger.info( "Repair complete" );
+    }
+
+
+    /**
+     * When our usernames are equal, we need to check if our emails are equal. 
If they're not, we need to change the one
+     * that DOES NOT get returned on a lookup by username
+     */
+    private void resolveUsernameConflicts( String userName, 
Collection<OrgUser> users ) throws Exception {
+
+        // lookup the admin id
+        OrgUser existing = manager.lookupOrgUserByUsername( userName );
+
+        if ( existing == null ) {
+            logger.warn( "Could not determine an admin for colliding username 
'{}'.  Skipping", userName );
+            return;
+        }
+
+        users.remove( existing );
+
+        boolean collision = false;
+
+        for ( OrgUser other : users ) {
+
+            // same username and email, these will be merged later in the 
process,
+            // skip it
+            if ( other != null && other.getEmail() != null && 
other.getEmail().equals( existing.getEmail() ) ) {
+                logger.info(
+                    "Users with the same username '{}' have the same email 
'{}'. This will be resolved later in "
+                        + "the process, skipping", userName, 
existing.getEmail() );
+                continue;
+            }
+
+            // if we get here, the emails do not match, but the usernames do. 
Force
+            // both usernames to emails
+            collision = true;
+
+            setUserName(other, other.getEmail() );
+        }
+
+        if ( collision ) {
+            setUserName(existing, existing.getEmail() );
+        }
+    }
+
+
+    /** Set the username to the one provided, if we can't due to duplicate 
property issues, we fall back to user+uuid */
+    private void setUserName( OrgUser other, String newUserName ) throws 
Exception {
+
+        if ( dryRun ) {
+            logger.info("Would rename user {}:{} to {}", new Object[] {
+                other.getUsername(), other.getId(), newUserName });
+        } else {
+            logger.info( "Setting username to {} for user {}:{}", new Object[] 
{
+                newUserName, other.getUsername(), other.getId() } );
+
+            manager.setOrgUserName( other, newUserName );
+        }
+    }
+
+
+    /** Merge the source admin to the target admin by copying oranizations. 
Then deletes the source admin */
+    private void mergeAdmins( OrgUser sourceUser, OrgUser targetUser ) throws 
Exception {
+
+        logger.info("---> Merging user {}:{} into {}:{}", new Object[] {
+            sourceUser.getEmail(), sourceUser.getId(), targetUser.getEmail(), 
targetUser.getId()
+        } );
+
+        Set<Org> sourceOrgs = manager.getUsersOrgs( sourceUser );
+
+        for ( Org org : sourceOrgs ) {
+
+            if ( dryRun ) {
+                logger.info( "Would add organization {}:{} to admin with email 
{} and id {}",
+                    new Object[] { org.getName(), org.getId(), 
targetUser.getEmail(), targetUser.getId() } );
+
+            } else {
+                logger.info( "Adding organization {}:{} to admin with email {} 
and id {}",
+                    new Object[] { org.getName(), org.getId(), 
targetUser.getEmail(), targetUser.getId() } );
+
+                // add targetUser to sourceUser's org
+                manager.addUserToOrg( targetUser, org );
+            }
+        }
+
+        if ( dryRun ) {
+            logger.info( "Would remove admin with email {} and id {} from 
system",
+                sourceUser.getEmail(), sourceUser.getId() );
+
+        } else {
+            logger.info( "Removing admin with email {} and id {} from system",
+                sourceUser.getEmail(), sourceUser.getId() );
+
+            manager.removeOrgUser( sourceUser );
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateOrgRepair.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateOrgRepair.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateOrgRepair.java
new file mode 100644
index 0000000..0f93ed4
--- /dev/null
+++ 
b/stack/tools/src/main/java/org/apache/usergrid/tools/DuplicateOrgRepair.java
@@ -0,0 +1,421 @@
+/*
+ * 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.usergrid.tools;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.persistence.*;
+import rx.Scheduler;
+import rx.functions.Action1;
+import rx.schedulers.Schedulers;
+
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.apache.usergrid.tools.UserOrgInterface.Org;
+import static org.apache.usergrid.tools.UserOrgInterface.OrgUser;
+
+/**
+ * Find duplicate orgs, delete all but oldest of each and assign users to it.
+ */
+public class DuplicateOrgRepair extends ToolBase {
+
+    UserOrgInterface        manager = null;
+
+    Map<String, Set<Org>>   orgsByName = new HashMap<String, Set<Org>>();
+
+    Map<UUID, Org>          orgsById = new HashMap<UUID, Org>();
+
+    Map<OrgUser, Set<Org>>  orgsByUser = new HashMap<OrgUser, Set<Org>>();
+
+    Map<String, Set<Org>>   duplicatesByName = new HashMap<String, Set<Org>>();
+
+    static final String     THREADS_ARG_NAME = "threads";
+
+    int                     threadCount = 5;
+
+    static final String     DRYRUN_ARG_NAME = "dryrun";
+
+    boolean                 dryRun = false;
+
+    static final String     NO_AUGMENT_ARG_NAME = "noaugment";
+
+    boolean                 noAugment = false;
+
+    static final String     ORG1_ID = "org1";
+
+    static final String     ORG2_ID = "org2";
+
+    boolean                 testing = false;
+
+
+    DuplicateOrgRepair() {
+        super();
+    }
+
+    DuplicateOrgRepair( EntityManagerFactory emf, ManagementService 
managementService ) {
+        this();
+        this.emf = emf;
+        this.managementService = managementService;
+    }
+
+    @Override
+    @SuppressWarnings("static-access")
+    public Options createOptions() {
+
+        Options options = super.createOptions();
+
+        Option dryRunOption = OptionBuilder.hasArg()
+            .withType(Boolean.TRUE)
+            .withDescription( "-" + DRYRUN_ARG_NAME + " true to print what 
tool would do and do not alter data.")
+            .create( DRYRUN_ARG_NAME );
+        options.addOption( dryRunOption );
+
+        Option noAugmentRunOption = OptionBuilder.hasArg()
+            .withType(Boolean.TRUE)
+            .withDescription( "-" + NO_AUGMENT_ARG_NAME + " true to skip the 
augment step.")
+            .create( NO_AUGMENT_ARG_NAME );
+        options.addOption( noAugmentRunOption );
+
+
+        Option writeThreadsOption = OptionBuilder.hasArg()
+            .withType(0)
+            .withDescription( "Write Threads -" + THREADS_ARG_NAME )
+            .create(THREADS_ARG_NAME);
+        options.addOption( writeThreadsOption );
+
+        Option org1Option = OptionBuilder.hasArg()
+            .withType(0)
+            .withDescription( "Duplicate org #1 id -" + ORG1_ID)
+            .create(ORG1_ID);
+        options.addOption( org1Option );
+
+        Option org2Option = OptionBuilder.hasArg()
+            .withType(0)
+            .withDescription( "Duplicate org #2 id -" + ORG2_ID)
+            .create(ORG2_ID);
+        options.addOption( org2Option );
+
+        return options;
+    }
+
+    @Override
+    public void runTool(CommandLine line) throws Exception {
+
+        startSpring();
+        setVerbose( line );
+
+        UUID org1uuid = null;
+        UUID org2uuid = null;
+
+        String org1string = line.getOptionValue( ORG1_ID );
+        String org2string = line.getOptionValue( ORG2_ID );
+
+        if ( org1string != null && org2string == null ) {
+            logger.error("- if {} is specified you must also specify {} and 
vice-versa", ORG1_ID, ORG2_ID);
+            return;
+
+        } else if ( org2string != null && org1string == null ) {
+            logger.error("- if {} is specified you must also specify {} and 
vice-versa", ORG2_ID, ORG1_ID);
+            return;
+
+        } else if ( org1string != null && org2string != null ) {
+
+            try {
+                org1uuid = UUID.fromString( org1string );
+                org2uuid = UUID.fromString( org2string );
+            } catch (Exception e) {
+                logger.error("{} and {} must be specified as UUIDs", ORG1_ID, 
ORG2_ID);
+                return;
+            }
+        }
+
+        if (StringUtils.isNotEmpty( line.getOptionValue( THREADS_ARG_NAME ) )) 
{
+            try {
+                threadCount = Integer.parseInt( line.getOptionValue( 
THREADS_ARG_NAME ) );
+            } catch (NumberFormatException nfe) {
+                logger.error( "-" + THREADS_ARG_NAME + " must be specified as 
an integer. Aborting..." );
+                return;
+            }
+        }
+
+        if ( StringUtils.isNotEmpty( line.getOptionValue( DRYRUN_ARG_NAME ) )) 
{
+            dryRun = Boolean.parseBoolean( line.getOptionValue( 
DRYRUN_ARG_NAME ));
+        }
+
+        if ( StringUtils.isNotEmpty( line.getOptionValue( NO_AUGMENT_ARG_NAME 
) )) {
+            noAugment = Boolean.parseBoolean( line.getOptionValue( 
NO_AUGMENT_ARG_NAME ));
+        }
+
+        if ( manager == null ) { // we use a special manager when mockTesting
+            if (dryRun) {
+                manager = new DryRunUserOrgManager( emf, managementService );
+            } else {
+                manager = new UserOrgManager( emf, managementService );
+            }
+        }
+
+        logger.info( "DuplicateOrgRepair tool starting up... manager: " + 
manager.getClass().getSimpleName() );
+
+        if ( org1uuid != null && org2uuid != null ) {
+
+            Org org1 = manager.getOrg( org1uuid );
+            Org org2 = manager.getOrg( org2uuid );
+
+            if ( org1.getName().equalsIgnoreCase( org2.getName() )) {
+                buildOrgMaps( org1, org2 );
+            } else {
+                logger.error("org1 and org2 do not have same duplicate name");
+                return;
+            }
+
+        } else {
+            buildOrgMaps();
+        }
+
+        if ( noAugment == false ) {
+            augmentUserOrgsMap();
+        }
+
+        manager.logDuplicates( duplicatesByName );
+
+        mergeDuplicateOrgs();
+
+        removeDuplicateOrgs();
+
+        logger.info( "DuplicateOrgRepair work is done!");
+    }
+
+
+    public UserOrgManager createNewRepairManager() {
+        return new UserOrgManager( emf, managementService );
+    }
+
+
+    private void buildOrgMaps(Org org1, Org org2) {
+
+        Set<Org> orgs = new HashSet<Org>();
+        orgs.add( org1 );
+        orgs.add( org2 );
+        orgsByName.put(       org1.getName().toLowerCase(), orgs );
+        duplicatesByName.put( org1.getName().toLowerCase(), orgs );
+
+        orgsById.put( org1.getId(), org1 );
+        orgsById.put( org2.getId(), org2 );
+
+        for ( Org org : orgs ) {
+            try {
+                Set<OrgUser> orgUsers = manager.getOrgUsers( org );
+                for (OrgUser user : orgUsers) {
+                    Set<Org> usersOrgs = orgsByUser.get( user );
+                    if (usersOrgs == null) {
+                        usersOrgs = new HashSet<Org>();
+                        orgsByUser.put( user, usersOrgs );
+                    }
+                    usersOrgs.add( org );
+                }
+            } catch (Exception e) {
+                logger.error( "Error getting users for org {}:{}", 
org.getName(), org.getId() );
+                logger.error( "Stack trace is: ", e );
+            }
+        }
+
+    }
+
+
+    /**
+     * build map of orgs by name, orgs by id, orgs by user and duplicate orgs 
by name
+     */
+    private void buildOrgMaps() throws Exception {
+
+        manager.getOrgs().doOnNext( new Action1<Org>() {
+            @Override
+            public void call(Org org) {
+
+                // orgs by name and duplicate orgs by name maps
+
+                Set<Org> orgs = orgsByName.get( org.getName().toLowerCase() );
+                if (orgs == null) {
+                    orgs = new HashSet<Org>();
+                    orgsByName.put( org.getName().toLowerCase(), orgs );
+                } else {
+                    duplicatesByName.put( org.getName().toLowerCase(), orgs );
+                }
+                orgs.add( org );
+
+                orgsById.put( org.getId(), org );
+
+                // orgs by user map, created via org -> user connections
+
+                try {
+                    Set<OrgUser> orgUsers = manager.getOrgUsers( org );
+                    for ( OrgUser user : orgUsers ) {
+                        Set<Org> usersOrgs = orgsByUser.get( user );
+                        if (usersOrgs == null) {
+                            usersOrgs = new HashSet<Org>();
+                            orgsByUser.put( user, usersOrgs );
+                        }
+                        usersOrgs.add( org );
+                    }
+                } catch (Exception e) {
+                    logger.error("Error getting users for org {}:{}", 
org.getName(), org.getId());
+                    logger.error("Stack trace is: ", e);
+                }
+
+            }
+
+        } ).toBlocking().lastOrDefault( null );
+
+        logger.info( "DuplicateOrgRepair tool built org maps");
+    }
+
+
+    /**
+     * augment user orgs map via user -> org connections
+     */
+    private void augmentUserOrgsMap() throws Exception {
+
+        ExecutorService writeThreadPoolExecutor = 
Executors.newFixedThreadPool( threadCount );
+        Scheduler scheduler = Schedulers.from( writeThreadPoolExecutor );
+
+        manager.getUsers().doOnNext( new Action1<OrgUser>() {
+            @Override
+            public void call(OrgUser user) {
+                try {
+                    Set<Org> connectedToOrgs = manager.getUsersOrgs(user);
+                    Set<Org> usersOrgs = orgsByUser.get(user);
+                    if ( usersOrgs == null ) {
+                        usersOrgs = new HashSet<Org>();
+                    }
+                    for ( Org org : connectedToOrgs ) {
+                        if (!usersOrgs.contains(org)) {
+                            usersOrgs.add(org);
+                        }
+                    }
+
+                } catch (Exception e) {
+                    logger.error("Error getting orgs for user {}:{}", 
user.getUsername(), user.getId());
+                    logger.error("Stack trace is: ", e);
+                }
+            }
+        } ).subscribeOn( scheduler ).toBlocking().lastOrDefault( null );
+
+        logger.info( "DuplicateOrgRepair augmented user orgs map");
+    }
+
+
+    /**
+     * For each duplicate name, pick best org and merge apps and users into it
+     */
+    private void mergeDuplicateOrgs() throws Exception {
+
+        for ( String dupName : duplicatesByName.keySet() ) {
+            Set<Org> duplicateOrgs = duplicatesByName.get(dupName);
+            Org bestOrg = manager.selectBest( duplicateOrgs );
+
+            for ( Org org : duplicateOrgs ) {
+
+                if ( !org.equals( bestOrg )) {
+
+                    Set<OrgUser> orgUsers = new HashSet<OrgUser>( 
manager.getOrgUsers( org ) );
+
+                    for (OrgUser user : orgUsers) {
+                        if (dryRun) {
+                            Object[] args = new Object[]{
+                                user.getUsername(), user.getId(), 
bestOrg.getName(), bestOrg.getId()};
+                            logger.info( "Would add user {}:{} to org {}:{}", 
args );
+                            args = new Object[]{
+                                user.getUsername(), user.getId(), 
org.getName(), org.getId()};
+                            logger.info( "Would remove user {}:{}  org {}:{}", 
args );
+                        } else {
+                            try {
+                                manager.addUserToOrg( user, bestOrg );
+                            } catch ( Exception e ) {
+                                Object[] args = new Object[]{
+                                    user.getUsername(), user.getId(), 
bestOrg.getName(), bestOrg.getId()};
+                                logger.error( "Error adding user {}:{} to org 
{}:{}", args );
+                            }
+                            try {
+                                manager.removeUserFromOrg( user, org );
+                            } catch ( Exception e ) {
+                                Object[] args = new Object[]{
+                                    user.getUsername(), user.getId(), 
org.getName(), org.getId()};
+                                logger.info( "Error removing user {}:{}  org 
{}:{}", args );
+                            }
+                        }
+                    }
+
+                    Set<UUID> orgApps = new HashSet<UUID>( manager.getOrgApps( 
org ) );
+
+                    for (UUID appId : orgApps) {
+                        if (dryRun) {
+                            Object[] args = new Object[]{ appId, 
bestOrg.getName(), bestOrg.getId()};
+                            logger.info( "Would add app {} to org {}:{}", args 
);
+                            args = new Object[]{ appId, org.getName(), 
org.getId()};
+                            logger.info( "Would remove app {} org {}:{}", args 
);
+                        } else {
+                            try {
+                                manager.addAppToOrg( appId, bestOrg );
+                            } catch ( Exception e ) {
+                                Object[] args = new Object[]{ appId, 
bestOrg.getName(), bestOrg.getId()};
+                                logger.error( "Error adding app {} to org 
{}:{}", args );
+                            }
+                            try {
+                                manager.removeAppFromOrg( appId, org );
+                            } catch (Exception e  ) {
+                                Object[] args = new Object[]{ appId, 
org.getName(), org.getId()};
+                                logger.info( "Error removing app {} org 
{}:{}", args );
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+
+        logger.info( "DuplicateOrgRepair merged duplicate orgs");
+    }
+
+
+    /**
+     * remove/rename duplicate orgs so they no longer impact operation of 
system
+     */
+    private void removeDuplicateOrgs() throws Exception {
+        for ( String dupName : duplicatesByName.keySet() ) {
+            Set<Org> orgs = duplicatesByName.get( dupName );
+            Org best = manager.selectBest( orgs );
+            for ( Org candidate : orgs ) {
+                if ( !candidate.equals(best) ) {
+                    if ( dryRun ) {
+                        logger.info("Would rename/remove org {}:{}",
+                            new Object[] { candidate.getName(), 
candidate.getId() });
+                    } else {
+                        manager.removeOrg( best, candidate );
+                    }
+                }
+            }
+        }
+
+        logger.info( "DuplicateOrgRepair renamed/removed duplicate orgs");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/ExportAdmins.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/ExportAdmins.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/ExportAdmins.java
index 479d740..070a499 100644
--- a/stack/tools/src/main/java/org/apache/usergrid/tools/ExportAdmins.java
+++ b/stack/tools/src/main/java/org/apache/usergrid/tools/ExportAdmins.java
@@ -16,6 +16,7 @@
  */
 package org.apache.usergrid.tools;
 
+
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import org.apache.commons.cli.CommandLine;
@@ -28,6 +29,7 @@ import org.apache.usergrid.persistence.Entity;
 import org.apache.usergrid.persistence.EntityManager;
 import org.apache.usergrid.persistence.Query;
 import org.apache.usergrid.persistence.Results;
+import org.apache.usergrid.persistence.cassandra.CassandraService;
 import org.apache.usergrid.utils.StringUtils;
 import org.codehaus.jackson.JsonGenerator;
 import org.slf4j.Logger;
@@ -38,6 +40,7 @@ import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
+
 /**
  * Export Admin Users and metadata including organizations and passwords.
  *
@@ -64,11 +67,12 @@ public class ExportAdmins extends ExportingToolBase {
     public static final String ADMIN_USER_METADATA_PREFIX = 
"admin-user-metadata";
 
     // map admin user UUID to list of organizations to which user belongs
-    private Map<UUID, List<Org>> userToOrgsMap = new HashMap<UUID, 
List<Org>>(50000);
+    private Map<UUID, List<Org>> userToOrgsMap = new HashMap<UUID, 
List<Org>>(100000);
 
-    private Map<String, UUID> orgNameToUUID = new HashMap<String, UUID>(50000);
+    private Map<String, UUID> orgNameToUUID = new HashMap<String, 
UUID>(100000);
 
-    private Set<UUID> orgsWritten = new HashSet<UUID>(50000);
+    private Set<UUID> orgsWritten = new HashSet<UUID>(100000);
+    private Set<String> orgsNamesWritten = new HashSet<String>(100000);
 
     private Set<UUID> duplicateOrgs = new HashSet<UUID>();
 
@@ -146,10 +150,12 @@ public class ExportAdmins extends ExportingToolBase {
         // start read queue workers
 
         BlockingQueue<UUID> readQueue = new LinkedBlockingQueue<UUID>();
+        List<AdminUserReader> readers = new ArrayList<AdminUserReader>();
         for (int i = 0; i < readThreadCount; i++) {
             AdminUserReader worker = new AdminUserReader( readQueue, 
writeQueue );
             Thread readerThread = new Thread( worker, "AdminUserReader-" + i );
             readerThread.start();
+            readers.add( worker );
         }
         logger.debug( readThreadCount + " read worker threads started" );
 
@@ -191,7 +197,7 @@ public class ExportAdmins extends ExportingToolBase {
         Options options = super.createOptions();
 
         Option readThreads = OptionBuilder
-                .hasArg().withType(0).withDescription("Read Threads -" + 
READ_THREAD_COUNT).create(READ_THREAD_COUNT);
+            .hasArg().withType(0).withDescription("Read Threads -" + 
READ_THREAD_COUNT).create(READ_THREAD_COUNT);
 
         options.addOption( readThreads );
         return options;
@@ -205,7 +211,7 @@ public class ExportAdmins extends ExportingToolBase {
 
         logger.info( "Building org map" );
 
-        ExecutorService execService = Executors.newFixedThreadPool( 
readThreadCount );
+        ExecutorService execService = Executors.newFixedThreadPool( 
this.readThreadCount );
 
         EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
         String queryString = "select *";
@@ -332,8 +338,8 @@ public class ExportAdmins extends ExportingToolBase {
                     String actionTaken = "Processed";
 
                     if (ignoreInvalidUsers && (task.orgNamesByUuid.isEmpty()
-                            || task.dictionariesByName.isEmpty()
-                            || task.dictionariesByName.get( "credentials" 
).isEmpty())) {
+                        || task.dictionariesByName.isEmpty()
+                        || task.dictionariesByName.get( "credentials" 
).isEmpty())) {
 
                         actionTaken = "Ignored";
 
@@ -342,9 +348,9 @@ public class ExportAdmins extends ExportingToolBase {
                     }
 
                     Map<String, Object> creds = (Map<String, Object>) 
(task.dictionariesByName.isEmpty() ?
-                                                0 : 
task.dictionariesByName.get( "credentials" ));
+                        0 : task.dictionariesByName.get( "credentials" ));
 
-                    logger.error( "{} admin user {}:{}:{} has organizations={} 
dictionaries={} credentials={}",
+                    logger.warn( "{} admin user {}:{}:{} has organizations={} 
dictionaries={} credentials={}",
                         new Object[]{
                             actionTaken,
                             task.adminUser.getProperty( "username" ),
@@ -370,7 +376,7 @@ public class ExportAdmins extends ExportingToolBase {
             Set<String> dictionaries = em.getDictionaries( entity );
 
             if ( dictionaries.isEmpty() ) {
-                logger.error("User {}:{} has no dictionaries", 
task.adminUser.getName(), task.adminUser.getUuid() );
+                logger.warn("User {}:{} has no dictionaries", 
task.adminUser.getName(), task.adminUser.getUuid() );
                 return;
             }
 
@@ -385,7 +391,7 @@ public class ExportAdmins extends ExportingToolBase {
 
             task.orgNamesByUuid = 
managementService.getOrganizationsForAdminUser( task.adminUser.getUuid() );
 
-            List<Org> orgs = userToOrgsMap.get( task.adminUser.getUuid() );
+            List<Org> orgs = userToOrgsMap.get( task.adminUser.getProperty( 
"username" ).toString().toLowerCase() );
 
             if ( orgs != null && task.orgNamesByUuid.size() < orgs.size() ) {
 
@@ -423,12 +429,12 @@ public class ExportAdmins extends ExportingToolBase {
 
             // write one JSON file for management application users
             JsonGenerator usersFile =
-                    getJsonGenerator( createOutputFile( ADMIN_USERS_PREFIX, 
em.getApplication().getName() ) );
+                getJsonGenerator( createOutputFile( ADMIN_USERS_PREFIX, 
em.getApplication().getName() ) );
             usersFile.writeStartArray();
 
             // write one JSON file for metadata: collections, connections and 
dictionaries of those users
             JsonGenerator metadataFile =
-                    getJsonGenerator( createOutputFile( 
ADMIN_USER_METADATA_PREFIX, em.getApplication().getName() ) );
+                getJsonGenerator( createOutputFile( 
ADMIN_USER_METADATA_PREFIX, em.getApplication().getName() ) );
             metadataFile.writeStartObject();
 
             while ( true ) {
@@ -470,7 +476,8 @@ public class ExportAdmins extends ExportingToolBase {
             usersFile.writeEndArray();
             usersFile.close();
 
-            logger.info( "Exported TOTAL {} admin users and {} organizations", 
userCount.get(), orgsWritten.size() );
+            logger.info( "Exported TOTAL {} admin users and {} organizations, 
org names = {}",
+                new Object[] { userCount.get(), orgsWritten.size(), 
orgsNamesWritten.size() } );
         }
 
 
@@ -523,7 +530,9 @@ public class ExportAdmins extends ExportingToolBase {
                 jg.writeEndObject();
 
                 synchronized (orgsWritten) {
+                    logger.info("Exported org {}:{}", uuid, orgs.get(uuid));
                     orgsWritten.add( uuid );
+                    orgsNamesWritten.add( orgs.get(uuid) );
                 }
             }
 
@@ -531,4 +540,3 @@ public class ExportAdmins extends ExportingToolBase {
         }
     }
 }
-

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgInterface.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgInterface.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgInterface.java
new file mode 100644
index 0000000..2debd4d
--- /dev/null
+++ b/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgInterface.java
@@ -0,0 +1,173 @@
+/*
+ * 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.usergrid.tools;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import rx.Observable;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+
+/**
+ * Mockable abstraction of user-org management.
+ */
+interface UserOrgInterface {
+
+    Observable<Org> getOrgs() throws Exception;
+
+    Observable<OrgUser> getUsers() throws Exception;
+
+    Set<Org> getUsersOrgs(OrgUser user) throws Exception;
+
+    Set<OrgUser> getOrgUsers(Org org ) throws Exception;
+
+    void removeOrg(Org keeper, Org duplicate) throws Exception;
+
+    void removeUserFromOrg( OrgUser user, Org org ) throws Exception;
+
+    void addUserToOrg( OrgUser user, Org org ) throws Exception;
+
+    Set<UUID> getOrgApps(Org org) throws Exception;
+
+    void removeAppFromOrg( UUID appId, Org org ) throws Exception;
+
+    void addAppToOrg( UUID appId, Org org ) throws Exception;
+
+    void logDuplicates(Map<String, Set<Org>> duplicatesByName);
+
+    Org getOrg(UUID id ) throws Exception;
+
+    OrgUser getOrgUser(UUID id ) throws Exception;
+
+    OrgUser lookupOrgUserByUsername( String username ) throws Exception;
+
+    OrgUser lookupOrgUserByEmail( String email ) throws Exception;
+
+    void removeOrgUser( OrgUser orgUser ) throws Exception;
+
+    void updateOrgUser(OrgUser targetUserEntity) throws Exception;
+
+    void setOrgUserName(OrgUser other, String newUserName) throws Exception;
+
+    Org selectBest( Set<Org> candidates ) throws Exception;
+
+    class Org implements Comparable<Org> {
+        private UUID id;
+        private String name;
+        private long created;
+        public Object sourceValue;
+
+        public Org( UUID id, String name, long created) {
+            this.id = id;
+            this.name = name;
+            this.created = created;
+            this.created = System.currentTimeMillis();
+        }
+
+        public Org( UUID id, String name) {
+            this( id, name, System.currentTimeMillis());
+        }
+
+        @Override
+        public boolean equals( Object o ) {
+            if ( o instanceof Org ) {
+                Org other = (Org)o;
+                return getId().equals( other.getId() );
+            }
+            return false;
+        }
+
+        @Override
+        public int compareTo(Org o) {
+            return Long.compare( this.created, o.created );
+        }
+
+        public UUID getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public long getCreated() {
+            return created;
+        }
+    }
+
+    class OrgUser implements Comparable<OrgUser> {
+        private UUID id;
+        private String username;
+        private String email;
+        private long created;
+        public Object sourceValue;
+
+        public OrgUser( UUID id, String name, String email, long created ) {
+            this.id = id;
+            this.username = name;
+            this.email = email;
+            this.created = created;
+        }
+
+        public OrgUser( UUID id, String name, String email ) {
+            this( id, name, email, System.currentTimeMillis());
+        }
+
+        public UUID getId() {
+            return id;
+        }
+
+        public String getEmail() {
+            return email;
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+        public void setUsername( String username ) {
+            this.username = username;
+        }
+
+        public long getCreated() {
+            return created;
+        }
+
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == null) { return false; }
+            if (obj == this) { return true; }
+            if (obj.getClass() != getClass()) {
+                return false;
+            }
+            OrgUser rhs = (OrgUser) obj;
+            return new EqualsBuilder().appendSuper(super.equals(obj))
+                .append(id,       rhs.id)
+                .append(username, rhs.username)
+                .append(email,    rhs.email)
+                .append(created,  rhs.created)
+                .isEquals();
+        }
+
+        @Override
+        public int compareTo(OrgUser o) {
+            return Long.compare( this.created, o.created );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgManager.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgManager.java 
b/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgManager.java
new file mode 100644
index 0000000..7f973ce
--- /dev/null
+++ b/stack/tools/src/main/java/org/apache/usergrid/tools/UserOrgManager.java
@@ -0,0 +1,389 @@
+/*
+ * 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.usergrid.tools;
+
+import com.google.common.collect.BiMap;
+import org.apache.usergrid.corepersistence.util.CpNamingUtils;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.management.OrganizationInfo;
+import org.apache.usergrid.management.UserInfo;
+import org.apache.usergrid.persistence.*;
+import org.apache.usergrid.persistence.entities.Group;
+import org.apache.usergrid.persistence.entities.User;
+import 
org.apache.usergrid.persistence.exceptions.DuplicateUniquePropertyExistsException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import rx.Observable;
+import rx.Subscriber;
+
+import java.util.*;
+
+
+class UserOrgManager implements UserOrgInterface {
+
+    static final Logger logger = LoggerFactory.getLogger( UserOrgManager.class 
);
+
+    EntityManagerFactory emf;
+    ManagementService managementService;
+
+    public UserOrgManager(EntityManagerFactory emf, ManagementService 
managementService) {
+        this.emf = emf;
+        this.managementService = managementService;
+    }
+
+    @Override
+    public Observable<Org> getOrgs() throws Exception {
+
+        return Observable.create( new Observable.OnSubscribe<Org>() {
+
+            @Override
+            public void call(Subscriber<? super Org> subscriber) {
+                subscriber.onStart();
+                try {
+                    int count = 0;
+
+                    Query query = new Query();
+                    query.setLimit( ToolBase.MAX_ENTITY_FETCH );
+                    query.setResultsLevel( Query.Level.ALL_PROPERTIES );
+                    EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+                    Results results = em.searchCollection( 
em.getApplicationRef(), "groups", query );
+
+                    while (results.size() > 0) {
+                        for (Entity orgEntity : results.getList()) {
+
+                            Org org = new Org(
+                                orgEntity.getUuid(),
+                                orgEntity.getProperty( "path" ) + "",
+                                orgEntity.getCreated() );
+                            org.sourceValue = orgEntity;
+
+                            subscriber.onNext( org );
+
+                            if (count++ % 1000 == 0) {
+                                logger.info( "Emitted {} orgs", count );
+                            }
+
+                        }
+                        if (results.getCursor() == null) {
+                            break;
+                        }
+                        query.setCursor( results.getCursor() );
+                        results = em.searchCollection( em.getApplicationRef(), 
"groups", query );
+                    }
+
+                } catch (Exception e) {
+                    subscriber.onError( e );
+                }
+                subscriber.onCompleted();
+            }
+        } );
+    }
+
+    @Override
+    public Observable<OrgUser> getUsers() throws Exception {
+
+        return Observable.create( new Observable.OnSubscribe<OrgUser>() {
+
+            @Override
+            public void call(Subscriber<? super OrgUser> subscriber) {
+                subscriber.onStart();
+                try {
+                    int count = 0;
+
+                    Query query = new Query();
+                    query.setLimit( ToolBase.MAX_ENTITY_FETCH );
+                    query.setResultsLevel( Query.Level.ALL_PROPERTIES );
+                    EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+                    Results results = em.searchCollection( 
em.getApplicationRef(), "users", query );
+
+                    while (results.size() > 0) {
+                        for (Entity entity : results.getList()) {
+
+                            OrgUser orgUser = new OrgUser(
+                                entity.getUuid(),
+                                entity.getProperty( "username" ) + "",
+                                entity.getProperty( "email" ) + "",
+                                entity.getCreated()
+                            );
+                            orgUser.sourceValue = entity;
+
+                            subscriber.onNext( orgUser );
+
+                            if (count++ % 1000 == 0) {
+                                logger.info( "Emitted {} users", count );
+                            }
+                        }
+                        if (results.getCursor() == null) {
+                            break;
+                        }
+                        query.setCursor( results.getCursor() );
+                        results = em.searchCollection( em.getApplicationRef(), 
"users", query );
+                    }
+
+                } catch (Exception e) {
+                    subscriber.onError( e );
+                }
+                subscriber.onCompleted();
+            }
+        } );
+    }
+
+    @Override
+    public Set<Org> getUsersOrgs(OrgUser user) throws Exception {
+
+        Set<Org> ret = new HashSet<Org>();
+
+        Map<String, Object> orgData = 
managementService.getAdminUserOrganizationData( user.getId() );
+
+        Map<String, Object> orgs = (Map<String, Object>) orgData.get( 
"organizations" );
+        for (String orgName : orgs.keySet()) {
+
+            Map<String, Object> orgMap = (Map<String, Object>) orgs.get( 
orgName );
+            Group group = managementService.getOrganizationProps(
+                UUID.fromString( orgMap.get( "uuid" ).toString() ) );
+
+            Org org = new Org(
+                group.getUuid(),
+                group.getPath(),
+                group.getCreated()
+            );
+            ret.add( org );
+        }
+
+        return ret;
+    }
+
+
+    @Override
+    public void removeOrg(Org keeper, Org duplicate) throws Exception {
+
+        // rename org so that it is no longer a duplicate
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        em.delete( new SimpleEntityRef( "group", duplicate.getId() ) );
+        logger.info( "Deleted org {}:{}", new Object[]{duplicate.getName(), 
duplicate.getId()} );
+
+        // fix the org name index
+        OrganizationInfo orgInfoKeeper = 
managementService.getOrganizationByUuid( keeper.getId() );
+        try {
+            Entity orgKeeper = em.get( keeper.getId() );
+            em.update( orgKeeper );
+            //managementService.updateOrganizationUniqueIndex( orgInfoKeeper, 
duplicate.getId() );
+            logger.info( "Updated index for keeper {}:{} not dup {}", new 
Object[]{
+                orgInfoKeeper.getName(), orgInfoKeeper.getUuid(), 
duplicate.getId()} );
+
+        } catch (Exception e) {
+            // if there are multiple duplicates this will fail for all but one 
of them. That's OK
+            logger.warn( "Error repairing unique value keeper {} duplicate {}",
+                keeper.getId(), duplicate.getId() );
+        }
+    }
+
+
+    @Override
+    public Set<OrgUser> getOrgUsers(Org org) throws Exception {
+
+        Set<OrgUser> ret = new HashSet<OrgUser>();
+
+        List<UserInfo> userInfos = 
managementService.getAdminUsersForOrganization( org.getId() );
+
+        for (UserInfo userInfo : userInfos) {
+            OrgUser orgUser = new OrgUser( userInfo.getUuid(), 
userInfo.getUsername(), userInfo.getEmail() );
+            ret.add( orgUser );
+        }
+
+        return ret;
+    }
+
+
+    @Override
+    public void removeUserFromOrg(OrgUser user, Org org) throws Exception {
+        // forcefully remove admin user from org
+        managementService.removeAdminUserFromOrganization( user.getId(), 
org.getId(), true );
+        logger.info( "Removed user {}:{} from org {}:{}", new Object[]{
+            user.getUsername(), user.getId(), org.getName(), org.getId()} );
+    }
+
+
+    @Override
+    public void addUserToOrg(OrgUser user, Org org) throws Exception {
+        UserInfo userInfo = managementService.getAdminUserByUsername( 
user.getUsername() );
+        OrganizationInfo orgInfo = managementService.getOrganizationByUuid( 
org.getId() );
+        managementService.addAdminUserToOrganization( userInfo, orgInfo, false 
);
+        logger.info( "Added user {}:{} to org {}:{}", new Object[]{
+            user.getUsername(), user.getId(), org.getName(), org.getId()} );
+    }
+
+
+    @Override
+    public Set<UUID> getOrgApps(Org org) throws Exception {
+        BiMap<UUID, String> apps = 
managementService.getApplicationsForOrganization( org.getId() );
+        return apps.keySet();
+    }
+
+
+    @Override
+    public void removeAppFromOrg(UUID appId, Org org) throws Exception {
+        managementService.removeOrganizationApplication( org.getId(), appId );
+        logger.info( "Removed app {} from org {}:{}", new Object[]{
+            appId, org.getName(), org.getId()} );
+    }
+
+
+    @Override
+    public void addAppToOrg(UUID appId, Org org) throws Exception {
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        Entity appEntity = em.getApplication();
+        managementService.addApplicationToOrganization( org.getId(), appEntity 
);
+        logger.info( "Added app {} to org {}:{}", new Object[]{
+            appId, org.getName(), org.getId()} );
+    }
+
+
+    @Override
+    public void logDuplicates(Map<String, Set<Org>> duplicatesByName) {
+
+        for (String orgName : duplicatesByName.keySet()) {
+            Set<Org> orgs = duplicatesByName.get( orgName );
+            for (Org org : orgs) {
+                Entity orgEntity = (Entity) org.sourceValue;
+
+                StringBuilder sb = new StringBuilder();
+                sb.append( orgEntity.toString() ).append( ", " );
+
+                try {
+                    BiMap<UUID, String> apps =
+                        managementService.getApplicationsForOrganization( 
orgEntity.getUuid() );
+                    String sep = "";
+                    for (UUID uuid : apps.keySet()) {
+                        String appName = apps.get( uuid );
+                        sb.append( appName ).append( ":" ).append( uuid 
).append( sep );
+                        sep = ", ";
+                    }
+
+                } catch (Exception e) {
+                    logger.error( "Error getting applications for org {}:{}", 
org.getName(), org.getId() );
+                }
+
+                logger.info( sb.toString() );
+            }
+        }
+    }
+
+
+    @Override
+    public Org getOrg(UUID uuid) throws Exception {
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        Group entity = em.get( uuid , Group.class );
+        if ( entity != null ) {
+            Org org = new Org(
+                entity.getUuid(),
+                entity.getPath(),
+                entity.getCreated() );
+            org.sourceValue = entity;
+            return org;
+        }
+        return null;
+    }
+
+
+    @Override
+    public OrgUser getOrgUser(UUID uuid) throws Exception {
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        User entity = em.get( uuid, User.class );
+        if ( entity != null ) {
+            OrgUser user = new OrgUser(
+                entity.getUuid(),
+                entity.getUsername(),
+                entity.getEmail(),
+                entity.getCreated()
+            );
+            return user;
+        }
+        return null;
+    }
+
+
+    @Override
+    public OrgUser lookupOrgUserByUsername(String username) throws Exception {
+        UserInfo info = managementService.getAdminUserByUsername( username );
+        return info == null ? null : getOrgUser( info.getUuid() );
+    }
+
+
+    @Override
+    public OrgUser lookupOrgUserByEmail(String email) throws Exception {
+        UserInfo info = managementService.getAdminUserByEmail( email );
+        return info == null ? null : getOrgUser( info.getUuid() );
+    }
+
+
+    @Override
+    public void removeOrgUser(OrgUser orgUser) throws Exception {
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        em.delete( new SimpleEntityRef( "user", orgUser.getId() ));
+    }
+
+
+    @Override
+    public void updateOrgUser(OrgUser targetUserEntity ) throws Exception {
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        User user = em.get(targetUserEntity.getId(), User.class);
+        user.setUsername( targetUserEntity.getUsername() );
+        user.setEmail( targetUserEntity.getEmail() );
+        em.update( user );
+    }
+
+
+    @Override
+    public void setOrgUserName(OrgUser other, String newUserName ) throws 
Exception {
+
+        EntityManager em = emf.getEntityManager( 
CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+
+        logger.info( "Setting username to {} for user with username {} and id 
{}", new Object[] {
+            newUserName, other.getUsername(), other.getId()
+        } );
+
+        try {
+            em.setProperty( new SimpleEntityRef( "user", other.getId() ), 
"username", newUserName, true );
+        }
+        catch ( DuplicateUniquePropertyExistsException e ) {
+            logger.warn( "More than 1 user has the username of {}.  Setting 
the username to their username+UUID as a "
+                + "fallback", newUserName );
+
+            setOrgUserName( other, String.format( "%s-%s", 
other.getUsername(), other.getId() ) );
+        }
+    }
+
+
+    /**
+     * Select best org from a set of duplicates by picking the one that is 
indexed, or the oldest.
+     */
+    @Override
+    public Org selectBest(Set<Org> orgs) throws Exception {
+        Org oldest = null;
+        for ( Org org :orgs ) {
+            OrganizationInfo info = managementService.getOrganizationByName( 
org.getName() );
+            if ( info != null ) {
+                return org;
+            }
+            if ( oldest == null || org.compareTo( oldest ) < 0 ) {
+                oldest = org;
+            }
+        }
+        return oldest;
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/e5ebb375/stack/tools/src/test/java/org/apache/usergrid/tools/ExportAppTest.java
----------------------------------------------------------------------
diff --git 
a/stack/tools/src/test/java/org/apache/usergrid/tools/ExportAppTest.java 
b/stack/tools/src/test/java/org/apache/usergrid/tools/ExportAppTest.java
index 2946ddb..6009968 100644
--- a/stack/tools/src/test/java/org/apache/usergrid/tools/ExportAppTest.java
+++ b/stack/tools/src/test/java/org/apache/usergrid/tools/ExportAppTest.java
@@ -34,10 +34,6 @@ import static org.junit.Assert.assertTrue;
 public class ExportAppTest extends AbstractServiceIT {
     static final Logger logger = LoggerFactory.getLogger( ExportAppTest.class 
);
 
-    int NUM_COLLECTIONS = 10;
-    int NUM_ENTITIES = 50;
-    int NUM_CONNECTIONS = 3;
-
 
     @org.junit.Test
     public void testBasicOperation() throws Exception {

Reply via email to