UsersPresenter.java

/*
** Module   : UsersPresenter.java
** Abstract : UsersPresenter implements common business methods to manage user accounts.
**
** Copyright (c) 2017-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 SBI 20170601 Created initial version.
** 002 EVL 20170926 Javadoc fixes.
** 003 GBB 20230825 Javadoc fixes.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/


package com.goldencode.p2j.admin.client.application.home.accounts.users;

import java.io.Serializable;
import java.util.*;
import java.util.logging.Level;

import javax.inject.Named;

import com.goldencode.p2j.admin.client.*;
import com.goldencode.p2j.admin.client.application.Alarm;
import com.goldencode.p2j.admin.client.application.PrintingContext;
import com.goldencode.p2j.admin.client.application.home.HomePresenter;
import com.goldencode.p2j.admin.client.application.home.ViewStateMachine;
import com.goldencode.p2j.admin.client.application.home.accounts.AccountsPresenter;
import com.goldencode.p2j.admin.client.application.home.acl.*;
import com.goldencode.p2j.admin.client.widget.dialog.InputDialog;
import com.goldencode.p2j.admin.client.widget.dialog.MessageType;
import com.goldencode.p2j.admin.client.widget.dialog.ModalDialogs;
import com.goldencode.p2j.admin.shared.AdminServiceAsync;
import com.goldencode.p2j.admin.TaggedName;
import com.goldencode.p2j.admin.UserDef;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.web.bindery.event.shared.EventBus;
import com.gwtplatform.mvp.client.HasUiHandlers;
import com.gwtplatform.mvp.client.View;
import com.gwtplatform.mvp.client.annotations.NameToken;
import com.gwtplatform.mvp.client.annotations.ProxyStandard;
import com.gwtplatform.mvp.client.presenter.slots.NestedSlot;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.client.proxy.ProxyPlace;
import com.gwtplatform.mvp.shared.proxy.PlaceRequest;


/**
 * UsersPresenter implements common business methods to manage user accounts.
 */
