SessionView.java

/*
** Module   : SessionView.java
** Abstract : SessionView is a view to manage sessions.
**
** Copyright (c) 2017-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 SBI 20170601 Created initial version.
** 002 CA  20180605 Added program trace in the session view.
** 003 GBB 20231117 Added sessionUuid, relatedSessionUuid, pid, osUser, driverType, 
**                  browserWebSocket, sessionDescription, httpPort. Col width added.
*/
/*
** 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.console.sessions;

import java.util.Date;

import org.gwtbootstrap3.client.ui.Button;
import org.gwtbootstrap3.client.ui.gwt.DataGrid;

import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.admin.client.application.home.BaseViewWithUiHandlers;
import com.goldencode.p2j.admin.client.widget.GridHandle;
import com.goldencode.p2j.admin.client.widget.GridHelper;
import com.goldencode.p2j.admin.client.widget.GridHelper.ColumnInfo;
import com.goldencode.p2j.admin.client.widget.dialog.ModalDialogs;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.i18n.shared.DateTimeFormat;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.safehtml.shared.*;
import com.google.gwt.uibinder.client.*;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.inject.*;


/**
 * SessionView is a view to manage sessions.
 */
public class SessionView
extends BaseViewWithUiHandlers<SessionUIHandler>
implements SessionPresenter.MyView
{
   /**
    * GWT UI creator
    */
   interface Binder
   extends UiBinder<Widget, SessionView>
   {}

   /** The session table view */
   @UiField(provided = true)
   DataGrid<SessionInfo> dataGrid = GridHelper.create();

   /** The Refresh button */
   @UiField
   Button refresh;

   /** The Filter button */
   @UiField
   Button filter;

   /** The View Program Trace button */
   @UiField
   Button viewTrace;

   /** The Terminate button */
   @UiField
   Button terminate;

   /** Modal container */
   @UiField
   Widget modalFragment;

   /** The Filter Session View */
   private final FilterSessionsView filterSessionsView;
   
   /** The Terminate Session Alert */
   private final TerminateSessionAlert terminateSessionAlert;

   /** Modal dialogs */
   private final ModalDialogs dialogs;

   /** The grid handle of the session table view */
   private final GridHandle<SessionInfo> gridHandle;
   
   /** The session selection model */
   private final SingleSelectionModel<SessionInfo> selectionModel;
   
   //private final DateTimeFormat fmt = DateTimeFormat.getFormat("MM/dd/yyyy hh:mm:ss Z, ZZ");
   
   /** The data and time formatter */
   private final DateTimeFormat fmt = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.RFC_2822);

   /**
    * 
    * 
    * @param    binder
    *           The injected GWT UI Binder
    * @param    dialogs
    *           The injected ModalDialogs provider
    * @param    filterSessionsView
    *           The injected the Filter Session View
    * @param    terminateSessionAlert
    *           The injected the Terminate Session Alert
    */
   @Inject
   SessionView(SessionView.Binder binder,
               ModalDialogs dialogs,
               FilterSessionsView filterSessionsView,
               TerminateSessionAlert terminateSessionAlert)
   {
      this.dialogs = dialogs;
      
      initWidget(binder.createAndBindUi(this));

      ColumnInfo<SessionInfo> col0 = new ColumnInfo<SessionInfo>("Logon Time",
               (ri) -> { return fmt.format(new Date(ri.logonTime));},
               (r1, r2) -> { return Long.valueOf(r1.logonTime).compareTo(r2.logonTime);});
      col0.setWidthInPixels(200);
      ColumnInfo<SessionInfo> col1 = new ColumnInfo<SessionInfo>("Account",
               (ri) -> {return ri.name;},
               (r1, r2) -> { return r1.name.compareTo(r2.name);});
      col1.setWidthInPercents(5);
      ColumnInfo<SessionInfo> col2 = new ColumnInfo<SessionInfo>("Account Description",
               (ri) -> { return ri.descr;},
               (r1, r2) -> { return r1.descr.compareTo(r2.descr);});
      col2.setWidthInPercents(5);
      ColumnInfo<SessionInfo> col3 = new ColumnInfo<SessionInfo>("Account Type",
               (ri) -> { return ri.user ? "User" : "Process";},
               (r1, r2) -> { return Boolean.valueOf(r1.user).compareTo(r2.user);});
      col3.setWidthInPercents(5);
      ColumnInfo<SessionInfo> col4 = new ColumnInfo<SessionInfo>("OS User",
              (ri) -> {return ri.osUser;},
              (r1, r2) -> { return r1.osUser.compareTo(r2.osUser);});
      col4.setWidthInPercents(4);
      ColumnInfo<SessionInfo> col5 = new ColumnInfo<SessionInfo>("Session Description",
               (ri) -> {return ri.sessionDescription;},
               (r1, r2) -> { return r1.sessionDescription.compareTo(r2.sessionDescription);});
      ColumnInfo<SessionInfo> col6 = new ColumnInfo<SessionInfo>("Session Token ID",
               (ri) -> {return String.valueOf(ri.id);},
               (r1, r2) -> { return Integer.valueOf(r1.id).compareTo(r2.id);});
      col6.setWidthInPercents(4);
      ColumnInfo<SessionInfo> col7 = new ColumnInfo<SessionInfo>("Session UUID",
               (ri) -> {return ri.uuid;},
               (r1, r2) -> { return r1.uuid.compareTo(r2.uuid);});
      ColumnInfo<SessionInfo> col8 = new ColumnInfo<SessionInfo>("Related Session UUID",
               (ri) -> {return ri.relatedSessionUuid;},
               (r1, r2) -> { return r1.relatedSessionUuid.compareTo(r2.relatedSessionUuid);});
      ColumnInfo<SessionInfo> col9 = new ColumnInfo<SessionInfo>("Program Trace",
               (ri) -> { return buildStackTrace(ri.stacktrace); }, true);
      ColumnInfo<SessionInfo> col10 = new ColumnInfo<SessionInfo>("Client Driver",
               (ri) -> {return ri.driverType;},
               (r1, r2) -> { return r1.driverType.compareTo(r2.driverType);});
      ColumnInfo<SessionInfo> col11 = new ColumnInfo<SessionInfo>("Client Web Port",
               (ri) -> { return ri.httpPort > 0 ? String.valueOf(ri.httpPort) : "";},
               (r1, r2) -> { return Integer.valueOf(r1.httpPort).compareTo(r2.httpPort);});
      col11.setWidthInPercents(4);
      ColumnInfo<SessionInfo> col12 = new ColumnInfo<SessionInfo>("Client PID",
               (ri) -> { return String.valueOf(ri.pid);},
               (r1, r2) -> { return Long.valueOf(r1.pid).compareTo(r2.pid);});
      col12.setWidthInPercents(4);
      ColumnInfo<SessionInfo> col13 = new ColumnInfo<SessionInfo>("Browser Socket",
               (ri) -> { return ri.browserWebSocket;},
               (r1, r2) -> { return r1.browserWebSocket.compareTo(r2.browserWebSocket);});
      col13.setWidthInPercents(5.8);
      ColumnInfo<SessionInfo> col14 = new ColumnInfo<SessionInfo>("Client Socket",
               (ri) -> { return ri.remoteNet;},
               (r1, r2) -> { return r1.remoteNet.compareTo(r2.remoteNet);});
      col14.setWidthInPercents(5.8);
      ColumnInfo<SessionInfo> col15 = new ColumnInfo<SessionInfo>("Server Socket",
               (ri) -> { return String.valueOf(ri.localNet);},
               (r1, r2) -> { return r1.localNet.compareTo(r2.localNet);});
      col15.setWidthInPercents(5.8);
      ColumnInfo<SessionInfo> col16 = new ColumnInfo<SessionInfo>("Secure",
               (ri) -> { return ri.secure ? "yes" : "no";},
               (r1, r2) -> { return Boolean.valueOf(r1.secure).compareTo(r2.secure);});
      col16.setWidthInPercents(3);
      ColumnInfo<SessionInfo> col17 = new ColumnInfo<SessionInfo>("Gen #",
               (ri) -> { return String.valueOf(ri.cacheGen);},
               (r1, r2) -> { return Integer.valueOf(r1.cacheGen).compareTo(r2.cacheGen);});
      col17.setWidthInPercents(3);
      
      this.gridHandle = GridHelper.initListGrid(dataGrid, false, 
                                                col0, col1, col2,
                                                col3, col4, col5,
                                                col6, col7, col8,
                                                col9, col10, col11,
                                                col12, col13, col14,
                                                col15, col16, col17);
      
      this.selectionModel = (SingleSelectionModel<SessionInfo>) this.gridHandle.getSelectionModel();

      this.filterSessionsView = filterSessionsView;
      this.terminateSessionAlert = terminateSessionAlert;
      setupModalSlot(SessionPresenter.MODAL_CONTENT, modalFragment);
      addDialog(filterSessionsView);
      addDialog(terminateSessionAlert);
      this.viewTrace.setEnabled(false);
      this.terminate.setEnabled(false);
      
      this.selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler()
      {
         @Override
         public void onSelectionChange(SelectionChangeEvent event)
         {
            boolean enabled = SessionView.this.selectionModel.getSelectedObject() != null;
            SessionView.this.viewTrace.setEnabled(enabled);
            SessionView.this.terminate.setEnabled(enabled);
         }
      });
   }
   
   /**
    * Build the HTML representation of these program traces.
    * 
    * @param    traces
    *           The stacktrace information.
    *           
    * @return   The GWT {@link SafeHtml} object holding this cell's content.
    */
   public static SafeHtml buildStackTrace(StacktraceInfo[] traces)
   {
      SafeHtmlBuilder html = new SafeHtmlBuilder();
      if (traces == null)
      {
         html.appendHtmlConstant("(no trace available)");
         return html.toSafeHtml();
      }
      
      // the structure is:
      // <b>program1<br/>
      // method1(line1 -> line2 -> line3)
      // method2(line1 -> line2 -> line3)
      // <b>program2<br/>
      // method1(line1 -> line2 -> line3)
      // method2(line1 -> line2 -> line3)
      // ...
      
      String lastProgram = null;
      String lastMethod = null;
      for (StacktraceInfo trace : traces)
      {
         if (lastProgram == null || !trace.program.equals(lastProgram))
         {
            if (lastMethod != null)
            {
               html.append(')').appendHtmlConstant("<br/>");
            }
            html.appendHtmlConstant("<b>" + trace.program + "</b><br/>");
            lastProgram = trace.program;
            lastMethod = null;
         }
         
         if (lastMethod == null || !trace.method.equals(lastMethod))
         {
            if (lastMethod != null)
            {
               html.append(')').appendHtmlConstant("<br/>");
            }
            html.appendHtmlConstant(trace.method + "(" + trace.line);
            lastMethod = trace.method;
         }
         else
         {
            html.appendHtmlConstant(" -&gt; " + trace.line);
         }
      }
      
      if (lastMethod != null)
      {
         html.append(')');
      }
      
      return html.toSafeHtml();
   }

   /**
    * Sets the session table model.
    * 
    * @param    sessions
    *           The array of sessions
    * @param    filter
    *           The filter criteria
    */
   @Override
   public void setSessions(SessionInfo[] sessions, RegExp filter)
   {
      gridHandle.clearGrid();
      
      if (sessions == null)
      {
         return;
      }
      
      gridHandle.setRows(sessions, filter);
   }

   /**
    * The Refresh button click handler.
    * 
    * @param    e
    *           Click event
    */
   @UiHandler("refresh")
   void onRefresh(ClickEvent e)
   {
      getUiHandlers().updateSessionList();
   }

   /**
    * The View Program Trace button click handler.
    * 
    * @param    e
    *           Click event
    */
   @UiHandler("viewTrace")
   void onViewTrace(ClickEvent e)
   {
      SessionInfo row = this.selectionModel.getSelectedObject();

      StringBuilder sb = new StringBuilder();
      sb.append("<textarea style='height:400px;width:100%'>");
      if (row.javaStacktrace == null)
      {
         sb.append("(no stacktrace available)");
      }
      else
      {
         for (String trace : row.javaStacktrace)
         {
            sb.append(trace);
            sb.append("\n");
         }
      }
      
      sb.append("</textarea>");
      dialogs.showInfo("Java Stacktrace", sb.toString(), null);
   }
   
   /**
    * The Terminate button click handler.
    * 
    * @param    e
    *           Click event
    */
   @UiHandler("terminate")
   void onTerminate(ClickEvent e)
   {
      getTerminateSessionAlert().setAlert(this.selectionModel.getSelectedObject());
   }

   /**
    * Gets a reference to the Filter Session View.
    * 
    * @return   The Filter Session View
    */
   @Override
   public FilterSessionsView getFilterView()
   {
      return this.filterSessionsView;
   }

   /**
    * Gets a reference to the Terminate Session Alert.
    * 
    * @return   The Terminate Session Alert
    */
   @Override
   public TerminateSessionAlert getTerminateSessionAlert()
   {
      return this.terminateSessionAlert;
   }

   /**
    * Tests if the tested session is selected.
    * 
    * @param    session
    *           The tested session
    * 
    * @return   True iff the tested session is selected, otherwise false.
    */
   @Override
   public boolean isSessionSelected(SessionInfo session)
   {
      return this.selectionModel.isSelected(session);
   }
}