TerminateSessions.java
/*
** Module : TerminateSessions.java
** Abstract : Terminate sessions widget.
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 HC 20170612 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.runtime.customlib;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.admin.client.widget.*;
import com.goldencode.p2j.admin.client.widget.dialog.*;
import com.goldencode.p2j.admin.shared.*;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.i18n.client.*;
import com.google.gwt.uibinder.client.*;
import com.google.gwt.user.client.rpc.*;
import com.google.gwt.user.client.ui.*;
import com.google.inject.*;
import org.gwtbootstrap3.client.ui.*;
import org.gwtbootstrap3.client.ui.gwt.*;
import org.gwtbootstrap3.client.ui.html.*;
import java.util.*;
import java.util.function.*;
/**
* Terminate sessions widget.
*/
public class TerminateSessions
extends Composite
{
/**
* Action type enum.
*/
public enum JarActionType
{
JAR_DEREGISTER,
JAR_RELOAD,
API_DEREGISTER
}
/**
* GWT binder.
*/
interface Binder
extends UiBinder<Modal, TerminateSessions>
{}
/** sessions grid */
@UiField
DataGrid grid;
/** sessions grid title */
@UiField
Heading gridTitle;
/** widget */
@UiField
Text helpText;
/** widget */
@UiField
Modal modal;
/** jar action type */
private JarActionType actionType;
/** target jar */
private String targetJar;
/** api interface class */
private String apiInterfaceClass;
/** close handler */
private Consumer<Boolean> closeHandler;
/** sessions grid handle */
private GridHandle<SessionInfo> gridHandle;
/** Admin service reference */
private final AdminServiceAsync adm;
/** Modal dialogs */
private final ModalDialogs dialogs;
/**
* Ctor.
*
* @param adm
* Admin service reference.
* @param dialogs
* Modal dialogs.
*/
@Inject
public TerminateSessions(com.goldencode.p2j.admin.shared.AdminServiceAsync adm, ModalDialogs dialogs)
{
this.adm = adm;
this.dialogs = dialogs;
}
/**
* Displays this widget.
*
* @param parent
* Parent where this widget is attached.
* @param actionType
* Action type.
* @param targetJar
* Target jar.
* @param apiInterfaceClass
* API interface class.
* @param closeHandler
* Close handler.
*/
public void show(HasWidgets parent,
JarActionType actionType,
String targetJar,
String apiInterfaceClass,
Consumer<Boolean> closeHandler)
{
this.modal.addHiddenHandler(evt ->
{
if (closeHandler != null)
{
closeHandler.accept(null);
}
});
this.actionType = actionType;
this.targetJar = targetJar;
this.apiInterfaceClass = apiInterfaceClass;
this.closeHandler = closeHandler;
String title = "";
switch (actionType)
{
case JAR_DEREGISTER:
title = "Customer Library Deregistration";
break;
case JAR_RELOAD:
title = "Customer Library Reload";
break;
case API_DEREGISTER:
title = "API Deregistration";
break;
}
modal.setTitle(title);
gridTitle.setText(actionType == JarActionType.API_DEREGISTER ?
"Sessions which have executed the selected API" :
"Sessions which have executed API(s) associated to the selected " +
"customer library");
// create help text
String replacement1 = null;
String replacement2 = null;
String replacement3 = null;
String terminateCaption = null;
switch (actionType)
{
case JAR_DEREGISTER:
replacement1 = "API(s) associated to the selected customer library";
replacement2 = "customer library";
replacement3 = "deregistered";
terminateCaption = "Deregister Customer Library";
break;
case JAR_RELOAD:
replacement1 = "API(s) associated to the selected customer library";
replacement2 = "customer library";
replacement3 = "reloaded";
terminateCaption = "Reload Customer Library";
break;
case API_DEREGISTER:
replacement1 = "the selected API";
replacement2 = "API";
replacement3 = "deregistered";
terminateCaption = "Deregister API";
break;
}
StringBuilder sb = new StringBuilder();
sb.append("The sessions displayed in this table have executed ");
sb.append(replacement1);
sb.append(". The ");
sb.append(replacement2);
sb.append(" cannot be ");
sb.append(replacement3);
sb.append(" while these sessions are active. ");
sb.append("You can finish them manually or forcibly terminate them. ");
sb.append("If you decide to finish them manually, then after you finish them, you should " +
"refresh the session list,");
sb.append("check that there are no more active sessions and use the \"");
sb.append(terminateCaption);
sb.append("\" button. ");
sb.append("If you decide to forcibly terminated them, just use the \"");
sb.append(terminateCaption);
sb.append("\" button, you will be prompted whether you really want to terminate them.");
helpText.setText(sb.toString());
parent.add(this);
modal.addShownHandler(evt ->
{
// create the grid in the modal shown handler, this is because the grid layout
// logic needs to know the element's client height, which is non-zero only after
// the modal is made visible
//noinspection NonJREEmulationClassesInClientCode
gridHandle = GridHelper.initListGrid(
grid,
new GridHelper.ColumnInfo<>("Account", si -> si.name,
(si1, si2) -> si1.name.compareTo(si2.name)),
new GridHelper.ColumnInfo<>("Description", si -> si.descr, null),
new GridHelper.ColumnInfo<>("Type", si -> si.user ? "User" : "Process",
null),
new GridHelper.ColumnInfo<>("Session ID", si -> Integer.toString(si.id),
null),
new GridHelper.ColumnInfo<>("Remote Node", si -> si.remoteNet,
(si1, si2) -> si1.remoteNet.compareTo(si2.remoteNet)),
new GridHelper.ColumnInfo<SessionInfo>("Logon Timestamp",
si -> formatLogonTime(si),
(si1, si2) -> Long.compare(si1.logonTime,
si2.logonTime))
);
refreshSessions(null);
});
modal.show();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bCancel")
public void bCancelClick(ClickEvent event)
{
close(false);
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bRefresh")
public void bRefreshClick(ClickEvent event)
{
refreshSessions(null);
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bTerminate")
public void bTerminateClick(ClickEvent event)
{
handleTerminateSessions();
}
/**
* Refresh the list of active sessions. If the list cannot be refreshed
* then an error message is displayed.
*
* @param onSuccess
* Success callback.
*/
private void refreshSessions(Runnable onSuccess)
{
String err = "Cannot load the list of sessions.";
adm.getActiveSessions(targetJar, apiInterfaceClass, new AsyncCallback<SessionInfo[]>()
{
@Override
public void onFailure(Throwable caught)
{
dialogs.showError(err);
}
@Override
public void onSuccess(SessionInfo[] result)
{
if (result == null)
{
dialogs.showError(err);
}
else
{
gridHandle.clearGrid();
gridHandle.getDataProvider().getList().addAll(Arrays.asList(result));
if (onSuccess != null)
{
onSuccess.run();
}
}
}
});
}
/**
* Perform the desired action, optionally, forcibly terminating sessions.
*/
private void handleTerminateSessions()
{
boolean force = !gridHandle.getDataProvider().getList().isEmpty();
if (force)
{
// confirm force termination of sessions
StringBuilder sb = new StringBuilder();
sb.append("Are you sure you want to ");
switch (actionType)
{
case JAR_DEREGISTER:
sb.append("deregister customer library");
break;
case JAR_RELOAD:
sb.append("reload customer library");
break;
case API_DEREGISTER:
sb.append("deregister API");
break;
}
sb.append(" and forcibly terminate all related sessions?");
String confirmationTitle = null;
switch (actionType)
{
case JAR_DEREGISTER:
confirmationTitle = "Force Deregister Customer Library";
break;
case JAR_RELOAD:
confirmationTitle = "Force Reload Customer Library";
break;
case API_DEREGISTER:
confirmationTitle = "Force Deregister API";
break;
}
dialogs.showYesNo(confirmationTitle, sb.toString(), MessageType.QUESTION, res ->
{
if (res == 0)
{
// an error has occurred
String err;
switch (actionType)
{
case JAR_DEREGISTER:
case JAR_RELOAD:
err = "Cannot deregister customer library.";
break;
case API_DEREGISTER:
err = "Cannot deregister API.";
break;
default:
err = "";
}
// YES selected
// perform desired action
JarOpErrorCode result = null;
switch (actionType)
{
case JAR_DEREGISTER:
adm.deregisterJar(targetJar, force, true, new AsyncCallback<JarOpErrorCode>()
{
@Override
public void onFailure(Throwable caught)
{
dialogs.showError(err, () -> close(false));
}
@Override
public void onSuccess(JarOpErrorCode result)
{
handleDeregisterResult(force, result);
}
});
break;
case JAR_RELOAD:
adm.deregisterJar(targetJar, force, false, new AsyncCallback<JarOpErrorCode>()
{
@Override
public void onFailure(Throwable caught)
{
dialogs.showError(err, () -> close(false));
}
@Override
public void onSuccess(JarOpErrorCode result)
{
handleDeregisterResult(force, result);
}
});
break;
case API_DEREGISTER:
adm.deregisterJarAPI(targetJar, apiInterfaceClass, force, new AsyncCallback<JarOpErrorCode>()
{
@Override
public void onFailure(Throwable caught)
{
dialogs.showError(err, () -> close(false));
}
@Override
public void onSuccess(JarOpErrorCode result)
{
handleDeregisterResult(force, result);
}
});
break;
}
}
else
{
close(false);
}
});
}
}
/**
* Last phase of library deregister.
*
* @param force
* Force flag from the previous phase.
* @param result
* Result from the previous phase.
*/
private void handleDeregisterResult(boolean force, JarOpErrorCode result)
{
if (!force && result == JarOpErrorCode.ACTIVE_SESSIONS_PRESENT)
{
// some new related sessions have appeared since the last time the
// list of session has been refreshed, unable to perform desired
// action in non-forcible way
refreshSessions(() ->
{
StringBuilder sb = new StringBuilder();
sb.append("Some new sessions have appeared. Unable to perform non-forcible ");
switch (actionType)
{
case JAR_DEREGISTER:
sb.append("customer library deregistration");
break;
case JAR_RELOAD:
sb.append("customer library reloading");
break;
case API_DEREGISTER:
sb.append("API deregistration");
break;
}
sb.append(". Session list has been refreshed.");
dialogs.showError("Operation Error", sb.toString(), () -> close(false));
});
}
else if (result != JarOpErrorCode.SUCCESS)
{
// an error has occurred
String errorText = null;
switch (actionType)
{
case JAR_DEREGISTER:
case JAR_RELOAD:
errorText = "Cannot deregister customer library.";
break;
case API_DEREGISTER:
errorText = "Cannot deregister API.";
break;
}
dialogs.showError(errorText, () -> close(false));
}
else
{
close(true);
}
}
/**
* Time format helper.
*
* @param sessionInfo
* Session info.
*
* @return Resulting time as string.
*/
private String formatLogonTime(SessionInfo sessionInfo)
{
Date d = new Date(sessionInfo.logonTime);
DateTimeFormat dtf = DateTimeFormat.getFormat("MMddyyyy HH:mm:ss Z");
return dtf.format(d);
}
/**
* Calls the close handler and closes the dialog.
*
* @param code
* The result code that will be passed to the close handler.
*/
private void close(Boolean code)
{
Consumer<Boolean> closeHandler = this.closeHandler;
// clear the handler so that it is not fired again in the modal hidden handler
this.closeHandler = null;
modal.hide();
removeFromParent();
closeHandler.accept(code);
}
}