public class UsersPresenter
extends AccountsPresenter<UsersPresenter.MyView, UsersPresenter.MyProxy>
implements UsersUIHandlers
{
   /**
    * Defines user accounts view states.
    */
   public static enum UsersViewStates
   implements ViewStateMachine.TransitionState
   {
      /** Users list has been refreshed. */
      USERS_LIST_REFRESHED(MASK_WEST_CENTER, STATE_INITIAL_EMPTY),
      
      /** The user account has been added currently. */
      ADD_USER_ACCOUNT(MASK_WEST_CENTER, STATE_USER_ADD),
      
      /** The user account has been edited currently. */
      EDIT_USER_ACCOUNT(MASK_WEST_CENTER, STATE_USER_EDIT),
      
      /** The single user has been selected currently. */
      SINGLE_USER_SELECTED(MASK_WEST_CENTER, STATE_USERS_SINGLE),
      
      /** The multiple users have been selected currently. */
      MULTI_USERS_SELECTED(MASK_WEST_CENTER, STATE_USERS_MULTI),
      
      /**
       *  The group accounts to which a selected user belongs have been cleared due to the user
       *  accounts selection has been cleared currently. */
      USER_GROUP_CLEARED(MASK_CENTER, STATE_INITIAL_EMPTY),
      
      /**
       * The user account and the group to which the selected user belongs have been
       * selected currently.
       */
      USER_GROUP_SELECTED(MASK_CENTER, STATE_USERS_SINGLE_GROUP_SELECTED),
      
      /** The all groups table has been filled recently */
      ALL_GROUPS_POPULATED(MASK_EAST, STATE_ALL_GROUPS);

      /** The current components states to be enabled or disabled */
      private final boolean[] state;
      
      /** The current mask that defines components to which the current state is applied */
      private final boolean[] mask;
      
      /**
       * Creates a target view state.
       * 
       * @param    mask
       *           The current mask
       * @param    state
       *           The current state
       */
      private UsersViewStates(boolean[] mask, boolean[] state)
      {
         this.mask  = mask;
         this.state = state;
      }
      
      /**
       * Gets the current state that defines the affected components states.
       * 
       * @return   The current state
       */
      @Override
      public boolean[] getState()
      {
         return this.state;
      }

      /**
       * Gets the current mask that defines the set of affected components (widgets).
       * 
       * @return   The current mask
       */
      @Override
      public boolean[] getMask()
      {
         return mask;
      }
   }
   
   /** state definition - initial empty */
   private static final boolean[] STATE_INITIAL_EMPTY =
   {
      /*       refresh               */ true,
      /*       addUserAccount        */ true,
      /*       deleteUserAccount     */ false,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ true,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ false,
      /*       assignNewGroup        */ false,
      /*       deleteGroup           */ false,
      
      /*       populateGroups       */ true,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };
   
   /** state definition - group definition creation */
   private static final boolean[] STATE_USER_ADD =
   {
   /*       refresh               */ false,
   /*       addUserAccount        */ false,
   /*       deleteUserAccount     */ false,
   /*       cloneUserAccount      */ false,
   /*       editUserAccount       */ false,
   /*       disableUserAccount    */ false,
   /*       findUser              */ false,
   /*       switchTOACLs          */ false,
   
   /*       newGroup,             */ false,
   /*       assignNewGroup        */ false,
   /*       deleteGroup           */ false,
   
   /*       populateGroups       */ false,
   /*       addSelected,         */ false,
   /*       filterSelector       */ false,
   /*       filterGroups         */ false
   };

   /** state definition - group definition editing */
   private static final boolean[] STATE_USER_EDIT =
   {
      /*       refresh               */ false,
      /*       addUserAccount        */ false,
      /*       deleteUserAccount     */ false,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ false,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ false,
      /*       assignNewGroup        */ false,
      /*       deleteGroup           */ false,
      
      /*       populateGroups       */ false,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };

   /** state definition - single selection in users list */
   private static final boolean[] STATE_USERS_SINGLE =
   {
   /*       refresh               */ true,
   /*       addUserAccount        */ true,
   /*       deleteUserAccount     */ true,
   /*       cloneUserAccount      */ true,
   /*       editUserAccount       */ true,
   /*       disableUserAccount    */ true,
   /*       findUser              */ true,
   /*       switchTOACLs          */ true,
   
   /*       newGroup,             */ true,
   /*       assignNewGroup        */ true,
   /*       deleteGroup           */ false,
   
   /*       populateGroups       */ true,
   /*       addSelected,         */ false,
   /*       filterSelector       */ false,
   /*       filterGroups         */ false
   };

   /** state definition - single selection in groups list */
   private static final boolean[] STATE_USERS_SINGLE_GROUP_SELECTED =
   {
      /*       refresh               */ true,
      /*       addUserAccount        */ true,
      /*       deleteUserAccount     */ true,
      /*       cloneUserAccount      */ true,
      /*       editUserAccount       */ true,
      /*       disableUserAccount    */ true,
      /*       findUser              */ true,
      /*       switchTOACLs          */ true,
      
      /*       newGroup,             */ true,
      /*       assignNewGroup        */ true,
      /*       deleteGroup           */ true,
      
      /*       populateGroups       */ true,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };
   
   /** state definition - multiple selection in users list */
   private static final boolean[] STATE_USERS_MULTI =
   {
      /*       refresh               */ true,
      /*       addUserAccount        */ true,
      /*       deleteUserAccount     */ true,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ true,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ false,
      /*       assignNewGroup        */ false,
      /*       deleteGroup           */ false,
      
      /*       populateGroups       */ true,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };
   
   /** state definition - all groups list populated */
   private static final boolean[] STATE_ALL_GROUPS =
   {
      /*       refresh               */ true,
      /*       addUserAccount        */ true,
      /*       deleteUserAccount     */ false,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ true,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ false,
      /*       assignNewGroup        */ false,
      /*       deleteGroup           */ false,
      
      /*       populateGroups       */ true,
      /*       addSelected,         */ false,
      /*       filterSelector       */ true,
      /*       filterGroups         */ true
   };
   
   /** mask definition - west-center */
   private static final boolean[] MASK_WEST_CENTER =
   {
      /*       refresh               */ true,
      /*       addUserAccount        */ true,
      /*       deleteUserAccount     */ true,
      /*       cloneUserAccount      */ true,
      /*       editUserAccount       */ true,
      /*       disableUserAccount    */ true,
      /*       findUser              */ true,
      /*       switchTOACLs          */ true,
      
      /*       newGroup,             */ true,
      /*       assignNewGroup        */ true,
      /*       deleteGroup           */ true,
      
      /*       populateGroups       */ false,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };
   
   /** mask definition - center */
   private static final boolean[] MASK_CENTER =
   {
      /*       refresh               */ false,
      /*       addUserAccount        */ false,
      /*       deleteUserAccount     */ false,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ false,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ true,
      /*       assignNewGroup        */ true,
      /*       deleteGroup           */ true,
      
      /*       populateGroups       */ false,
      /*       addSelected,         */ false,
      /*       filterSelector       */ false,
      /*       filterGroups         */ false
   };
   
   /** mask definition - east */
   private static final boolean[] MASK_EAST =
   {
      /*       refresh               */ false,
      /*       addUserAccount        */ false,
      /*       deleteUserAccount     */ false,
      /*       cloneUserAccount      */ false,
      /*       editUserAccount       */ false,
      /*       disableUserAccount    */ false,
      /*       findUser              */ false,
      /*       switchTOACLs          */ false,
      
      /*       newGroup,             */ false,
      /*       assignNewGroup        */ false,
      /*       deleteGroup           */ false,
      
      /*       populateGroups       */ true,
      /*       addSelected,         */ true,
      /*       filterSelector       */ true,
      /*       filterGroups         */ true
   };

   /** The attachment point for modal dialogs on this view*/
   public static final NestedSlot MODAL_CONTENT = new NestedSlot();
   
   /** Administration server interface */
   private final AdminServiceAsync adminService;
   
   /** The common modal dialogs manager */
   private final ModalDialogs modalDialogsManager;
   
   /** The server alarms manager */
   private final Alarm serverAlarmsManager;
   
   /** The currently selected user */
   private UserDef selectedUser;
   
   /** The set of selected users */
   private final Set<UserDef> selectedUsers;
   
   /** The user account definition provider */
   private final Provider<UserAccountDefinition> uadProvider;
   
   /** The common input dialogs provider */
   private final Provider<InputDialog> inputDialogProvider;

   /** The Find User Account manager */
   private final FindUserAccount findUserAccountManager;
   
   /**
    * This view interface that must be implemented by the concrete view according to GWTP
    * framework. Defines the exposed business methods.
    */
   public interface MyView
   extends View, HasUiHandlers<UsersUIHandlers>, Provider<HasEnabled[]>, PrintingContext
   {
      /**
       * Sets the all users table's model with users accounts data given by tagged names
       * 
       * @param    users
       *           The array of users accounts given by tagged names
       */
      void setUsers(TaggedName[] users);
      
      /**
       * Sets the user's groups table's model with all assigned groups for the single selected
       * user from the all users table view.
       * 
       * @param    groups
       *           The array of assigned groups given by tagged names
       */
      void setAssignedGroups(TaggedName[] groups);
      
      /**
       * Sets the all groups accounts table's model with all existing groups accounts.
       *  
       * @param    allGroups
       *           The array of all existing groups accounts
       * @param    filter
       *           The regular expression to select groups with accounts names according to this
       *           filter criteria
       */
      void setAllGroups(TaggedName[] allGroups, RegExp filter);
      
      /**
       * Tests if this group has been selected from the user's assigned groups table.
       * 
       * @param    group
       *           The target group account given by its tagged name
       * 
       * @return   True iff the target group has been selected, otherwise false.
       */
      boolean isSelectedFromUserGroups(TaggedName group);
      
      /**
       * Tests if these tested groups have been selected from the user's assigned groups table.
       * 
       * @param    groups
       *           The array of tested groups given by tagged names
       * 
       * @return   True iff the tested groups have been selected, otherwise false.
       */
      boolean isSelectedFromUserGroups(TaggedName[] groups);
      
      /**
       * Tests if these tested groups have been selected from the all groups table.
       * 
       * @param    groups
       *           The array of tested groups given by tagged names
       * 
       * @return   True iff the tested groups have been selected, otherwise false.
       */
      boolean isSelectedFromAllGroups(TaggedName[] groups);
      
      /**
       * Tests if these tested users have been selected from the all users table.
       * 
       * @param    users
       *           The array of tested user accounts given by tagged names
       * 
       * @return   True if all tested users have been selected, otherwise false.
       */
      boolean isSelectedFromUsers(TaggedName[] users);
      
      /**
       * Tests if the tested user has been selected from the all users table.
       * 
       * @param    user
       *           The tested user account given by its tagged name
       * 
       * @return   True if the tested user has been selected, otherwise false.
       */
      boolean isSelectedFromUsers(TaggedName user);
      
      /**
       * Selects the user's data row from the all users table by a given user's tagged name.
       * 
       * @param    user
       *           The user account given by its tagged name
       */
      void selectUser(TaggedName user);
      
      /**
       * Selects the user's data row from the all users table by a given user's account name.
       * 
       * @param    name
       *           The account name.
       */
      void selectUserByName(String name);
      
      /**
       * Changes the Populate button's label from "Populate" to "Refresh"
       * 
       * @param    populated
       *           The flag which true value means to change the Populate label to Refresh one
       */
      void togglePopulateGroupsButton(boolean populated);
      
      /**
       * Toggles the Disable/Enable User button's label.
       * 
       * @param    enabled
       *           Indicates the user's enabled state. The true value switches this button's label
       *           from "Enable User" to "Disable User" and vice versa the false value switches
       *           from "Disable User" to "Enable User".
       */
      void toggleDisableUserButton(boolean enabled);
      
      /**
       * Gets a reference to the Deleting Users Alert.
       * 
       * @return   The Deleting Users Alert
       */
      DeletingUsersAlert getDeletingUsersAlert();

      /**
       * Gets a reference to the Clone User Account View.
       * 
       * @return   The Clone User Account View
       */
      CloneUserAccountView getCloneUserAccountView();

      /**
       * Gets a reference to the Removing Groups Alert.
       * 
       * @return   The Removing Groups Alert
       */
      RemovingGroupsAlert getRemovingGroupsAlert();

      /**
       * Returns the parent container where the Add/Edit User Definition View will be revealed in.
       * 
       * @return   The parent container for the Add/Edit User Definition View.
       */
      HasWidgets getUserDefModalParent();
   }

   /**
    * Defines the proxy place for GWTP framework. Binds /accounts/users url to this presenter.
    */
   @ProxyStandard
   @NameToken(NameTokens.USERS)
   public interface MyProxy
   extends ProxyPlace<UsersPresenter>
   {}
   
   /**
    * Defines the Users presenter.
    * 
    * @param    eventBus
    *           The event bus
    * @param    view
    *           The associated view
    * @param    proxy
    *           The proxy place
    * @param    placeManager
    *           The place manager
    * @param    viewStateMachine
    *           The view state machine
    * @param    adminService
    *           The administration service
    * @param    modalDialogsManager
    *           The common modal dialogs manager
    * @param    serverAlarmsManager
    *           The server alarms
    * @param    uadProvider
    *           The user account definition provider
    * @param    inputDialogProvider
    *           InputDialog provider
    * @param    acctExtensionHolder
    *           The account extension holder
    * @param    aclPopupPresenterProvider
    *           The ACL popup presenter provider
    */
   @Inject
   public UsersPresenter(EventBus eventBus,
                         MyView view,
                         MyProxy proxy,
                         PlaceManager placeManager,
                         @Named("UsersViewStateMachine") ViewStateMachine viewStateMachine,
                         AdminServiceAsync adminService,
                         ModalDialogs modalDialogsManager,
                         Alarm serverAlarmsManager,
                         Provider<UserAccountDefinition> uadProvider,
                         Provider<InputDialog> inputDialogProvider,
                         AccountExtensionHolder acctExtensionHolder,
                         ACLPopupPresenter aclPopupPresenterProvider)
   {
      super(eventBus,
            view,
            proxy,
            HomePresenter.SLOT_CONTENT,
            placeManager,
            viewStateMachine,
            aclPopupPresenterProvider);

      this.adminService = adminService;
      this.modalDialogsManager = modalDialogsManager;
      this.serverAlarmsManager = serverAlarmsManager;
      this.uadProvider = uadProvider;
      this.inputDialogProvider = inputDialogProvider;
      this.findUserAccountManager = new FindUserAccount(acctExtensionHolder);
      this.selectedUsers = new HashSet<UserDef>();
      getView().setUiHandlers(this);
   }
   
   /**
    * Lifecycle method called on all visible presenters whenever a
    * presenter is revealed anywhere in the presenter hierarchy.
    * <p>
    * <b>Important:</b> Make sure you call your superclass {@link #onReset()} if
    * you override. Also, do not call directly, fire a
    * {@link com.gwtplatform.mvp.client.proxy.ResetPresentersEvent} to perform a reset manually.
    * See {@link com.gwtplatform.mvp.client.PresenterWidget} for more details on lifecycle
    * methods.
    * <p>
    * This is one of the most frequently used lifecycle method. This is usually a good
    * place to refresh any information displayed by your presenter.
    * <p>
    * Note that {@link #onReset()} is not called only when using
    * {@link #addToSlot(Object, PresenterWidget)}, {@link #addToPopupSlot(PresenterWidget)}
    * or {@link #setInSlot(Object, PresenterWidget, boolean)} with {@code false} as the third
    * parameter.
    * <p>
    * In a presenter hierarchy, this method is called top-down: first on the
    * parent presenters, then on the children.
    */
   @Override
   protected void onReset()
   {
      super.onReset();
      updateUsersList();
      setState(UsersViewStates.USERS_LIST_REFRESHED);
   }

   /**
    * Triggers an action to refresh the all users table.
    */
   @Override
   public void onRefresh()
   {
      updateUsersList();
      changeState(UsersViewStates.USERS_LIST_REFRESHED);
   }
   
   /**
    * Retrieves the user list and fills the all users table model.
    */
   private void updateUsersList()
   {
      updateUsersList(()->{});
   }
   
   /**
    * Retrieves the user list and fills the all users table model and executes a given command on
    * getting the user list.
    * 
    * @param    callback
    *           The given command to execute on getting the data
    */
   private void updateUsersList(Runnable callback)
   {
      this.adminService.listUsers(new AsyncCallback<TaggedName[]>()
      {
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setUsers(result);
            callback.run();
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            //TODO notify an admin user
            logger.log(Level.INFO, "Can't get all USERS");
            callback.run();
         }
      });
   }
   
   /**
    * Retrieves all assigned groups for a given user account and fills the user's group table
    * model.
    * 
    * @param    user
    *           The user account given by its tagged name
    */
   private void updateAssignedGroups(TaggedName user)
   {
      final String name = user.getName();
      this.adminService.listUserGroups(name, new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setAssignedGroups(result);
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            //TODO notify an admin user
            logger.log(Level.INFO, "Can't get USERS for GROUP " + name);
         }
      });
   }

   /**
    * Fills and/or updates the all groups accounts table.
    */
   @Override
   public void populateAndRefreshAllGroups()
   {
      this.adminService.listGroups(new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setAllGroups(result, filterExp);
            getView().togglePopulateGroupsButton(true);
            if (result != null && result.length > 0)
            {
               changeState(UsersViewStates.ALL_GROUPS_POPULATED);
            }
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            //TODO notify an admin user
            logger.log(Level.INFO, "Can't get all Groups");
         }
      });
   }

   /**
    * Shows the Add User Account Dialog.
    */
   @Override
   public void showAddUserAccountDialog()
   {
      logger.log(Level.INFO, "showAddUserAccountDialog");
      UserAccountDefinition uad = uadProvider.get();
      uad.build(null, r ->
      {
         if (!r)
         {
            return;
         }

         uad.show(getView().getUserDefModalParent(), new UserDef(), udata ->
         {
            if (udata == null)
            {
               return;
            }

            addUser(udata.userDef, udata.extDef);
            updateUsersList();
         });
      });
   }

   /**
    * Setups the Deleting Users Alert.
    * 
    * @param    users
    *           The array of users accounts given by tagged names
    */
   @Override
   public void setupDeletingUsersAlert(TaggedName[] users)
   {
      logger.log(Level.INFO, "setupDeletingUsersAlert");
      getView().getDeletingUsersAlert().setAlert(users);
   }

   /**
    * Setups the Clone User Account Dialog.
    * 
    * @param    clonedUserAccount
    *           The cloned user account given by its tagged name
    */
   @Override
   public void setupCloneUserAccountDialog(TaggedName clonedUserAccount)
   {
      logger.log(Level.INFO, "setupCloneUserAccountDialog");
      getView().getCloneUserAccountView().setupCloneUserAccountView(clonedUserAccount);
   }

   /**
    * Shows the Edit User Account Dialog to edit the target user account.
    * 
    * @param    updatedUserAccount
    *           The target user account given by its tagged name
    */
   @Override
   public void showEditUserAccountDialog(TaggedName updatedUserAccount)
   {
      logger.log(Level.INFO, "showEditUserAccountDialog");
      UserAccountDefinition uad = uadProvider.get();
      uad.build(selectedUser, r ->
      {
         if (!r)
         {
            return;
         }

         uad.show(getView().getUserDefModalParent(), selectedUser, udata ->
         {
            if (udata == null)
            {
               return;
            }

            updateUser(udata.userDef, udata.extDef);
            updateUsersList();
         });
      });
   }

   /**
    * Cancels the Add User Account Dialog and rolls back any related changes.
    */
   @Override
   public void cancelAddUserAccountDialog()
   {
      logger.log(Level.INFO, "cancelAddUserAccountDialog");
      restoreState();
   }

   /**
    * Cancels the Deleting Users Alert and rolls back any related changes.
    */
   @Override
   public void cancelDeletingUsersAlert()
   {
      logger.log(Level.INFO, "cancelDeletingUsersAlert");
   }

   /**
    * Cancels the Clone User Account Dialog and rolls back any related changes.
    */
   @Override
   public void cancelCloneUserAccountDialog()
   {
      logger.log(Level.INFO, "cancelCloneUserAccountDialog");
   }

   /**
    * Cancels the Edit User Account Dialog and rolls back any related changes.
    */
   @Override
   public void cancelEditUserAccountDialog()
   {
      logger.log(Level.INFO, "cancelEditUserAccountDialog");
      restoreState();
   }

   /**
    * Deletes the given selected users accounts.
    * 
    * @param    users
    *           The array of users accounts given by tagged names
    */
   @Override
   public void deleteSelectedUsers(TaggedName[] users)
   {
      if (!getView().isSelectedFromUsers(users))
      {
         return;
      }
      MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
      LinkedHashMap<TaggedName, AdminCallback<Boolean>> callbacks = new LinkedHashMap<TaggedName, AdminCallback<Boolean>>();
      for(TaggedName user : users)
      {
         AdminCallback<Boolean> callback = mc.newCallback();
         callbacks.put(user, callback);
         logger.info("user account to delete " + user.getName());
      };
      // the admin service must be called only after all callbacks have been created otherwise
      // the onDone method is called unpredictably multiple times
      for (Map.Entry<TaggedName, AdminCallback<Boolean>> e : callbacks.entrySet())
      {
         this.adminService.deleteUser(e.getKey().getName(), e.getValue());
      }
      mc.onDone(() ->
      {
         if (!mc.isSuccess())
         {
            // this branch is executed when one or more callbacks !isSuccess()
            for (Map.Entry<TaggedName, AdminCallback<Boolean>> e : callbacks.entrySet())
            {
               if (e.getValue().getResult() == null || !e.getValue().getResult())
               {
                  logger.info("failed to delete user account " + e.getKey().getName());
               }
            }
         }
         updateUsersList(()->{changeState(UsersViewStates.USERS_LIST_REFRESHED);});
      });
   }
   
   /**
    * Setups the Removing Groups Alert for deleting a given user account from selected
    * groups accounts.
    * 
    * @param    user
    *           The given user account
    * @param    groups
    *           The selected groups accounts from which the given user is about to be deleted
    */
   @Override
   public void setupRemovingGroupsAlert(TaggedName user, TaggedName[] groups)
   {
      getView().getRemovingGroupsAlert().setAlert(user, groups);
   }

   /**
    * Clones the target user account with its ACL if it is required preserving the same password
    * for a new created account.
    * 
    * @param    nameFrom
    *           The cloned user account name
    * @param    nameTo
    *           The new user account name
    * @param    acls
    *           True indicates that the ACL of the cloned user will be copied too, otherwise
    *           the ACL of the new created user account will be empty.
    */
   @Override
   public void cloneUserAccount(final String nameFrom, String nameTo, boolean acls)
   {
      // TODO security issue: remove cloneUserAccount from UsersUIHandlers
      this.adminService.cloneUser(nameFrom, nameTo, acls, new AsyncCallback<Boolean>()
      {
         @Override
         public void onSuccess(Boolean result)
         {
            logger.log(Level.INFO, "cloneUser " + nameFrom + ", " + result);
            updateUsersList(()->{changeState(UsersViewStates.USERS_LIST_REFRESHED);});
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(()->{changeState(UsersViewStates.USERS_LIST_REFRESHED);});
         }
      });
   }
   
   /**
    * Clones the target user account with its ACL if it is required and having new password.
    * 
    * @param    nameFrom
    *           The cloned user account name
    * @param    nameTo
    *           The new user account name
    * @param    acls
    *           True indicates that the ACL of the cloned user will be copied too, otherwise
    *           the ACL of the new created user account will be empty.
    * @param    newPassword
    *           The password for the new created account
    */
   @Override
   public void cloneUserAccount(final String nameFrom, String nameTo, boolean acls, String newPassword)
   {
      // TODO security issue: remove cloneUserAccount from UsersUIHandlers
      this.adminService.cloneUser(nameFrom, nameTo, acls, newPassword, new AsyncCallback<Boolean>()
      {
         @Override
         public void onSuccess(Boolean result)
         {
            logger.log(Level.INFO, "cloneUser " + nameFrom + ", " + result);
            updateUsersList(()->{changeState(UsersViewStates.USERS_LIST_REFRESHED);});
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(()->{changeState(UsersViewStates.USERS_LIST_REFRESHED);});
         }
      });
   }
   
   /**
    * Triggers the users selection changed event.
    * 
    * @param    users
    *           The selected users accounts
    */
   @Override
   public void onUserSelected(Set<TaggedName> users)
   {
      if (users == null)
      {
         logger.info("Users selection is cleared.");
         changeState(UsersViewStates.USERS_LIST_REFRESHED);
         return;
      }
      MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
      LinkedHashMap<TaggedName, AdminCallback<UserDef>> callbacks = new LinkedHashMap<TaggedName, AdminCallback<UserDef>>();
      users.forEach(user ->
      {
         AdminCallback<UserDef> cb = mc.newCallback();
         callbacks.put(user, cb);
         logger.info("get user " + user.getName());
      });
      for (Map.Entry<TaggedName, AdminCallback<UserDef>> e : callbacks.entrySet())
      {
         this.adminService.getUser(e.getKey().getName(), e.getValue());
      }
      mc.onDone(() ->
      {
         if (mc.isSuccess())
         {
            selectedUsers.clear();
            for (Map.Entry<TaggedName, AdminCallback<UserDef>> e : callbacks.entrySet())
            {
               UserDef userDef = e.getValue().getResult();
               logger.info("selected user " + userDef);
               selectedUsers.add(userDef);
               selectedUser = userDef;
            }
            if (selectedUsers.size() > 1)
            {
               changeState(UsersViewStates.MULTI_USERS_SELECTED);
            }
            else if (selectedUsers.size() == 1)
            {
               changeState(UsersViewStates.SINGLE_USER_SELECTED);
               getView().toggleDisableUserButton(selectedUser.enabled);
            }
            else
            {
               changeState(UsersViewStates.USERS_LIST_REFRESHED);
            }
         }
         else
         {
            for (Map.Entry<TaggedName, AdminCallback<UserDef>> e : callbacks.entrySet())
            {
               if (e.getValue().getResult() == null)
               {
                  logger.info("failed to get user definition for " + e.getKey().getName());
               }
            }
            changeState(UsersViewStates.USERS_LIST_REFRESHED);
         }
      });

      if (callbacks.size() == 1)
      {
         TaggedName selectedUser = callbacks.keySet().toArray(new TaggedName [1])[0];
         updateAssignedGroups(selectedUser);
      }
      else
      {
         getView().setAssignedGroups(null);
      }
   }

   /**
    * Triggers the groups selection changed event.
    * 
    * @param    selectedGroups
    *           The selected groups accounts
    */
   @Override
   public void onGroupSelected(Set<TaggedName> selectedGroups)
   {
      logger.info("onGroupSelected");
      if (selectedGroups == null || selectedGroups.isEmpty())
      {
         changeState(UsersViewStates.USER_GROUP_CLEARED);
      }
      else
      {
         changeState(UsersViewStates.USER_GROUP_SELECTED);
      }
   }

   /**
    * Cancels the Removing Groups Alert and rolls back any related changes.
    */
   @Override
   public void cancelRemovingGroupsAlert()
   {
   }

   /**
    * Makes a given user account to be a member of a given group account.
    * 
    * @param    user
    *           The target user account given by its tagged name
    * @param    groupName
    *           The target group account name
    */
   @Override
   public void addUserInNewGroup(TaggedName user, String groupName)
   {
      if (!getView().isSelectedFromUsers(user))
      {
         //TODO
         return;
      }
      this.adminService.checkGroup(groupName, new AsyncCallback<TaggedName>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            // TODO Auto-generated method stub
            
         }

         @Override
         public void onSuccess(TaggedName result)
         {
            adminService.changeGroupUsers(groupName, new String[] {user.getName()}, new String[0],
                     new AsyncCallback<Boolean>()
            {
               @Override
               public void onSuccess(Boolean result)
               {
                  updateUsersList(()-> {getView().selectUser(user);});
               }
               
               @Override
               public void onFailure(Throwable caught)
               {
                  updateUsersList(()-> {getView().selectUser(user);});
               }
            });
         }
      });
   }

   /**
    * Displays only groups accounts that satisfy the given search criteria.
    * 
    * @param    value
    *           The given search criteria
    */
   @Override
   public void setGroupsFilter(String value)
   {
      setFilter(value);
      
      populateAndRefreshAllGroups();
   }

   /**
    * Adds a new user.
    * 
    * @param    newUser
    *           The new user definition
    * @param    ext
    *           The extended account data.
    */
   @Override
   public void addUser(UserDef newUser, Serializable ext)
   {
      this.adminService.addUser(newUser, ext, new AsyncCallback<Boolean>()
      {
         @Override
         public void onSuccess(Boolean result)
         {
            logger.log(Level.INFO, "addUser " + newUser.subjectId + ", Is added?" + result);
            updateUsersList(()->{restoreState();});

            if (result == null || !result)
            {
               serverAlarmsManager.ring("Couldn't add user account " + newUser.subjectId);
            }
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(()->{restoreState();});
         }
      });
   }

   /**
    * Updates a given user account.
    * 
    * @param    user
    *           The given user definition
    * @param    ext
    *           The given extended account data
    */
   @Override
   public void updateUser(UserDef user, Serializable ext)
   {
      this.adminService.setUser(user, ext, new AsyncCallback<Boolean>()
      {
         @Override
         public void onSuccess(Boolean result)
         {
            logger.log(Level.INFO, "updateUser " + user.subjectId + ", Is updated?" + result);
            updateUsersList(()->{getView().selectUserByName(user.subjectId); restoreState();});
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(()->{getView().selectUserByName(user.subjectId); restoreState();});
         }
      });
   }
   
   /**
    * Cancels the Find User Account Dialog and rolls back any related changes.
    */
   @Override
   public void cancelFindUserAccountDialog()
   {
   }

   /**
    * Setups the Find User Account Dialog.
    */
   @Override
   public void setupFindUserAccountDialog()
   {
      InputDialog dialog = inputDialogProvider.get();
      IFindAccountScreen findAccount = new IFindAccountScreen()
      {
         @Override
         public void findAccountByName(String name)
         {
            adminService.checkUser(
                     name, 
                     new AsyncCallback<TaggedName>()
                     {
                        @Override
                        public void onFailure(Throwable caught)
                        {
                           serverAlarmsManager.ring();
                           modalDialogsManager.showInfo("Can't find user " + name + ".");
                        }

                        @Override
                        public void onSuccess(TaggedName result)
                        {
                           if (result != null)
                           {
                              getView().selectUser(result);
                              dialog.dismiss();
                           }
                           else
                           {
                              modalDialogsManager.showInfo("Can't find user " + name + ".");
                           }
                        }
                     });
         }
      };
      
      findUserAccountManager.show(getView().getUserDefModalParent(), dialog, findAccount);
   }

   /**
    * Disables the target user account.
    * 
    * @param    user
    *           The target user account given by its tagged name
    */
   @Override
   public void disableUserAccount(TaggedName user)
   {
      if (selectedUser == null || selectedUser.subjectId == null ||
               !selectedUser.subjectId.equals(user.getName()))
      {
         return;
      }
      boolean enable = !selectedUser.enabled;
      String title   = enable ? "Enable User" : "Disable User";
      String message = (enable ? "Enable user " : "Disable user ") + user.getName() + "?";
      modalDialogsManager.showYesNo(title, message, MessageType.QUESTION, button -> {
         
         if (button == 0)
         {
            selectedUser.enabled = enable;
            adminService.setUser(selectedUser, new AsyncCallback<Boolean>()
            {
               
               @Override
               public void onFailure(Throwable caught)
               {
                  updateUsersList(() -> {getView().selectUser(user);});
                  logger.log(Level.WARNING, "setUser " + user.getName() + " got " + caught);
                  modalDialogsManager.showError("Can't " + (enable ? "enable" : "disable") +
                           user.getName() + " user account.");
               }
               
               @Override
               public void onSuccess(Boolean result)
               {
                  updateUsersList(() -> {getView().selectUser(user);});
                  if (!result)
                  {
                     modalDialogsManager.showError("Can't update to " +
                              (enable ? "enable" : "disable") + user.getName() + " user account.");
                  }
               }
            });
         }
      });
   }

   /**
    * Removes a given user account from given groups accounts.
    * 
    * @param    user
    *           The target user account given by its tagged name
    * @param    groups
    *           The array of target groups accounts given by tagged names
    */
   @Override
   public void removeUserFromGroups(TaggedName user, TaggedName[] groups)
   {
      if (!getView().isSelectedFromUsers(user) || ! getView().isSelectedFromUserGroups(groups) || 
               selectedUser == null || selectedUser.subjectId == null ||
               !selectedUser.subjectId.equals(user.getName()))
      {
         return;
      }
      
      LinkedHashSet<String> assignedGroups = new LinkedHashSet<String>(Arrays.asList(selectedUser.groups));
      String[] unassignedGroups = new String[groups.length];
      int i = 0;
      for(TaggedName group : groups)
      {
         assignedGroups.remove(group.getName());
         unassignedGroups[i++] = group.getName();
      }
      
      selectedUser.groups = assignedGroups.toArray(new String[assignedGroups.size()]);
      
      adminService.setUser(selectedUser, new AsyncCallback<Boolean>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(() -> {getView().selectUser(user);});
            logger.log(Level.WARNING, "setUser " + user.getName() + " got " + caught);
            modalDialogsManager.showError("Can't remove " + user.getName() + " from " +
                     Arrays.toString(unassignedGroups));
         }

         @Override
         public void onSuccess(Boolean result)
         {
            updateUsersList(() -> {getView().selectUser(user);});
            if (!result)
            {
               modalDialogsManager.showError("Can't remove " + user.getName() + " from " +
                        Arrays.toString(unassignedGroups));
            }
         }
      });
   }

   /**
    * Makes a given user account to be a member of given groups accounts.
    * 
    * @param    user
    *           The target user account given by its tagged name
    * @param    groups
    *           The array of target groups accounts given by tagged names
    */
   @Override
   public void addUserToGroups(TaggedName user, TaggedName[] groups)
   {
      if (!getView().isSelectedFromUsers(user) || ! getView().isSelectedFromAllGroups(groups) || 
               selectedUser == null || selectedUser.subjectId == null ||
               !selectedUser.subjectId.equals(user.getName()))
      {
         return;
      }
      
      LinkedHashSet<String> assignedGroups = new LinkedHashSet<String>(Arrays.asList(selectedUser.groups));
      String[] additionalGroups = new String[groups.length];
      int i = 0;
      for(TaggedName group : groups)
      {
         assignedGroups.add(group.getName());
         additionalGroups[i++] = group.getName();
      }
      
      selectedUser.groups = assignedGroups.toArray(new String[assignedGroups.size()]);
      
      adminService.setUser(selectedUser, new AsyncCallback<Boolean>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            updateUsersList(() -> {getView().selectUser(user);});
            logger.log(Level.WARNING, "setUser " + user.getName() + " got " + caught);
            modalDialogsManager.showError("Can't add " + user.getName() + " to " +
                     Arrays.toString(additionalGroups));
         }

         @Override
         public void onSuccess(Boolean result)
         {
            updateUsersList(() -> {getView().selectUser(user);});
            if (!result)
            {
               modalDialogsManager.showError("Can't add " + user.getName() + " to " +
                        Arrays.toString(additionalGroups));
            }
         }
      });
   }
}