# 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);
       }
    }