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