ServerImpl.java
/*
** Module : ServerImpl.java
** Abstract : A class that implements the app server specific method and attribute APIs.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 CS 20130211 Created initial version.
** 002 CA 20130221 Added resourceDelete stub. setType was renamed to setResourceType.
** 003 OM 20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
** 004 CS 20130321 Added missing versions of connect() method.
** 005 CA 20130529 Added runtime support for appservers.
** 006 CA 20130822 Added support for async requests.
** 007 CA 20130908 Moved connect-related code to ConnectHelper.
** 008 CA 20130927 Resource type is computed from the annotation. Removed setUnknown(false) call.
** 009 OM 20131018 Updated to conform the new SessionListener API.
** 010 CA 20131220 Refactored to hide the type of the connection (appserver or web service) in a
** ServerHelper implementation. Added support for the web service connection.
** 011 CA 20140326 Fixed a NPE bug when cleaning up async requests for a session-managed
** connection.
** 012 VIG 20141213 Added SUBTYPE setters stubs.
** 013 CA 20150416 Changed CONNECTED: check if the socket connection (if permanent) is still up.
** 014 OM 20160323 Moved web services networking access from the P2J server to the client.
** 015 CA 20190118 disconnect() must be aware if the FWD session is gone.
** 016 CA 20190811 Fixes related to -URL appserver connection option.
** 017 CA 20200110 Fixed first/last async resource APIs.
** 018 CA 20200427 Added REQUEST-INFO and RESPONSE-INFO support.
** CA 20200611 For an AppServer SERVER, the NAME attribute must be the connection ID.
** RFB 20200611 The connect method does not need to overwrite the NAME attribute previous set to the
** connection ID. Instead, we move the setting of the name to connectToWebService.
** 019 CA 20200827 Reworked asynchronous invocations to perform all context-local work on the Conversation
** thread, and let only the actual invocation be performed in an AssociatedThread.
** CA 20210204 The requestInfo and responseInfo must be vars.
** CA 20220531 Fixed connectToAppServer, to check if the scheme starts with 'appserver'.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 020 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 021 GBB 20230620 createThreadName method moved to Utils.
** 022 CA 20230620 Do not use 'return' from the 'finally' block, this will consume any thrown exceptions.
** 023 GBB 20240101 ConnectHelper renamed to ServerConnectHelper.
** 024 CA 20240223 NAME attribute is writeable for SERVER resource.
** 025 GBB 20240318 Adding errors on missing sessionModel.
** 026 CA 20240910 The ASYNC-REQUEST resources get deleted when the server handle gets deleted, and not on
** disconnect - as the PROCEDURE-COMPLETE event can be processed even if the server got
** disconnected.
** 027 GBB 20250403 AppServerManager method renamed to isMultiSession.
*/
/*
** 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;
import java.net.*;
import java.util.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.net.Session;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.logging.*;
/**
* A class for implementing APPSERVER methods and attributes.
*/
public class ServerImpl
extends HandleChain
implements Server,
AsyncRequestCountAttribute,
FirstLastProcedureAttribute,
SslServerAttribute,
SubTypeAttribute,
ConnectableServer,
SessionListener,
AsyncRequestListener
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(ServerImpl.class);
/** The server helper for this server, set after the {@link #connect} method is called. */
private ServerHelper helper = null;
/** The first procedure in this server's proxy procedure list. */
private ProxyProcedureWrapper firstProcedure = null;
/** The last procedure in this server's proxy procedure list. */
private ProxyProcedureWrapper lastProcedure = null;
/** Flag indicating if the server is valid. */
private boolean valid = true;
/** Count the number of active async requests. */
private long asyncRequestCount = 0;
/**
* The currently active timer thread which will cancel the async requests.
* May be <code>null</code> if no timer is active.
*/
private Thread cancelReqTimer = null;
/** An special instance used to perform async request. */
private AsyncInvoker invoker = null;
/** The reference to the REQUEST-INFO attribute. */
private object<? extends OerequestInfo> requestInfo = new ObjectVar<>(OerequestInfo.class);
/** The reference to the RESPONSE-INFO attribute. */
private object<? extends OerequestInfo> responseInfo = new ObjectVar<>(OerequestInfo.class);
/** The client types associated with this server handle. Depends on the connect call. */
private ClientType[] clientTypes;
/**
* Default constructor which initializes Server specific settings.
*/
public ServerImpl()
{
requestInfo.assign(ObjectOps.newInstance(OerequestInfo.class));
requestInfo.ref.setClientContextId(new character(AppServerManager.nextContextId(false)));
}
/**
* Check if the given handle refers a server resource.
*
* @param h
*
* @return <code>true</code> if the handle's resource is a {@link ServerImpl server}.
*/
public static boolean isServer(handle h)
{
return h != null && !h.isUnknown() && (h.get() instanceof ServerImpl);
}
/**
* Returns the client types associated with this server handle. Depends on the connect call.
*
* @return See above.
*/
public ClientType[] getClientTypes()
{
return clientTypes;
}
/**
* Invoke the given request on this server.
*
* @param request
* The async request to be executed.
*/
public void invokeAsync(AsyncRequestImpl request)
{
AsyncInvoker invoker = getAsyncInvoker();
invoker.invoke(request);
}
/**
* Reports if this object is valid for use (has not been deleted).
*
* @return <code>true</code> if we are valid (can be used).
*/
@Override
public boolean valid()
{
return valid;
}
/**
* Get the REQUEST-INFO legacy attribute.
*
* @return See above.
*/
@Override
public object<? extends OerequestInfo> getRequestInfo()
{
return new object<>(requestInfo);
}
/**
* Get the RESPONSe-INFO legacy attribute.
*
* @return See above.
*/
@Override
public object<? extends OerequestInfo> getResponseInfo()
{
return new object<>(responseInfo);
}
/**
* Returns the number of asynchronous requests.
*
* @return See above.
*/
@Override
public synchronized integer getAsyncRequestCount()
{
return new integer(asyncRequestCount);
}
/**
* Returns the subtype of the handle.
* <p>
* For SERVER handles this represents the type of server to which this is connected.
* The possible values are "APPSERVER" and "WEBSERVICE".
*
* @return See above.
*/
@Override
public character getSubType()
{
return !_connected() ? new character() : new character(helper.getSubType());
}
/**
* Sets the the subtype of the object.
*
* @param value
* The new value of the subtype object attribute.
*/
@Override
public void setSubType(character value)
{
handle.readOnlyError(new handle(this), "SUB-TYPE");
}
/**
* Sets the the subtype of the object.
*
* @param value
* The new value of the subtype object attribute.
*/
@Override
public void setSubType(String value)
{
handle.readOnlyError(new handle(this), "SUB-TYPE");
}
/**
* Java implementation for getting the SSL-SERVER-NAME attribute which contains the SSL
* server name if the connection is made as SSL.
*
* @return The SSL server name, if the connection is made as SSL or unknown
* otherwise
*/
@Override
public character getSslServerName()
{
// TODO Implement the actual logic.
return new character();
}
/**
* Returns the first entry in the list of persistent procedures or procedure objects.
*
* @return See above.
*/
@Override
public handle firstProcedure()
{
return new handle(firstProcedure);
}
/**
* Returns the first entry in the list of persistent procedures or procedure objects.
*
* @return See above.
*/
@Override
public handle lastProcedure()
{
return new handle(lastProcedure);
}
/**
* This returns the id for the AppServer connection to this handle. For Web Services, the
* returned value will be an empty string.
*
* @return See above.
*/
@Override
public character getClientConnectionId()
{
return new character(!_connected() ? "" : helper.getConnectionID());
}
/**
* Returns a handle to the first asynchronous requests in the AppServer or WebService list of
* asynchronous requests.
*
* @return See above.
*/
@Override
public handle getFirstAsyncRequest()
{
handle h = HandleChain.firstResource(LegacyResource.ASYNC_REQUEST);
while (!h.isUnknown())
{
handle hs = ((AsyncRequest) h.getResource()).getServerHandle();
if (hs.getResource() == this)
{
return new handle(h);
}
h = ((CommonHandleChain) h.getResource()).getNextSibling();
}
return new handle(h);
}
/**
* Returns a handle to the last asynchronous requests in the AppServer or WebService list of
* asynchronous requests.
*
* @return See above.
*/
@Override
public handle getLastAsyncRequest()
{
handle h = HandleChain.lastResource(LegacyResource.ASYNC_REQUEST);
while (!h.isUnknown())
{
handle hs = ((AsyncRequest) h.getResource()).getServerHandle();
if (hs.getResource() == this)
{
return new handle(h);
}
h = ((CommonHandleChain) h.getResource()).getPrevSibling();
}
return new handle(h);
}
/**
* Cancel all running or queued requests. Depending on the server's operating mode, the active
* requests will treat a cancel command differently:
* <ul>
* <li>Session-managed: STOP is sent to the executing request; the COMPLETE flag will remain
* set to TRUE. All the queued requests are purged and their CANCELLED flag is set to
* TRUE.</li>
* <li>Session-free: STOP is sent to all requests; their CANCELLED flag is set to TRUE.</li>
* </ul>
*
* @return <code>true</code> if CANCEL-REQUESTS() method returns <code>true</code>,
* <code>false</code> otherwise.
*/
@Override
public logical cancelRequests()
{
if (!_connected())
{
return new logical(false);
}
AsyncInvoker invoker = getAsyncInvoker();
return new logical(invoker.cancelRequests());
}
/**
* Calls the {@link #cancelRequests()} method when the specified number of seconds have passed.
*
* @param timeSec
* The time in seconds after the {@link #cancelRequests()} method will be called. 4GL
* allows only integer or int64 values for this parameter.
*
* @return <code>false</code> if timer cannot be set or {@link #cancelRequests()}'s result
* otherwise
*/
@Override
public logical cancelRequestsAfter(integer timeSec)
{
return cancelRequestsAfter(timeSec.longValue());
}
/**
* Calls the {@link #cancelRequests()} method when the specified number of seconds have passed.
*
* @param timeSec
* The time in seconds after the {@link #cancelRequests()} method will be called. 4GL
* allows only integer or int64 values for this parameter.
*
* @return <code>false</code> if timer cannot be set or {@link #cancelRequests()}'s result
* otherwise
*/
@Override
public logical cancelRequestsAfter(int64 timeSec)
{
return cancelRequestsAfter(timeSec.longValue());
}
/**
* Calls the {@link #cancelRequests()} method when the specified number of seconds have passed;
* if another timer exists for this server, it will be terminated and a new timer will start.
* <p>
* If <code>timeSec</code> is zero or negative, then the current {@link #cancelReqTimer timer}
* will be stopped.
*
* @param timeSec
* The time in seconds after the {@link #cancelRequests()} method will be called. 4GL
* allows only integer or int64 values for this parameter.
*
* @return <code>false</code> if timer cannot be set or {@link #cancelRequests()}'s result
* otherwise
*/
@Override
public synchronized logical cancelRequestsAfter(long timeSec)
{
if (!_connected())
{
return new logical(false);
}
final long millis = timeSec * 1000;
if (millis <= 0 && cancelReqTimer != null)
{
cancelReqTimer.interrupt();
return new logical(true);
}
Runnable task = new Runnable()
{
@Override
public void run()
{
try
{
Thread.sleep(millis);
// time has elapsed, send cancel to all currently running or scheduled requests
cancelRequests();
}
catch (InterruptedException e)
{
// stopped, nothing to do
}
synchronized (ServerImpl.this)
{
// clear the timer
cancelReqTimer = null;
}
}
};
// if there is an existing timer, stop it
if (cancelReqTimer != null)
{
cancelReqTimer.interrupt();
}
// start the new timer
cancelReqTimer = new AssociatedThread(task);
String serverName = helper.getServerName();
String timerName = String.format("Cancel requests timer [%s]", serverName);
String name = Utils.createThreadName(timerName);
cancelReqTimer.setName(name);
cancelReqTimer.start();
return new logical(true);
}
/**
* Method declaration for the CONNECT() procedure supported by web-services and server handles.
*
* @param options
* The set of parameters to establish the connection. 4GL allows only
* {@link character} values for this parameter.
*
* @return <code>true</code> if connection succeeded, or <code>false</code>
* otherwise
*/
@Override
public logical connect(String options)
{
return connect(new character(options));
}
/**
* Method declaration for the CONNECT() procedure supported by web-services and server handles.
*
* @param options
* The set of parameters to establish the connection. 4GL allows only
* character values for this parameter.
*
* @return <code>true</code> if connection succeeded, or <code>false</code>
* otherwise
*/
@Override
public logical connect(character options)
{
return connect(options, new character(), new character(), new character());
}
/**
* Method declaration for the CONNECT() procedure supported by web-services and server handles.
*
* @param options
* The set of parameters to establish the connection. 4GL allows only
* character values for this parameter.
* @param user
* The user to send to the CONNECT procedure on the appserver side.
*
* @return <code>true</code> if connection succeeded, or <code>false</code>
* otherwise
*/
@Override
public logical connect(character options, character user)
{
return connect(options, user, new character(), new character());
}
/**
* Method declaration for the CONNECT() procedure supported by web-services and server handles.
*
* @param options
* The set of parameters to establish the connection. 4GL allows only
* character values for this parameter.
* @param user
* The user to send to the CONNECT procedure on the appserver side.
* @param pwd
* The password to send to the CONNECT procedure on the appserver side.
*
* @return <code>true</code> if connection succeeded, or <code>false</code>
* otherwise
*/
@Override
public logical connect(character options, character user, character pwd)
{
return connect(options, user, pwd, new character());
}
/**
* Method declaration for the CONNECT() procedure supported by web-services and server handles.
*
* @param options
* The set of parameters to establish the connection. 4GL allows only
* character values for this parameter.
* @param user
* The user to send to the CONNECT procedure on the appserver side.
* @param pwd
* The password to send to the CONNECT procedure on the appserver side.
* @param serverInfo
* Other info to send to the CONNECT procedure on the appserver side.
*
* @return <code>true</code> if connection succeeded, or <code>false</code> otherwise
*/
@Override
public logical connect(character options, character user, character pwd, character serverInfo)
{
if (_connected())
{
ErrorManager.displayError(5535, "Server is already connected", false);
return new logical(false);
}
name = null;
synchronized (this)
{
helper = null;
}
Map<String, String> parms = parseOptions(options);
if (parms == null)
{
return new logical(false);
}
boolean res = false;
// check the -WSDL parameter, which is required for web services
boolean webService = parms.containsKey(WebServiceHelper.CONNECT_OPTION_WSDL);
if (webService)
{
// the user, pwd and serverInfo params are ignored
res = connectToWebService(parms);
if (!AppServerManager.isMultiSession())
{
clientTypes = new ClientType[]{ ClientType._4GLCLIENT, ClientType.WSA };
}
}
else
{
res = connectToAppServer(parms, user, pwd, serverInfo);
if (!AppServerManager.isMultiSession() && parms.containsKey("-URL"))
{
clientTypes = new ClientType[]{ ClientType._4GLCLIENT, ClientType.AIA };
}
}
if (!res)
{
return new logical(false);
}
// register this server with the session
Session sess = SessionManager.get().getSession();
sess.addSessionListener(this);
helper.setServer(this);
return new logical(true);
}
/**
* Method declaration for the CONNECTED() method, supported by web-services and server handles.
* This will return <code>true</code> if the web service is connected or <code>false</code>
* otherwise.
*
* @return <code>true</code> if the server is connected or <code>false</code> otherwise.
*/
@Override
public logical connected()
{
return new logical(_connected());
}
/**
* Method declaration for the CONNECTED() method, supported by web-services and server handles.
* This will return <code>true</code> if the web service is connected or <code>false</code>
* otherwise.
*
* @return <code>true</code> if the server is connected or <code>false</code> otherwise.
*/
public synchronized boolean _connected()
{
return helper != null && helper.isConnected();
}
/**
* Method declaration for the DISCONNECT() method supported by web-services and server handles.
* This will attempt to disconnect the resource and returns <code>true</code> if succeeded or
* <code>false</code> otherwise.
*
* @return <code>true</code> if disconnect succeeded or <code>false</code> otherwise.
*/
@Override
public logical disconnect()
{
return disconnectImpl(false);
}
/**
* This method is called when the session is ending. It disconnects the current server, if
* the session which created it is ending.
*
* @param session
* The session that is ending.
*/
@Override
public void terminate(Session session)
{
if (_connected())
{
disconnectImpl(true);
}
}
/**
* This method is called when the session is starting, just after the context was created.
* @param session
* The session that is starting.
*/
@Override
public void initialize(Session session)
{
// no op
}
/**
* Notification that a remote procedure is invoked via this server.
*/
@Override
public synchronized void notifyStart()
{
asyncRequestCount++;
}
/**
* Notification that a remote procedure invoked via this server is finished.
*/
@Override
public synchronized void notifyFinish()
{
asyncRequestCount--;
}
/**
* Check if this resource supports the NAME attribute in read-only mode.
*
* @return Always <code>false</code>.
*/
protected boolean hasNameReadOnly()
{
return false;
}
/**
* Check if the server has running async requests. If so, display appropriate error message.
*
* @return <code>true</code> if the server has running async requests.
*/
synchronized boolean hasAsyncRequests()
{
if (asyncRequestCount != 0)
{
String msg = "Server %s has outstanding asynchronous requests. Cannot RUN";
msg = String.format(msg, helper.getConnectionID());
ErrorManager.recordOrThrowError(9004, msg, false);
return true;
}
return false;
}
/**
* Get the {@link ServerHelper helper} for this server resource.
*
* @return See above.
*/
synchronized ServerHelper getServerHelper()
{
return helper;
}
/**
* Check if this server is connected to a web-service.
*
* @return See above.
*/
boolean isWebService()
{
return _connected() && helper.isWebService();
}
/**
* Set the first proxy procedure of this server's chain.
*
* @param proc
* The procedure instance.
*/
void setFirstProcedure(ProxyProcedureWrapper proc)
{
this.firstProcedure = proc;
}
/**
* Set the last proxy procedure of this server's chain.
*
* @param proc
* The procedure instance.
*/
void setLastProcedure(ProxyProcedureWrapper proc)
{
this.lastProcedure = proc;
}
/**
* Delete the resource. If the server is still connected, delete is not possible.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
if (_connected())
{
final String msg = "SERVER %s is still connected. Cannot DELETE";
final String err = String.format(msg, getClientConnectionId().toStringMessage());
ErrorManager.recordOrThrowError(5457, err);
return false;
}
this.valid = false;
if (requestInfo._isValid())
{
ObjectOps.delete(requestInfo);
}
if (responseInfo._isValid())
{
ObjectOps.delete(responseInfo);
}
// now delete all requests
handle async = getFirstAsyncRequest();
while (!async.isUnknown())
{
HandleOps.delete(async);
async = getFirstAsyncRequest();
}
return true;
}
/**
* Connect to a web service, using the specified parameters.
*
* @param parms
* The connection parameters.
*
* @return <code>true</code> if the connection was possible.
*/
private boolean connectToWebService(Map<String, String> parms)
{
synchronized (this)
{
helper = WebServiceHelper.connect(parms);
}
boolean connected = _connected();
if (connected)
{
name = helper.getServerName();
}
return connected;
}
/**
* Connect to an appserver using the specified parameters.
*
* @param parms
* The set of parameters to establish the connection. 4GL allows only character
* values for this parameter.
* @param user
* The user to send to the CONNECT procedure on the appserver side.
* @param pwd
* The password to send to the CONNECT procedure on the appserver side.
* @param serverInfo
* Other info to send to the CONNECT procedure on the appserver side.
*
* @return <code>true</code> if the connection was possible.
*/
private boolean connectToAppServer(Map<String, String> parms,
character user,
character pwd,
character serverInfo)
{
String appService = parms.get("-AppService");
String host = parms.get("-H");
String serviceName = parms.get("-S");
boolean directConnect = parms.containsKey("-DirectConnect");
int port = 0;
try
{
port = Integer.parseInt(serviceName);
}
catch (NumberFormatException e)
{
// ignore
}
String url = parms.get("-URL");
if (url != null)
{
// get them from the URL
String err = null;
try
{
URI u = new URI(url);
String protocol = u.getScheme().toLowerCase();
if (!(protocol.startsWith("appserver") ||
protocol.equals("http") ||
protocol.equals("https")))
{
// TODO: is this really needed?
// if we are not connecting to an AppServer, than abort
err = protocol;
}
else
{
host = u.getHost();
port = u.getPort();
URI uri = new URI(url);
serviceName = uri.getPath();
directConnect = true;
}
}
catch (URISyntaxException e)
{
err = url;
}
if (err != null)
{
ErrorManager.displayError(String.format("Invalid URL syntax: %s", err));
ErrorManager.displayError(5468, "Application server connect failure", false);
return false;
}
}
String sessionModel = ServerConnectHelper.getString(parms, "-sessionModel", null);
boolean sessionFree = "Session-free".equalsIgnoreCase(sessionModel);
if (parms.containsKey("-sessionModel") && (sessionModel == null || sessionModel.isEmpty()))
{
ErrorManager.recordOrShowWarning(1403, "You have not supplied a parameter for argument " +
"-sessionModel.");
ErrorManager.recordOrShowWarning(5509, "Unable to process parameters.");
return false;
}
// TODO: in session-free mode, is possible to establish more than one connection (to allow
// async requests). the following parameters are related to session-free mode
int connectionLifetime = ServerConnectHelper.getInt(parms, "-connectionLifetime", 300);
int initialConnections = ServerConnectHelper.getInt(parms, "-initialConnections", 1);
int maxConnections = ServerConnectHelper.getInt(parms, "-maxConnections", 0);
// the -ns* options are not used by P2J
try
{
synchronized (this)
{
Object[] reqInfo = requestInfo.ref.toArray();
this.helper = AppServerHelper.connectLegacyMode(appService, host, serviceName, port,
directConnect, sessionModel,
user, pwd, serverInfo, reqInfo);
if (this.helper == null)
{
return false;
}
// if it was connected, build the response
((AppServerHelper) helper).restoreResponse(null, responseInfo);
// set the NAME attribute to the connection's ID
name = this.helper.getConnectionID();
return true;
}
}
catch (NumberedException e)
{
ErrorManager.recordOrThrowError(e);
}
finally
{
if (!_connected())
{
LOG.fine("Application server connect failure");
}
}
return false;
}
/**
* Method declaration for the DISCONNECT() method supported by web-services and server handles.
* This will attempt to disconnect the resource and returns <code>true</code> if succeeded or
* <code>false</code> otherwise.
*
* @param disconnecting
* Flag indicating if the session is being terminated.
*
* @return <code>true</code> if disconnect succeeded or <code>false</code> otherwise.
*/
private logical disconnectImpl(boolean disconnecting)
{
if (!_connected())
{
ErrorManager.displayError(5537, "Server is not connected", false);
return new logical(false);
}
// if there is an existing timer, stop it
if (cancelReqTimer != null)
{
cancelReqTimer.interrupt();
}
// cancel requests before deleting
cancelRequests();
helper.disconnect(disconnecting);
synchronized (this)
{
helper = null;
}
if (invoker != null)
{
invoker.terminate();
invoker = null;
}
if (!disconnecting)
{
Session sess = SessionManager.get().getSession();
sess.removeSessionListener(this);
}
return new logical(true);
}
/**
* Parse and validate the given options; a map is returned with each parameters's value. If a
* parameter doesn't have a value, the map will contain <code>null</code>.
*
* @param options
* The options from which the parameters will be extracted.
*
* @return The parameter-to-value map.
*/
private Map<String, String> parseOptions(character options)
{
if (options.isUnknown())
{
// if unknown, it assumes it connects to an AppServer, and not WebService
return new HashMap<>();
}
final Set<String> knownAppServerOptions = new HashSet<>();
knownAppServerOptions.add("-AppService");
knownAppServerOptions.add("-H");
knownAppServerOptions.add("-S");
knownAppServerOptions.add("-DirectConnect"); // no value
knownAppServerOptions.add("-ssl"); // no value
knownAppServerOptions.add("-nosessionreuse"); // no value
knownAppServerOptions.add("-nohostverify"); // no value
knownAppServerOptions.add("-pf");
knownAppServerOptions.add("-AppServerKeepalive");
knownAppServerOptions.add("-URL");
knownAppServerOptions.add("-sessionModel");
knownAppServerOptions.add("-connectionLifetime");
knownAppServerOptions.add("-initialConnections");
knownAppServerOptions.add("-maxConnections");
knownAppServerOptions.add("-nsClientMaxPort");
knownAppServerOptions.add("-nsClientMinPort");
knownAppServerOptions.add("-nsClientPicklistExpiration");
knownAppServerOptions.add("-nsClientPicklistSize");
knownAppServerOptions.add("-nsClientPortRetry");
knownAppServerOptions.add("-nsClientDelay");
final Set<String> noValueOptions = new HashSet<>();
noValueOptions.add("-DirectConnect"); // no value
noValueOptions.add("-ssl"); // no value
noValueOptions.add("-nosessionreuse"); // no value
noValueOptions.add("-nohostverify"); // no value
final Set<String> knownWebServiceOptions = WebServiceHelper.getKnownConnectOptions();
final Map<String, Integer> intParams = new HashMap<>();
intParams.put("-connectionLifetime", 0);
intParams.put("-initialConnections", 0);
intParams.put("-maxConnections", 0);
String s = options.getValue();
String[] opts = s.split(" ");
// determine the target; assume appServer
Set<String> knownOptions = knownAppServerOptions;
for (String opt : opts)
{
if (WebServiceHelper.CONNECT_OPTION_WSDL.equals(opt))
{
knownOptions = knownWebServiceOptions;
break;
}
}
return ServerConnectHelper.parseOptions(options, knownOptions, noValueOptions, intParams);
}
/**
* Get the {@link #invoker asynchronous invoker} for this server; if {@link #invoker} is null,
* it will create a {@link SessionManagedInvoker} if the server is in the Session-managed
* operating mode or a {@link SessionFreeInvoker} if the server is in the Session-free
* operating mode.
*
* @return See above.
*/
private AsyncInvoker getAsyncInvoker()
{
if (invoker == null)
{
invoker = (helper.isSessionFree() ? new SessionFreeInvoker()
: new SessionManagedInvoker());
// start in a different thread, but same context
invoker.start();
}
return invoker;
}
/**
* Provides a mechanism of performing asynchronous requests, by hiding the operating state of
* this server (State-free or State-managed).
*/
private abstract class AsyncInvoker
{
/** Flag indicating the server is still available for */
private boolean terminated;
/**
* Invoke the async request, based on the operating mode.
*
* @param request
* The request to be invoked.
*/
public abstract void invoke(AsyncRequestImpl request);
/**
* Start this async invoker.
*/
public abstract void start();
/**
* Cancel all the active requests.
*
* @return <code>true</code> if cancellation was possible.
*/
public abstract boolean cancelRequests();
/**
* Terminate this async invoker by setting the {@link #terminated} flag to <code>true</code>.
*/
public synchronized void terminate()
{
this.terminated = true;
}
/**
* Check if this async invoker is terminated.
*
* @return The state of the {@link #terminated} flag.
*/
public synchronized boolean isTerminated()
{
return terminated;
}
}
/**
* Asynchronous invoker implementation for Session-managed operating mode. In this mode, a
* separate thread is started which listens for async requests added to the queue, and executes
* them in a FIFO mode.
*/
private class SessionManagedInvoker
extends AsyncInvoker
implements Runnable
{
/** Queue of async requests pending execution. */
private ArrayDeque<AsyncRequestImpl> queue = new ArrayDeque<AsyncRequestImpl>();
/** The current async request being executed. */
private AsyncRequestImpl request = null;
/**
* In Session-managed operating mode, invoking an async request means adding it to the
* {@link #queue async queue}; from there, a specialized thread will pick it up and execute
* it.
*
* @param request
* The async request to be executed.
*/
@Override
public void invoke(AsyncRequestImpl request)
{
synchronized (queue)
{
queue.addLast(request);
queue.notify();
}
}
/**
* In Session-managed operating mode, a dedicated thread is started which listens for
* async requests added to the {@link #queue}.
*/
@Override
public void start()
{
Thread t = new AssociatedThread(this);
// register this thread as async
SessionManager.get().registerAsyncThread(t);
String serverName = helper.getServerName();
String asyncName = String.format("Session-managed invoker [%s]", serverName);
String name = Utils.createThreadName(asyncName);
t.setName(name);
t.start();
}
/**
* Listens for incoming async requests and executes them in FIFO order.
*/
@Override
public void run()
{
try
{
// listen for requests while the server is connected
while (!isTerminated() && _connected())
{
synchronized (queue)
{
if (queue.isEmpty())
{
try
{
// as we can't sync on this object, make this wait a max amount of time
queue.wait(5000);
}
catch (InterruptedException e)
{
// restart the loop
continue;
}
}
if (queue.isEmpty())
{
// if we are still empty, restart the loop, maybe it was terminated
continue;
}
// get all requests and process them separately, in a FIFO order
request = queue.removeFirst();
}
// 'false' is used because we execute the request on this thread.
request.execute(false);
}
}
finally
{
// de-register this thread from the async list
SessionManager.get().deregisterAsyncThread(Thread.currentThread());
}
}
/**
* Cancel all queued or running requests.
*/
public boolean cancelRequests()
{
// process the queue
synchronized (queue)
{
if (request != null && !request._isComplete())
{
// send STOP to current request and wait to complete
request.stop();
}
// notify all requests that they are cancelled
for (AsyncRequestImpl req : queue)
{
req.cancelled();
}
// clear the queue
queue.clear();
}
return true;
}
}
/**
* Asynchronous invoker implementation for Session-free operating mode. In this mode, each
* async request is executed in its separate thread.
*/
private class SessionFreeInvoker
extends AsyncInvoker
{
/** A set of running requests. */
private Set<AsyncRequestImpl> requests = new HashSet<AsyncRequestImpl>();
/**
* Invoking an async request in State-free mode needs to request to be executed in its
* separated thread. For this, all running requests are registered in the {@link #requests}
* set, to be able to cancel them.
*
* @param request
* The request to be executed.
*/
@Override
public void invoke(final AsyncRequestImpl request)
{
synchronized (requests)
{
requests.add(request);
}
// each request has its own thread
Thread t = new AssociatedThread(new Runnable()
{
public void run()
{
try
{
// 'false' is used because we execute the request on this thread.
request.execute(false);
}
finally
{
synchronized (requests)
{
requests.remove(request);
}
// de-register this thread from the async list
SessionManager.get().deregisterAsyncThread(Thread.currentThread());
}
}
});
// register this thread as async
SessionManager.get().registerAsyncThread(t);
String pname = request.getProcedureName().toStringMessage();
String serverName = helper.getServerName();
String asyncName = String.format("Session-free invoker [%s:%s]", serverName, pname);
String name = Utils.createThreadName(asyncName);
t.setName(name);
t.start();
}
/**
* This is a no-op, as the State-free invoker doesn't need a dedicated thread.
*/
@Override
public void start()
{
// no-op
}
/**
* Cancel all running requests by sending the STOP conditions to them.
*
* @return <code>true</code> if cancel was possible.
*/
@Override
public boolean cancelRequests()
{
synchronized (requests)
{
for (AsyncRequestImpl request : requests)
{
// send STOP to this request
if (!request._isComplete())
{
// send stop to current request and wait it to complete
request.stop();
// mark as cancelled too
request.cancelled();
}
}
requests.clear();
}
return true;
}
}
}