Listing of Source sesspool/DefaultSessionPoolingHandler.javapackage se.entra.phantom.server;
import java.awt.EventQueue;
import java.awt.Point;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import org.w3c.dom.Element;
import se.entra.phantom.common.TerminalKeys;
import se.entra.phantom.common.Utilities;
import se.entra.phantom.server.rconsole.AdminConfigUser;
import se.entra.phantom.server.rconsole.AdminConfigUsers;
/**
* When a session is started, "pinged", checked for validity, reclaimed or is about
* to be disposed, the DefaultSessionPoolingHandler reads what to do for the action
* in question. This is done in a script definition file. The script definition file
* is written in XML as described below.
*
* <p>Each session pool can have a Phantom Runtime file associated with it. This eases
* the interaction with the host system, to check for matching screens, use host fields,
* etc. The Runtime file is only used for screen identification and host field processing,
* i.e. no panels or objects (REXX or other) are used.
*
* <p><i>Note: In the JavaDoc of the class, tags are written as (tagname [params]/)
* or (/tagname) for better readability in the code, instead of <tagname [params]/>
* or </tagname>.</i>
*
* <p><b>File Format</b>
*
* <p>The file must have the following format (note that XML is case sensitive as opposed
* to HTML):
* <pre>
* (?xml version="1.0"?)
*
* (SessionPoolingScript
* version="1.00"
* implclass="se.entra.phantom.server.DefaultSessionPoolingHandler"
* runtimefile="samples/tutor/TUTOR.NPR")
*
* (extensions)
* (function name="SaveGlobalHostData"
* method="saveGlobalHostData"
* class="com.acme.SessionPool"/)
*
* (function name="YourHostCheck" method="yourHostCheck"/)
* (/extensions)
*
* (actions)
* (action name="START" maxtime="seconds")
* (script)
* ... the script here ...
* (/script)
* (/action)
*
* (action name="PING" maxtime="seconds")
* (script)
* ... the script here ...
* (/script)
* (/action)
*
* (action name="CHECK" maxtime="seconds")
* (script)
* ... the script here ...
* (/script)
* (/action)
*
* (action name="RECLAIM" maxtime="seconds")
* (script)
* ... the script here ...
* (/script)
* (/action)
*
* (action name="DISPOSE" maxtime="seconds")
* (script)
* ... the script here ...
* (/script)
* (/action)
* (/actions)
* (/SessionPoolingScript)
* </pre>
*
* Any main tags not recognized are not processed. The action tag must contain a
* script tag describing what "instructions" are to be processed. For the action tag,
* a parameter maxtime can be specified in seconds. If the script has not completed
* within this time limit, the session is considered invalid and is disposed of (not
* calling the script of the dispose action).
*
* <p>The <code>DefaultSessionPoolingHandler</code> is a class provided as a default handler
* for the session pooling. It provides by default the option to add external
* functions in new classes (using the extensions section in the XML file). This is
* done using <i>Custom Method Calls</i> (see the documentation in PDF form).
*
* <p>An extending Java class can also override it in order to provide a higher or more
* specialized functionality. For more advanced processing, the script tag handling can
* be overridden.
*/
public class DefaultSessionPoolingHandler extends HostSessionManagerAdapter
{
////////////////////////
/// States constants ///
////////////////////////
/**
* State indicating that the thread that will perform the Start script
* is about to start executing.
*/
public static final int STATE_STARTING = 0;
/**
* State indicating that the Start script is executing.
*/
public static final int STATE_START = 1;
/**
* State indicating that the session is started and ready (since last
* Start, Ping or Reclaim).
*/
public static final int STATE_READY = 2;
/**
* State indicating that the thread that will perform the Ping script
* is about to start executing.
*/
public static final int STATE_PINGING = 3;
/**
* State indicating that the Ping script is executing.
*/
public static final int STATE_PING = 4;
/**
* State indicating that the Check script is executing.
*/
public static final int STATE_CHECK = 5;
/**
* State indicating that this session is in use by an application.
*/
public static final int STATE_INUSE = 6;
/**
* State indicating that the thread that will perform the Reclaim script
* is about to start executing.
*/
public static final int STATE_RECLAIMING = 7;
/**
* State indicating that the Reclaim script is executing.
*/
public static final int STATE_RECLAIM = 8;
/**
* State indicating that the session is about to be disposed
* (i.e. the thread that will perform the Disposing script
* is about to start executing or is executing).
*/
public static final int STATE_DISPOSING = 9;
/**
* State indicating that the session is disposed.
*/
public static final int STATE_DISPOSED = 10;
/**
* The string represenation of the numerical STATE_nnn in english.
* The array is not made "final" if it needs to contain another language.
*/
public static String [] stringStates =
{
"Starting",
"Start",
"Ready",
"Ping",
"Pinging",
"Check",
"In use",
"Reclaiming",
"Reclaim",
"Disposing",
"Disposed"
};
/**
* To cascade the internal frames.
*/
private static int windowCount;
//////////////////////
/// Static methods ///
//////////////////////
/**
* Creates and initializes a pool.
*
* <p>This method is overridable in order to create an instance
* of an extended class of <code>SessionPoolingData</code>.
*
* <p>If an error occurs in e.g. the script file, the calling
* party is responsible for logging the event.
*
* @return the original data, must be overridden in order
* to return an instance of an extended
* <code>SessionPoolingData</code> class instance.
*/
public static SessionPoolingData createSessionPoolingData(SessionPoolingData original)
{
// Create the session data.
return original;
}
/////////////////////
/// Instance data ///
/////////////////////
/**
* Flag set if session is disposed by the system and should
* abort any scripting thread executing.
*/
protected boolean isDisposed;
/**
* The pool data shared by all sessions in the same pool.
*/
protected SessionPoolingData globalPoolData;
/**
* The client connection data for this session.
*/
protected ClientConnectionData clientConnectionData;
/**
* The state variable, by default STATE_STARTING.
*/
protected int currentState;
/**
* The internal frame window.
*/
protected JInternalFrame iframe;
/**
* The time in milliseconds when an action completed,
* i.e. finished starting or pinging.
*/
protected long completeTime;
/**
* The host session manager can handle multiple host sessions,
* but only one is the current one that notifies the listener
* of events.
*/
protected HostSessionManager hostSessionManager;
/**
* The screen that currently matches (null when none single-matches).
*/
protected PhantomHostScreen currentScreen;
/**
* Flag indicating that the host screen has changed.
*/
protected boolean hasHostScreenChanged;
/**
* Flag indicating that the host cursor position has changed.
*/
protected boolean hasHostCursorChanged;
/**
* Flag indicating that the host fields has changed.
*/
protected boolean hasHostFieldsChanged;
/**
* Flag indicating that any host change has occured.
*/
protected boolean hasHostChanged;
/**
* The current timeout in milliseconds.
*/
protected int timeoutValue = 60000;
///////////////////////////////////////////////////
/// Constructor, initializer & access functions ///
///////////////////////////////////////////////////
/**
* The constructor does nothing at all.
*/
public DefaultSessionPoolingHandler()
{
// Nothing!
}
/**
* Called to initialize this instance with the global
* pool data.
*/
public void initialize(SessionPoolingData globalPoolData,ClientConnectionData clientConnectionData)
{
this.globalPoolData=globalPoolData;
this.clientConnectionData=clientConnectionData;
}
/**
* Gets the client connection data for this session.
*/
public ClientConnectionData getClientConnectionData()
{
return clientConnectionData;
}
/**
* Gets the global pooling data.
*/
public SessionPoolingData getGlobalPoolData()
{
return globalPoolData;
}
/**
* Gets a method from a script tag in the global pool data.
* The name of the method is case insensitive.
*
* @return null if no method is found, otherwise the
* <code>Method</code> instance.
*/
public Method getScriptTagMethod(String name)
{
return globalPoolData.getScriptTagMethod(name);
}
///////////////////////////
/// Disposing functions ///
///////////////////////////
/**
* Sets the disposed flag and throws the SessionPoolingDisposed exception.
*
* @throws SessionPoolingDisposed if the session has been disposed of.
*/
public final void checkDisposed() throws SessionPoolingDisposed
{
if ( isDisposed )
{
trace("Thread exception thrown");
throw new SessionPoolingDisposed("Script disposed");
}
}
/**
* Checks if the session is disposed of.
*
* @return true if the session has been disposed of.
*/
public final boolean isDisposed()
{
return isDisposed;
}
/**
* Disposes of the session due to a fatal error.
* This will disable the current pool and thus cause it
* not to start further sessions.
*
* @throws SessionPoolingDisposed always.
*/
public void disposeFatal() throws SessionPoolingDisposed
{
globalPoolData.setEnabled(false);
disposeNow("Fatal error dispose");
}
/**
* Disposes of the session (e.g. after an error) and throws
* the <code>SessionPoolingDisposed</code> exception.
*
* @throws SessionPoolingDisposed always.
*/
public void disposeNow() throws SessionPoolingDisposed
{
disposeNow(null);
}
/**
* Disposes of the session (e.g. after an error) and throws
* the <code>SessionPoolingDisposed</code> exception with the
* message <code>exceptionMsg</code>.
*
* @throws SessionPoolingDisposed always.
*/
public void disposeNow(String exceptionMsg) throws SessionPoolingDisposed
{
// Check if session is disposed of and if so, just throw
// the exception.
synchronized(this)
{
if ( isDisposed )
throw new SessionPoolingDisposed();
}
// Do the dispose.
dispose();
throw new SessionPoolingDisposed();
}
/**
* Call to dispose promptly of the session, without executing scripts, etc.
* It sets the disposed flag. This should terminate any scripting threads
* as soon as possible. The host session(s) started for this pool session
* are also disconnected.
*
* <p>This method MUST call the <code>SessionPoolingData
* sessionDisposed</code> method.
*
* <p>If the session already is disposed of, nothing will happen.
*/
public void dispose()
{
synchronized(this)
{
if ( isDisposed )
{
trace("Dispose called, but session already disposed");
return;
}
isDisposed=true;
currentState=STATE_DISPOSED;
}
// Close all host sessions.
HostSessionManager manager=hostSessionManager;
if ( manager!=null )
{
manager.bindHostSessionManagerListener(null);
manager.disconnectAll();
}
// Remove the GUI window if present.
removeWindow();
// Let the pool know...
globalPoolData.sessionDisposed(this);
logInfo("Session disposed");
trace("Session disposed");
}
////////////////////////
/// States functions ///
////////////////////////
/**
* Gets the current state. See the STATE_nnn values.
*/
public int getCurrentState()
{
return currentState;
}
/**
* Gets the string representation of the current state as set
* by the <code>stringStates</code> string array.
*/
public String getCurrentStringState()
{
return stringStates[currentState];
}
/**
* Checks if this session is valid and the check script has just completed successfully.
*/
public boolean isValidAndCheckSession()
{
synchronized(this)
{
// Check for ready state and check session.
if ( currentState==STATE_READY && runCheckScript() )
{
// Set session in use...
currentState=STATE_INUSE;
}
else
return false;
}
// Session is available.
logInfo("Client session will use pooled session ID "+getClientConnectionData().getConnectionID());
return true;
}
/**
* Attaches this session to a started client session.
* This will hide the window of the session pool until
* the client session is disposed.
*/
public void attachSession(ClientSession cs)
{
// Hide any GUI window is displayed.
JInternalFrame iframe=this.iframe;
if ( iframe!=null )
iframe.setVisible(false);
// Transfer host sessions between the managers.
cs.getHostSessionManager().transferHostSessions(hostSessionManager);
}
/**
* Detaches a client session from this pooled session.
* This will restore the host sessions and start running
* the reclaim or dispose scripts.
*/
public void detachSession(ClientSession cs)
{
// Check for ready state and check session.
synchronized(this)
{
if ( currentState!=STATE_INUSE )
{
logError("detachSession was called, but the session was not in use!");
return;
}
// Transfer the host sessions back to us...
hostSessionManager.transferHostSessions(cs.getHostSessionManager());
}
// Show any GUI window is displayed.
JInternalFrame iframe=this.iframe;
if ( iframe!=null )
iframe.setVisible(true);
// Check if session should be returned to pool with reclaim or disposed.
synchronized(this)
{
if ( globalPoolData.isReclaimRequired() )
{
currentState=STATE_RECLAIMING;
logInfo("Client session detaching from pooled session "+getClientConnectionData().getConnectionID()+" with action: reclaim to pool");
startReclaimThread();
}
else
{
logInfo("Client session detaching from pooled session "+getClientConnectionData().getConnectionID()+" with action: discard from pool");
if ( !isDisposed )
dispose();
}
}
}
/**
* Starts execution of the Reclaim thread. Before calling this method, the <code>currentState</code>
* variable should be set to STATE_RECLAIMING.
*/
public void startReclaimThread()
{
SessionPoolingScriptRunner runner=new SessionPoolingScriptRunner(this,SessionPoolingScriptRunner.SCRIPT_RECLAIM);
new ServerThread(getClientConnectionData(),runner,"SPReclm").start();
}
/**
* Starts execution of the Dispose thread.
*/
public void startDisposeThread(DefaultSessionPoolingHandler handler)
{
synchronized(this)
{
// Only dispose when doing nothing...
if ( currentState==STATE_READY )
{
currentState=STATE_DISPOSING;
SessionPoolingScriptRunner runner=new SessionPoolingScriptRunner(this,SessionPoolingScriptRunner.SCRIPT_DISPOSE);
new ServerThread(getClientConnectionData(),runner,"SPDisp").start();
return;
}
}
// Otherwise dispose.
dispose();
}
///////////////
/// Helpers ///
///////////////
/**
* Adds a trace output for session pooling if client verbose trace is turned on.
*/
public void trace(String txt)
{
//System.out.println(" >>> SESSION-POOLING: "+globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt);
if ( clientConnectionData.doClientVerboseTrace() )
BinaryTrace.dump(clientConnectionData,"SESSION-POOLING: "+globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt);
}
/**
* Adds a trace output for session pooling if client verbose trace is turned on.
*/
public void trace(String txt,String [] moreTxt)
{
if ( clientConnectionData.doClientVerboseTrace() )
{
// Build new array...
int cc=moreTxt.length;
String [] s=new String [cc+1];
s[0]="SESSION-POOLING: "+globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt;
for ( ; cc>0; --cc )
s[cc]=" "+moreTxt[cc-1];
BinaryTrace.dump(clientConnectionData,s);
}
}
/**
* Logs an session pooling error event in the event log.
*/
public void logError(String txt)
{
EventManager.logEvent(EventID.EVENT_E_SessionPooling,globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt);
}
/**
* Logs an session pooling warning event in the event log.
*/
public void logWarning(String txt)
{
EventManager.logEvent(EventID.EVENT_W_SessionPooling,globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt);
}
/**
* Logs an session pooling informational event in the event log.
*/
public void logInfo(String txt)
{
EventManager.logEvent(EventID.EVENT_I_SessionPooling,globalPoolData.poolName+", script "+globalPoolData.scriptFile+": "+txt);
}
//////////////////////
/// Ping functions ///
//////////////////////
/**
* Sets the time for completion of a start or ping action.
*/
public void setCompleteTime()
{
completeTime=System.currentTimeMillis();
}
/**
* Checks if a session ping is required for this session
* and if so this operation is initiated.
*
* <p>This will not be performed if there is no Ping script
* defined in the XML file.
*
* <p>If the session is busy or in use by an application,
* false is returned.
*
* @return true if a ping operation has begun, false otherwise.
*/
public boolean checkPingRequired()
{
synchronized(this)
{
// Skip if not ready (not in use also).
if ( currentState!=STATE_READY )
return false;
// Check last ping time.
long interval=globalPoolData.pingTime*1000L;
if ( interval==0L )
return false;
if ( System.currentTimeMillis()-completeTime<interval )
return false;
// Initiate the ping operation.
currentState=STATE_PINGING;
SessionPoolingScriptRunner runner=new SessionPoolingScriptRunner(this,SessionPoolingScriptRunner.SCRIPT_PING);
new ServerThread(clientConnectionData,runner,"SPPing").start();
}
return true;
}
/////////////////////////////////////////////
/// Host initialization and GUI functions ///
/////////////////////////////////////////////
/**
* Removes the GUI window if present.
*/
private void removeWindow()
{
// Dispose of the window (if any).
JInternalFrame f=this.iframe;
iframe=null;
if ( f!=null )
{
trace("Disposing of GUI window");
// The desktop pane can be null if not yet added to
// the desktop pane, e.g. when the host session cannot
// be created.
Utilities.disposeInternalFrame(f);
trace("GUI window disposed");
}
}
/**
* Creates the host session manager and a GUI window (if server
* runs with GUI). The host session is also started.
*/
@SuppressWarnings("deprecation")
protected void createHostSessionManager()
{
trace("Create session - BEGIN");
// Create the child window if required.
final JDesktopPane desktopPane=globalPoolData.desktopPane;
if ( desktopPane!=null )
{
trace("Creating GUI window");
// Call dispose of server when closing the window.
InternalFrameListener onClose=new InternalFrameAdapter()
{
@Override
public void internalFrameClosing(InternalFrameEvent e)
{
if ( !isDisposed )
{
logInfo("GUI window closed by operator");
trace("GUI window closed by operator");
}
dispose();
}
};
// Creation below needs 'iframe' before 'window', because
// otherwise members are null.
iframe=new JInternalFrame(globalPoolData.poolName+" - Pooled Session "+getClientConnectionData().getConnectionID(),true,true,true,true);
iframe.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
iframe.addInternalFrameListener(onClose);
}
// Create the host session manager.
trace("Creating HostSessionManager");
try
{
hostSessionManager=new HostSessionManager(this,iframe);
}
catch(IOException e)
{
String s="CREATE HOST SESSION MANAGER FAILURE: "+e.toString();
trace(s);
logError(s);
onHostSessionFailure(s);
return;
}
// Display the window.
final JInternalFrame f=iframe;
if ( f!=null )
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
windowCount=(windowCount+1)%100;
f.setBounds(20*(windowCount%10-windowCount/10),20*(windowCount%10),700,450);
desktopPane.add(iframe,JLayeredPane.DEFAULT_LAYER);
f.moveToFront();
f.validate();
f.repaint();
f.setVisible(true);
f.requestDefaultFocus();
}
});
}
// Bind HostSessionManager to us.
hostSessionManager.bindHostSessionManagerListener(this);
// Start connection.
hostSessionManager.setCurrentSession(globalPoolData.hostID);
trace("Create session - END");
}
//////////////////////
/// Host Functions ///
//////////////////////
/**
* This function clears any outstanding errors and returns the current
* host session. If the error cannot be cleared, a host error is thrown.
*
* @return the current host session.
*
* @throws SessionPoolingScriptHostError if there is no current host session
* or if there is a host error.
*/
public HostSession getCurrentClearedHostSession() throws SessionPoolingScriptHostError
{
// Get current host session.
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession==null )
throw new SessionPoolingScriptHostError();
if ( !hostSession.isConnected() || hostSession.hasError() )
throw new SessionPoolingScriptHostError();
// Do get the current matching screen (forces updates of host fields and screen + 5250 err msg).
getSingleMatchingScreen();
// Return the current host session.
return hostSession;
}
/**
* Process host field refresh, pop-up window analysis
* and screen identification after a host screen refresh.
*/
private void processHostRefreshed(boolean doFields,boolean doScreen)
{
HostScreen screen=hostSessionManager.getScreen();
if ( doFields )
hostSessionManager.refreshHostFields();
// When screen is updated or host fields are:
// - re-analyze the host pop-up windows.
// - re-check the matching screen.
if ( doFields || doScreen )
{
// Analyze pop-up windows.
screen.analyzePopupWindows();
// Re-check screens. Check if no runtime is defined.
PhantomRuntime rt=globalPoolData.runtime;
if ( rt==null )
return;
// Find the new matching screen: not in synchronized part, because it may take a while...
PhantomHostScreen newScreen=rt.getHostData().getMatchingScreen(screen);
// Check for change in screen and for AS/400 system error message in a pop-up window.
if ( newScreen!=null && newScreen.isPopup() )
{
HostSession hs=hostSessionManager.getCurrentSession();
if ( hs!=null && !hs.is3270() && hs.hasError() )
{
String msg=screen.getStringRelative(1,newScreen.getHeight()-2,newScreen.getWidth()-2).trim();
if ( msg.length()>0 && !msg.equals(hs.getLastError(false)) )
hs.setLastError(msg);
}
}
// Set the current screen.
currentScreen=newScreen;
}
}
/**
* Waits for some data on a screen after a new connection. The maximum wait time
* between two host events is 20 seconds. If the <code>data</code> parameter is null,
* disposal of script is not checked, but disposal of the session is.
*
* @throws SessionPoolingDisposed when the connection fails.
*/
public void waitInitialScreen(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
HostScreen screen=hostSessionManager.getScreen();
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession==null )
throw new SessionPoolingDisposed("No current host session");
synchronized(this)
{
for ( ;; )
{
// Check for disposal.
if ( data==null )
checkDisposed();
else
data.checkScriptDisposed();
// If connected and not connecting and the screen is non-empty, we're done.
getSingleMatchingScreen();
if ( hostSession.isConnected() && !hostSession.isConnecting() && !screen.isEmpty() )
break;
// Check if not connected and not connecting...
if ( !hostSession.isConnected() && !hostSession.isConnecting() )
throw new SessionPoolingDisposed("Host session disconnected");
// Wait max 20 seconds for a change.
try { wait(20000); }
catch(InterruptedException e) {}
}
}
}
/**
* Rematches the current matching screens and returns the single matching screen.
* This function only performs the matching if there has been some kind of host change
* that affects the match process since the last call.
*
* @return null if no screen or multiple screens match, otherwise the single matching screen.
*/
public PhantomHostScreen getSingleMatchingScreen()
{
synchronized(this)
{
boolean doFields=hasHostFieldsChanged;
boolean doScreen=hasHostScreenChanged;
hasHostFieldsChanged=false;
hasHostScreenChanged=false;
if ( doFields || doScreen )
processHostRefreshed(doFields,doScreen);
}
return currentScreen;
}
/**
* Waits a certain time for a condition to be true.
*
* <p>If the parameter <code>data</code> is non-null, the following parameters are
* extracted from the <code>data.currentElement</code> tag:
*
* <pre>
* screen="SCREENNAME" [multiple] [negate]
* </pre>
* or
* <pre>
* hosttext="text" [nocase] [negate] column="xx" row="yy" [relative] [width="nn" [wildcard]]
* </pre>
* or
* <pre>
* hostfield="NAME" text="text" [nocase] [negate] [line="nn"] [wildcard]
* </pre>
*
* Any of the combinations of the above parameters are allowed, e.g. waiting for a screen and a host field,
* a screen and a host text.
*
* <p>The wildcard match option enables text to be matched with Windows-like wildcards:
* <br> ? is any character,
* <br> * is any number of characters,
* <br> ^ is the escape character, i.e. the meaning of '?' and '*' becomes the
* actual character that directly follows,
* <br>^^ two escape characters becomes the escape character itself ('^').
*
* @return true if the condition(s) are met, false otherwise.
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptHostError when a host error is encountered.
*/
public boolean hostWait(SessionPoolingScriptData data,boolean hostlock,boolean hoststable,int time,int timeout) throws SessionPoolingDisposed
{
boolean multiple=false,wildcard=false,nocase=false,relative=false;
boolean doNegate=false;
String text=null,hostText=null;
PhantomHostScreen phantomScreen=null;
PhantomHostField hostField=null;
int col=0,row=0,width=-1,line=-1;
// Get the current matching screen.
PhantomHostScreen singleMatchScreen=getSingleMatchingScreen();
if ( data!=null )
{
// Set the negate option.
doNegate=data.hasElementAttribute("multiple");
///
/// Check for screen definition.
///
String s=data.getParamString("screen");
if ( s.length()>0 )
{
// Get the screen in the runtime file.
PhantomRuntime rt=globalPoolData.runtime;
if ( rt==null )
{
s="Waiting for screen "+s+" error, runtime file not loaded: no screen is assumed";
data.trace(s);
data.logWarning(s);
}
else
{
// Find the host screen and get the multiple option.
phantomScreen=rt.getHostData().getScreen(s);
if ( phantomScreen==null )
{
s="Waiting for screen "+s+" error, screen name not found: no screen is assumed";
data.trace(s);
data.logWarning(s);
}
else
multiple=data.hasElementAttribute("multiple");
}
}
///
/// Check for hostfield definition.
///
s=data.getParamString("hostfield");
if ( s.length()>0 )
{
PhantomHostScreen hfScreen=phantomScreen;
if ( hfScreen==null && singleMatchScreen==null )
{
s="Waiting for hostfield "+s+", but no screen matches: no field assumed";
data.trace(s);
data.logWarning(s);
}
else
{
// Find the host field to match for.
if ( hfScreen==null )
hfScreen=singleMatchScreen;
hostField=hfScreen.getHostField(s.toUpperCase());
if ( hostField==null )
{
s="Waiting for hostfield "+s+" on screen "+hfScreen.getName()+", but host field is not found: no field assumed";
data.trace(s);
data.logWarning(s);
}
else
{
text=data.getParamString("text");
line=data.getParamInt("line")-1;
if ( line<0 ) line=-1;
nocase=data.hasElementAttribute("nocase");
wildcard=data.hasElementAttribute("wildcard");
}
}
}
///
/// Check for host text definition.
///
s=data.getParamString("hosttext");
if ( s.length()>0 )
{
hostText=s;
col=data.getParamInt("column")-1;
if ( col<0 ) col=0;
row=data.getParamInt("row")-1;
if ( row<0 ) row=0;
nocase=data.hasElementAttribute("nocase");
relative=data.hasElementAttribute("relative");
width=data.getParamInt("width");
wildcard=(width>0 && data.hasElementAttribute("wildcard"));
}
}
///
/// Check for just wait (no host operations involved)...
///
if ( text==null && hostText==null && phantomScreen==null && hostField==null && !hostlock && !hoststable )
{
if ( time>0 )
{
try { Thread.sleep(time); }
catch(InterruptedException e) {}
}
return true;
}
// Get the current host session, no match if none...
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession==null )
return false;
// Check for no-case-sensitive for comparisons.
if ( nocase )
{
if ( text !=null ) text =text .toUpperCase();
if ( hostText!=null ) hostText=hostText.toUpperCase();
}
HostScreen screen=hostSessionManager.getScreen();
///
/// Do the wait...
///
boolean returnCode=true;
synchronized(this)
{
hasHostChanged=false;
for ( long timeoutTime=System.currentTimeMillis()+timeout;; )
{
// First check if there the conditions match, start by getting the currently
// matching screen.
PhantomHostScreen matchScreen=getSingleMatchingScreen();
// Check for error.
if ( hostSession.hasError() || !hostSession.isConnected() )
throw new SessionPoolingScriptHostError();
// Check lock state released.
boolean ok=true;
if ( hostlock )
ok=!hostSession.isLocked();
// Check screen matching.
if ( ok && phantomScreen!=null )
{
ok=(matchScreen==phantomScreen || (multiple && doesScreenMatch(phantomScreen)));
if ( doNegate )
ok=!ok;
}
// Check host field match.
if ( ok && hostField!=null )
{
String s=hostField.getHiddenHostString(screen,line);
if ( nocase )
s=s.toUpperCase();
ok=(wildcard)? WindowsLikeFilenameFilter.isMatching(s,text): s.equals(text);
if ( doNegate )
ok=!ok;
}
// Check host text match.
if ( ok && hostText!=null )
{
if ( wildcard )
{
String s=(relative)?
screen.getHiddenStringRelative(col,row,width):
screen.getHiddenStringAbsolute(col,row,width);
if ( nocase )
s=s.toUpperCase();
ok=WindowsLikeFilenameFilter.isMatching(s,hostText);
}
else
{
String s=(relative)?
screen.getHiddenStringRelative(col,row,hostText.length()):
screen.getHiddenStringAbsolute(col,row,hostText.length());
if ( nocase )
s=s.toUpperCase();
ok=s.equals(hostText);
}
if ( doNegate )
ok=!ok;
}
// Check if the conditions match.
if ( ok )
{
// Situation must be stable...
if ( time>0 )
{
hasHostChanged=false;
try { wait(time); }
catch(InterruptedException e) {}
if ( hasHostChanged )
continue;
}
// Conditions did match...
break;
}
// If the host has changed, check the conditions again.
if ( hasHostChanged )
{
hasHostChanged=false;
continue;
}
// Don't wait if specified.
if ( time==0 )
{
returnCode=ok;
break;
}
// Wait for an event up to the remaining timeout.
long remainingTime=timeoutTime-System.currentTimeMillis();
if ( remainingTime<=0 )
{
returnCode=false;
break;
}
// Wait for any change up to the timeout.
hasHostChanged=false;
try { wait(remainingTime); }
catch(InterruptedException e) {}
if ( !hasHostChanged || System.currentTimeMillis()>=timeoutTime )
{
returnCode=false;
break;
}
}
}
// Check for error.
if ( hostSession.hasError() || !hostSession.isConnected() )
throw (new SessionPoolingScriptHostError());
// Return the OK state.
return returnCode;
}
///////////////////////////////////////////////////////
/// Methods overridden in HostSessionManagerAdapter ///
///////////////////////////////////////////////////////
/**
* Gets a lock object that is used by all threads modifying
* the host screen. The instance of this class is used to
* handle all synchronization.
*/
@Override
public Object getLockObject()
{
return this;
}
/**
* Informs that the current host session had a connection change.
*/
@Override
public void onHostConnectChange(boolean connected)
{
synchronized(this)
{
// Notify all waiting threads.
hasHostScreenChanged=true;
hasHostFieldsChanged=true;
hasHostCursorChanged=true;
hasHostChanged=true;
notifyAll();
}
}
/**
* Informs that the current host session had a screen change.
*/
@Override
public void onHostScreenChange()
{
synchronized(this)
{
// Set flag & notify all waiting threads.
hasHostScreenChanged=true;
hasHostChanged=true;
notifyAll();
}
}
/**
* Informs that the cursor position has changed.
*/
@Override
public void onHostCursorPositionChange(int x,int y)
{
synchronized(this)
{
// Set flag & notify all waiting threads.
hasHostCursorChanged=true;
hasHostChanged=true;
notifyAll();
}
}
/**
* Informs that the current host session had a field change.
*/
@Override
public void onHostFieldChange()
{
synchronized(this)
{
// Set flag & notify all waiting threads.
hasHostFieldsChanged=true;
hasHostChanged=true;
notifyAll();
}
}
/**
* Informs that the current host session had a state change.
*/
@Override
public void onHostStateChange()
{
synchronized(this)
{
// Notify all waiting threads.
hasHostChanged=true;
notifyAll();
}
}
/**
* Informs that the current host session had a failure.
*/
@Override
public void onHostSessionFailure(String description)
{
synchronized(this)
{
// Log the error.
logError("Host session failure: "+description);
// Immediatly dispose of the session, regardless what is going on.
if ( !isDisposed )
dispose();
// Notify all waiting threads.
hasHostScreenChanged=true;
hasHostFieldsChanged=true;
hasHostCursorChanged=true;
hasHostChanged=true;
notifyAll();
}
}
/**
* Checks if a host screen currently matches.
*/
@Override
public boolean doesScreenMatch(PhantomHostScreen phantomScreen)
{
// Check if the screen is a a normal screen, i.e. not a popup.
HostScreen screen=hostSessionManager.getScreen();
if ( !phantomScreen.isPopup() )
{
// Set no pop-up window matching...
screen.setCurrentPopupWindow(-1);
return phantomScreen.isScreenMatching(screen,0,0);
}
// Screen is a popup. Check all possible windows identified for a match.
int w=phantomScreen.getWidth();
int h=phantomScreen.getHeight();
for ( int ii=0, cc=screen.getPopupWindowCount(); ii<cc; ++ii )
{
HostPopupWindow.Rect rect=screen.getPopupWindow(ii);
if ( rect.cx==w && rect.cy==h )
{
if ( phantomScreen.isScreenMatching(screen,rect.x,rect.y) )
{
// Set the current pop-up window...
screen.setCurrentPopupWindow(ii);
return true;
}
}
}
// Screen doesn't match. Set no pop-up window matching...
screen.setCurrentPopupWindow(-1);
return false;
}
///////////////////////////
/// The Session Actions ///
///////////////////////////
/**
* Runs the starts script for the session.
*
* <p>Called when a session is started in the pool. The start time is
* set before the terminal session is created. The script is called once
* the host session is unlocked and has a non-empty host screen. Typically,
* the script would navigate in the host so as to reach the "starting point
* screen".
*
* @throws SessionPoolingDisposed if the session is disposed.
*/
public void runStartScript() throws SessionPoolingDisposed
{
// Create the session first!
createHostSessionManager();
// Wait for the initial screen.
waitInitialScreen(null);
// Set start state.
trace("runStartScript()");
currentState=STATE_START;
// Run script...
globalPoolData.executeScript(this,SessionPoolingData.SCRIPT_START);
// Set ready state and last ping time.
setCompleteTime();
currentState=STATE_READY;
// Notify readiness.
globalPoolData.sessionStarted();
}
/**
* Runs the check script.
*
* <p>Called to check if a session is OK for usage for a new client
* session. Typically, the script would check that the host session is
* unlocked and not in error, and that the current screen is the "starting
* point screen".
*
* @return true if it's OK to use this session, false otherwise.
*/
private boolean runCheckScript()
{
// Set check state.
trace("runCheckScript()");
currentState=STATE_CHECK;
// Run script...
try
{
boolean rc=globalPoolData.executeScript(this,SessionPoolingData.SCRIPT_CHECK);
setCompleteTime();
currentState=STATE_READY;
return rc;
}
catch(SessionPoolingDisposed e)
{
return false;
}
}
/**
* Runs the ping script.
*
* <p>Called periodically by the session pool manager (optional setting).
* This enables, for example, the script to perform some action in the host
* in order to make sure it is not going to be disconnected by or logged from
* the host. The script should also verify that the host is at the "starting
* point screen".
*
* @throws SessionPoolingDisposed if the session is disposed.
*/
public void runPingScript() throws SessionPoolingDisposed
{
// Set ping state.
trace("runPingScript()");
currentState=STATE_PING;
// Run script...
globalPoolData.executeScript(this,SessionPoolingData.SCRIPT_PING);
// Set ready state and last ping time.
setCompleteTime();
currentState=STATE_READY;
}
/**
* Runs the reclaim script.
*
* <p>Called when a client session is closed in order to reclaim the host
* session into the pool. This enables the script to, for example, back out
* from a series of host screens to a starting point. When the reclaim action
* is called, the user who owned the host session could have navigated "deep
* down" in the host system.
*
* @throws SessionPoolingDisposed if the session is disposed.
*/
public void runReclaimScript() throws SessionPoolingDisposed
{
// Set ping state.
trace("runReclaimScript()");
currentState=STATE_RECLAIM;
// Run script...
globalPoolData.executeScript(this,SessionPoolingData.SCRIPT_RECLAIM);
// Set ready state and last ping time.
setCompleteTime();
currentState=STATE_READY;
}
/**
* Runs the dispose script for the session.
*
* <p>Called prior to disposing of the terminal session. Typically,
* the script would logoff from the host system in a controlled way.
* Note that the current host screen could be "anywhere" in the host system.
*
* @throws SessionPoolingDisposed if the session is disposed.
*/
public void runDisposeScript() throws SessionPoolingDisposed
{
trace("runDisposeScript()");
currentState=STATE_DISPOSING;
// Run script...
globalPoolData.executeScript(this,SessionPoolingData.SCRIPT_DISPOSE);
// Check if not disposed yet.
synchronized(this)
{
if ( isDisposed )
return;
dispose();
}
}
///////////////////////////////////
/// The internal script methods ///
///////////////////////////////////
/**
* Processes an <code>if</code> begin tag (and the else or elseif tags that follows).
*
* <p>Syntax:
* <pre>
* (if)
* (conditions [negate])
* ... conditions ...
* (/conditions)
* ... instructions ...
* (/if)
* (elseif)
* (conditions [negate])
* ... conditions ...
* (/conditions)
* ... instructions ...
* (/elseif)
* (else)
* ... instructions ...
* (/else)
* </pre>
*
* The elseif block can be repeated any number of times or not specified at
* all. The else block may only be specified once and can also be omitted.
*
* <p>Return code: true if there was an if or elseif condition that was true,
* false otherwise. This applies when no instructions have been executed.
* The return code is otherwise the return code of the last executed
* instruction (i.e. tag).
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptIF(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get the current element (the condition tag).
Element parent=data.currentElement;
// Do this until either ELSE or successful IF (or ELSEIF) has been processed.
try
{
for ( ;; )
{
// Execute the next statement (if it's a condition block, it will handle execution of
// all it's children.
data.returnCode=true;
data.currentElement=parent;
boolean doNegate=data.hasElementAttribute("negate");
data.currentElement=data.getFirstElement(parent);
if ( data.currentElement==null )
{
data.trace("No condition tag found after if tag");
data.logError("No condition tag found after if tag");
disposeFatal();
}
// Execute the condition.
data.invokeScriptMethod();
// If the return code is false, check if there is a next
// ELSEIF or ELSE statement.
if ( data.returnCode==doNegate )
{
Element next=data.getNextElement(parent);
if ( next==null )
break;
String name=next.getTagName();
if ( name.equals("elseif") )
{
// ELSEIF: continue loop as the IF statement that started
// the loop as the new parent.
parent=next;
continue;
}
else
if ( name.equals("else") )
{
// ELSE: execute all its child elements and set pointer
// after the ELSE.
data.currentElement=data.getFirstElement(next);
data.executeRemainingElements();
data.currentElement=next;
data.setNextCurrentElement();
}
else
{
// Other tag follows. Set this to be the next one to execute.
data.currentElement=next;
}
break;
}
// Process all statements after the condition. Then set the pointer
// after the IF or ELSEIF statement.
data.executeRemainingElements();
parent=data.getNextElement(parent);
// Skip all ELSEIF tags after the current parent.
while ( parent!=null && parent.getTagName().equals("elseif") )
parent=data.getNextElement(parent);
// Skip the ELSE tag after the current parent.
if ( parent!=null && parent.getTagName().equals("else") )
parent=data.getNextElement(parent);
// Set the next element.
data.currentElement=parent;
break;
}
}
catch(SessionPoolingScriptHostError e)
{
// Skip the IF statement.
if ( parent.getTagName().equals("if") )
parent=data.getNextElement(parent);
// Skip all ELSEIF tags after the current parent.
while ( parent!=null && parent.getTagName().equals("elseif") )
parent=data.getNextElement(parent);
// Skip the ELSE tag after the current parent.
if ( parent!=null && parent.getTagName().equals("else") )
parent=data.getNextElement(parent);
// Set the next element.
data.currentElement=parent;
throw e;
}
}
/**
* The conditions tag should normally be used together with the if,
* elseif and the while tags. All tags inside the "conditions block"
* are combined with the logical and operator. The conditions tag can
* also be used to stop processing in a block as soon as e.g. a wait tag
* returns false.
*
* <p>The negate option logically negates each condition tag processed
* before the logical and operation is performed. That is, a return code
* of true in a condition tag stops execution of the remaining tags,
* returning a value of false.
*
* <p>Syntax:
* <pre>
* (conditions [negate])
* ... conditions ...
* (/conditions)
* </pre>
*
* Return code: true if all tags inside the "conditions" block are true,
* false otherwise. Processing of execution of the tags inside the block
* is immediately stopped if one of the tags returns false (or true for
* the negate option).
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptCONDITIONS(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Check the negate parameter.
boolean doNegate=data.hasElementAttribute("negate");
// Get the current element (the condition tag) and set
// the initial return code to true.
Element parent=data.currentElement;
data.returnCode=true;
// Process all child nodes, but handle the on error exception.
try
{
for ( data.currentElement=data.getFirstElement(parent); data.currentElement!=null; )
{
// Execute the line.
data.invokeScriptMethod();
if ( doNegate )
data.returnCode=!data.returnCode;
// Stop for false.
if ( data.returnCode==false )
break;
}
}
catch(SessionPoolingScriptHostError e)
{
// Set next statement to execute and rethrow the error.
data.currentElement=parent;
data.setNextCurrentElement();
throw e;
}
// Set next statement to execute.
data.currentElement=parent;
data.setNextCurrentElement();
}
/**
* The while instructions are executed as long as the conditions are true.
*
* <p>Syntax:
* <pre>
* (while)
* (conditions [negate])
* ... conditions ...
* (/conditions)
* ... instructions ...
* (/while)
* </pre>
*
* or the do-while style:
*
* <pre>
* (while)
* ... instructions ...
* (conditions [negate])
* ... conditions ...
* (/conditions)
* (/while)
* </pre>
*
* Return code: The return code of the last executed instruction (i.e. tag).
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptWHILE(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get the current element (the condition tag) and set
// the initial return code to true.
boolean doNegate=data.hasElementAttribute("negate");
Element parent=data.currentElement;
data.returnCode=true;
// Get first child element.
Element firstChild=data.getFirstElement(parent);
Element lastConditionsChild=null;
if ( firstChild==null )
{
String s="While method has no children nodes";
data.trace(s);
data.logError(s);
disposeFatal();
}
// Check if the conditions test is last or first.
// If first element is not "conditions", check last element.
if ( !firstChild.getTagName().equals("conditions") )
{
// Check for last element being "conditions".
Element child=firstChild;
for ( ;; )
{
Element next=data.getNextElement(child);
if ( next==null )
{
if ( child!=firstChild && child.getTagName().equals("conditions") )
lastConditionsChild=child;
break;
}
child=next;
}
}
// Handle the break and host error exceptions.
try
{
if ( lastConditionsChild!=null )
{
// Do-while style, i.e. the last first.
for ( ;; )
{
// Process all children up to the last conditions tag.
data.currentElement=firstChild;
data.executeRemainingElements(lastConditionsChild);
// Check if while loop should be broken or not.
data.currentElement=lastConditionsChild;
data.invokeScriptMethod();
if ( data.returnCode==doNegate )
break;
}
}
else
{
// "Normal" while statement, i.e. conditions first.
for ( ;; )
{
// Check if while loop should be broken or not.
data.currentElement=firstChild;
data.invokeScriptMethod();
if ( data.returnCode==doNegate )
break;
// Process all children that follows.
data.executeRemainingElements();
}
}
}
catch(SessionPoolingScriptBreak e)
{
// Nothing.
}
catch(SessionPoolingScriptHostError e)
{
// Set next statement to execute and rethrow the error.
data.currentElement=parent;
data.setNextCurrentElement();
throw e;
}
// Set next statement to execute.
data.currentElement=parent;
data.setNextCurrentElement();
}
/**
* This tag resets any error condition present in host session,
* after e.g. typing data in a protected field or receiving a System
* Message for 5250.
*
* <p>Syntax:
* <pre>
* (reset/)
* </pre>
*
* Return code: true if the function was successful or no error state existed
* for the session, false otherwise (host was not in an error state that could
* not be reset due to e.g. communications failure).
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptRESET(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
HostSession hostSession=hostSessionManager.getCurrentSession();
boolean ok=false;
if ( hostSession!=null )
{
// Return code should be false if no previous error existed, true otherwise.
ok=!hostSession.hasError();
if ( !ok && hostSession.sendKey(TerminalKeys.KEY_Reset,true) )
ok=(ok && !hostSession.hasError());
}
data.returnCode=ok;
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* An error state is typically set if data is input in a protected field,
* there is some communications failure or, for 5250, if a "System Message"
* is present. To remove this condition, the reset key ("@R") must be sent
* or the reset tag executed.
*
* <p>Syntax:
* <pre>
* (hosterror/)
* </pre>
*
* Return code: true if the host is presently in an error state. If no error
* condition is present, false is returned.
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptHOSTERROR(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
data.returnCode=false;
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession!=null && (hostSession.hasError() || !hostSession.isConnected()) )
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* When an error condition occurs in a script, the next on error tag
* instructions are executed. If the session is not in an error state,
* this tag is skipped.
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptONERROR(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Nothing, because the host error situation is checked elsewhere
// in this class. Just set next statement to execute.
data.setNextCurrentElement();
}
/**
* Logs an event in the NetPhantom Event Log.
*
* <p>Syntax:
* <pre>
* (log [level="nn"] text="text" [lasterror]/)
* </pre>
*
* The value <i>nn</i> can be 0=informational (default), 1=warning, 2=error,
* 3=critical. It can also be the first character, i.e. I, W, E or C.
*
* <p>If the <i>lasterror</i> parameter is specified, the last encountered error
* message for the terminal session is added. Note for 5250 sessions: a system
* error message always begins with the text "last error 5250:".
*
* <p>Return code: always true.
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptLOG(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get the text for the trace.
String text=data.getParamString("text");
// Check for last error option.
if ( data.hasElementAttribute("lasterror") )
{
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession!=null && !hostSession.is3270() )
{
String err=hostSession.getLastError(false);
if ( err==null )
err="<none>";
text+=": last error 5250: "+err;
}
}
// Check the level (0=I=info(default), 1=W=Warning, 2=E=Error).
int level=0;
String s=data.getParamString("level").toUpperCase();
if ( s.length()>0 )
{
char ch=s.charAt(0);
if ( ch>='0' && ch<='2' ) level=(ch-'0');
else if ( ch=='W' ) level=1;
else if ( ch=='E' ) level=2;
}
// Log the event.
switch(level)
{
default: logInfo (text); break;
case 1 : logWarning(text); break;
case 2 : logError (text); break;
}
// Set return code.
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* Adds text to the trace file.
*
* <p>If the lasterror parameter is specified, the last encountered error message
* for the terminal session is added.
*
* <p>Note: for 5250 sessions, a system error message always begins with the text
* "last error 5250:".
*
* <p>If the parameter dumpscreen is present, the host screen character data is appended.
* <pre>
* (/trace text="text" [lasterror] [dumpscreen]/)
* </pre>
*
* Return code: always true.
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptTRACE(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get the text for the trace.
String text=data.getParamString("text");
// Check for last error option.
if ( data.hasElementAttribute("lasterror") )
{
HostSession hostSession=hostSessionManager.getCurrentSession();
if ( hostSession!=null && !hostSession.is3270() )
{
String err=hostSession.getLastError(false);
if ( err==null )
err="<none>";
text+=": last error 5250: "+err;
}
}
// Check for dumpscreen option.
if ( !data.hasElementAttribute("dumpscreen") )
trace("Script trace: "+text);
else
{
HostScreen screen=hostSessionManager.getScreen();
String [] scr;
synchronized(this)
{
int cc=screen.getHeight();
scr=new String [cc];
for ( int ii=0; ii<cc; ++ii )
scr[ii]=screen.getStringAbsolute(0,ii,cc);
}
trace("Script trace: "+text,scr);
}
// Set return code.
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* Breaks out of a while loop.
*
* <p>If not inside a while loop, this tag would have the same processing as
* a return tag, optionally setting the return code to true or false.
*
* <p>If the return code is not set, the last return code is used.
*
* <p>Syntax:
* <pre>
* (break [true]/)
*
* (break [false]/)
* </pre>
*
* Return code: true or false options if the parameters are set,
* otherwise the return code of the last instruction (i.e. tag).
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptBreak always.
*/
public void scriptBREAK(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get optional parameters for return code.
if ( data.hasElementAttribute("true") )
data.returnCode=true;
else
if ( data.hasElementAttribute("false") )
data.returnCode=false;
throw new SessionPoolingScriptBreak();
}
/**
* Stops execution in a script with the specified return code.
*
* <p>Syntax:
* <pre>
* (return true/)
*
* (return false/)
* </pre>
*
* Return code: true or false depending on the parameter.
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptReturn always.
*/
public void scriptRETURN(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Get parameters for return code.
boolean rc=false;
if ( data.hasElementAttribute("true") )
rc=true;
else
if ( !data.hasElementAttribute("false") )
data.logWarning("Return element does not have 'true' or 'false' specified, 'false' is assumed");
data.returnCode=rc;
throw new SessionPoolingScriptReturn();
}
/**
* Immediately disposes of this pooled session without executing a single additional tag.
*
* <p>Syntax:
* <pre>
* (dispose/)
* </pre>
*
* This tag does not return a value, an exception is thrown and the script is terminated.
*
* @throws SessionPoolingDisposed when a script has been disposed.
*/
public void scriptDISPOSE(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
disposeNow("Dispose script tag");
}
/**
* This tag is used to send a series of keystrokes to the terminal session formatted
* according to EHLLAPI codes (e.g. "userid@Tpassword@E").
*
* <p>Syntax:
* <pre>
* (send string="string"/)
*
* (send userid="USERID"/)
* </pre>
*
* The <code>userid</code> option will send the password for the "USERID"
* that is specified in the NetPhantom Users using the Server Administration program.
*
* <p>The send tag always waits for the session to be unlocked (due to e.g. sending an
* AID key such as Enter). The maximum wait time is the default timeout (60 seconds)
* or if it is changed using the <code>set</code> function.
*
* <p>An error with this function will resume execution at the next onerror tag.
*
* <p>Note that the type-ahead of keystrokes is enabled only if this option is
* enabled for host sessions using the Server Administration program.
*
* <p>Return code: always true.
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptHostError when a host error is encountered.
*/
public void scriptSEND(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Clear error state if possible, otherwise this throws an error.
HostSession hostSession=getCurrentClearedHostSession();
// Find the text.
boolean ok;
if ( data.hasElementAttribute("string") )
{
ok=hostSession.sendString(data.getParamString("string"));
}
else
if ( data.hasElementAttribute("userid") )
{
String userid=data.getParamString("userid");
AdminConfigUser user=AdminConfigUsers.getUser(userid);
if ( user==null )
{
data.logWarning("Send element cannot find userid '"+userid+"'");
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
return;
}
ok=hostSession.sendCharacterString(user.getPassword());
}
else
{
data.logWarning("Send element does not specify either 'string' or 'userid'");
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
return;
}
// Check for error.
if ( !ok || hostSession.hasError() || !hostSession.isConnected() )
throw new SessionPoolingScriptHostError();
// Wait for the host to unlock the maximum timeout time.
hostWait(null,true,false,-1,data.handler.timeoutValue);
data.returnCode=true;
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* The wait tag is used to wait a specified time in milliseconds or a maximum
* time in milliseconds (default 60000 milliseconds, or changed by the set timeout tag).
*
* <p>It is also used to wait for the host lock state, the host session to be stabilized
* within the timeout value, a screen to match or a host text to be present (using
* absolute or relative screen position, also including hidden/non-display text as an
* option). When the time option is defined with waiting functions for the host (e.g.
* screen and/or the host locked or host stable options), this function will wait an
* additional time before returning, e.g. if the host screen is changed or the cursor
* moved during this time, the wait time is reset to zero.
*
* <p>The option <i>negate</i> is used to invert this result.
*
* <p>Note that the (optional) parameters column, row and line are "one-based" numerical
* values.
*
* <p>The wildcard match option enables text to be matched with Windows-like wildcards:
* <br> ? is any character,
* <br> * is any number of characters,
* <br> ^ is the escape character, i.e. the meaning of '?' and '*' becomes the
* actual character that directly follows,
* <br>^^ two escape characters becomes the escape character itself ('^').
*
* <p>This tag is also used as a condition tag.
*
* <p>Syntax:
* <pre>
* (wait time="milliseconds"
* [hostlocked] [cursorstable]/)
*
* (wait screen="SCREENNAME" [multiple] [negate]
* [hostlocked] [hoststable]
* [time="milliseconds"]
* [timeout="milliseconds"]/)
*
* (wait hosttext="text" [nocase] [negate]
* column="xx" row="yy" [relative] [width="nn" [wildcard]]
* [hostlocked] [hoststable]
* [time="milliseconds"]
* [timeout="milliseconds"]/)
*
* (wait hostfield="NAME" text="text" [nocase] [negate]
* [line="nn"] [wildcard]
* [hostlocked] [hoststable]
* [time="milliseconds"]
* [timeout="milliseconds"]/)
* </pre>
*
* Return code: true when the condition is reached within the timeout time or false
* if not.
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptHostError when a host error is encountered.
*/
public void scriptWAIT(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
// Do the wait with the parameters set.
data.returnCode=hostWait(data,
data.hasElementAttribute("hostlock"),
data.hasElementAttribute("hoststable"),
data.getParamInt("time"),
data.getParamInt("timeout",timeoutValue,false));
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* The instructions inside the block are executed if the current host screen name
* matches uniquely. The option <i>multiple</i> allows other screen names to match
* as well. If this option is set, the current host screen is set to the screen named
* in the tag. If the option <i>negate</i> is used, then the block inside this tag
* is executed if the screen does not match.
*
* <p>If the screen does not match, execution continues at the tag below.
*
* <p>Syntax:
* <pre>
* (screen name="ABC" [multiple] [negate])
* ... instructions ...
* (/screen)
* </pre>
*
* Return code: true if the screen matches (uniquely), false otherwise. The
* <i>negate</i> option inverts this result.
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptHostError when a host error is encountered.
*/
public void scriptSCREEN(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
PhantomHostScreen matchScreen=getSingleMatchingScreen();
// Get the screen to match.
PhantomRuntime rt=globalPoolData.runtime;
String s=data.getParamString("name");
if ( rt==null )
{
s="Screen matching "+s+" error, runtime file not loaded: skipping statement";
data.trace(s);
data.logWarning(s);
// Set next statement to execute.
data.setNextCurrentElement();
// Set no match.
data.returnCode=false;
return;
}
PhantomHostScreen phantomScreen=(s==null)? null: rt.getHostData().getScreen(s);
if ( phantomScreen==null )
{
s="Screen matching "+s+" error, screen name not found: skipping statement";
if ( data!=null )
{
data.trace(s);
data.logWarning(s);
// Set no match.
data.returnCode=false;
}
}
else
{
// Check screen matching (or the negate option: no match).
boolean multiple=data.hasElementAttribute("multiple");
boolean doNegate=data.hasElementAttribute("negate");
boolean match=(matchScreen==phantomScreen || (multiple && doesScreenMatch(phantomScreen)));
if ( doNegate )
match=!match;
if ( match )
{
// Execute all children and handle the on error exception.
Element parent=data.currentElement;
try
{
data.currentElement=data.getFirstElement(parent);
data.executeRemainingElements();
data.currentElement=parent;
}
catch(SessionPoolingScriptHostError e)
{
// Set return code, next element and throw the on error.
data.returnCode=match;
data.currentElement=parent;
data.setNextCurrentElement();
throw e;
}
}
// Set the return code.
data.returnCode=match;
}
// Set next statement to execute.
data.setNextCurrentElement();
}
/**
* This tag can be used to set data in a host field of the currently matching
* host screen. It can also set the cursor position to xx and yy with absolute as an
* option (used when inside a host pop-up window).
*
* <p>Syntax:
* <pre>
* (set hostfield="NAME" [line="nn"] text="text"/)
* </pre>
* or
* <pre>
* (set hostfield="NAME" [line="nn"] userid="USERID"/)
* </pre>
* or
* <pre>
* (set cursor [relative] column="xx" row="yy"/)
* </pre>
* or
* <pre>
* (set cursor hostfield="NAME" [line="nn"] [pos="charpos"]/)
* </pre>
* or
* <pre>
* (set timeout="milliseconds"/)
* </pre>
*
* <p>The userid option will set the password for the "USERID" that is specified in
* the NetPhantom Users using the Server Administration program.
*
* <p>Note: all numeric parameters are "one-based" for line, pos, column, row.
*
* <p>Note: the currently matching screen is only set when the screen or wait screen
* tag has been executed prior to this tag (this also applies to host pop-up window
* recognition only executed with these tags).
*
* <p>An error with this function will resume execution at the next onerror tag.
*
* <p>Return code: true if the function caused a change, false otherwise (i.e. cursor
* didn't move, timeout didn't change, host field already contained the text to set).
*
* @throws SessionPoolingDisposed when a script has been disposed.
* @throws SessionPoolingScriptHostError when a host error is encountered.
*/
public void scriptSET(SessionPoolingScriptData data) throws SessionPoolingDisposed
{
String s=data.getParamString("hostfield");
boolean changed=false;
if ( data.hasElementAttribute("cursor") )
{
///
/// Set cursor.
///
if ( s.length()>0 )
{
// According to host field.
PhantomHostScreen matchScreen=getSingleMatchingScreen();
if ( matchScreen==null )
{
s="Set cursor to hostfield "+s+" error, no matching screen: ignored";
data.trace(s);
data.logWarning(s);
}
else
{
PhantomHostField hf=matchScreen.getHostField(s.toUpperCase());
if ( hf==null )
{
s="Set cursor to hostfield "+s+" error, field not found: ignored";
data.trace(s);
data.logWarning(s);
}
else
{
int line=data.getParamInt("line")-1;
if ( line<0 ) line=-1;
int pos=data.getParamInt("pos")-1;
if ( pos<0 ) pos=0;
HostSession hostSession=getCurrentClearedHostSession();
Point p=hostSession.getCursor();
changed=hf.setCursor(hostSession,line,pos);
if ( changed )
{
Point p2=hostSession.getCursor();
changed=(p.x!=p2.x || p.y!=p2.y);
}
}
}
}
else
{
// According to row/column.
int x=data.getParamInt("column")-1;
int y=data.getParamInt("row")-1;
if ( x>=0 && y>=0 )
{
if ( data.hasElementAttribute("relative") )
{
HostScreen screen=hostSessionManager.getScreen();
x+=screen.getCurrentPopupWindowXOffset();
y+=screen.getCurrentPopupWindowYOffset();
}
HostSession hostSession=getCurrentClearedHostSession();
Point p=hostSession.getCursor();
if ( p.x!=x || p.y!=y )
changed=hostSession.setCursor(x,y);
}
}
}
else
if ( s.length()>0 )
{
///
/// Set host field.
///
PhantomHostScreen matchScreen=getSingleMatchingScreen();
if ( matchScreen==null )
{
s="Set hostfield text "+s+" error, no matching screen: ignored";
data.trace(s);
data.logWarning(s);
}
else
{
PhantomHostField hf=matchScreen.getHostField(s.toUpperCase());
if ( hf==null )
{
s="Set hostfield text "+s+" error, field not found: ignored";
data.trace(s);
data.logWarning(s);
}
else
{
int line=data.getParamInt("line")-1;
String text=data.getParamString("userid");
if ( text.length()>0 )
{
// Set host field user ID.
AdminConfigUser user=AdminConfigUsers.getUser(text);
if ( user==null )
{
data.logWarning("Set hostfield userid error: cannot find userid '"+text+"'");
text=null;
}
else
text=user.getPassword();
}
else
{
// Set host field text.
text=data.getParamString("text");
}
// Check for OK.
if ( text!=null )
{
HostScreen screen=hostSessionManager.getScreen();
HostSession hostSession=getCurrentClearedHostSession();
if ( line<0 )
{
String oldText=hf.getHiddenHostString(screen);
changed=hf.setHostField(hostSession,text);
if ( changed )
changed=!oldText.equals(text);
}
else
{
String oldText=hf.getHiddenHostString(screen,line);
changed=hf.setHostField(hostSession,text,line);
if ( changed )
changed=!oldText.equals(text);
}
}
}
}
}
else
{
int to=data.getParamInt("timeout");
if ( to>0 )
{
///
/// Set timeout.
///
changed=(timeoutValue!=to);
timeoutValue=to;
data.trace("Timeout set to "+to+ "milliseconds");
}
else
{
// Nothing matching.
data.trace("Set tag contains no valid parameters, skipping");
data.logWarning("Set tag contains no valid parameters, skipping");
}
}
// Set the return code.
data.returnCode=changed;
// Set next statement to execute.
data.setNextCurrentElement();
}
}