OsUsersPresenter.java
/*
** Module : OsUsersPresenter.java
** Abstract : OsUsersPresenter implements common business methods to manage OS user accounts.
**
** Copyright (c) 2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 GBB 20230807 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.osusers;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.admin.client.*;
import com.goldencode.p2j.admin.client.application.*;
import com.goldencode.p2j.admin.client.application.home.*;
import com.goldencode.p2j.admin.client.application.home.accounts.*;
import com.goldencode.p2j.admin.client.application.home.acl.*;
import com.goldencode.p2j.admin.client.widget.dialog.*;
import com.goldencode.p2j.admin.shared.*;
import com.google.gwt.user.client.rpc.*;
import com.google.gwt.user.client.ui.*;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.web.bindery.event.shared.*;
import com.gwtplatform.mvp.client.*;
import com.gwtplatform.mvp.client.annotations.*;
import com.gwtplatform.mvp.client.presenter.slots.*;
import com.gwtplatform.mvp.client.proxy.*;
import javax.inject.*;
import java.util.*;
import java.util.logging.*;
/** OsUsersPresenter implements common business methods to manage OS user accounts. */
public class OsUsersPresenter
extends AccountsPresenter<OsUsersPresenter.MyView, OsUsersPresenter.MyProxy>
implements OsUsersUIHandlers
{
/**
* Defines user accounts view states.
*/
private enum OsUsersViewStates
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 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
*/
OsUsersViewStates(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,
/* addOsUserAccount */ true,
/* deleteOsUserAccount */ false,
/* editOsUserAccount */ false,
/* disableOsUserAccount */ false,
/* findOsUser */ true
};
/** state definition - group definition creation */
private static final boolean[] STATE_USER_ADD =
{
/* refresh */ false,
/* addOsUserAccount */ false,
/* deleteOsUserAccount */ false,
/* editOsUserAccount */ false,
/* disableOsUserAccount */ false,
/* findOsUser */ false
};
/** state definition - group definition editing */
private static final boolean[] STATE_USER_EDIT =
{
/* refresh */ false,
/* addOsUserAccount */ false,
/* deleteOsUserAccount */ false,
/* editOsUserAccount */ false,
/* disableOsUserAccount */ false,
/* findOsUser */ false
};
/** state definition - single selection in users list */
private static final boolean[] STATE_USERS_SINGLE =
{
/* refresh */ true,
/* addOsUserAccount */ true,
/* deleteOsUserAccount */ true,
/* editOsUserAccount */ true,
/* disableOsUserAccount */ true,
/* findOsUser */ true
};
/** state definition - multiple selection in users list */
private static final boolean[] STATE_USERS_MULTI =
{
/* refresh */ true,
/* addOsUserAccount */ true,
/* deleteOsUserAccount */ true,
/* editOsUserAccount */ false,
/* disableOsUserAccount */ false,
/* findOsUser */ true
};
/** mask definition - west-center */
private static final boolean[] MASK_WEST_CENTER =
{
/* refresh */ true,
/* addOsUserAccount */ true,
/* deleteOsUserAccount */ true,
/* editOsUserAccount */ true,
/* disableOsUserAccount */ true,
/* findOsUser */ 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 OsUserDef selectedUser;
/** The set of selected users */
private final Set<OsUserDef> selectedUsers;
/** The user account definition provider */
private final Provider<AddEditOsUserWidget> uadProvider;
/** The common input dialogs provider */
private final Provider<InputDialog> inputDialogProvider;
/** The Find OS User Account manager */
private final FindOsUserAccount findOsUserAccountManager;
/**
* 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<OsUsersUIHandlers>, 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);
/**
* 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);
/**
* 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);
/**
* 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
*/
DeletingOsUsersAlert getDeletingUsersAlert();
/**
* 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 getOsUserDefModalParent();
/**
* Sets the FWD users table's model with all associated FWD users for the single selected OS user
* from the OS users table view.
*
* @param fwdUsers
* The array of FWD users given by tagged names
*/
void setAssociatedFwdUsers(TaggedName[] fwdUsers);
}
/**
* Defines the proxy place for GWTP framework. Binds /accounts/os-users url to this presenter.
*/
@ProxyStandard
@NameToken(NameTokens.OS_USERS)
public interface MyProxy
extends ProxyPlace<OsUsersPresenter>
{}
/**
* 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 OsUsersPresenter(EventBus eventBus,
MyView view,
MyProxy proxy,
PlaceManager placeManager,
@Named("OsUsersViewStateMachine") ViewStateMachine viewStateMachine,
AdminServiceAsync adminService,
ModalDialogs modalDialogsManager,
Alarm serverAlarmsManager,
Provider<AddEditOsUserWidget> 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.findOsUserAccountManager = new FindOsUserAccount(acctExtensionHolder);
this.selectedUsers = new HashSet<>();
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(OsUsersViewStates.USERS_LIST_REFRESHED);
}
/**
* Triggers an action to refresh the all users table.
*/
@Override
public void onRefresh()
{
updateUsersList();
changeState(OsUsersViewStates.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.listOsUsers(new AsyncCallback<TaggedName[]>()
{
@Override
public void onSuccess(TaggedName[] result)
{
getView().setUsers(result);
callback.run();
}
@Override
public void onFailure(Throwable caught)
{
logger.log(Level.INFO, "Can't get all OS Users", caught);
callback.run();
}
});
}
/**
* Shows the Add OS User Account Dialog.
*/
@Override
public void showAddUserAccountDialog()
{
logger.log(Level.INFO, "showAddOsUserAccountDialog");
AddEditOsUserWidget uad = uadProvider.get();
uad.build(null, r ->
{
if (!r)
{
return;
}
uad.show(getView().getOsUserDefModalParent(), new OsUserDef(), userDef ->
{
if (userDef == null)
{
return;
}
addUser(userDef);
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");
if (users == null || users.length == 0)
{
return;
}
checkAssociatedFwdUsers(users, 0);
}
/**
* Checks if the selected OS users are associated with FWD accounts. If any mapping found, a warning
* message is displayed to notify the delete action will not be executed. If no mapping is found, the
* search continues with the next user in the array. If no mappings are found up to the last user, a
* confirmation alert is displayed.
*
* @param osUsers
* The array of OS users to be checked
* @param currentIndex
* The index of the current element in the array of OS users. Used for recursion.
*/
private void checkAssociatedFwdUsers(TaggedName[] osUsers, int currentIndex)
{
String fwdUserId = osUsers[currentIndex].getName();
adminService.listAssociatedFwdUsers(fwdUserId,
new AdminCallback<TaggedName[]>()
{
@Override
public void onDone(TaggedName[] result)
{
if (result != null && result.length > 0)
{
getView().getDeletingUsersAlert().setWarningAlert(osUsers);
}
else if (currentIndex + 1 < osUsers.length)
{
checkAssociatedFwdUsers(osUsers, currentIndex + 1);
}
else
{
getView().getDeletingUsersAlert().setAlert(osUsers);
}
}
});
}
/**
* 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");
AddEditOsUserWidget uad = uadProvider.get();
uad.build(selectedUser, r ->
{
if (!r)
{
return;
}
uad.show(getView().getOsUserDefModalParent(), selectedUser, userDef ->
{
if (userDef == null)
{
return;
}
updateUser(userDef);
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 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<>();
for (TaggedName user : users)
{
AdminCallback<Boolean> callback = mc.newCallback();
callbacks.put(user, callback);
logger.info("OS 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.deleteOsUser(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 OS user account " + e.getKey().getName());
}
}
}
updateUsersList(()-> changeState(OsUsersViewStates.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(OsUsersViewStates.USERS_LIST_REFRESHED);
return;
}
MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
LinkedHashMap<TaggedName, AdminCallback<OsUserDef>> callbacks = new LinkedHashMap<>();
users.forEach(user ->
{
AdminCallback<OsUserDef> cb = mc.newCallback();
callbacks.put(user, cb);
logger.info("get user " + user.getName());
});
for (Map.Entry<TaggedName, AdminCallback<OsUserDef>> e : callbacks.entrySet())
{
this.adminService.getOsUser(e.getKey().getName(), e.getValue());
}
mc.onDone(() ->
{
if (mc.isSuccess())
{
selectedUsers.clear();
for (Map.Entry<TaggedName, AdminCallback<OsUserDef>> e : callbacks.entrySet())
{
OsUserDef OsUserDef = e.getValue().getResult();
logger.info("selected user " + OsUserDef);
selectedUsers.add(OsUserDef);
selectedUser = OsUserDef;
}
if (selectedUsers.size() > 1)
{
changeState(OsUsersViewStates.MULTI_USERS_SELECTED);
}
else if (selectedUsers.size() == 1)
{
changeState(OsUsersViewStates.SINGLE_USER_SELECTED);
getView().toggleDisableUserButton(selectedUser.enabled);
}
else
{
changeState(OsUsersViewStates.USERS_LIST_REFRESHED);
}
}
else
{
for (Map.Entry<TaggedName, AdminCallback<OsUserDef>> e : callbacks.entrySet())
{
if (e.getValue().getResult() == null)
{
logger.info("failed to get user definition for " + e.getKey().getName());
}
}
changeState(OsUsersViewStates.USERS_LIST_REFRESHED);
}
});
if (callbacks.size() == 1)
{
TaggedName selectedUser = callbacks.keySet().toArray(new TaggedName [1])[0];
final String name = selectedUser.getName();
this.adminService.listAssociatedFwdUsers(name, new AsyncCallback<TaggedName[]>()
{
@Override
public void onSuccess(TaggedName[] result)
{
getView().setAssociatedFwdUsers(result);
}
@Override
public void onFailure(Throwable caught)
{
logger.log(Level.INFO, "Can't get associated FWD users for the OS user" + name);
}
});
}
else
{
getView().setAssociatedFwdUsers(null);
}
}
/**
* Adds a new user.
*
* @param newUser
* The new user definition
*/
@Override
public void addUser(OsUserDef newUser)
{
this.adminService.addOsUser(newUser, 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 OS user account " + newUser.subjectId);
}
}
@Override
public void onFailure(Throwable caught)
{
updateUsersList(()-> restoreState());
}
});
}
/**
* Updates a given user account.
*
* @param user
* The given user definition
*/
@Override
public void updateUser(OsUserDef user)
{
this.adminService.setOsUser(user, 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.checkOsUser(
name,
new AsyncCallback<TaggedName>()
{
@Override
public void onFailure(Throwable caught)
{
serverAlarmsManager.ring();
modalDialogsManager.showInfo("Can't find OS user " + name + ".");
}
@Override
public void onSuccess(TaggedName result)
{
if (result != null)
{
getView().selectUser(result);
dialog.dismiss();
}
else
{
modalDialogsManager.showInfo("Can't find OS user " + name + ".");
}
}
});
}
};
findOsUserAccountManager.show(getView().getOsUserDefModalParent(), 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)
{
return;
}
selectedUser.enabled = enable;
adminService.setOsUser(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.");
}
}
});
});
}
}