class Users {

  public void processLdapSync(LdapBatchDto batchInfo) {
    final Map<String, UserEntity> allUsers = new HashMap<>();
    final Map<String, GroupEntity> allGroups = new HashMap<>();

    // prefetch all user and group data to avoid heavy queries in membership creation

    for (UserEntity userEntity : userDAO.findAll()) {
      allUsers.put(userEntity.getUserName(), userEntity);
    }

    for (GroupEntity groupEntity : groupDAO.findAll()) {
      allGroups.put(groupEntity.getGroupName(), groupEntity);
    }

    final PrincipalTypeEntity groupPrincipalType = principalTypeDAO
        .ensurePrincipalTypeCreated(PrincipalTypeEntity.GROUP_PRINCIPAL_TYPE);

    /* *****
     * Remove users
     *   First remove the relevant LDAP entries for this user.
     *   If no more user authentication items exists for the user, then remove the user.
     * ***** */
    final Set<UserEntity> usersToRemove = new HashSet<>();
    final Set<UserAuthenticationEntity> authenticationEntitiesToRemove = new HashSet<>();
    for (LdapUserDto user : batchInfo.getUsersToBeRemoved()) {
      UserEntity userEntity = userDAO.findUserByName(user.getUserName());
      if (userEntity != null) {
        List<UserAuthenticationEntity> authenticationEntities = userAuthenticationDAO.findByUser(userEntity);
        Iterator<UserAuthenticationEntity> iterator = authenticationEntities.iterator();
        while (iterator.hasNext()) {
          UserAuthenticationEntity authenticationEntity = iterator.next();

          if (authenticationEntity.getAuthenticationType() == UserAuthenticationType.LDAP) {
            String dn = user.getDn();
            String authenticationKey = authenticationEntity.getAuthenticationKey();

            // DN's are case-insensitive.
            if (StringUtils.isEmpty(dn) || StringUtils.isEmpty(authenticationKey) || dn.equalsIgnoreCase(authenticationKey)) {
              authenticationEntitiesToRemove.add(authenticationEntity);
            }
            iterator.remove();
          }
        }

        if (authenticationEntities.isEmpty()) {
          allUsers.remove(userEntity.getUserName());
          usersToRemove.add(userEntity);
        }
      }
    }
    userAuthenticationDAO.remove(authenticationEntitiesToRemove);
    userDAO.remove(usersToRemove);

    // remove groups
    final Set<GroupEntity> groupsToRemove = new HashSet<>();
    for (LdapGroupDto group : batchInfo.getGroupsToBeRemoved()) {
      final GroupEntity groupEntity = groupDAO.findGroupByName(group.getGroupName());
      allGroups.remove(groupEntity.getGroupName());
      groupsToRemove.add(groupEntity);
    }
    groupDAO.remove(groupsToRemove);

    /* *****
     * Update users
     * ***** */
    final Set<UserEntity> userEntitiesToUpdate = new HashSet<>();
    for (LdapUserDto user : batchInfo.getUsersToBecomeLdap()) {
      // Ensure the username is all lowercase
      String userName = user.getUserName();

      UserEntity userEntity = userDAO.findUserByName(userName);
      if (userEntity != null) {
        LOG.trace("Enabling LDAP authentication for the user account with the username {}.", userName);

        if (configuration.getLdapSyncCollisionHandlingBehavior() == Configuration.LdapUsernameCollisionHandlingBehavior.CONVERT) {
          // If converting the user to only an LDAP user, then remove all other authentication methods
          Collection<UserAuthenticationEntity> existingEntities = userEntity.getAuthenticationEntities();
          if(existingEntities != null) {
            Iterator<UserAuthenticationEntity> iterator = existingEntities.iterator();
            while(iterator.hasNext()) {
              UserAuthenticationEntity userAuthenticationEntity = iterator.next();
              if(userAuthenticationEntity.getAuthenticationType() != UserAuthenticationType.LDAP) {
                removeAuthentication(userEntity, userAuthenticationEntity.getUserAuthenticationId());
                iterator.remove();
              }
            }
          }
        }

        try {
          addLdapAuthentication(userEntity, user.getDn(), false);
          userEntitiesToUpdate.add(userEntity);
        } catch (AmbariException e) {
          LOG.warn(String.format("Failed to enable LDAP authentication for the user account with the username %s: %s", userName, e.getLocalizedMessage()), e);
        }
      } else {
        LOG.warn("Failed to find user account for {} while enabling LDAP authentication for the user.", userName);
      }
    }
    userDAO.merge(userEntitiesToUpdate);

    // update groups
    final Set<GroupEntity> groupsToBecomeLdap = new HashSet<>();
    for (LdapGroupDto group : batchInfo.getGroupsToBecomeLdap()) {
      final GroupEntity groupEntity = groupDAO.findGroupByName(group.getGroupName());
      groupEntity.setGroupType(GroupType.LDAP);
      allGroups.put(groupEntity.getGroupName(), groupEntity);
      groupsToBecomeLdap.add(groupEntity);
    }
    groupDAO.merge(groupsToBecomeLdap);

    // prepare create principals
    final List<PrincipalEntity> principalsToCreate = new ArrayList<>();

    // Create users
    for (LdapUserDto user : batchInfo.getUsersToBeCreated()) {
      String userName = user.getUserName();
      UserEntity userEntity;

      try {
        userEntity = createUser(userName, userName, userName, true);
      } catch (AmbariException e) {
        LOG.error(String.format("Failed to create new user: %s", userName), e);
        userEntity = null;
      }

      if (userEntity != null) {
        LOG.trace("Enabling LDAP authentication for the user account with the username {}.", userName);
        try {
          addLdapAuthentication(userEntity, user.getDn(), false);
        } catch (AmbariException e) {
          LOG.warn(String.format("Failed to enable LDAP authentication for the user account with the username %s: %s", userName, e.getLocalizedMessage()), e);
        }

        userDAO.merge(userEntity);

        // Add the new user to the allUsers map.
        allUsers.put(userEntity.getUserName(), userEntity);
      }
    }

    // prepare create groups
    final Set<GroupEntity> groupsToCreate = new HashSet<>();
    for (LdapGroupDto group: batchInfo.getGroupsToBeCreated()) {
      final PrincipalEntity principalEntity = new PrincipalEntity();
      principalEntity.setPrincipalType(groupPrincipalType);
      principalsToCreate.add(principalEntity);

      final GroupEntity groupEntity = new GroupEntity();
      groupEntity.setGroupName(group.getGroupName());
      groupEntity.setPrincipal(principalEntity);
      groupEntity.setGroupType(GroupType.LDAP);

      allGroups.put(groupEntity.getGroupName(), groupEntity);
      groupsToCreate.add(groupEntity);
    }

    // create groups
    principalDAO.create(principalsToCreate);
    groupDAO.create(groupsToCreate);

    // create membership
    final Set<MemberEntity> membersToCreate = new HashSet<>();
    final Set<GroupEntity> groupsToUpdate = new HashSet<>();
    for (LdapUserGroupMemberDto member : batchInfo.getMembershipToAdd()) {
      final MemberEntity memberEntity = new MemberEntity();
      final GroupEntity groupEntity = allGroups.get(member.getGroupName());
      memberEntity.setGroup(groupEntity);
      memberEntity.setUser(allUsers.get(member.getUserName()));
      groupEntity.getMemberEntities().add(memberEntity);
      groupsToUpdate.add(groupEntity);
      membersToCreate.add(memberEntity);
    }

    // handle adminGroupMappingRules
    processLdapAdminGroupMappingRules(membersToCreate);

    memberDAO.create(membersToCreate);
    groupDAO.merge(groupsToUpdate); // needed for Derby DB as it doesn't fetch newly added members automatically

    // remove membership
    final Set<MemberEntity> membersToRemove = new HashSet<>();
    for (LdapUserGroupMemberDto member : batchInfo.getMembershipToRemove()) {
      MemberEntity memberEntity = memberDAO.findByUserAndGroup(member.getUserName(), member.getGroupName());
      if (memberEntity != null) {
        membersToRemove.add(memberEntity);
      }
    }
    memberDAO.remove(membersToRemove);

    // clear cached entities
    entityManagerProvider.get().getEntityManagerFactory().getCache().evictAll();
  }

}