MultiSessionAppserverDefinition.java

/*
** Module   : MultiSessionAppserverDefinition.java
** Abstract : Multi-session appserver definition.
**
** Copyright (c) 2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 GBB 20250122 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.util.appserver;

import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.osresource.*;

import java.util.*;
import java.util.concurrent.*;

/** Multi-session appserver definition. */
public class MultiSessionAppserverDefinition
extends AppserverDefinition
{
   /** A map of all initialized appserver definitions. */
   private static final Map<String, MultiSessionAppserverDefinition> definitions = new ConcurrentHashMap<>();

   /** Enabled server-side OS resources for the appserver processes. */
   private final Set<OsResourceType> serverSideResources = new HashSet<>();
   
   /** Startup params for agents */
   private final String agentStartupParam;
   
   /** Max worker threads per agent */
   private final int maxConnectionsPerAgent;

   /** Max MSA sessions per agent */
   private final int maxMsaSessionsPerAgent;

   /** Initial agents */
   private final int numInitialAgents;

   /** Max agents */
   private final int maxAgents;

   /** Min agents */
   private final int minAgents;
   
   /** Maximum number of agents that may be started at the same time */
   private final int agentStartLimit;
   
   /** Wait time before marking an idle agent for termination */
   private final int idleAgentTimeout;
   
   /** Wait time before a new agent is considered unresponsive */
   private final int agentListenerTimeout;

   /**
    * In FWD the worker thread idle time before it gets terminated.
    * In OE the idle time of the connection between the agent and the session manager.
    */
   private final int idleConnectionTimeout;
   
   /**
    * Watchdog interval for terminating idle resources (e.g. connection, agent, or client session).
    * In milliseconds.
    */
   private final int idleResourceTimeout;

   /** Initial MSA sessions per agent */
   private final int numInitialSessions;

   /** Wait time before marking an idle client connection for termination. */
   private final int idleSessionTimeout;

   /** Wait time to find an existing available connection. NOT USED. */
   private final int connectionWaitTimeout;

   /**
    * Wait time to start a new connection, run the agent and session startup procs and hear back from the agent.
    * In milliseconds.
    */
   private final int requestWaitTimeout;

   /** Max age of the MSA session before being marked for termination. In milliseconds. */
   private final int maxMsaSessionAge;
   
   /**
    * Raises STOP condition in the MSA session if the request process time exceeds the limit. In seconds.
    */
   private final int sessionExecutionTimeLimit;

   /** Runs once per agent in the first MSA session */
   private final String agentStartupProc;
   
   /** Params for agentStartupProc */
   private final String agentStartupProcParam;
   
   /** Runs once per agent on agent termination */
   private final String agentShutdownProc;
   
   /** Runs once per MSA session, sets context accessible from only the same MSA session */
   private final String sessionStartupProc;
   
   /** Params for sessionStartupProc  */
   private final String sessionStartupProcParam;

   /** Runs once per MSA session on termination */
   private final String sessionShutdownProc;
   
   /** Runs on starting execution of a new request from a session-free or unbound session-managed connection. */
   private final String sessionActivateProc;
   
   /** Runs on ending execution of a request from a session-free or unbound session-managed connection. */
   private final String sessionDeactivateProc;
   
   /** Runs once in the agent when a session-managed connection connects. */
   private final String sessionConnectProc;
   
   /** Runs once in the agent when a session-managed connection disconnects */
   private final String sessionDisconnProc;
   
   /** Wait timeout for a request on idleAgentTimeout agent shutdown. */
   private final int defaultAgentWaitToFinish;

   /**
    * Wait timeout for a request to complete before forcefully terminating the agent. Triggered by 
    * idleAgentTimeout agent shutdown.
    */
   private final int defaultAgentWaitAfterStop;

   /** Emergency shutdown active request wait timeout. */
   private final int completeActiveReqTimeout;
   
   /** OS user the JVM runs under. */
   private final String systemUser;

   /** Client-side parameters. */
   private final ClientParameters clientParameters;
   
   /** Enable debugging for multi-session agents. */
   private final boolean debugMsa;
   
   /** LOG-MANAGER file name patterned. */
   private String agentLogFile;
   
   /** LOG-MANAGER entry types. */
   private String agentLogEntryTypes;

   /** LOG-MANAGER logging level. */
   private Integer agentLoggingLevel;

   /** LOG-MANAGER file threshold size. */
   private Integer agentLogThreshold;

   /** LOG-MANAGER max files count. */
   private Integer agentNumLogFiles;

   /**
    * Package-private constructor. Must be called from threads which have their context authorized to 
    * access the <code>appservers</code> node for the P2J server.
    *
    * @param    appserverName
    *           The application's server name.
    * @param    ds
    *           Directory service.
    * @param    appSrvPath
    *           The appserver node path in directory.
    */
   MultiSessionAppserverDefinition(String appserverName, DirectoryService ds, String appSrvPath)
   {
      super(appserverName, ds, appSrvPath);
      
      this.debugMsa = resolveBoolean(ds, appSrvPath, "debugMsa", false);
      this.agentStartupParam = resolveString(ds, appSrvPath, "agentStartupParam", null);
      this.maxConnectionsPerAgent = resolveInt(ds, appSrvPath, "maxConnectionsPerAgent", 16);
      this.maxMsaSessionsPerAgent = resolveInt(ds, appSrvPath, "maxMsaSessionsPerAgent", 200);
      this.numInitialAgents = resolveInt(ds, appSrvPath, "numInitialAgents", 1);
      this.maxAgents = resolveInt(ds, appSrvPath, "maxAgents", 10);
      this.minAgents = resolveInt(ds, appSrvPath, "minAgents", 0);
      this.agentStartLimit = resolveInt(ds, appSrvPath, "agentStartLimit", 1);
      this.idleAgentTimeout = resolveInt(ds, appSrvPath, "idleAgentTimeout", 300_000);
      this.agentListenerTimeout = resolveInt(ds, appSrvPath, "agentListenerTimeout", 300_000);
      this.idleConnectionTimeout = resolveInt(ds, appSrvPath, "idleConnectionTimeout", 300_000);
      this.idleResourceTimeout = resolveInt(ds, appSrvPath, "idleResourceTimeout", 1000 * 60 * 30);
      this.numInitialSessions = resolveInt(ds, appSrvPath, "numInitialSessions", 5);
      this.idleSessionTimeout = resolveInt(ds, appSrvPath, "idleSessionTimeout", 1_800_000);
      this.connectionWaitTimeout = resolveInt(ds, appSrvPath, "connectionWaitTimeout", 3000);
      this.requestWaitTimeout = resolveInt(ds, appSrvPath, "requestWaitTimeout", 15_000);
      this.maxMsaSessionAge = resolveInt(ds, appSrvPath, "maxMsaSessionAge", 0);
      this.sessionExecutionTimeLimit = resolveInt(ds, appSrvPath, "sessionExecutionTimeLimit", 0);
      
      this.agentLoggingLevel = resolveInt(ds,
                                          appSrvPath,
                                          "agentLoggingLevel",
                                          LegacyLogOps.DEFAULT_LOGGING_LEVEL);
      this.agentLogThreshold = resolveInt(ds,
                                          appSrvPath,
                                          "agentLogThreshold",
                                          LegacyLogOps.DEFAULT_THRESHOLD);
      this.agentNumLogFiles = resolveInt(ds,
                                         appSrvPath,
                                         "agentNumLogFiles",
                                         LegacyLogOps.DEFAULT_NUM_FILES);
      this.agentLogFile = resolveString(ds,
                                        appSrvPath,
                                        "agentLogFile",
                                        LegacyLogOps.DEFAULT_APPSERVER_LOGFILE_NAME);
      this.agentLogEntryTypes = resolveString(ds,
                                              appSrvPath,
                                              "agentLogEntryTypes",
                                              LegacyLogOps.DEFAULT_APPSERVER_TYPES);

      this.agentStartupProc = resolveString(ds, appSrvPath, "agentStartupProc", null);
      this.agentStartupProcParam = resolveString(ds, appSrvPath, "agentStartupProcParam", null);
      this.agentShutdownProc = resolveString(ds, appSrvPath, "agentShutdownProc", null);
      this.sessionStartupProc = resolveString(ds, appSrvPath, "sessionStartupProc", null);
      this.sessionStartupProcParam = resolveString(ds, appSrvPath, "sessionStartupProcParam", null);
      this.sessionShutdownProc = resolveString(ds, appSrvPath, "sessionShutdownProc", null);
      this.sessionActivateProc = resolveString(ds, appSrvPath, "sessionActivateProc", null);
      this.sessionDeactivateProc = resolveString(ds, appSrvPath, "sessionDeactivateProc", null);
      this.sessionConnectProc = resolveString(ds, appSrvPath, "sessionConnectProc", null);
      this.sessionDisconnProc = resolveString(ds, appSrvPath, "sessionDisconnProc", null);
      this.defaultAgentWaitToFinish = resolveInt(ds, appSrvPath, "defaultAgentWaitToFinish", 30_000);
      this.defaultAgentWaitAfterStop = resolveInt(ds, appSrvPath, "defaultAgentWaitAfterStop", 30_000);
      this.completeActiveReqTimeout = resolveInt(ds, appSrvPath, "completeActiveReqTimeout", 600_000);

      this.systemUser = System.getProperty("user.name");

      // load the native library to find the pid and os user.
      if (!ClientCore.isNativeLayerInitialized())
      {
         System.loadLibrary("p2j");
      }
      
      this.clientParameters = new ClientParameters();
      this.clientParameters.osUserName = systemUser;
      this.clientParameters.osDeviceId = ClientCore.getOsDeviceId();
      this.clientParameters.chui = true;
      this.clientParameters.isHeadless = true;
      this.clientParameters.pid = ClientCore.getPid();
      this.clientParameters.clientTimezone = ClientCore.getTimezone();
      // clientParameters.terminalName used by ConnectTableUpdater.databaseConnected
      
      definitions.put(appserverName, this);
      
      serverSideResources.addAll(getServerSideResForAccount(ds, getProcessName()));

      if (serverSideResources.contains(OsResourceType.ALL))
      {
         serverSideResources.clear();
         serverSideResources.add(OsResourceType.ALL);
      }
      
      LegacyLogManagerConfigs legacyLogManagerConfigs =
         new LegacyLogManagerConfigs(agentLogFile,
                                     String.valueOf(agentLoggingLevel),
                                     agentLogEntryTypes,
                                     agentLogThreshold,
                                     agentNumLogFiles,
                                     clientParameters.pid,
                                     0,
                                     systemUser,
                                     getProcessName(),
                                     true);

      legacyLogManagerConfigs.setAppServer(appserverName);
      LegacyLogOps.appserverNameLogConfigPairs.putIfAbsent(appserverName, legacyLogManagerConfigs);
   }
   
   /**
    * Get the settings for the specified appserver.
    *
    * @param    appServer
    *           The name of the appserver.
    *
    * @return   The instance containing the settings for the specified appserver.
    */
   public static MultiSessionAppserverDefinition get(String appServer)
   {
      return definitions.get(appServer);
   }

   /**
    * Returns an array with all instantiated appserver process definitions.
    *
    * @return   An array with all instantiated appserver process definitions.
    */
   public static MultiSessionAppserverDefinition[] getAll()
   {
      return definitions.values().toArray(new MultiSessionAppserverDefinition[0]);
   }

   /**
    * Returns the number of MSA appservers defined in directory.
    *
    * @return    See above.
    */
   public static int count()
   {
      return definitions.size();
   }

   /**
    * Returns a Map of MSA appserver definitions by their names.
    *
    * @return    See above.
    */
   public static Map<String, MultiSessionAppserverDefinition> getDefinitions()
   {
      return definitions;
   }

   /**
    * Returns <code>true</code> if this definition is for a multi-session agent appserver.
    *
    * @return    See above.
    */
   @Override
   public boolean isMultiSession()
   {
      return true;
   }

   /**
    * Getter for {@link #systemUser}.
    *
    * @return  See above.
    */
   @Override
   public String getSystemUser()
   {
      // MSA server-side runs under the same user as the server JVM
      return systemUser;
   }
   
   /**
    * Returns an unmodifiable copy of the collection of OS resources configured for the appserver account.
    *
    * @return  See above.
    */
   public Collection<OsResourceType> getEnabledServerSideResources()
   {
      return Collections.unmodifiableCollection(serverSideResources);
   }

   /**
    * Returns <code>true</code> if the OS resources configured for the appserver account contain "all", 
    * <code>false</code> otherwise.
    *
    * @return  See above.
    */
   public boolean isAllServerSide()
   {
      return serverSideResources.contains(OsResourceType.ALL);
   }

   /**
    * Getter for {@link #agentStartupParam}.
    *
    * @return  See above.
    */
   public String getAgentStartupParam()
   {
      return agentStartupParam;
   }

   /**
    * Getter for {@link #maxConnectionsPerAgent}.
    *
    * @return  See above.
    */
   public int getMaxConnectionsPerAgent()
   {
      return maxConnectionsPerAgent;
   }

   /**
    * Getter for {@link #maxMsaSessionsPerAgent}.
    *
    * @return  See above.
    */
   public int getMaxMsaSessionsPerAgent()
   {
      return maxMsaSessionsPerAgent;
   }

   /**
    * Getter for {@link #numInitialAgents}.
    *
    * @return  See above.
    */
   public int getNumInitialAgents()
   {
      return numInitialAgents;
   }

   /**
    * Getter for {@link #maxAgents}.
    *
    * @return  See above.
    */
   public int getMaxAgents()
   {
      return maxAgents;
   }

   /**
    * Getter for {@link #minAgents}.
    *
    * @return  See above.
    */
   public int getMinAgents()
   {
      return minAgents;
   }

   /**
    * Getter for {@link #agentStartLimit}.
    *
    * @return  See above.
    */
   public int getAgentStartLimit()
   {
      return agentStartLimit;
   }

   /**
    * Getter for {@link #idleAgentTimeout}.
    *
    * @return  See above.
    */
   public int getIdleAgentTimeout()
   {
      return idleAgentTimeout;
   }

   /**
    * Getter for {@link #agentListenerTimeout}.
    *
    * @return  See above.
    */
   public int getAgentListenerTimeout()
   {
      return agentListenerTimeout;
   }

   /**
    * Getter for {@link #idleConnectionTimeout}.
    *
    * @return  See above.
    */
   public int getIdleConnectionTimeout()
   {
      return idleConnectionTimeout;
   }

   /**
    * Getter for {@link #idleResourceTimeout}.
    *
    * @return  See above.
    */
   public int getIdleResourceTimeout()
   {
      return idleResourceTimeout;
   }

   /**
    * Getter for {@link #numInitialSessions}.
    *
    * @return  See above.
    */
   public int getNumInitialSessions()
   {
      return numInitialSessions;
   }

   /**
    * Getter for {@link #idleSessionTimeout}.
    *
    * @return  See above.
    */
   public int getIdleSessionTimeout()
   {
      return idleSessionTimeout;
   }

   /**
    * Getter for {@link #connectionWaitTimeout}.
    *
    * @return  See above.
    */
   public int getConnectionWaitTimeout()
   {
      return connectionWaitTimeout;
   }

   /**
    * Getter for {@link #requestWaitTimeout}.
    *
    * @return  See above.
    */
   public int getRequestWaitTimeout()
   {
      return requestWaitTimeout;
   }

   /**
    * Getter for {@link #maxMsaSessionAge}.
    *
    * @return  See above.
    */
   public int getMaxMsaSessionAge()
   {
      return maxMsaSessionAge;
   }

   /**
    * Getter for {@link #sessionExecutionTimeLimit}.
    *
    * @return  See above.
    */
   public int getSessionExecutionTimeLimit()
   {
      return sessionExecutionTimeLimit;
   }

   /**
    * Getter for {@link #agentStartupProc}.
    *
    * @return  See above.
    */
   public String getAgentStartupProc()
   {
      return agentStartupProc;
   }

   /**
    * Getter for {@link #agentStartupProcParam}.
    *
    * @return  See above.
    */
   public String getAgentStartupProcParam()
   {
      return agentStartupProcParam;
   }

   /**
    * Getter for {@link #agentShutdownProc}.
    *
    * @return  See above.
    */
   public String getAgentShutdownProc()
   {
      return agentShutdownProc;
   }

   /**
    * Getter for {@link #sessionStartupProc}.
    *
    * @return  See above.
    */
   public String getSessionStartupProc()
   {
      return sessionStartupProc;
   }

   /**
    * Getter for {@link #sessionStartupProcParam}.
    *
    * @return  See above.
    */
   public String getSessionStartupProcParam()
   {
      return sessionStartupProcParam;
   }

   /**
    * Getter for {@link #sessionShutdownProc}.
    *
    * @return  See above.
    */
   public String getSessionShutdownProc()
   {
      return sessionShutdownProc;
   }

   /**
    * Getter for {@link #sessionActivateProc}.
    *
    * @return  See above.
    */
   public String getSessionActivateProc()
   {
      return sessionActivateProc;
   }

   /**
    * Getter for {@link #sessionDeactivateProc}.
    *
    * @return  See above.
    */
   public String getSessionDeactivateProc()
   {
      return sessionDeactivateProc;
   }

   /**
    * Getter for {@link #sessionConnectProc}.
    *
    * @return  See above.
    */
   public String getSessionConnectProc()
   {
      return sessionConnectProc;
   }

   /**
    * Getter for {@link #sessionDisconnProc}.
    *
    * @return  See above.
    */
   public String getSessionDisconnProc()
   {
      return sessionDisconnProc;
   }

   /**
    * Getter for {@link #defaultAgentWaitToFinish}.
    *
    * @return  See above.
    */
   public int getDefaultAgentWaitToFinish()
   {
      return defaultAgentWaitToFinish;
   }

   /**
    * Getter for {@link #defaultAgentWaitAfterStop}.
    *
    * @return  See above.
    */
   public int getDefaultAgentWaitAfterStop()
   {
      return defaultAgentWaitAfterStop;
   }

   /**
    * Getter for {@link #completeActiveReqTimeout}.
    *
    * @return  See above.
    */
   public int getCompleteActiveReqTimeout()
   {
      return completeActiveReqTimeout;
   }

   /**
    * Getter for {@link #clientParameters}.
    *
    * @return  See above.
    */
   public ClientParameters getClientParameters()
   {
      return clientParameters;
   }

   /**
    * Getter for {@link #agentLogFile}.
    *
    * @return  See above.
    */
   public String getLogFilename()
   {
      return agentLogFile;
   }

   /**
    * Getter for {@link #agentLoggingLevel}.
    *
    * @return  See above.
    */
   public Integer getLoggingLevel()
   {
      return agentLoggingLevel;
   }

   /**
    * Getter for {@link #agentLogThreshold}.
    *
    * @return  See above.
    */
   public Integer getLogFileThresholdSize()
   {
      return agentLogThreshold;
   }

   /**
    * Getter for {@link #agentNumLogFiles}.
    *
    * @return  See above.
    */
   public Integer getLogFilesMaxNumber()
   {
      return agentNumLogFiles;
   }

   /**
    * Getter for {@link #agentLogEntryTypes}.
    *
    * @return  See above.
    */
   public String geLogEntryTypes()
   {
      return agentLogEntryTypes;
   }
   
   /**
    * Getter for {@link #debugMsa}.
    *
    * @return  See above.
    */
   public boolean isDebugMsa()
   {
      return debugMsa;
   }

   /**
    * Reads the OS resources configured for the appserver account and returns a collection of the types.
    * 
    * @param    ds
    *           Directory service.
    * @param    account
    *           The FWD account for the appserver.
    *           
    * @return   A collection of the OS resource types, configured for the appserver account.
    */
   private Collection<OsResourceType> getServerSideResForAccount(DirectoryService ds, String account)
   {
      Set<OsResourceType> serverSideResources = new HashSet<>();
      String res = Utils.getDirectoryNodeString(ds,
                                                "server-side-resources",
                                                "",
                                                Utils.DirScope.BOTH,
                                                new String[]{account});

      for (String resName : res.split(","))
      {
         resName = resName.trim();
         if (resName.isEmpty())
         {
            continue;
         }
         OsResourceType resType = OsResourceType.find(resName);
         if (resType == null)
         {
            continue;
         }

         serverSideResources.add(resType);
      }
      return serverSideResources;
   }

   /**
    * Returns the int value of the node.
    * 
    * @param    ds
    *           Directory service.
    * @param    appSrvPath
    *           The appserver node path in directory.
    * @param    nodeId
    *           The node id to read.
    * @param    defaultValue
    *           The default value.
    *
    * @return   The int value of the node.
    */
   private int resolveInt(DirectoryService ds, String appSrvPath, String nodeId, int defaultValue)
   {
      Integer value = ds.getNodeInteger(appSrvPath, nodeId);
      return value == null ? defaultValue : value;
   }

   /**
    * Returns the String value of the node.
    *
    * @param    ds
    *           Directory service.
    * @param    appSrvPath
    *           The appserver node path in directory.
    * @param    nodeId
    *           The node id to read.
    * @param    defaultValue
    *           The default value.
    *
    * @return   The String value of the node.
    */
   private String resolveString(DirectoryService ds, String appSrvPath, String nodeId, String defaultValue)
   {
      String value = ds.getNodeString(appSrvPath, nodeId);
      return value == null || value.isEmpty() ? defaultValue : value.trim();
   }

   /**
    * Returns the Boolean value of the node.
    *
    * @param    ds
    *           Directory service.
    * @param    appSrvPath
    *           The appserver node path in directory.
    * @param    nodeId
    *           The node id to read.
    * @param    defaultValue
    *           The default value.
    *
    * @return   The String value of the node.
    */
   private Boolean resolveBoolean(DirectoryService ds, String appSrvPath, String nodeId, Boolean defaultValue)
   {
      Boolean value = ds.getNodeBoolean(appSrvPath, nodeId);
      return value == null ? defaultValue : value;
   }
}