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