private void migrateUser(
      final long fromDirectoryId,
      final long toDirectoryId,
      final String remoteUser,
      final User user,
      final AtomicLong migratedCount)
      throws Exception {
    if (!user.getName().equalsIgnoreCase(remoteUser)) {
      UserWithAttributes userWithAttributes =
          directoryManager.findUserWithAttributesByName(fromDirectoryId, user.getName());
      try {
        final UserTemplate newUser = new UserTemplate(user);
        newUser.setDirectoryId(toDirectoryId);
        directoryManager.addUser(
            toDirectoryId, newUser, new PasswordCredential(generatePassword()));
      } catch (InvalidUserException e) {
        // That's fine just go on to the next user.  Don't copy the groups.
        return;
      }
      // Migrate attributes
      Set<String> keys = userWithAttributes.getKeys();
      Map<String, Set<String>> attributes = new HashMap<String, Set<String>>();
      for (String key : keys) {
        Set<String> values = userWithAttributes.getValues(key);
        attributes.put(key, values);
      }
      directoryManager.storeUserAttributes(toDirectoryId, user.getName(), attributes);

      MembershipQuery<Group> groupQuery =
          QueryBuilder.queryFor(Group.class, EntityDescriptor.group())
              .parentsOf(EntityDescriptor.user())
              .withName(user.getName())
              .returningAtMost(EntityQuery.ALL_RESULTS);
      List<Group> groups =
          directoryManager.searchDirectGroupRelationships(fromDirectoryId, groupQuery);
      for (Group group : groups) {
        // We may need to add the group first
        try {
          directoryManager.findGroupByName(toDirectoryId, group.getName());
        } catch (GroupNotFoundException ex) {
          final GroupTemplate newGroup = new GroupTemplate(group);
          newGroup.setDirectoryId(toDirectoryId);
          directoryManager.addGroup(toDirectoryId, newGroup);
        }
        directoryManager.addUserToGroup(toDirectoryId, user.getName(), group.getName());
        directoryManager.removeUserFromGroup(fromDirectoryId, user.getName(), group.getName());
      }
      directoryManager.removeUser(fromDirectoryId, user.getName());
      migratedCount.addAndGet(1);
    }
  }
  private void migrateUsers(
      final long fromDirectoryId,
      final long toDirectoryId,
      final String remoteUser,
      final MigrateDirectoryUsersCommand migrateUsersCommand,
      final BindException errors) {
    // Check the to & from directories

    Directory from = validateDirectory(fromDirectoryId, errors, "fromDirectoryId");
    Directory to = validateDirectory(toDirectoryId, errors, "toDirectoryId");
    if (to != null && to.equals(from)) {
      errors.addError(
          new FieldError(
              "migration",
              "toDirectoryId",
              i18nResolver.getText("embedded.crowd.directory.migrate.users.field.directory.same")));
    }
    if (errors.hasErrors()) {
      return;
    }

    setDirectoryEnabled(from, false);
    setDirectoryEnabled(to, false);

    // Copy
    SearchRestriction restriction = NullRestrictionImpl.INSTANCE;
    UserQuery<User> query = new UserQuery<User>(User.class, restriction, 0, -1);
    try {
      // TODO: Sucking all users in like this may be problematic for Confluence.  If planning to
      // enable this
      // TODO: feature for other products make sure the performance impacts are understood.
      List<User> users = directoryManager.searchUsers(fromDirectoryId, query);
      final AtomicLong migratedCount = new AtomicLong(0);
      for (Iterator<User> iter = users.iterator(); iter.hasNext(); ) {
        final User user = iter.next();
        transactionTemplate.execute(
            new TransactionCallback() {
              public Object doInTransaction() {
                try {
                  migrateUser(fromDirectoryId, toDirectoryId, remoteUser, user, migratedCount);
                } catch (Exception e) {
                  throw new RuntimeException(e);
                }
                return null;
              }
            });
      }
      migrateUsersCommand.setTestSuccessful(true);
      migrateUsersCommand.setTotalCount(users.size());
      migrateUsersCommand.setMigratedCount(migratedCount.get());
    } catch (Exception e) {
      log.error("User migration failed", e);
      errors.addError(
          new ObjectError(
              "migration",
              i18nResolver.getText(
                  "embedded.crowd.directory.migrate.users.error",
                  htmlEncoder.encode(e.getMessage()))));
    }

    // Enable both directories
    setDirectoryEnabled(from, true);
    setDirectoryEnabled(to, true);
  }