GroupsPresenter.java

/*
** Module   : GroupsPresenter.java
** Abstract : GroupsPresenter represents a controller to manage groups.
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 SBI 20170601 Created initial version.
*/
/*
** 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.groups;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
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.accounts.UseCaseMode;
import com.goldencode.p2j.admin.client.application.home.acl.*;
import com.goldencode.p2j.admin.client.widget.dialog.ModalDialogs;
import com.goldencode.p2j.admin.shared.AdminServiceAsync;
import com.goldencode.p2j.admin.GroupDef;
import com.goldencode.p2j.admin.TaggedName;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasEnabled;
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;

/**
 * GroupsPresenter represents a controller to manage groups.
 */
public class GroupsPresenter
extends AccountsPresenter<GroupsPresenter.MyView, GroupsPresenter.MyProxy>
implements GroupsUIHandlers
{
   /**
    * Defines the Group Accounts View states
    */
   public static enum GroupsViewStates
   implements ViewStateMachine.TransitionState
   {
      /** Groups list has been refreshed. */
      GROUPS_LIST_REFRESHED(MASK_WEST_CENTER, STATE_INITIAL_EMPTY),
      
      /** The new group account has been added currently. */
      ADD_GROUP_ACCOUNT(MASK_WEST_CENTER, STATE_GROUP_ADD),
      
      /** The group has been edited currently. */
      EDIT_GROUP_ACCOUNT(MASK_WEST_CENTER, STATE_GROUP_EDIT),
      
      /** The single group has been selected. */
      SINGLE_GROUP_SELECTED(MASK_WEST_CENTER, STATE_GROUPS_SINGLE),
      
      /** The more than one group have been selected. */
      MULTI_GROUPS_SELECTED(MASK_WEST_CENTER, STATE_GROUPS_MULTI),
      
      /** The group and user have been selected. */
      GROUP_USER_SELECTED(MASK_CENTER, STATE_GROUPS_SINGLE_USER_SELECTED),
      
      /** The all user table has been filled. */
      USERS_LIST_POPULATED(MASK_EAST, STATE_ALL_USERS),
      
      /** The group users table has been refreshed. */
      GROUP_USERS_LIST_REFRESHED(MASK_CENTER_TOP, STATE_INITIAL_EMPTY);
      
      /** 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 GroupsViewStates(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,
   /*       addGroup,          */       true,
   /*       deleteGroup,             */ false,
   /*       cloneGroup,              */ false,
   /*       editGroup,               */ false,
   /*       switchTOACLs,            */ false,
   
   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,
   
   /*        populateUsers,     */       true,
   /*        addSelected,             */ false,
   /*           filterSelector,       */ false,
   /*           filterUsers           */ false
   };
   
   /** state definition - group definition creation */
   private static final boolean[] STATE_GROUP_ADD =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs,         */ false,
   
   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,
   
   /*        populateUsers,       */ false,
   /*        addSelected,         */ false,
   /*           filterSelector,   */ false,
   /*           filterUsers       */ false
   };

   /** state definition - group definition editing */
   private static final boolean[] STATE_GROUP_EDIT =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs,         */ false,

   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,

   /*        populateUsers,          */ false,
   /*        addSelected,            */ false,
   /*           filterSelecto        */ false,
   /*           filterUsers          */ false
   };

   /** state definition - single selection in groups list */
   private static final boolean[] STATE_GROUPS_SINGLE =
   {
   /*       refresh,              */       true,
   /*       addGroup,             */       true,
   /*       deleteGroup,          */       true,
   /*       cloneGroup,           */       true,
   /*       editGroup,            */       true,
   /*       switchTOACLs,         */       true,

   /*       newUser,           */       true,
   /*       addUser,           */       true,
   /*       deleteUser,              */ false,

   /*        populateUsers,     */       true,
   /*        addSelected,             */ false,
   /*           filterSelector,       */ false,
   /*           filterUsers           */ false
   };
   
   /** A single user selected state*/ 
   private static final boolean[] STATE_GROUPS_SINGLE_USER_SELECTED =
   {
   /*       refresh,              */       true,
   /*       addGroup,             */       true,
   /*       deleteGroup,          */       true,
   /*       cloneGroup,           */       true,
   /*       editGroup,            */       true,
   /*       switchTOACLs,         */       true,

   /*       newUser,           */       true,
   /*       addUser,           */       true,
   /*       deleteUser,              */ true,

   /*        populateUsers,     */       true,
   /*        addSelected,             */ false,
   /*           filterSelector,       */ false,
   /*           filterUsers           */ false
   };
   
   /** state definition - multiple selection in groups list */
   private static final boolean[] STATE_GROUPS_MULTI =
   {
   /*       refresh,          */       true,
   /*       addGroup,         */       true,
   /*       deleteGroup,      */       true,
   /*       cloneGroup,             */ false,
   /*       editGroup,              */ false,
   /*       switchTOACLs,           */ false,

   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,

   /*        populateUsers,           */ true,
   /*        addSelected,             */ false,
   /*           filterSelecto         */ false,
   /*           filterUsers           */ false
   };
   
   /** state definition - all users list populated */
   private static final boolean[] STATE_ALL_USERS =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs          */ false,

   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,

   /*        populateUsers,           */ true,
   /*        addSelected,             */ false,
   /*           filterSelector,      */  true,
   /*           filterUsers           */ true,
   };
   
   /** mask definition - west-center */
   private static final boolean[] MASK_WEST_CENTER =
   {
   /*       refresh,              */ true,
   /*       addGroup,             */ true,
   /*       deleteGroup,          */ true,
   /*       cloneGroup,           */ true,
   /*       editGroup,            */ true,
   /*       switchTOACLs          */ true,

   /*       newUser,              */ true,
   /*       addUser,              */ true,
   /*       deleteUser,           */ true,

   /*        populateUsers,       */ false,
   /*        addSelected,         */ false,
   /*           filterSelector,   */ false,
   /*           filterUsers       */ false
   };
   
   /** mask definition - center */
   private static final boolean[] MASK_CENTER =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs          */ false,

   /*       newUser,              */ true,
   /*       addUser,              */ true,
   /*       deleteUser,           */ true,

   /*        populateUsers,       */ false,
   /*        addSelected,         */ false,
   /*           filterSelector,   */ false,
   /*           filterUsers       */ false
   };

   /** mask definition - center top */
   private static final boolean[] MASK_CENTER_TOP =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs          */ false,

   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ true,

   /*        populateUsers,       */ false,
   /*        addSelected,         */ false,
   /*           filterSelector,   */ false,
   /*           filterUsers       */ false
   };

   /** mask definition - east */
   private static final boolean[] MASK_EAST =
   {
   /*       refresh,              */ false,
   /*       addGroup,             */ false,
   /*       deleteGroup,          */ false,
   /*       cloneGroup,           */ false,
   /*       editGroup,            */ false,
   /*       switchTOACLs,         */ false,
   
   /*       newUser,              */ false,
   /*       addUser,              */ false,
   /*       deleteUser,           */ false,
   
   /*        populateUsers,           */ true,
   /*        addSelected,             */ true,
   /*           filterSelector,       */ true,
   /*           filterUsers           */ 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 set of selected groups definitions */
   private final Set<GroupDef> selectedGroups;
   
   /** The currently selected group */
   private GroupDef selectedGroup;
   
   /** The common modal dialogs manager */
   private final ModalDialogs modalDialogsManager;
   
   /** The server alarms manager */
   private final Alarm serverAlarmsManager;
   
   /**
    * 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<GroupsUIHandlers>, Provider<HasEnabled[]>, PrintingContext
   {
      /**
       * Sets the all groups table model.
       * 
       * @param    groups
       *           The array of groups given by tagged names
       */
      void setGroups(TaggedName[] groups);
      
      /**
       * Sets the group users table model.
       * 
       * @param    users
       *           The array of group users given by tagged names
       */
      void setUsersInGroup(TaggedName[] users);
      
      /**
       * Sets all users model.
       * 
       * @param    allUsers
       *           The array of all users accounts given by tagged names
       * @param    filter
       *           The search template to filter the displayed users
       */
      void setAllUsers(TaggedName[] allUsers, RegExp filter);
      
      /**
       * Tests if this group has been selected.
       * 
       * @param    group
       *           The target group
       * 
       * @return   True iff the target group has been selected, otherwise false.
       */
      boolean isSelectedGroup(TaggedName group);
      
      /**
       * Tests if these tested users have been selected.
       * 
       * @param    users
       *           The array of tested user accounts given by tagged names
       * 
       * @return   True if all tested users have been selected, otherwise false.
       */
      boolean isSelectedUsers(TaggedName[] users);
      
      /**
       * Select a group by its name.
       * 
       * @param    groupName
       *           The group name
       */
      void selectGroupByName(String groupName);
      
      /**
       * 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 togglePopulateUsersButton(boolean populated);
      
      /**
       * Gets a reference to the Group Definition View.
       * 
       * @return   The Group Definition View
       */
      GroupDefinitionView getGroupDefinitionView();
      
      /**
       * Gets a reference to the Deleting Groups Alert View.
       * 
       * @return   The Deleting Groups Alert View
       */
      DeletingGroupsAlert getDeletingGroupsAlert();
      
      /**
       * Gets a reference to the Clone Group View.
       * 
       * @return   The Clone Group View
       */
      CloneGroupView getCloneGroupView();
      
      /**
       * Gets a reference to the Unassigning Users Alert View.
       * 
       * @return   The Unassigning Users Alert View
       */
      UnassigningUsersAlert getUnassigningUsersAlert();
   }

   /**
    * Defines the proxy place for GWTP framework.
    */
   @ProxyStandard
   @NameToken(NameTokens.GROUPS)
   public interface MyProxy
   extends ProxyPlace<GroupsPresenter>
   {}
   
   /**
    * Defines the Groups 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    aclPopupPresenter
    *           The ACL popup presenter
    */
   @Inject
   public GroupsPresenter(EventBus eventBus,
                          MyView view,
                          MyProxy proxy,
                          PlaceManager placeManager,
                          @Named("GroupsViewStateMachine") ViewStateMachine viewStateMachine,
                          AdminServiceAsync adminService,
                          ModalDialogs modalDialogsManager,
                          Alarm serverAlarmsManager,
                          ACLPopupPresenter aclPopupPresenter)
   {
      super(eventBus,
            view,
            proxy,
            HomePresenter.SLOT_CONTENT,
            placeManager,
            viewStateMachine,
            aclPopupPresenter);
      
      this.adminService = adminService;
      
      this.modalDialogsManager = modalDialogsManager;
      
      this.serverAlarmsManager = serverAlarmsManager;
      
      this.selectedGroups = new HashSet<GroupDef>();
      
      getView().setUiHandlers(this);
   }
   
   /**
    * Loads the data before the associated view will be displayed.
    * 
    * @param    request
    *           The current request
    */
   @Override
   public void prepareFromRequest(PlaceRequest request)
   {
      super.prepareFromRequest(request);
      updateCustomAuthPlugins();
      updateGroupsList(()->{setState(GroupsViewStates.GROUPS_LIST_REFRESHED);});
   }

   /**
    * Performs the user action to refresh the all groups table.
    */
   @Override
   public void onRefresh()
   {
      updateGroupsList(()->{changeState(GroupsViewStates.GROUPS_LIST_REFRESHED);});
   }
   
   /**
    * Requests the groups from the server asynchronously and then updates the All Groups grid with
    * actual values and executes the given command after getting the server response.
    * 
    * @param    command
    *           The given command to execute
    */
   private void updateGroupsList(Runnable command)
   {
      this.adminService.listGroups(new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setGroups(result);
            command.run();
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call listGroups", caught);
            command.run();
         }
      });
   }

   /**
    * Requests the users that belongs to the given group asynchronously and then updates the
    * Group Users grid with actual values after getting the server response.
    * 
    * @param    group
    *           The group given by its tagged name
    */
   private void updateUsersInGroup(TaggedName group)
   {
      final String name = group.getName();
      this.adminService.listGroupUsers(name, new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setUsersInGroup(result);
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call listGroupUsers for GROUP " + name, caught);
         }
      });
   }

   /**
    * Populates the all users table if it has been empty or updates its rows with the current data.
    */
   @Override
   public void populateAndRefreshAllUsers()
   {
      this.adminService.listUsers(new AsyncCallback<TaggedName[]>()
      {
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setAllUsers(result, filterExp);
            getView().togglePopulateUsersButton(true);
            if (result != null && result.length > 0)
            {
               changeState(GroupsViewStates.USERS_LIST_POPULATED);
            }
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call listUsers", caught);
         }
      });
   }

   /**
    * Retrieves the custom authentication plugins data and fills the custom authentication plugins
    * combobox.
    */
   @Override
   public void updateCustomAuthPlugins()
   {
      this.adminService.listAuthPlugins(new AsyncCallback<TaggedName[]>()
      {
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().getGroupDefinitionView().setCustomAuthPlugins(result);
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call listAuthPlugins", caught);
         }
      });
   }

   /**
    * Setups the Add Group Dialog.
    */
   @Override
   public void setupAddGroupDialog()
   {
      logger.log(Level.INFO, "setupAddGroupDialog");
      getView().getGroupDefinitionView().setUseCaseMode(UseCaseMode.ADD_GROUP, new GroupDef());
   }

   /**
    * Setups the Deleting Groups Alert dialog.
    * 
    * @param    groups
    *           The array of the selected groups to delete
    */
   @Override
   public void setupDeletingGroupsAlert(TaggedName[] groups)
   {
      logger.log(Level.INFO, "setupDeleteSelectedGroupsDialog");
      getView().getDeletingGroupsAlert().setAlert(groups);
   }

   /**
    * Setups the Clone Group Dialog.
    * 
    * @param    clonedGroup
    *           The target group to clone
    */
   @Override
   public void setupCloneGroupDialog(TaggedName clonedGroup)
   {
      logger.log(Level.INFO, "setupCloneGroupDialog");
      getView().getCloneGroupView().setupClonedGroup(clonedGroup);
   }

   /**
    * Setups the Edit Group Dialog.
    * 
    * @param    updatedGroup
    *           The group definition to edit
    */
   @Override
   public void setupEditGroupDialog(TaggedName updatedGroup)
   {
      logger.log(Level.INFO, "setupEditGroupDialog");
      getView().getGroupDefinitionView().setUseCaseMode(UseCaseMode.EDIT_GROUP, selectedGroup);
   }

   /**
    * Cancels the Add Group Dialog.
    */
   @Override
   public void cancelAddGroupDialog()
   {
      logger.log(Level.INFO, "cancelAddGroupDialog");
      restoreState();
   }

   /**
    * Cancels the Deleting Groups Alert dialog.
    */
   @Override
   public void cancelDeletingGroupsAlert()
   {
      logger.log(Level.INFO, "cancelDeleteSelectedGroupsDialog");
   }

   /**
    * Cancels the Clone Group Dialog.
    */
   @Override
   public void cancelCloneGroupDialog()
   {
      logger.log(Level.INFO, "cancelCloneGroupDialog");
   }

   /**
    * Cancels the Edit Group Dialog.
    */
   @Override
   public void cancelEditGroupDialog()
   {
      logger.log(Level.INFO, "cancelEditGroupDialog");
      restoreState();
   }

   /**
    * Adds new group to the group accounts represented by the all groups table.
    * 
    * @param    newGroup
    *           The new group definition object representing the new adding group
    */
   @Override
   public void addGroup(final GroupDef newGroup)
   {
      this.adminService.addGroup(newGroup, new AdminCallback<Boolean>(adminService,
                                                                      serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            updateGroupsList(()->{ getView().selectGroupByName(newGroup.subjectId); });
         }
      });
   }

   /**
    * Delete selected groups given by the array of group tagged names
    * 
    * @param    groups
    *           The array of groups to delete
    */
   @Override
   public void deleteSelectedGroups(TaggedName[] groups)
   {
      for(TaggedName group : groups)
      {
         if (!getView().isSelectedGroup(group))
         {
            return;
         }
      }
      MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
      LinkedHashMap<TaggedName, AdminCallback<Boolean>> callbacks = new LinkedHashMap<TaggedName, AdminCallback<Boolean>>();
      for(TaggedName group : groups)
      {
         AdminCallback<Boolean> callback = mc.newCallback();
         callbacks.put(group, callback);
         logger.info("group to delete " + group.getName());
      };
      for (Map.Entry<TaggedName, AdminCallback<Boolean>> e : callbacks.entrySet())
      {
         this.adminService.deleteGroup(e.getKey().getName(), e.getValue());
      }
      mc.onDone(() ->
      {
         if (mc.isSuccess())
         {
            updateGroupsList(()->{setState(GroupsViewStates.GROUPS_LIST_REFRESHED);});
         }
         else
         {
            for (Map.Entry<TaggedName, AdminCallback<Boolean>> e : callbacks.entrySet())
            {
               if (e.getValue().getResult() == null || !e.getValue().getResult())
               {
                  logger.info("failed to delete group " + e.getKey().getName());
               }
            }
            updateGroupsList(()->{setState(GroupsViewStates.GROUPS_LIST_REFRESHED);});
         }
      });
   }

   /**
    * Removes the selected users from the given group.
    * 
    * @param    group
    *           The group tagged name
    * @param    users
    *           The array of selected users given by their tagged names
    */
   @Override
   public void unassignSelectedUsers(final TaggedName group, TaggedName[] users)
   {
      if (!getView().isSelectedGroup(group) || !getView().isSelectedUsers(users))
      {
         return;
      }
      String[] selected = new String[users.length];
      for(int i = 0; i < users.length; i++)
      {
         selected[i] = users[i].getName();
      }
      this.adminService.changeGroupUsers(group.getName(), new String[0], selected,
                                         new AdminCallback<Boolean>(adminService,
                                                                    serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            updateUsersInGroup(group);
            restoreState();
         }
      });
   }

   /**
    * Setups the Unassigning Users Alert dialog.
    * 
    * @param    group
    *           The group tagged name
    * @param    users
    *           The array of selected users given by their tagged names
    */
   @Override
   public void setupUnassigningUsersAlert(TaggedName group, TaggedName[] users)
   {
      getView().getUnassigningUsersAlert().setAlert(group, users);
   }

   /**
    * Clones the given group by creating the new one.
    * 
    * @param    nameFrom
    *           The name of the group to clone
    * @param    nameTo
    *           The name for the new cloned group
    * @param    acls
    *           True indicates that the ACL of target group will be cloned too, otherwise
    *           the ACL of the new created group will be empty.
    */
   @Override
   public void cloneGroup(final String nameFrom, String nameTo, boolean acls)
   {
      this.adminService.cloneGroup(nameFrom, nameTo, acls,
                                   new AdminCallback<Boolean>(adminService, serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            logger.log(Level.INFO, "cloneGroup " + nameFrom + ", " + result);
            updateGroupsList(()->{changeState(GroupsViewStates.GROUPS_LIST_REFRESHED);});
         }
      });
   }

   /**
    * Commits changes in the target group definition.
    * 
    * @param    updatedGroup
    *           The group definition to commit its changes
    */
   @Override
   public void updateGroup(final GroupDef updatedGroup)
   {
      this.adminService.setGroup(updatedGroup,
                                 new AdminCallback<Boolean>(adminService, serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            logger.log(Level.INFO, "setGroup " + updatedGroup.subjectId + ", " + result);
            updateGroupsList(()->{getView().selectGroupByName(updatedGroup.subjectId);});
         }
      });
   }

   /**
    * Triggers the groups selection changed.
    * 
    * @param    groups
    *           The new set of selected groups
    */
   @Override
   public void onGroupSelected(Set<TaggedName> groups)
   {
      if (groups == null)
      {
         logger.info("Groups selection is cleared.");
         changeState(GroupsViewStates.GROUPS_LIST_REFRESHED);
         return;
      }
      MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
      LinkedHashMap<TaggedName, AdminCallback<GroupDef>> callbacks = new LinkedHashMap<TaggedName, AdminCallback<GroupDef>>();
      groups.forEach(group ->
      {
         AdminCallback<GroupDef> cb = mc.newCallback();
         callbacks.put(group, cb);
         logger.info("get group " + group.getName());
      });
      for (Map.Entry<TaggedName, AdminCallback<GroupDef>> e : callbacks.entrySet())
      {
         this.adminService.getGroup(e.getKey().getName(), e.getValue());
      }
      mc.onDone(() ->
      {
         if (mc.isSuccess())
         {
            selectedGroups.clear();
            for (Map.Entry<TaggedName, AdminCallback<GroupDef>> e : callbacks.entrySet())
            {
               GroupDef groupDef = e.getValue().getResult();
               logger.info("selected group " + groupDef);
               selectedGroups.add(groupDef);
               selectedGroup = groupDef;
            }
            if (selectedGroups.size() > 1)
            {
               changeState(GroupsViewStates.MULTI_GROUPS_SELECTED);
            }
            else if (selectedGroups.size() == 1)
            {
               changeState(GroupsViewStates.SINGLE_GROUP_SELECTED);
            }
            else
            {
               changeState(GroupsViewStates.GROUPS_LIST_REFRESHED);
            }
         }
         else
         {
            for (Map.Entry<TaggedName, AdminCallback<GroupDef>> e : callbacks.entrySet())
            {
               if (e.getValue().getResult() == null)
               {
                  logger.info("Can't get group definition for " + e.getKey().getName());
               }
            }
            changeState(GroupsViewStates.GROUPS_LIST_REFRESHED);
         }
      });

      if (callbacks.size() == 1)
      {
         TaggedName groupTaggedName = callbacks.keySet().toArray(new TaggedName [1])[0];
         updateUsersInGroup(groupTaggedName);
      } 
      else
      {
         getView().setUsersInGroup(null);
      }
      
   }

   /**
    * Triggers the users selection changed.
    * 
    * @param    selectedUsers
    *           The new set of selected users
    */
   @Override
   public void onUserSelected(Set<TaggedName> selectedUsers)
   {
      if (selectedUsers == null || selectedUsers.isEmpty())
      {
         changeState(GroupsViewStates.GROUP_USERS_LIST_REFRESHED);

      }
      else
      {
         changeState(GroupsViewStates.GROUP_USER_SELECTED);
      }
   }

   /**
    * Defines the method that is invoked if the Unassigning Users Alert has been cancelled.
    */
   @Override
   public void cancelUnassigningUsersAlert()
   {}

   /**
    * Adds a user to the given group.
    * 
    * @param    group
    *           The group tagged name
    * @param    user
    *           The user account name
    */
   @Override
   public void addUserToGroup(TaggedName group, String user)
   {
      if (!getView().isSelectedGroup(group))
      {
         return;
      }
      
      this.adminService.changeGroupUsers(group.getName(), new String[] {user}, new String[0],
                                         new AdminCallback<Boolean>(adminService,
                                                                    serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            updateUsersInGroup(group);
         }
      });
   }

   /**
    * Sets the target user search template.
    * 
    * @param    value
    *           The target search template
    */
   @Override
   public void setUsersFilter(String value)
   {
      this.setFilter(value);
      
      populateAndRefreshAllUsers();
   }

   /**
    * Adds the selected users to the given group.
    * 
    * @param    group
    *           The group tagged name
    * @param    selectedUsers
    *           The array of selected users given by their tagged names
    */
   @Override
   public void addUsersToGroup(TaggedName group, Set<TaggedName> selectedUsers)
   {
      if (!getView().isSelectedGroup(group))
      {
         //TODO
         return;
      }
      int size = selectedUsers.size();
      ArrayList<String> usersList = new ArrayList<String>(size);
      selectedUsers.forEach(user -> usersList.add(user.getName()));
      this.adminService.changeGroupUsers(group.getName(),
                                         usersList.toArray(new String[size]),
                                         new String[0],
                                         new AdminCallback<Boolean>(adminService,
                                                                    serverAlarmsManager)
      {
         @Override
         public void onDone(Boolean result)
         {
            updateUsersInGroup(group);
         }
      });
   }
}