package org.tanukisoftware.wrapper;
/*
* Copyright (c) 1999, 2009 Tanuki Software, Ltd.
* http://www.tanukisoftware.com
* All rights reserved.
*
* This software is the proprietary information of Tanuki Software.
* You shall use it only in accordance with the terms of the
* license agreement you entered into with Tanuki Software.
* http://wrapper.tanukisoftware.org/doc/english/licenseOverview.html
*
*
* Portions of the Software have been derived from source code
* developed by Silver Egg Technology under the following license:
*
* Copyright (c) 2001 Silver Egg Technology
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sub-license, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*/
import java.io.DataInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.tanukisoftware.wrapper.event.WrapperControlEvent;
import org.tanukisoftware.wrapper.event.WrapperEvent;
import org.tanukisoftware.wrapper.event.WrapperEventListener;
import org.tanukisoftware.wrapper.event.WrapperLogFileChangedEvent;
import org.tanukisoftware.wrapper.event.WrapperPingEvent;
import org.tanukisoftware.wrapper.event.WrapperServiceControlEvent;
import org.tanukisoftware.wrapper.event.WrapperTickEvent;
import org.tanukisoftware.wrapper.resources.ResourceManager;
import org.tanukisoftware.wrapper.security.WrapperEventPermission;
import org.tanukisoftware.wrapper.security.WrapperPermission;
import org.tanukisoftware.wrapper.security.WrapperServicePermission;
/**
* Handles all communication with the native portion of the Wrapper code.
* The native wrapper code will launch Java in a separate process and set
* up a server socket which the Java code is expected to open a socket to
* on startup. When the server socket is created, a port will be chosen
* depending on what is available to the system. This port will then be
* passed to the Java process as property named "wrapper.port".
*
* For security reasons, the native code will only allow connections from
* localhost and will expect to receive the key specified in a property
* named "wrapper.key".
*
* This class is implemented as a singleton class.
*
* Generate JNI Headers with the following command in the build/classes
* directory:
* javah -jni -classpath ./ org.tanukisoftware.wrapper.WrapperManager
*
* @author Leif Mortenson <leif@tanukisoftware.com>
*/
public final class WrapperManager
implements Runnable
{
private static final String WRAPPER_CONNECTION_THREAD_NAME = "Wrapper-Connection";
private static final int DEFAULT_PORT = 15003;
private static final int DEFAULT_SO_TIMEOUT = 10000;
private static final int DEFAULT_CPU_TIMEOUT = 10000;
/** The number of milliseconds in one tick. Used for internal system
* time independent time keeping. */
private static final int TICK_MS = 100;
private static final int TIMER_FAST_THRESHOLD = 2 * 24 * 3600 * 1000 / TICK_MS; // 2 days.
private static final int TIMER_SLOW_THRESHOLD = 2 * 24 * 3600 * 1000 / TICK_MS; // 2 days.
private static final byte WRAPPER_MSG_START = (byte)100;
private static final byte WRAPPER_MSG_STOP = (byte)101;
private static final byte WRAPPER_MSG_RESTART = (byte)102;
private static final byte WRAPPER_MSG_PING = (byte)103;
private static final byte WRAPPER_MSG_STOP_PENDING = (byte)104;
private static final byte WRAPPER_MSG_START_PENDING = (byte)105;
private static final byte WRAPPER_MSG_STARTED = (byte)106;
private static final byte WRAPPER_MSG_STOPPED = (byte)107;
private static final byte WRAPPER_MSG_KEY = (byte)110;
private static final byte WRAPPER_MSG_BADKEY = (byte)111;
private static final byte WRAPPER_MSG_LOW_LOG_LEVEL = (byte)112;
private static final byte WRAPPER_MSG_PING_TIMEOUT = (byte)113;
private static final byte WRAPPER_MSG_SERVICE_CONTROL_CODE = (byte)114;
private static final byte WRAPPER_MSG_PROPERTIES = (byte)115;
/** Log commands are actually 116 + the LOG LEVEL. */
private static final byte WRAPPER_MSG_LOG = (byte)116;
private static final byte WRAPPER_MSG_LOGFILE = (byte)134;
/** Received when the user presses CTRL-C in the console on Windows or UNIX platforms. */
public static final int WRAPPER_CTRL_C_EVENT = 200;
/** Received when the user clicks on the close button of a Console on Windows. */
public static final int WRAPPER_CTRL_CLOSE_EVENT = 201;
/** Received when the user logs off of a Windows system. */
public static final int WRAPPER_CTRL_LOGOFF_EVENT = 202;
/** Received when a Windows system is shutting down. */
public static final int WRAPPER_CTRL_SHUTDOWN_EVENT = 203;
/** Received when a SIG TERM is received on a UNIX system. */
public static final int WRAPPER_CTRL_TERM_EVENT = 204;
/** Received when a SIG HUP is received on a UNIX system. */
public static final int WRAPPER_CTRL_HUP_EVENT = 205;
/** Received when a SIG USR1 is received on a UNIX system. */
public static final int WRAPPER_CTRL_USR1_EVENT = 206;
/** Received when a SIG USR2 is received on a UNIX system. */
public static final int WRAPPER_CTRL_USR2_EVENT = 207;
/** Log message at debug log level. */
public static final int WRAPPER_LOG_LEVEL_DEBUG = 1;
/** Log message at info log level. */
public static final int WRAPPER_LOG_LEVEL_INFO = 2;
/** Log message at status log level. */
public static final int WRAPPER_LOG_LEVEL_STATUS = 3;
/** Log message at warn log level. */
public static final int WRAPPER_LOG_LEVEL_WARN = 4;
/** Log message at error log level. */
public static final int WRAPPER_LOG_LEVEL_ERROR = 5;
/** Log message at fatal log level. */
public static final int WRAPPER_LOG_LEVEL_FATAL = 6;
/** Log message at advice log level. */
public static final int WRAPPER_LOG_LEVEL_ADVICE = 7;
/** Service Control code which can be sent to start a service. */
public static final int SERVICE_CONTROL_CODE_START = 0x10000;
/** Service Control code which can be sent or received to stop a service. */
public static final int SERVICE_CONTROL_CODE_STOP = 1;
/** Service Control code which can be sent to pause a service. */
public static final int SERVICE_CONTROL_CODE_PAUSE = 2;
/** Service Control code which can be sent to resume a paused service. */
public static final int SERVICE_CONTROL_CODE_CONTINUE = 3;
/** Service Control code which can be sent to or received interrogate the status of a service. */
public static final int SERVICE_CONTROL_CODE_INTERROGATE = 4;
/** Service Control code which can be received when the system is shutting down. */
public static final int SERVICE_CONTROL_CODE_SHUTDOWN = 5;
/** Service Control code which is received when the system being suspended. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_QUERYSUSPEND = 0x0D00;
/** Service Control code which is received when permission to suspend the
* computer was denied by a process. Support for this event was removed
* from the Windows OS starting with Vista.*/
public static final int SERVICE_CONTROL_CODE_POWEREVENT_QUERYSUSPENDFAILED = 0x0D02;
/** Service Control code which is received when the computer is about to
* enter a suspended state. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_SUSPEND = 0x0D04;
/** Service Control code which is received when the system has resumed
* operation. This event can indicate that some or all applications did
* not receive a SERVICE_CONTROL_CODE_POWEREVENT_SUSPEND event.
* Support for this event was removed from the Windows OS starting with
* Vista. See SERVICE_CONTROL_CODE_POWEREVENT_RESUMEAUTOMATIC. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMECRITICAL = 0x0D06;
/** Service Control code which is received when the system has resumed
* operation after being suspended. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMESUSPEND = 0x0D07;
/** Service Control code which is received when the battery power is low.
* Support for this event was removed from the Windows OS starting with
* Vista. See SERVICE_CONTROL_CODE_POWEREVENT_POWERSTATUSCHANGE. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_BATTERYLOW = 0x0D09;
/** Service Control code which is received when there is a change in the
* power status of the computer, such as a switch from battery power to
* A/C. The system also broadcasts this event when remaining battery
* power slips below the threshold specified by the user or if the
* battery power changes by a specified percentage. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_POWERSTATUSCHANGE = 0x0D0A;
/** Service Control code which is received when the APM BIOS has signaled
* an APM OEM event. Support for this event was removed from the Windows
* OS starting with Vista. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_OEMEVENT = 0x0D0B;
/** Service Control code which is received when the computer has woken up
* automatically to handle an event. */
public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMEAUTOMATIC = 0x0D12;
/** Reference to the original value of System.out. */
private static PrintStream m_out;
/** Reference to the original value of System.err. */
private static PrintStream m_err;
/** Info level log channel */
private static WrapperPrintStream m_outInfo;
/** Error level log channel */
private static WrapperPrintStream m_outError;
/** Debug level log channel */
private static WrapperPrintStream m_outDebug;
/** Flag that will be set to true once a SecurityManager has been detected and tested. */
private static boolean m_securityManagerChecked = false;
private static boolean m_disposed = false;
/** The starting flag is set when the Application has been asked to start. */
private static boolean m_starting = false;
/** The started flag is set when the Application has completed its startup. */
private static boolean m_started = false;
private static WrapperManager m_instance = null;
private static Thread m_hook = null;
private static boolean m_hookTriggered = false;
/* Flag which records when the shutdownJVM method has completed. */
private static boolean m_shutdownJVMComplete = false;
/** Map which stores shutdown locks for each thread. */
private static Map m_shutdownLockMap = new HashMap();
/** Tracks the total number of outstanding shutdown locks. */
private static int m_shutdownLocks = 0;
private static String[] m_args;
private static int m_port = DEFAULT_PORT;
private static int m_jvmPort;
private static int m_jvmPortMin;
private static int m_jvmPortMax;
private static String m_key;
private static int m_soTimeout = DEFAULT_SO_TIMEOUT;
private static long m_cpuTimeout = DEFAULT_CPU_TIMEOUT;
/** Tick count when the start method completed. */
private static int m_startedTicks;
/** The lowest configured log level in the Wrapper's configuration. This
* is set to a high value by default to disable all logging if the
* Wrapper does not register its low level or is not present. */
private static int m_lowLogLevel = WRAPPER_LOG_LEVEL_ADVICE + 1;
/** The maximum amount of time in ms to allow to pass without the JVM
* pinging the server before the JVM is terminated to allow a resynch. */
private static int m_pingTimeout = 30000;
/** Flag, set when the JVM is launched that is used to remember whether
* or not system signals are supposed to be ignored. */
private static boolean m_ignoreSignals = false;
/** Thread which processes all communications with the native code. */
private static Thread m_commRunner;
private static boolean m_commRunnerStarted = false;
private static Thread m_eventRunner;
private static int m_eventRunnerTicks;
private static Thread m_startupRunner;
/** True if the system time should be used for internal timeouts. */
private static boolean m_useSystemTime;
/** The threashold of how many ticks the timer can be fast before a
* warning is displayed. */
private static int m_timerFastThreshold;
/** The threashold of how many ticks the timer can be slow before a
* warning is displayed. */
private static int m_timerSlowThreshold;
/** Flag which controls whether or not the WrapperListener.stop method will
* be called on shutdown when the WrapperListener.start method has not
* returned or returned an exit code. */
private static boolean m_listenerForceStop;
/**
* Bit depth of the currently running JVM. Will be 32 or 64.
* A 64-bit JVM means that the system is also 64-bit, but a 32-bit JVM
* can be run either on a 32 or 64-bit system.
*/
private static int m_jvmBits;
/** An integer which stores the number of ticks since the
* JVM was launched. Using an int rather than a long allows the value
* to be used without requiring any synchronization. This is only
* used if the m_useSystemTime flag is false. */
private static volatile int m_ticks;
private static WrapperListener m_listener;
private static int m_lastPingTicks;
private static ServerSocket m_serverSocket;
private static Socket m_socket;
private static boolean m_appearHung = false;
private static Method m_addShutdownHookMethod = null;
private static Method m_removeShutdownHookMethod = null;
private static boolean m_ignoreUserLogoffs = false;
private static boolean m_service = false;
private static boolean m_debug = false;
private static int m_jvmId = 0;
private static boolean m_stopping = false;
private static Thread m_stoppingThread;
private static int m_exitCode;
private static boolean m_libraryOK = false;
private static byte[] m_commandBuffer = new byte[512];
private static File m_logFile = null;
/** The contents of the wrapper configuration. */
private static WrapperProperties m_properties;
/** List of registered WrapperEventListeners and their registered masks. */
private static List m_wrapperEventListenerMaskList = new ArrayList();
/** Array of registered WrapperEventListeners and their registered masks.
* Should not be referenced directly. Access by calling
* getWrapperEventListenerMasks(). */
private static WrapperEventListenerMask[] m_wrapperEventListenerMasks = null;
/** Flag used to tell whether or not WrapperCoreEvents should be produced. */
private static boolean m_produceCoreEvents = false;
// message resources: eventually these will be split up
private static ResourceManager m_res = ResourceManager.getResourceManager();
private static ResourceManager m_error = m_res;
private static ResourceManager m_warning = m_res;
private static ResourceManager m_info = m_res;
/*---------------------------------------------------------------
* Class Initializer
*-------------------------------------------------------------*/
/**
* When the WrapperManager class is first loaded, it attempts to load the
* configuration file specified using the 'wrapper.config' system property.
* When the JVM is launched from the Wrapper native code, the
* 'wrapper.config' and 'wrapper.key' parameters are specified.
* The 'wrapper.key' parameter is a password which is used to verify that
* connections are only coming from the native Wrapper which launched the
* current JVM.
*/
static
{
// The wraper.jar must be given AllPermissions if a security manager
// has been configured. This is not a problem if one of the standard
// Wrapper helper classes is used to launch the JVM.
// If however a custom WrapperListener is being implemented then this
// class will most likely be loaded by code that is neither part of
// the system, nor part of the Wrapper code base. To avoid having
// to also give those classes AllPermissions as well, we do all of
// initialization in a Privileged block. This means that the code
// only requires that the wrapper.jar has been given the required
// permissions.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedClassInit();
return null;
}
}
);
}
/**
* The body of the static initializer is moved into a seperate method so
* it can be run as a PrivilegedAction.
*/
private static void privilegedClassInit()
{
// Store references to the original System.out and System.err
// PrintStreams. The WrapperManager will always output to the
// original streams so its output will always end up in the
// wrapper.log file even if the end user code redirects the
// output to another log file.
// This is also important to be protect the Wrapper's functionality
// from the case where the user PrintStream enters a deadlock state.
m_out = System.out;
m_err = System.err;
// Set up some log channels
m_outInfo = new WrapperPrintStream( m_out, "WrapperManager: " );
m_outError = new WrapperPrintStream( m_out, "WrapperManager Error: " );
m_outDebug = new WrapperPrintStream( m_out, "WrapperManager Debug: " );
// Always create an empty properties object in case we are not running
// in the Wrapper or the properties are never sent.
m_properties = new WrapperProperties();
m_properties.lock();
// This must be done before attempting to access any System Properties
// as that could cause a SecurityException if it is too strict.
checkSecurityManager();
// Check for the debug flag
m_debug = WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.debug", false );
if ( m_debug )
{
m_outDebug.println( "WrapperManager class initialized by thread: "
+ Thread.currentThread().getName()
+ " Using classloader: " + WrapperManager.class.getClassLoader() );
}
// The copyright banner was moved into the wrapper binary. In order to
// aid in the debugging of user integrations, some kind of a known message
// needs to be displayed on startup so it is obvious whether or not the
// WrapperManager class is being initialized.
m_outInfo.println( "Initializing..." );
// Check for the jvmID
m_jvmId = WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvmid", 1 );
if ( m_debug )
{
m_outDebug.println( "JVM #" + m_jvmId );
}
// Decide whether this is a 32 or 64 bit version of Java.
m_jvmBits = Integer.getInteger( "sun.arch.data.model", -1 ).intValue();
if ( m_jvmBits == -1 )
{
m_jvmBits = Integer.getInteger( "com.ibm.vm.bitmode", -1 ).intValue();
}
if ( m_debug )
{
if ( m_jvmBits > 0 )
{
m_outDebug.println( "Running a " + m_jvmBits + "-bit JVM." );
}
else
{
m_outDebug.println( "The bit depth of this JVM could not be determined." );
}
}
// Initialize the timerTicks to a very high value. This means that we will
// always encounter the first rollover (200 * WRAPPER_MS / 1000) seconds
// after the Wrapper the starts, which means the rollover will be well
// tested.
m_ticks = Integer.MAX_VALUE - 200;
m_useSystemTime = WrapperSystemPropertyUtil.getBooleanProperty(
"wrapper.use_system_time", false );
m_timerFastThreshold = WrapperSystemPropertyUtil.getIntProperty(
"wrapper.timer_fast_threshold", TIMER_FAST_THRESHOLD ) * 1000 / TICK_MS;
m_timerSlowThreshold = WrapperSystemPropertyUtil.getIntProperty(
"wrapper.timer_slow_threshold", TIMER_SLOW_THRESHOLD ) * 1000 / TICK_MS;
// Check to see if we should register a shutdown hook
boolean disableShutdownHook = WrapperSystemPropertyUtil.getBooleanProperty(
"wrapper.disable_shutdown_hook", false );
// Check to see if the listener stop method should always be called.
m_listenerForceStop = WrapperSystemPropertyUtil.getBooleanProperty(
"wrapper.listener.force_stop", false );
// Locate the add and remove shutdown hook methods using reflection so
// that this class can be compiled on 1.2.x versions of java.
try
{
m_addShutdownHookMethod =
Runtime.class.getMethod( "addShutdownHook", new Class[] { Thread.class } );
m_removeShutdownHookMethod =
Runtime.class.getMethod( "removeShutdownHook", new Class[] { Thread.class } );
}
catch ( NoSuchMethodException e )
{
if ( m_debug )
{
m_outDebug.println( "Shutdown hooks not supported by current JVM." );
}
m_addShutdownHookMethod = null;
m_removeShutdownHookMethod = null;
disableShutdownHook = true;
}
// If the shutdown hook is not disabled, then register it.
if ( !disableShutdownHook )
{
if ( m_debug )
{
m_outDebug.println( "Registering shutdown hook" );
}
m_hook = new Thread( "Wrapper-Shutdown-Hook" )
{
/**
* Run the shutdown hook. (Triggered by the JVM when it is about to shutdown)
*/
public void run()
{
// Stop the Wrapper cleanly.
m_hookTriggered = true;
if ( m_debug )
{
m_outDebug.println( "ShutdownHook started" );
}
// Let the startup thread die since the shutdown hook is running.
m_startupRunner = null;
// If we are not already stopping, then do so.
WrapperManager.stop( 0 );
if ( m_debug )
{
m_outDebug.println( "ShutdownHook complete" );
}
}
};
// Actually register the shutdown hook using reflection.
try
{
m_addShutdownHookMethod.invoke( Runtime.getRuntime(), new Object[] { m_hook } );
}
catch ( IllegalAccessException e )
{
m_outError.println( "Unable to register shutdown hook: " + e );
}
catch ( InvocationTargetException e )
{
Throwable t = e.getTargetException();
if ( t == null )
{
t = e;
}
m_outError.println( "Unable to register shutdown hook: " + t );
}
}
// A key is required for the wrapper to work correctly. If it is not
// present, then assume that we are not being controlled by the native
// wrapper.
if ( ( m_key = System.getProperty( "wrapper.key" ) ) == null )
{
if ( m_debug )
{
m_outDebug.println( "Not using wrapper. (key not specified)" );
}
// The wrapper will not be used, so other values will not be used.
m_port = 0;
m_jvmPort = 0;
m_jvmPortMin = 0;
m_jvmPortMax = 0;
m_service = false;
m_cpuTimeout = 31557600000L; // One Year. Effectively never.
}
else
{
if ( m_debug )
{
m_outDebug.println( "Using wrapper" );
}
if ( WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.disable_console_input", false ) )
{
// Replace the System.in stream with one of our own to disable it.
System.setIn( new WrapperInputStream() );
}
// A port must have been specified.
String sPort;
if ( ( sPort = System.getProperty( "wrapper.port" ) ) == null )
{
String msg = m_res.format( "MISSING_PORT" );
m_outError.println( msg );
throw new ExceptionInInitializerError( msg );
}
try
{
m_port = Integer.parseInt( sPort );
}
catch ( NumberFormatException e )
{
String msg = m_res.format( "BAD_PORT", sPort );
m_outError.println( msg );
throw new ExceptionInInitializerError( msg );
}
m_jvmPort =
WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port", 0 );
m_jvmPortMin =
WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port.min", 31000 );
m_jvmPortMax =
WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port.max", 31999 );
// Check for the ignore signals flag
m_ignoreSignals = WrapperSystemPropertyUtil.getBooleanProperty(
"wrapper.ignore_signals", false );
// If this is being run as a headless server, then a flag would have been set
m_service = WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.service", false );
// Get the cpuTimeout
String sCPUTimeout = System.getProperty( "wrapper.cpu.timeout" );
if ( sCPUTimeout == null )
{
m_cpuTimeout = DEFAULT_CPU_TIMEOUT;
}
else
{
try
{
m_cpuTimeout = Integer.parseInt( sCPUTimeout ) * 1000L;
}
catch ( NumberFormatException e )
{
String msg = m_res.format( "BAD_CPU_TIMEOUT", sCPUTimeout );
m_outError.println( msg );
throw new ExceptionInInitializerError( msg );
}
}
}
// Make sure that the version of the Wrapper is correct.
verifyWrapperVersion();
// Register the MBeans if configured to do so.
if ( WrapperSystemPropertyUtil.getBooleanProperty(
WrapperManager.class.getName() + ".mbean", true ) )
{
registerMBean( new org.tanukisoftware.wrapper.jmx.WrapperManager(),
"org.tanukisoftware.wrapper:type=WrapperManager" );
}
if ( WrapperSystemPropertyUtil.getBooleanProperty(
WrapperManager.class.getName() + ".mbean.testing", false ) )
{
registerMBean( new org.tanukisoftware.wrapper.jmx.WrapperManagerTesting(),
"org.tanukisoftware.wrapper:type=WrapperManagerTesting" );
}
// Initialize the native code to trap system signals
initializeNativeLibrary();
if ( m_libraryOK )
{
// Make sure that the native library's version is correct.
verifyNativeLibraryVersion();
// Get the PID of the current JVM from the native library. Be careful as the method
// will not exist if the library is old.
try
{
System.setProperty( "wrapper.java.pid", Integer.toString( nativeGetJavaPID() ) );
}
catch ( Throwable e )
{
if ( m_debug )
{
m_outDebug.println( "Call to nativeGetJavaPID() failed: " + e );
}
}
}
// Start a thread which looks for control events sent to the
// process. The thread is also used to keep track of whether
// the VM has been getting CPU to avoid invalid timeouts and
// to maintain the number of ticks since the JVM was launched.
m_eventRunnerTicks = getTicks();
m_eventRunner = new Thread( "Wrapper-Control-Event-Monitor" )
{
public void run()
{
if ( m_debug )
{
m_outDebug.println( "Control event monitor thread started." );
}
try
{
WrapperTickEventImpl tickEvent = new WrapperTickEventImpl();
int lastTickOffset = 0;
boolean first = true;
boolean stoppingLogged = false;
// This loop should never exit because the tick counting is required
// for the life of the JVM.
while ( true )
{
int offsetDiff;
if ( !m_useSystemTime )
{
// Get the tick count based on the system time.
int sysTicks = getSystemTicks();
// Increment the tick counter by 1. This loop takes just slightly
// more than the length of a "tick" but it is a good enough
// approximation for our purposes. The accuracy of the tick length
// falls sharply when the system is under heavly load, but this
// has the desired effect as the Wrapper is also much less likely
// to encounter false timeouts due to the heavy load.
// The ticks field is volatile and a single integer, so it is not
// necessary to synchronize this.
// When the ticks count reaches the upper limit of the int range,
// it is ok to just let it overflow and wrap.
m_ticks++;
// Calculate the offset between the two tick counts.
// This will always work due to overflow.
int tickOffset = sysTicks - m_ticks;
// The number we really want is the difference between this tickOffset
// and the previous one.
offsetDiff = tickOffset - lastTickOffset;
if ( first )
{
first = false;
}
else
{
if ( offsetDiff > m_timerSlowThreshold )
{
m_outInfo.println( "The timer fell behind the system clock by "
+ ( offsetDiff * TICK_MS ) + "ms." );
}
else if ( offsetDiff < - m_timerFastThreshold )
{
m_outInfo.println( "The system clock fell behind the timer by "
+ ( -1 * offsetDiff * TICK_MS ) + "ms." );
}
}
// Store this tick offset for the net time through the loop.
lastTickOffset = tickOffset;
}
else
{
offsetDiff = 0;
}
//m_outInfo.println( " UNIX Time: "
// + Long.toHexString( System.currentTimeMillis() )
// + ", ticks=" + Integer.toHexString( getTicks() ) + ", sysTicks="
// + Integer.toHexString( getSystemTicks() ) );
// Attempt to detect whether or not we are being starved of CPU.
// This will only have any effect if the m_useSystemTime flag is
// set.
int nowTicks = getTicks();
long age = getTickAge( m_eventRunnerTicks, nowTicks );
if ( ( m_cpuTimeout > 0 ) && ( age > m_cpuTimeout ) )
{
m_outInfo.println( "JVM Process has not received any CPU time for "
+ ( age / 1000 ) + " seconds. Extending timeouts." );
// Make sure that we don't get any ping timeouts in this event
m_lastPingTicks = nowTicks;
}
m_eventRunnerTicks = nowTicks;
// If there are any listeners interrested in core events then fire
// off a tick event.
if ( m_produceCoreEvents )
{
tickEvent.m_ticks = nowTicks;
tickEvent.m_tickOffset = offsetDiff;
fireWrapperEvent( tickEvent );
}
if ( m_libraryOK )
{
// To avoid the JVM shutting down while we are in the middle of a JNI call,
if ( !isShuttingDown() )
{
// Look for control events in the wrapper library.
// There may be more than one.
int event = 0;
do
{
event = WrapperManager.nativeGetControlEvent();
if ( event != 0 )
{
WrapperManager.controlEvent( event );
}
}
while ( event != 0 );
}
else if ( !stoppingLogged )
{
stoppingLogged = true;
if ( m_debug )
{
m_outDebug.println( "Stopped checking for control events." );
}
}
}
// Wait before checking for another control event.
try
{
Thread.sleep( TICK_MS );
}
catch ( InterruptedException e )
{
}
}
}
finally
{
if ( m_debug )
{
m_outDebug.println( "Control event monitor thread stopped." );
}
}
}
};
m_eventRunner.setDaemon( true );
m_eventRunner.start();
// Resolve the system thread count based on the Java Version
String fullVersion = System.getProperty( "java.fullversion" );
String vendor = System.getProperty( "java.vm.vendor", "" );
String os = System.getProperty( "os.name", "" ).toLowerCase();
if ( fullVersion == null )
{
fullVersion = System.getProperty( "java.runtime.version" ) + " "
+ System.getProperty( "java.vm.name" );
}
if ( m_debug )
{
// Display more JVM info right after the call initialization of the library.
m_outDebug.println( "Java Version : " + fullVersion );
m_outDebug.println( "Java VM Vendor : " + vendor );
m_outDebug.println( "OS Name : " + System.getProperty( "os.name", "" ) );
m_outDebug.println( "OS Arch : " + System.getProperty( "os.arch", "" ) );
m_outDebug.println();
}
// This thread will most likely be thread which launches the JVM.
// Once this method returns however, the main thread will likely
// quit. There will be a slight delay before the Wrapper binary
// has a change to send a command to start the application.
// During this lag, the JVM may not have any non-daemon threads
// running and would exit. To keep it from doing so, start a
// simple non-daemon thread which will run until the
// WrapperListener.start() method returns or the Wrapper's
// shutdown thread has started.
m_startupRunner = new Thread( "Wrapper-Startup-Runner" )
{
public void run()
{
if ( m_debug )
{
m_outDebug.println( "Startup runner thread started." );
}
try
{
while ( m_startupRunner != null )
{
try
{
Thread.sleep( 100 );
}
catch ( InterruptedException e )
{
// Ignore.
}
}
}
finally
{
if ( m_debug )
{
m_outDebug.println( "Startup runner thread stopped." );
}
}
}
};
// This thread must not be a daemon thread.
m_startupRunner.setDaemon( false );
m_startupRunner.start();
// Create the singleton
m_instance = new WrapperManager();
}
/*---------------------------------------------------------------
* Native Methods
*-------------------------------------------------------------*/
private static native void nativeInit( boolean debug );
private static native String nativeGetLibraryVersion();
private static native int nativeGetJavaPID();
private static native boolean nativeIsProfessionalEdition();
private static native boolean nativeIsStandardEdition();
private static native int nativeGetControlEvent();
private static native void nativeRequestThreadDump();
private static native void accessViolationInner();
private static native void nativeSetConsoleTitle( byte[] titleBytes );
private static native WrapperUser nativeGetUser( boolean groups );
private static native WrapperUser nativeGetInteractiveUser( boolean groups );
private static native WrapperWin32Service[] nativeListServices();
private static native WrapperWin32Service nativeSendServiceControlCode( byte[] serviceName, int controlCode );
/*---------------------------------------------------------------
* Methods
*-------------------------------------------------------------*/
/**
* Returns a tick count calculated from the system clock.
*/
private static int getSystemTicks()
{
// Calculate a tick count using the current system time. The
// conversion from a long in ms, to an int in TICK_MS increments
// will result in data loss, but the loss of bits and resulting
// overflow is expected and Ok.
return (int)( System.currentTimeMillis() / TICK_MS );
}
/**
* Returns the number of ticks since the JVM was launched. This
* count is not good enough to be used where accuracy is required but
* it allows us to implement timeouts in environments where the system
* time is modified while the JVM is running.
* <p>
* An int is used rather than a long so the counter can be implemented
* without requiring any synchronization. At the tick resolution, the
* tick counter will overflow and wrap (every 6.8 years for 100ms ticks).
* This behavior is expected. The getTickAge method should be used
* in cases where the difference between two ticks is required.
*
* Returns the tick count.
*/
private static int getTicks()
{
if ( m_useSystemTime )
{
return getSystemTicks();
}
else
{
return m_ticks;
}
}
/**
* Returns the number of milliseconds that have elapsed between the
* start and end counters. This method assumes that both tick counts
* were obtained by calling getTicks(). This method will correctly
* handle cases where the tick counter has overflowed and reset.
*
* @param start A base tick count.
* @param end An end tick count.
*
* @return The number of milliseconds that are represented by the
* difference between the two specified tick counts.
*/
private static long getTickAge( int start, int end )
{
// Important to cast the first value so that negative values are correctly
// cast to negative long values.
return (long)( end - start ) * TICK_MS;
}
/**
* Attempts to load the a native library file.
*
* @param name Name of the library to load.
* @param file Name of the actual library file.
*
* @return null if the library was successfully loaded, an error message
* otherwise.
*/
private static String loadNativeLibrary( String name, String file )
{
try
{
System.loadLibrary( name );
if ( m_debug )
{
m_outDebug.println( " Loaded native library: " + file );
}
return null;
}
catch ( UnsatisfiedLinkError e )
{
if ( m_debug )
{
m_outDebug.println( " Unable to load native library: " + file + " Cause: " + e.getMessage() );
}
String error = e.getMessage();
if ( error == null )
{
error = e.toString();
}
return error;
}
catch ( Throwable e )
{
if ( m_debug )
{
m_outDebug.println( "Loading native library failed: " + file + " Cause: " + e );
}
String error = e.toString();
return error;
}
}
/**
* Java 1.5 and above supports the ability to register the WrapperManager
* MBean internally.
*/
private static void registerMBean( Object mbean, String name )
{
Class classManagementFactory;
try
{
classManagementFactory = Class.forName( "java.lang.management.ManagementFactory" );
}
catch ( ClassNotFoundException e )
{
if ( m_debug )
{
m_outDebug.println( "Registering MBeans not supported by current JVM: " + name );
}
return;
}
try
{
// This code uses reflection so it combiles on older JVMs.
// The original code is as follows:
// javax.management.MBeanServer mbs =
// java.lang.management.ManagementFactory.getPlatformMBeanServer();
// javax.management.ObjectName oName = new javax.management.ObjectName( name );
// mbs.registerMBean( mbean, oName );
// The version of the above code using reflection follows.
Class classMBeanServer = Class.forName( "javax.management.MBeanServer" );
Class classObjectName = Class.forName( "javax.management.ObjectName" );
Method methodGetPlatformMBeanServer =
classManagementFactory.getMethod( "getPlatformMBeanServer", null );
Constructor constructorObjectName =
classObjectName.getConstructor( new Class[] {String.class} );
Method methodRegisterMBean = classMBeanServer.getMethod(
"registerMBean", new Class[] {Object.class, classObjectName} );
Object mbs = methodGetPlatformMBeanServer.invoke( null, null );
Object oName = constructorObjectName.newInstance( new Object[] {name} );
methodRegisterMBean.invoke( mbs, new Object[] {mbean, oName} );
if ( m_debug )
{
m_outDebug.println( "Registered MBean with Platform MBean Server: " + name );
}
}
catch ( Throwable t )
{
m_outError.println( "Unable to register the " + name + " MBean." );
t.printStackTrace( m_outError );
}
}
/**
* Searches for a file on a path.
*
* @param file File to look for.
* @param path Path to be searched.
*
* @return Reference to thr file object if found, otherwise null.
*/
private static File locateFileOnPath( String file, String path )
{
// A library path exists but the library was not found on it.
String pathSep = System.getProperty( "path.separator" );
// Search for the file on the library path to verify that it does not
// exist, it could be some other problem
StringTokenizer st = new StringTokenizer( path, pathSep );
while( st.hasMoreTokens() )
{
File libFile = new File( new File( st.nextToken() ), file );
if ( libFile.exists() )
{
return libFile;
}
}
return null;
}
/**
* Generates a detailed native library base name which is made up of the
* base name, the os name, architecture, and the bits of the current JVM,
* not the platform.
*
* @return A detailed native library base name.
*/
private static String generateDetailedNativeLibraryBaseName( String baseName,
int jvmBits )
{
// Generate an os name. Most names are used as is, but some are modified.
String os = System.getProperty( "os.name", "" ).toLowerCase();
if ( os.startsWith( "windows" ) )
{
os = "windows";
}
else if ( os.equals( "sunos" ) )
{
os = "solaris";
}
else if ( os.equals( "hp-ux" ) || os.equals( "hp-ux64" ) )
{
os = "hpux";
}
else if ( os.equals( "mac os x" ) )
{
os = "macosx";
}
else if ( os.equals( "unix_sv" ) )
{
os = "unixware";
}
else if ( os.equals( "os/400" ) )
{
os = "os400";
}
else if ( os.equals( "z/os" ) )
{
os = "zos";
}
// Generate an architecture name.
String arch;
if ( os.equals( "macosx" ) )
{
arch = "universal";
}
else
{
arch = System.getProperty( "os.arch", "" ).toLowerCase();
if ( arch.equals( "amd64" ) || arch.equals( "athlon" ) || arch.equals( "x86_64" ) ||
arch.equals( "i686" ) || arch.equals( "i586" ) || arch.equals( "i486" ) ||
arch.equals( "i386" ) )
{
arch = "x86";
}
else if ( arch.startsWith( "ia32" ) || arch.startsWith( "ia64" ) )
{
arch = "ia";
}
else if ( arch.startsWith( "sparc" ) )
{
arch = "sparc";
}
else if ( arch.equals( "power" ) || arch.equals( "powerpc" ) || arch.equals( "ppc64" ) )
{
arch = "ppc";
}
else if ( arch.startsWith( "pa_risc" ) || arch.startsWith( "pa-risc" ) )
{
arch = "parisc";
}
}
return baseName + "-" + os + "-" + arch + "-" + jvmBits;
}
/**
* Searches for and then loads the native library. This method will attempt
* locate the wrapper library using one of the following 3 naming
*/
private static void initializeNativeLibrary()
{
// Look for the base name of the library.
String baseName = System.getProperty( "wrapper.native_library" );
if ( baseName == null )
{
// This should only happen if an old version of the Wrapper binary is being used.
m_outInfo.println( "WARNING - The wrapper.native_library system property was not" );
m_outInfo.println( " set. Using the default value, 'wrapper'." );
baseName = "wrapper";
}
String[] detailedNames = new String[4];
if ( m_jvmBits > 0 )
{
detailedNames[0] = generateDetailedNativeLibraryBaseName( baseName, m_jvmBits );
}
else
{
detailedNames[0] = generateDetailedNativeLibraryBaseName( baseName, 32 );
detailedNames[1] = generateDetailedNativeLibraryBaseName( baseName, 64 );
}
// Construct brief and detailed native library file names.
String file = System.mapLibraryName( baseName );
String[] detailedFiles = new String[detailedNames.length];
for ( int i = 0; i < detailedNames.length; i++ )
{
if ( detailedNames[i] != null )
{
detailedFiles[i] = System.mapLibraryName( detailedNames[i] );
}
}
String[] detailedErrors = new String[detailedNames.length];
String baseError = null;
// Try loading the native library using the detailed name first. If that fails, use
// the brief name.
if ( m_debug )
{
m_outDebug.println( "Load native library. One or more attempts may fail if platform "
+ "specific libraries do not exist. This is NORMAL and is only a problem if they "
+ "all fail." );
}
m_libraryOK = false;
for ( int i = 0; i < detailedNames.length; i++ )
{
if ( detailedNames[i] != null )
{
detailedErrors[i] = loadNativeLibrary( detailedNames[i], detailedFiles[i] );
if ( detailedErrors[i] == null )
{
m_libraryOK = true;
break;
}
}
}
if ( ( !m_libraryOK ) && ( ( baseError = loadNativeLibrary( baseName, file ) ) == null ) )
{
m_libraryOK = true;
}
if ( m_libraryOK )
{
// The library was loaded correctly, so initialize it.
if ( m_debug )
{
m_outDebug.println( "Calling native initialization method." );
}
nativeInit( m_debug );
}
else
{
// The library could not be loaded, so we want to give the user a useful
// clue as to why not.
String libPath = System.getProperty( "java.library.path" );
m_outInfo.println();
if ( libPath.equals( "" ) )
{
// No library path
m_outInfo.println(
"WARNING - Unable to load the Wrapper's native library because the" );
m_outInfo.println(
" java.library.path was set to ''. Please see the" );
m_outInfo.println(
" documentation for the wrapper.java.library.path " );
m_outInfo.println(
" configuration property.");
}
else
{
// Attempt to locate the actual files on the path.
String error = null;
File libFile = null;
for ( int i = 0; i < detailedNames.length; i++ )
{
if ( detailedFiles[i] != null )
{
libFile = locateFileOnPath( detailedFiles[i], libPath );
if ( libFile != null )
{
error = detailedErrors[i];
break;
}
}
}
if ( libFile == null )
{
libFile = locateFileOnPath( file, libPath );
if ( libFile != null )
{
error = baseError;
}
}
if ( libFile == null )
{
// The library could not be located on the library path.
m_outInfo.println(
"WARNING - Unable to load the Wrapper's native library because none of the" );
m_outInfo.println(
" following files:" );
for ( int i = 0; i < detailedNames.length; i++ )
{
if ( detailedFiles[i] != null )
{
m_outInfo.println(
" " + detailedFiles[i] );
}
}
m_outInfo.println(
" " + file );
m_outInfo.println(
" could be located on the following java.library.path:" );
String pathSep = System.getProperty( "path.separator" );
StringTokenizer st = new StringTokenizer( libPath, pathSep );
while ( st.hasMoreTokens() )
{
File pathElement = new File( st.nextToken() );
m_outInfo.println( " " + pathElement.getAbsolutePath() );
}
m_outInfo.println(
" Please see the documentation for the "
+ "wrapper.java.library.path" );
m_outInfo.println(
" configuration property." );
}
else
{
// The library file was found but could not be loaded for some reason.
m_outInfo.println(
"WARNING - Unable to load the Wrapper's native library '" + libFile.getName() + "'." );
m_outInfo.println(
" The file is located on the path at the following location but" );
m_outInfo.println(
" could not be loaded:" );
m_outInfo.println(
" " + libFile.getAbsolutePath() );
m_outInfo.println(
" Please verify that the file is readable by the current user" );
m_outInfo.println(
" and that the file has not been corrupted in any way." );
m_outInfo.println(
" One common cause of this problem is running a 32-bit version" );
m_outInfo.println(
" of the Wrapper with a 64-bit version of Java, or vica versa." );
if ( m_jvmBits > 0 )
{
m_outInfo.println(
" This is a " + m_jvmBits + "-bit JVM." );
}
else
{
m_outInfo.println(
" The bit depth of this JVM could not be determined." );
}
m_outInfo.println(
" Reported cause:" );
m_outInfo.println(
" " + error );
}
}
m_outInfo.println( " System signals will not be handled correctly." );
m_outInfo.println();
}
}
/**
* Compares the version of the wrapper which launched this JVM with that of
* the jar. If they differ then a Warning message will be displayed. The
* Wrapper application will still be allowed to start.
*/
private static void verifyWrapperVersion()
{
// If we are not being controlled by the wrapper then return.
if ( !WrapperManager.isControlledByNativeWrapper() )
{
return;
}
// Lookup the version from the wrapper. It should have been set as a property
// when the JVM was launched.
String wrapperVersion = System.getProperty( "wrapper.version" );
if ( wrapperVersion == null )
{
wrapperVersion = "unknown";
}
if ( wrapperVersion.endsWith( "-pro" ) )
{
wrapperVersion = wrapperVersion.substring( 0, wrapperVersion.length() - 4 );
}
else if ( wrapperVersion.endsWith( "-st" ) )
{
wrapperVersion = wrapperVersion.substring( 0, wrapperVersion.length() - 3 );
}
if ( !WrapperInfo.getVersion().equals( wrapperVersion ) )
{
m_outInfo.println(
"WARNING - The Wrapper jar file currently in use is version \""
+ WrapperInfo.getVersion() + "\"" );
m_outInfo.println(
" while the version of the Wrapper which launched this JVM is " );
m_outInfo.println(
" \"" + wrapperVersion + "\"." );
m_outInfo.println(
" The Wrapper may appear to work correctly but some features may" );
m_outInfo.println(
" not function correctly. This configuration has not been tested" );
m_outInfo.println(
" and is not supported." );
m_outInfo.println();
}
}
/**
* Compares the version of the native library with that of this jar. If
* they differ then a Warning message will be displayed. The Wrapper
* application will still be allowed to start.
*/
private static void verifyNativeLibraryVersion()
{
// Request the version from the native library. Be careful as the method
// will not exist if the library is old.
String jniVersion;
try
{
jniVersion = nativeGetLibraryVersion();
}
catch ( Throwable e )
{
if ( m_debug )
{
m_outDebug.println( "Call to nativeGetLibraryVersion() failed: " + e );
}
jniVersion = "unknown";
}
String wrapperVersion = System.getProperty( "wrapper.version" );
if ( wrapperVersion == null )
{
wrapperVersion = "unknown";
}
if ( !wrapperVersion.equals( jniVersion ) )
{
m_outInfo.println(
"WARNING - The version of the Wrapper which launched this JVM is " );
m_outInfo.println(
" \"" + wrapperVersion + "\" while the version of the native library " );
m_outInfo.println(
" is \"" + jniVersion + "\"." );
m_outInfo.println(
" The Wrapper may appear to work correctly but some features may" );
m_outInfo.println(
" not function correctly. This configuration has not been tested" );
m_outInfo.println(
" and is not supported." );
m_outInfo.println();
}
}
/**
* Obtain the current version of Wrapper.
*
* @return The version of the Wrapper.
*/
public static String getVersion()
{
return WrapperInfo.getVersion();
}
/**
* Obtain the build time of Wrapper.
*
* @return The time that the Wrapper was built.
*/
public static String getBuildTime()
{
return WrapperInfo.getBuildTime();
}
/**
* Returns the Id of the current JVM. JVM Ids increment from 1 each time
* the wrapper restarts a new one.
*
* @return The Id of the current JVM.
*/
public static int getJVMId()
{
return m_jvmId;
}
/**
* Returns true if the current Wrapper edition has support for Professional
* Edition features.
*
* @return True if professional features are supported.
*/
public static boolean isProfessionalEdition()
{
// Be careful as this will not exist in older versions
if ( m_libraryOK )
{
try
{
return nativeIsProfessionalEdition();
}
catch ( Throwable e )
{
if ( m_debug )
{
m_outDebug.println( "Call to nativeIsProfessionalEdition() failed: " + e );
}
return false;
}
}
else
{
return false;
}
}
/**
* Returns true if the current Wrapper edition has support for Standard
* Edition features.
*
* @return True if standard features are supported.
*/
public static boolean isStandardEdition()
{
// Be careful as this will not exist in older versions
if ( m_libraryOK )
{
try
{
return nativeIsStandardEdition();
}
catch ( Throwable e )
{
if ( m_debug )
{
m_outDebug.println( "Call to nativeIsStandardEdition() failed: " + e );
}
return false;
}
}
else
{
return false;
}
}
/**
* Sets the title of the console in which the Wrapper is running. This
* is currently only supported on Windows platforms.
* <p>
* As an alternative, it is also possible to set the console title from
* within the wrapper.conf file using the wrapper.console.title property.
*
* @param title The new title. The specified string will be encoded
* to a byte array using the default encoding for the
* current platform.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("setConsoleTitle")
* permission.
*
* @see WrapperPermission
*/
public static void setConsoleTitle( String title )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "setConsoleTitle" ) );
}
if ( m_libraryOK )
{
// Convert the unicode string to a string of bytes using the default
// platform encoding.
byte[] titleBytes = title.getBytes();
// We need a null terminated string.
byte[] nullTermBytes = new byte[titleBytes.length + 1];
System.arraycopy( titleBytes, 0, nullTermBytes, 0, titleBytes.length );
nullTermBytes[titleBytes.length] = 0;
nativeSetConsoleTitle( nullTermBytes );
}
}
/**
* Returns a WrapperUser object which describes the user under which the
* Wrapper is currently running. Additional platform specific information
* can be obtained by casting the object to a platform specific subclass.
* WrapperWin32User, for example.
*
* @param groups True if the user's groups should be returned as well.
* Requesting the groups that a user belongs to increases
* the CPU load required to complete the call.
*
* @return An object describing the current user.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("getUser") permission.
*
* @see WrapperPermission
*/
public static WrapperUser getUser( boolean groups )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "getUser" ) );
}
WrapperUser user = null;
if ( m_libraryOK )
{
user = nativeGetUser( groups );
}
return user;
}
/**
* Returns a WrapperUser object which describes the interactive user whose
* desktop is being interacted with. When a service running on a Windows
* platform has its interactive flag set, this method will return the user
* who is currently logged in. Additional platform specific information
* can be obtained by casting the object to a platform specific subclass.
* WrapperWin32User, for example.
* <p>
* If a user is not currently logged on then this method will return null.
* User code can repeatedly call this method to detect when a user has
* logged in. To detect when a user has logged out, there are two options.
* 1) The user code can continue to call this method until it returns null.
* 2) Or if the WrapperListener method is being implemented, the
* WrapperListener.controlEvent method will receive a WRAPPER_CTRL_LOGOFF_EVENT
* event when the user logs out.
* <p>
* On XP systems, it is possible to switch to another account rather than
* actually logging out. In such a case, the interactive user will be
* the first user that logged in. This will also be the only user with
* which the service will interact. If other users are logged in when the
* interactive user logs out, the service will not automatically switch to
* another logged in user. Rather, the next user to log in will become
* the new user which the service will interact with.
* <p>
* This method will always return NULL on versions of NT prior to Windows
* 2000. This can not be helped as some required functions were not added
* to the windows API until NT version 5.0, also known as Windows 2000.
*
* @param groups True if the user's groups should be returned as well.
* Requesting the groups that a user belongs to increases
* the CPU load required to complete the call.
*
* @return The current interactive user, or null.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("getInteractiveUser")
* permission.
*
* @see WrapperPermission
*/
public static WrapperUser getInteractiveUser( boolean groups )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "getInteractiveUser" ) );
}
WrapperUser user = null;
if ( m_libraryOK )
{
user = nativeGetInteractiveUser( groups );
}
return user;
}
/**
* Returns a Properties object containing expanded the contents of the
* configuration file used to launch the Wrapper.
*
* All properties are included so it is possible to define properties
* not used by the Wrapper in the configuration file and have then
* be available in this Properties object.
*
* @return The contents of the Wrapper configuration file.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("getProperties")
* permission.
*
* @see WrapperPermission
*/
public static Properties getProperties()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "getProperties" ) );
}
return m_properties;
}
/**
* Returns the PID of the Wrapper process.
*
* A PID of 0 will be returned if the JVM was launched standalone.
*
* This value can also be obtained using the 'wrapper.pid' system property.
*
* @return The PID of the Wrpper process.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("getWrapperPID") permission.
*
* @see WrapperPermission
*/
public static int getWrapperPID()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "getWrapperPID" ) );
}
return WrapperSystemPropertyUtil.getIntProperty( "wrapper.pid", 0 );
}
/**
* Returns the PID of the Java process.
*
* A PID of 0 will be returned if the native library has not been initialized.
*
* This value can also be obtained using the 'wrapper.java.pid' system property.
*
* @return The PID of the Java process.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("getJavaPID") permission.
*
* @see WrapperPermission
*/
public static int getJavaPID()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "getJavaPID" ) );
}
return WrapperSystemPropertyUtil.getIntProperty( "wrapper.java.pid", 0 );
}
/**
* Requests that the current JVM process request a thread dump. This is
* the same as pressing CTRL-BREAK (under Windows) or CTRL-\ (under Unix)
* in the the console in which Java is running. This method does nothing
* if the native library is not loaded.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("requestThreadDump")
* permission.
*
* @see WrapperPermission
*/
public static void requestThreadDump()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "requestThreadDump" ) );
}
if ( m_libraryOK )
{
nativeRequestThreadDump();
}
else
{
m_outInfo.println( " wrapper library not loaded." );
}
}
/**
* (Testing Method) Causes the WrapperManager to go into a state which makes the JVM appear
* to be hung when viewed from the native Wrapper code. Does not have any effect when the
* JVM is not being controlled from the native Wrapper. Useful for testing the Wrapper
* functions.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("test.appearHung") permission.
*
* @see WrapperPermission
*/
public static void appearHung()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "test.appearHung" ) );
}
m_outInfo.println( "WARNING: Making JVM appear to be hung..." );
m_appearHung = true;
}
/**
* (Testing Method) Cause an access violation within the Java code. Useful
* for testing the Wrapper functions. This currently only crashes Sun
* JVMs and takes advantage of Bug #4369043 which does not exist in newer
* JVMs. Use of the accessViolationNative() method is preferred.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("test.accessViolation")
* permission.
*
* @see WrapperPermission
*/
public static void accessViolation()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "test.accessViolation" ) );
}
m_outInfo.println( "WARNING: Attempting to cause an access violation..." );
try
{
Class c = Class.forName( "java.lang.String" );
java.lang.reflect.Method m = c.getDeclaredMethod( null, null );
}
catch( NoSuchMethodException ex )
{
// Correctly did not find method. access_violation attempt failed. Not Sun JVM?
}
catch( Exception ex )
{
if ( ex instanceof NoSuchFieldException )
{
// Can't catch this in a catch because the compiler doesn't think it is being
// thrown. But it is thrown on IBM jvms at least
// Correctly did not find method. access_violation attempt failed. Not Sun JVM?
}
else
{
// Shouldn't get here.
ex.printStackTrace( m_outError );
}
}
m_outInfo.println( " Attempt to cause access violation failed. JVM is still alive." );
}
/**
* (Testing Method) Cause an access violation within native JNI code.
* Useful for testing the Wrapper functions. This currently causes the
* access violation by attempting to write to a null pointer.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("test.accessViolationNative")
* permission.
*
* @see WrapperPermission
*/
public static void accessViolationNative()
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "test.accessViolationNative" ) );
}
m_outInfo.println( "WARNING: Attempting to cause an access violation..." );
if ( m_libraryOK )
{
accessViolationInner();
m_outInfo.println( " Attempt to cause access violation failed. JVM is still alive." );
}
else
{
m_outInfo.println( " wrapper library not loaded." );
}
}
/**
* Returns true if the JVM was launched by the Wrapper application. False
* if the JVM was launched manually without the Wrapper controlling it.
*
* @return True if the current JVM was launched by the Wrapper.
*/
public static boolean isControlledByNativeWrapper()
{
return m_key != null;
}
/**
* Returns true if the Wrapper was launched as an NT service on Windows or
* as a daemon process on UNIX platforms. False if launched as a console.
* This can be useful if you wish to display a user interface when in
* Console mode. On UNIX platforms, this is not as useful because an
* X display may not be visible even if launched in a console.
*
* @return True if the Wrapper is running as an NT service or daemon
* process.
*/
public static boolean isLaunchedAsService()
{
return m_service;
}
/**
* Returns true if the JVM should ignore user logoff events. Mainly used
* within WrapperListener.controlEvent() method implemenations.
* @return True if user logoff events should be ignroed.
*/
public static boolean isIgnoreUserLogoffs()
{
return m_ignoreUserLogoffs;
}
/**
* Returns true if the wrapper.debug property, or any of the logging
* channels are set to DEBUG in the wrapper configuration file. Useful
* for deciding whether or not to output certain information to the
* console.
*
* @return True if the Wrapper is logging any Debug level output.
*/
public static boolean isDebugEnabled()
{
return m_debug;
}
/**
* Start the Java side of the Wrapper code running. This will make it
* possible for the native side of the Wrapper to detect that the Java
* Wrapper is up and running.
* <p>
* This method must be called on startup and then can only be called once
* so there is no reason for any security permission checks on this call.
*
* @param listener The WrapperListener instance which represents the
* application being started.
* @param args The argument list passed to the JVM when it was launched.
*/
public static void start( final WrapperListener listener, final String[] args )
{
// As was done in the static initializer, we need to execute the following
// code in a privileged action so it is not necessary for the calling code
// to have the same privileges as the wrapper jar.
// This is safe because this method can only be called once and that one call
// will presumably be made on JVM startup.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedStart( listener, args );
return null;
}
}
);
}
/**
* Called by the start method within a PrivilegedAction.
*
* @param WrapperListener The WrapperListener instance which represents
* the application being started.
* @param args The argument list passed to the JVM when it was launched.
*/
private static void privilegedStart( WrapperListener listener, String[] args )
{
// Check the SecurityManager here as it is possible that it was set before this call.
checkSecurityManager();
// Just in case the user failed to provide an argument list, recover by creating one
// here. This will avoid possible problems down stream.
if ( args == null )
{
args = new String[0];
}
if ( m_debug )
{
StringBuffer sb = new StringBuffer();
sb.append( "args[" );
for ( int i = 0; i < args.length; i++ )
{
if ( i > 0 )
{
sb.append( ", " );
}
sb.append( "\"" );
sb.append( args[i] );
sb.append( "\"" );
}
sb.append( "]" );
m_outDebug.println( "WrapperManager.start(" + listener + ", " + sb.toString() + ") "
+ "called by thread: " + Thread.currentThread().getName() );
}
synchronized( WrapperManager.class )
{
// Make sure that the class has not already been disposed.
if ( m_disposed)
{
throw new IllegalStateException( "WrapperManager has already been disposed." );
}
if ( m_listener != null )
{
throw new IllegalStateException(
"WrapperManager has already been started with a WrapperListener." );
}
if ( listener == null )
{
throw new IllegalStateException( "A WrapperListener must be specified." );
}
m_listener = listener;
m_args = args;
startRunner();
// If this JVM is being controlled by a native wrapper, then we want to
// wait for the command to start. However, if this is a standalone
// JVM, then we want to start now.
if ( !isControlledByNativeWrapper() )
{
startInner( true );
}
}
}
/**
* Returns true if the JVM is in the process of shutting down. This can be
* useful to avoid starting long running processes when it is known that the
* JVM will be shutting down shortly.
*
* @return true if the JVM is shutting down.
*/
public static boolean isShuttingDown()
{
return m_stopping;
}
private static class ShutdownLock
extends Object
{
private final Thread m_thread;
private int m_count;
private ShutdownLock( Thread thread )
{
m_thread = thread;
}
}
/**
* Increase the number of locks which will prevent the Wrapper from letting
* the JVM process exit on shutdown. This is primarily useful around
* calls to native JNI functions in daemon threads where it has been shown
* that premature JVM exits can cause the JVM process to crash on shutdown.
* <p>
* Normal non-daemon threads should not require these locks as the very
* fact that the non-daemon thread is still running will prevent the JVM
* from shutting down.
* <p>
* It is possible to make multiple calls within a single thread. Each call
* should always be paired with a call to releaseShutdownLock().
*
* @throws WrapperShuttingDownException If called after the Wrapper has
* already begun the shutdown of the
* JVM.
*/
public static void requestShutdownLock()
throws WrapperShuttingDownException
{
synchronized( WrapperManager.class )
{
if ( m_stopping )
{
throw new WrapperShuttingDownException();
}
Thread thisThread = Thread.currentThread();
ShutdownLock lock = (ShutdownLock)m_shutdownLockMap.get( thisThread );
if ( lock == null )
{
lock = new ShutdownLock( thisThread );
m_shutdownLockMap.put( thisThread, lock );
}
lock.m_count++;
m_shutdownLocks++;
if ( m_debug )
{
m_outDebug.println( "WrapperManager.requestShutdownLock() called by thread: "
+ thisThread.getName() + ". New thread lock count: " + lock.m_count
+ ", total lock count: " + m_shutdownLocks );
}
}
}
/**
* Called by a thread which has previously called requestShutdownLock().
*
* @throws IllgalStateException If called without first calling requestShutdownLock() from
* the same thread.
*/
public static void releaseShutdownLock()
throws IllegalStateException
{
synchronized( WrapperManager.class )
{
Thread thisThread = Thread.currentThread();
ShutdownLock lock = (ShutdownLock)m_shutdownLockMap.get( thisThread );
if ( lock == null )
{
throw new IllegalStateException( "requestShutdownLock was not called from this thread." );
}
lock.m_count--;
m_shutdownLocks--;
if ( m_debug )
{
m_outDebug.println( "WrapperManager.releaseShutdownLock() called by thread: "
+ thisThread.getName() + ". New thread lock count: " + lock.m_count
+ ", total lock count: " + m_shutdownLocks );
}
if ( lock.m_count <= 0 )
{
m_shutdownLockMap.remove( thisThread );
}
WrapperManager.class.notify();
}
}
/**
* Waits for any outstanding locks to be released before shutting down.
*/
private static void waitForShutdownLocks()
{
synchronized( WrapperManager.class )
{
if ( m_debug )
{
m_outDebug.println( "wait for " + m_shutdownLocks + " shutdown locs to be released." );
}
while ( m_shutdownLocks > 0 )
{
try
{
WrapperManager.class.wait( 5000 );
}
catch ( InterruptedException e )
{
// Ignore and continue.
}
if ( m_shutdownLocks > 0 )
{
m_outInfo.println( "Waiting for " + m_shutdownLocks + " shutdown locks to be released..." );
}
}
}
}
/**
* Tells the native wrapper that the JVM wants to restart, then informs
* all listeners that the JVM is about to shutdown before killing the JVM.
* <p>
* This method will not return.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("restart") permission.
*
* @see WrapperPermission
*/
public static void restart()
throws SecurityException
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "restart" ) );
}
if ( m_debug )
{
m_outDebug.println( "WrapperManager.restart() called by thread: "
+ Thread.currentThread().getName() );
}
restartInner();
}
/**
* Tells the native wrapper that the JVM wants to restart, then informs
* all listeners that the JVM is about to shutdown before killing the JVM.
* <p>
* This method requests that the JVM be restarted but then returns. This
* allows components to initiate a JVM exit and then continue, allowing
* a normal shutdown initiated by the JVM via shutdown hooks. In
* applications which are designed to be shutdown when the user presses
* CTRL-C, this may result in a cleaner shutdown.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("restart") permission.
*
* @see WrapperPermission
*/
public static void restartAndReturn()
throws SecurityException
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "restart" ) );
}
synchronized( WrapperManager.class )
{
if ( m_stopping )
{
if ( m_debug )
{
m_outDebug.println( "WrapperManager.restartAndReturn() called by thread: "
+ Thread.currentThread().getName() + " already stopping." );
}
return;
}
else
{
if ( m_debug )
{
m_outDebug.println( "WrapperManager.restartAndReturn() called by thread: "
+ Thread.currentThread().getName() );
}
}
}
// To make this possible, we have to create a new thread to actually do the shutdown.
Thread restarter = new Thread( "Wrapper-Restarter" )
{
public void run()
{
restartInner();
}
};
restarter.setDaemon( false );
restarter.start();
}
/**
* Common code used to restart the JVM. It is assumed that the calling
* thread has has passed security checks before this is called.
*/
private static void restartInner()
{
boolean stopping;
synchronized( WrapperManager.class )
{
stopping = m_stopping;
if ( !stopping )
{
m_stopping = true;
}
}
if ( !stopping )
{
// I used to check to make sure the commRunner was started, but that could fail
// for a number of reasons. If it is down then leave it alone.
//if ( !m_commRunnerStarted )
//{
// startRunner();
//}
// Always send the stop command
sendCommand( WRAPPER_MSG_RESTART, "restart" );
}
// Give the Wrapper a chance to register the stop command before stopping.
// This avoids any errors thrown by the Wrapper because the JVM died before
// it was expected to.
try
{
Thread.sleep( 1000 );
}
catch ( InterruptedException e )
{
}
// This is safe because we are already checking for the privilege to restart the JVM
// above. If we get this far then we want the Wrapper to be able to do everything
// necessary to stop the JVM.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedStopInner( 0 );
return null;
}
}
);
}
/**
* Tells the native wrapper that the JVM wants to shut down, then informs
* all listeners that the JVM is about to shutdown before killing the JVM.
* <p>
* This method will not return.
*
* @param exitCode The exit code that the Wrapper will return when it exits.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("stop") permission.
*
* @see WrapperPermission
*/
public static void stop( final int exitCode )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "stop" ) );
}
if ( m_debug )
{
m_outDebug.println( "WrapperManager.stop(" + exitCode + ") called by thread: "
+ Thread.currentThread().getName() );
}
stopCommon( exitCode, 1000 );
// This is safe because we are already checking for the privilege to stop the JVM
// above. If we get this far then we want the Wrapper to be able to do everything
// necessary to stop the JVM.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedStopInner( exitCode );
return null;
}
}
);
}
/**
* Tells the native wrapper that the JVM wants to shut down, then informs
* all listeners that the JVM is about to shutdown before killing the JVM.
* <p>
* This method requests that the JVM be shutdown but then returns. This
* allows components to initiate a JVM exit and then continue, allowing
* a normal shutdown initiated by the JVM via shutdown hooks. In
* applications which are designed to be shutdown when the user presses
* CTRL-C, this may result in a cleaner shutdown.
*
* @param exitCode The exit code that the Wrapper will return when it exits.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("stop") permission.
*
* @see WrapperPermission
*/
public static void stopAndReturn( final int exitCode )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "stop" ) );
}
synchronized( WrapperManager.class )
{
if ( m_stopping )
{
if ( m_debug )
{
m_outDebug.println(
"WrapperManager.stopAndReturn(" + exitCode + ") called by thread: "
+ Thread.currentThread().getName() + " already stopping." );
}
return;
}
else
{
if ( m_debug )
{
m_outDebug.println(
"WrapperManager.stopAndReturn(" + exitCode + ") called by thread: "
+ Thread.currentThread().getName() );
}
}
}
// To make this possible, we have to create a new thread to actually do the shutdown.
Thread stopper = new Thread( "Wrapper-Stopper" )
{
public void run()
{
stopCommon( exitCode, 1000 );
// This is safe because we are already checking for the privilege to stop the JVM
// above. If we get this far then we want the Wrapper to be able to do everything
// necessary to stop the JVM.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedStopInner( exitCode );
return null;
}
}
);
}
};
stopper.setDaemon( false );
stopper.start();
}
/**
* Tells the native wrapper that the JVM wants to shut down and then
* promptly halts. Be careful when using this method as an application
* will not be given a chance to shutdown cleanly.
*
* @param exitCode The exit code that the Wrapper will return when it exits.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("stopImmediate") permission.
*
* @see WrapperPermission
*/
public static void stopImmediate( final int exitCode )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "stopImmediate" ) );
}
if ( m_debug )
{
m_outDebug.println( "WrapperManager.stopImmediate(" + exitCode + ") called by thread: "
+ Thread.currentThread().getName() );
}
stopCommon( exitCode, 250 );
signalStopped( exitCode );
// Execute runtime.halt(0) using reflection so this class will
// compile on 1.2.x versions of Java.
Method haltMethod;
try
{
haltMethod =
Runtime.class.getMethod( "halt", new Class[] { Integer.TYPE } );
}
catch ( NoSuchMethodException e )
{
m_outError.println( "halt not supported by current JVM." );
haltMethod = null;
}
if ( haltMethod != null )
{
Runtime runtime = Runtime.getRuntime();
try
{
haltMethod.invoke( runtime, new Object[] { new Integer( exitCode ) } );
}
catch ( IllegalAccessException e )
{
m_outError.println( "Unable to call runtime.halt: " + e );
}
catch ( InvocationTargetException e )
{
Throwable t = e.getTargetException();
if ( t == null )
{
t = e;
}
m_outError.println( "Unable to call runtime.halt: " + t );
}
}
else
{
// Shutdown normally
// This is safe because we are already checking for the privilege to stop the JVM
// above. If we get this far then we want the Wrapper to be able to do everything
// necessary to stop the JVM.
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
privilegedStopInner( exitCode );
return null;
}
}
);
}
}
/**
* Signal the native wrapper that the startup is progressing but that more
* time is needed. The current startup timeout will be extended if
* necessary so it will be at least 'waitHint' milliseconds in the future.
* <p>
* This call will have no effect if the current startup timeout is already
* more than 'waitHint' milliseconds in the future.
*
* @param waitHint Time in milliseconds to allow for the startup to
* complete.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("signalStarting") permission.
*
* @see WrapperPermission
*/
public static void signalStarting( int waitHint )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "signalStarting" ) );
}
sendCommand( WRAPPER_MSG_START_PENDING, Integer.toString( waitHint ) );
}
/**
* Signal the native wrapper that the shutdown is progressing but that more
* time is needed. The current shutdown timeout will be extended if
* necessary so it will be at least 'waitHint' milliseconds in the future.
* <p>
* This call will have no effect if the current shutdown timeout is already
* more than 'waitHint' milliseconds in the future.
*
* @param waitHint Time in milliseconds to allow for the shutdown to
* complete.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("signalStopping") permission.
*
* @see WrapperPermission
*/
public static void signalStopping( int waitHint )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "signalStopping" ) );
}
m_stopping = true;
sendCommand( WRAPPER_MSG_STOP_PENDING, Integer.toString( waitHint ) );
}
/**
* This method should not normally be called by user code as it is called
* from within the stop and restart methods. However certain applications
* which stop the JVM may need to call this method to let the wrapper code
* know that the shutdown was intentional.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("signalStopped") permission.
*
* @see WrapperPermission
*/
public static void signalStopped( int exitCode )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "signalStopped" ) );
}
m_stopping = true;
sendCommand( WRAPPER_MSG_STOPPED, Integer.toString( exitCode ) );
// Give the socket time to actuall send the packet to the Wrapper
// as this call is often immediately followed by a halt command.
try
{
Thread.sleep( 250 );
}
catch ( InterruptedException e )
{
// Ignore.
}
}
/**
* Returns true if the ShutdownHook for the JVM has already been triggered.
* Some code needs to know whether or not the system is shutting down.
*
* @return True if the ShutdownHook for the JVM has already been triggered.
*/
public static boolean hasShutdownHookBeenTriggered()
{
return m_hookTriggered;
}
/**
* Requests that the Wrapper log a message at the specified log level.
* If the JVM is not being managed by the Wrapper then calls to this
* method will be ignored. This method has been optimized to ignore
* messages at a log level which will not be logged given the current
* log levels of the Wrapper.
* <p>
* Log messages will currently by trimmed by the Wrapper at 4k (4096 bytes).
* <p>
* Because of differences in the way console output is collected and
* messages logged via this method, it is expected that interspersed
* console and log messages will not be in the correct order in the
* resulting log file.
* <p>
* This method was added to allow simple logging to the wrapper.log
* file. This is not meant to be a full featured log file and should
* not be used as such. Please look into a logging package for most
* application logging.
*
* @param logLevel The level to log the message at can be one of
* WRAPPER_LOG_LEVEL_DEBUG, WRAPPER_LOG_LEVEL_INFO,
* WRAPPER_LOG_LEVEL_STATUS, WRAPPER_LOG_LEVEL_WARN,
* WRAPPER_LOG_LEVEL_ERROR, or WRAPPER_LOG_LEVEL_FATAL.
* @param message The message to be logged.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("log") permission.
*
* @see WrapperPermission
*/
public static void log( int logLevel, String message )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "log" ) );
}
// Make sure that the logLevel is valid to avoid problems with the
// command sent to the server.
if ( ( logLevel < WRAPPER_LOG_LEVEL_DEBUG ) || ( logLevel > WRAPPER_LOG_LEVEL_ADVICE ) )
{
throw new IllegalArgumentException( "The specified logLevel is not valid." );
}
if ( message == null )
{
throw new IllegalArgumentException( "The message parameter can not be null." );
}
if ( m_lowLogLevel <= logLevel )
{
sendCommand( (byte)( WRAPPER_MSG_LOG + logLevel ), message );
}
}
/**
* Returns an array of all registered services. This method is only
* supported on Windows platforms which support services. Calling this
* method on other platforms will result in null being returned.
*
* @return An array of services.
*
* @throws SecurityException If a SecurityManager has not been set in the
* JVM or if the calling code has not been
* granted the WrapperPermission "listServices"
* permission. A SecurityManager is required
* for this operation because this method makes
* it possible to learn a great deal about the
* state of the system.
*
* @see WrapperPermission
*/
public static WrapperWin32Service[] listServices()
throws SecurityException
{
SecurityManager sm = System.getSecurityManager();
if ( sm == null )
{
throw new SecurityException( "A SecurityManager has not yet been set." );
}
else
{
sm.checkPermission( new WrapperPermission( "listServices" ) );
}
if ( m_libraryOK )
{
return nativeListServices();
}
else
{
return null;
}
}
/**
* Sends a service control code to the specified service. The state of the
* service should be tested on return. If the service was not currently
* running then the control code will not be sent.
* <p>
* The control code sent can be one of the system control codes:
* WrapperManager.SERVICE_CONTROL_CODE_START,
* WrapperManager.SERVICE_CONTROL_CODE_STOP,
* WrapperManager.SERVICE_CONTROL_CODE_PAUSE,
* WrapperManager.SERVICE_CONTROL_CODE_CONTINUE, or
* WrapperManager.SERVICE_CONTROL_CODE_INTERROGATE. In addition, user
* defined codes in the range 128-255 can also be sent.
*
* @param serviceName Name of the Windows service which will receive the
* control code.
* @param controlCode The actual control code to be sent. User defined
* control codes should be in the range 128-255.
*
* @return A WrapperWin32Service containing the last known status of the
* service after sending the control code. This will be null if
* the currently platform is not a version of Windows which
* supports services.
*
* @throws WrapperServiceException If there are any problems accessing the
* specified service.
* @throws SecurityException If a SecurityManager has not been set in the
* JVM or if the calling code has not been
* granted the WrapperServicePermission
* permission for the specified service and
* control code. A SecurityManager is required
* for this operation because this method makes
* it possible to control any service on the
* system, which is of course rather dangerous.
*
* @see WrapperServicePermission
*/
public static WrapperWin32Service sendServiceControlCode( String serviceName, int controlCode )
throws WrapperServiceException, SecurityException
{
SecurityManager sm = System.getSecurityManager();
if ( sm == null )
{
throw new SecurityException( "A SecurityManager has not yet been set." );
}
else
{
String action;
switch( controlCode )
{
case SERVICE_CONTROL_CODE_START:
action = WrapperServicePermission.ACTION_START;
break;
case SERVICE_CONTROL_CODE_STOP:
action = WrapperServicePermission.ACTION_STOP;
break;
case SERVICE_CONTROL_CODE_PAUSE:
action = WrapperServicePermission.ACTION_PAUSE;
break;
case SERVICE_CONTROL_CODE_CONTINUE:
action = WrapperServicePermission.ACTION_CONTINUE;
break;
case SERVICE_CONTROL_CODE_INTERROGATE:
action = WrapperServicePermission.ACTION_INTERROGATE;
break;
default:
if ( ( controlCode >= 128 ) && ( controlCode <= 255 ) ) {
action = WrapperServicePermission.ACTION_USER_CODE;
} else {
throw new IllegalArgumentException( "The specified controlCode is invalid." );
}
break;
}
sm.checkPermission( new WrapperServicePermission( serviceName, action ) );
}
WrapperWin32Service service = null;
if ( m_libraryOK )
{
service = nativeSendServiceControlCode( serviceName.getBytes(), controlCode );
}
return service;
}
/**
* Adds a WrapperEventListener which will receive WrapperEvents. The
* specific events can be controlled using the mask parameter. This API
* was chosen to allow for additional events in the future.
*
* To avoid future compatibility problems, WrapperEventListeners should
* always test the class of an event before making use of it. This will
* avoid problems caused by new event classes added in future versions
* of the Wrapper.
*
* This method should only be called once for a given WrapperEventListener.
* Build up a single mask to receive events of multiple types.
*
* @param listener WrapperEventListener to be start receiving events.
* @param mask A mask specifying the event types that the listener is
* interrested in receiving. See the WrapperEventListener
* class for a full list of flags. A mask is created by
* combining multiple flags using the binary '|' OR operator.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the appropriate
* WrapperEventPermission(...) permission.
*
* @see WrapperEventPermission
*/
public static void addWrapperEventListener( WrapperEventListener listener, long mask )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
StringBuffer sb = new StringBuffer();
boolean first = true;
if ( ( mask & WrapperEventListener.EVENT_FLAG_SERVICE ) != 0 )
{
first = false;
sb.append( WrapperEventPermission.EVENT_TYPE_SERVICE );
}
if ( ( mask & WrapperEventListener.EVENT_FLAG_CONTROL ) != 0 )
{
if ( first )
{
first = false;
}
else
{
sb.append( "," );
}
sb.append( WrapperEventPermission.EVENT_TYPE_CONTROL );
}
if ( ( mask & WrapperEventListener.EVENT_FLAG_CORE ) != 0 )
{
if ( first )
{
first = false;
}
else
{
sb.append( "," );
}
sb.append( WrapperEventPermission.EVENT_TYPE_CORE );
}
sm.checkPermission( new WrapperEventPermission( sb.toString() ) );
}
synchronized( WrapperManager.class )
{
WrapperEventListenerMask listenerMask = new WrapperEventListenerMask();
listenerMask.m_listener = listener;
listenerMask.m_mask = mask;
m_wrapperEventListenerMaskList.add( listenerMask );
m_wrapperEventListenerMasks = null;
}
updateWrapperEventListenerFlags();
}
/**
* Removes a WrapperEventListener so it will not longer receive WrapperEvents.
*
* @param listener WrapperEventListener to be stop receiving events.
*
* @throws SecurityException If a SecurityManager is present and the
* calling thread does not have the
* WrapperPermission("removeWrapperEventListener")
* permission.
*
* @see WrapperPermission
*/
public static void removeWrapperEventListener( WrapperEventListener listener )
{
SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
sm.checkPermission( new WrapperPermission( "removeWrapperEventListener" ) );
}
synchronized( WrapperManager.class )
{
// Look for the first instance of a given listener in the list.
for ( Iterator iter = m_wrapperEventListenerMaskList.iterator(); iter.hasNext(); )
{
WrapperEventListenerMask listenerMask = (WrapperEventListenerMask)iter.next();
if ( listenerMask.m_listener == listener )
{
iter.remove();
m_wrapperEventListenerMasks = null;
break;
}
}
}
updateWrapperEventListenerFlags();
}
/**
* Returns the Log file currently being used by the Wrapper. If log file
* rolling is enabled in the Wrapper then this file may change over time.
*
* @throws IllegalStateException If this method is called before the Wrapper
* instructs this class to start the user
* application.
*/
public static File getWrapperLogFile()
{
File logFile = m_logFile;
if ( logFile == null )
{
throw new IllegalStateException( "Not yet initialized." );
}
return logFile;
}
/*---------------------------------------------------------------
* Constructors
*-------------------------------------------------------------*/
/**
* This class can not be instantiated.
*/
private WrapperManager()
{
}
/*---------------------------------------------------------------
* Private methods
*-------------------------------------------------------------*/
/**
* Checks for the existence of a SecurityManager and then makes sure that
* the Wrapper jar has been granted AllPermissions. If not then a warning
* will be displayed as this will most likely result in the Wrapper
* failing to function correctly.
*
* This method is called at various points in the startup as it is possible
* and in fact likely that any SecurityManager will be set by user code
* during or shortly after initialization. Once a SecurityManager has
* been located and tested then this method will become a noop.
*/
private static void checkSecurityManager()
{
if ( m_securityManagerChecked )
{
return;
}
SecurityManager securityManager = System.getSecurityManager();
if ( securityManager != null )
{
if ( m_debug )
{
m_outDebug.println(
"Detected a SecurityManager: " + securityManager.getClass().getName() );
}
try
{
securityManager.checkPermission( new java.security.AllPermission() );
}
catch ( SecurityException e )
{
m_outDebug.println();
m_outDebug.println(
"WARNING - Detected that a SecurityManager has been installed but the " );
m_outDebug.println(
" wrapper.jar has not been granted the java.security.AllPermission" );
m_outDebug.println(
" permission. This will most likely result in SecurityExceptions" );
m_outDebug.println(
" being thrown by the Wrapper." );
m_outDebug.println();
}
// Always set the flag.
m_securityManagerChecked = true;
}
}
/**
* Returns an array of WrapperEventListenerMask instances which can
* be safely used outside of synchronization.
*
* @return An array of WrapperEventListenerMask instances.
*/
private static WrapperEventListenerMask[] getWrapperEventListenerMasks()
{
WrapperEventListenerMask[] listenerMasks = m_wrapperEventListenerMasks;
if ( listenerMasks == null )
{
synchronized( WrapperManager.class )
{
if ( listenerMasks == null )
{
listenerMasks =
new WrapperEventListenerMask[m_wrapperEventListenerMaskList.size()];
m_wrapperEventListenerMaskList.toArray( listenerMasks );
m_wrapperEventListenerMasks = listenerMasks;
}
}
}
return listenerMasks;
}
/**
* Updates the internal flags based on the WrapperEventListeners currently
* registered.
*/
private static void updateWrapperEventListenerFlags()
{
boolean core = false;
WrapperEventListenerMask[] listenerMasks = getWrapperEventListenerMasks();
for ( int i = 0; i < listenerMasks.length; i++ )
{
long mask = listenerMasks[i].m_mask;
// See whether particular event types are required.
core = core | ( ( mask & WrapperEventListener.EVENT_FLAG_CORE ) != 0 );
}
m_produceCoreEvents = core;
}
/**
* Notifies registered listeners that an event has been fired.
*
* @param event Event to notify the listeners of.
*/
private static void fireWrapperEvent( WrapperEvent event )
{
long eventMask = event.getFlags();
WrapperEventListenerMask[] listenerMasks = getWrapperEventListenerMasks();
for ( int i = 0; i < listenerMasks.length; i++ )
{
long listenerMask = listenerMasks[i].m_mask;
// See if the event should be passed to this listner.
if ( ( listenerMask & eventMask ) != 0 )
{
// The listener wants the event.
WrapperEventListener listener = listenerMasks[i].m_listener;
try
{
listener.fired( event );
}
catch ( Throwable t )
{
m_outError.println( "Encountered an uncaught exception while notifying "
+ "WrapperEventListener of an event:" );
t.printStackTrace( m_outError );
}
}
}
}
/**
* Executed code common to the stop and stopImmediate methods.
*/
private static void stopCommon( int exitCode, int delay )
{
boolean stopping;
synchronized( WrapperManager.class )
{
stopping = m_stopping;
if ( !stopping )
{
m_stopping = true;
}
}
if ( !stopping )
{
// I used to check to make sure the commRunner was started, but that could fail
// for a number of reasons. If it is down then leave it alone.
//if ( !m_commRunnerStarted )
//{
// startRunner();
//}
// Always send the stop command
sendCommand( WRAPPER_MSG_STOP, Integer.toString( exitCode ) );
// Give the Wrapper a chance to register the stop command before stopping.
// This avoids any errors thrown by the Wrapper because the JVM died before
// it was expected to.
try
{
Thread.sleep( delay );
}
catch ( InterruptedException e )
{
}
}
}
/**
* Dispose of all resources used by the WrapperManager. Closes the server
* socket which is used to listen for events from the
*/
private static void dispose()
{
synchronized( WrapperManager.class )
{
m_disposed = true;
// Close the open socket if it exists.
closeSocket();
// Give the Connection Thread a chance to stop itself.
try
{
Thread.sleep( 500 );
}
catch ( InterruptedException e )
{
}
}
}
/**
* Called by startInner when the WrapperListner.start method has completed.
*
* Only called when WrapperManager.class is synchronized.
*/
private static void startCompleted()
{
m_startedTicks = getTicks();
// Let the startup thread die since the application has been started.
m_startupRunner = null;
// Check the SecurityManager here as it is possible that it was set in the
// listener's start method.
checkSecurityManager();
// Signal that the application has started.
signalStarted();
// Wake up any threads waiting for this.
WrapperManager.class.notifyAll();
}
/**
* Informs the listener that it should start.
*
* WrapperManager.class will be synchronized when called.
*
* @param block True if this call should block for the WrapperListener.start
* method to complete. This is true when java is being run in
* standalone mode without the Wrapper.
*/
private static void startInner( boolean block )
{
// Set the thread priority back to normal so that any spawned threads
// will use the normal priority
int oldPriority = Thread.currentThread().getPriority();
Thread.currentThread().setPriority( Thread.NORM_PRIORITY );
m_starting = true;
// This method can be called from the connection thread which must be a
// daemon thread by design. We need to call the WrapperListener.start method
// from a non-daemon thread. This means that if the current thread is a
// daemon we need to launch a new thread while we wait for the start method
// to return.
if ( m_listener == null )
{
if ( m_debug )
{
m_outDebug.println( "No WrapperListener has been set. Nothing to start." );
}
startCompleted();
}
else
{
if ( m_debug )
{
m_outDebug.println( "calling WrapperListener.start()" );
}
// These arrays aren't pretty, but we need final variables for the inline
// class and this makes it possible to get the values back.
final Integer[] resultF = new Integer[1];
final Throwable[] tF = new Throwable[1];
// Start in a dedicated thread.
Thread startRunner = new Thread( "WrapperListener_start_runner" )
{
public void run()
{
if ( m_debug )
{
m_outDebug.println( "WrapperListener.start runner thread started." );
}
try
{
// This is user code, so don't trust it.
try
{
resultF[0] = m_listener.start( m_args );
}
catch ( Throwable t )
{
tF[0] = t;
}
}
finally
{
// Now that we are back, handle the results.
if ( tF[0] != null )
{
m_outError.println( "Error in WrapperListener.start callback. " + tF[0] );
tF[0].printStackTrace( m_outError );
// Kill the JVM, but don't tell the wrapper that we want to stop.
// This may be a problem with this instantiation only.
privilegedStopInner( 1 );
// Won't make it here.
return;
}
if ( m_debug )
{
m_outDebug.println( "returned from WrapperListener.start()" );
}
if ( resultF[0] != null )
{
int exitCode = resultF[0].intValue();
if ( m_debug )
{
m_outDebug.println(
"WrapperListener.start() returned an exit code of " + exitCode + "." );
}
// Signal the native code.
WrapperManager.stop( exitCode );
// Won't make it here.
return;
}
synchronized( WrapperManager.class )
{
startCompleted();
}
if ( m_debug )
{
m_outDebug.println( "WrapperListener.start runner thread stopped." );
}
}
}
};
startRunner.setDaemon( false );
startRunner.start();
// Crank the priority back up.
Thread.currentThread().setPriority( oldPriority );
if ( block )
{
// Wait for the start runner to complete.
if ( m_debug )
{
m_outDebug.println(
"Waiting for WrapperListener.start runner thread to complete." );
}
while ( ( startRunner != null ) && ( startRunner.isAlive() ) )
{
try
{
WrapperManager.class.wait();
}
catch ( InterruptedException e )
{
// Ignore and keep waiting.
}
}
}
}
}
private static void shutdownJVM( int exitCode )
{
if ( m_debug )
{
m_outDebug.println(
"shutdownJVM(" + exitCode + ") Thread:" + Thread.currentThread().getName() );
}
// Make sure that any shutdown locks are released.
waitForShutdownLocks();
// Signal that the application has stopped and the JVM is about to shutdown.
signalStopped( exitCode );
// Dispose the wrapper.
dispose();
m_shutdownJVMComplete = true;
// Do not call System.exit if this is the ShutdownHook
if ( Thread.currentThread() == m_hook )
{
// This is the shutdown hook, so fall through because things are
// already shutting down.
}
else
{
if ( m_debug )
{
m_outDebug.println( "calling System.exit(" + exitCode + ")" );
}
safeSystemExit( exitCode );
}
}
/**
* A user ran into a JVM bug where a call to System exit was causing an
* IllegalThreadStateException to be thrown. Not sure how widespread
* this problem is. But it is easy to avoid it causing serious problems
* for the wrapper.
*/
private static void safeSystemExit( int exitCode )
{
try
{
System.exit( exitCode );
}
catch ( IllegalThreadStateException e )
{
m_outError.println( "Attempted System.exit(" + exitCode + ") call failed: " + e.toString() );
m_outError.println( " Trying Runtime.halt(" + exitCode + ")" );
Runtime.getRuntime().halt( exitCode );
}
}
/**
* Informs the listener that the JVM will be shut down.
*
* This should only be called from within a PrivilegedAction or in a
* context that came from a PrivilegedAction.
*/
private static void privilegedStopInner( int exitCode )
{
boolean block;
synchronized( WrapperManager.class )
{
// Always set the stopping flag.
m_stopping = true;
// Only one thread can be allowed to continue.
if ( m_stoppingThread == null )
{
m_stoppingThread = Thread.currentThread();
block = false;
}
else
{
if ( Thread.currentThread() == m_stoppingThread )
{
throw new IllegalStateException(
"WrapperManager.stop() can not be called recursively." );
}
block = true;
}
}
if ( block )
{
if ( m_debug )
{
m_outDebug.println( "Thread, " + Thread.currentThread().getName()
+ ", waiting for the JVM to exit." );
if ( Thread.currentThread() == m_hook )
{
m_outDebug.println(
"System.exit appears to have been called from within the" );
m_outDebug.println(
" WrapperListener.stop() method. If possible the application" );
m_outDebug.println(
" should be modified to avoid this behavior." );
m_outDebug.println(
" To avoid a deadlock, this thread will only wait 5 seconds" );
m_outDebug.println(
" for the application to shutdown. This may result in the" );
m_outDebug.println(
" application failing to shutdown completely before the JVM" );
m_outDebug.println(
" exists. Removing the offending System.exit call will" );
m_outDebug.println(
" resolve this." );
}
}
// This thread needs to be put into an infinite loop until the JVM exits.
// This thread can not be allowed to return to the caller, but another
// thread is already responsible for shutting down the JVM, so this
// one can do nothing but wait.
int loops = 0;
int wait = 50;
while( true )
{
try
{
Thread.sleep( wait );
}
catch ( InterruptedException e )
{
}
// If this is the wrapper's shutdown hook then we only want to loop until
// the shutdownJVM method has completed. We will only get into this state
// if user code calls System.exit from within the WrapperListener.stop
// method. Failing to return here will cause the shutdown process to hang.
// If the user code calls System.exit directly in the stop method then the
// m_shutdownJVMComplete flag will never be set. Always time out after
// 5 seconds so the JVM will not hang in such cases.
if ( Thread.currentThread() == m_hook )
{
if ( m_shutdownJVMComplete || ( loops > 5000 / wait ) )
{
if ( !m_shutdownJVMComplete )
{
if ( m_debug )
{
m_outDebug.println( "Thread, " + Thread.currentThread().getName()
+ ", continuing after 5 seconds." );
}
}
// To keep the wrapper from showing a JVM exited unexpectedly message
// on shutdown, tell the wrapper that we are ready to stop.
// If the WrapperListener.stop method is taking a long time, we will
// also get here. In that case, the Wrapper will still wait for
// the configured exit timeout before killing the JVM process.
// In theory, the shutdown process of an application will only call
// System.exit after the shutdown is complete so this should be Ok.
// Use the exit code from the thread which initiated the call rather
// than this call as that one is the one we really want.
signalStopped( m_exitCode );
return;
}
}
loops++;
}
}
if ( m_debug )
{
m_outDebug.println( "Thread, " + Thread.currentThread().getName()
+ ", handling the shutdown process." );
}
m_exitCode = exitCode;
// If appropriate, unregister the shutdown hook. This must be done before the
// user stop code is called to avoid nested shutdowns.
if ( Thread.currentThread() != m_hook )
{
// We do not want the ShutdownHook to execute, so unregister it before calling exit.
// It can't be unregistered if it has already fired however. The only way that this
// could happen is if user code calls System.exit from within the listener stop
// method.
if ( ( !m_hookTriggered ) && ( m_hook != null ) )
{
// Remove the shutdown hook using reflection.
try
{
m_removeShutdownHookMethod.invoke(
Runtime.getRuntime(), new Object[] { m_hook } );
}
catch ( IllegalAccessException e )
{
m_outError.println( "Unable to unregister shutdown hook: " + e );
}
catch ( InvocationTargetException e )
{
Throwable t = e.getTargetException();
if ( t == null )
{
t = e;
}
m_outError.println( "Unable to unregister shutdown hook: " + t );
}
}
}
// Only stop the listener if the app has been asked to start. Does not need to have actually started.
int code = exitCode;
if ( ( m_listenerForceStop && m_starting ) || m_started )
{
// Set the thread priority back to normal so that any spawned threads
// will use the normal priority
int oldPriority = Thread.currentThread().getPriority();
Thread.currentThread().setPriority( Thread.NORM_PRIORITY );
// This method can be called from the connection thread which must be a
// daemon thread by design. We need to call the WrapperListener.stop method
// from a non-daemon thread. This means that if the current thread is a
// daemon we need to launch a new thread while we wait for the stop method
// to return.
if ( m_listener == null )
{
if ( m_debug )
{
m_outDebug.println( "No WrapperListener has been set. Nothing to stop." );
}
}
else
{
if ( m_debug )
{
m_outDebug.println( "calling listener.stop()" );
}
if ( Thread.currentThread().isDaemon() )
{
// This array isn't pretty, but we need final variables for the inline
// class and this makes it possible to get the values back.
final Integer[] codeF = new Integer[] {new Integer(code)};
// Start in a dedicated thread.
Thread stopRunner = new Thread( "WrapperListener_stop_runner" )
{
public void run()
{
if ( m_debug )
{
m_outDebug.println( "WrapperListener.stop runner thread started." );
}
try
{
// This is user code, so don't trust it.
try
{
codeF[0] = new Integer( m_listener.stop( codeF[0].intValue() ) );
}
catch ( Throwable t )
{
m_outError.println(
"Error in WrapperListener.stop callback." );
t.printStackTrace( m_outError );
}
}
finally
{
if ( m_debug )
{
m_outDebug.println(
"WrapperListener.stop runner thread stopped." );
}
}
}
};
stopRunner.setDaemon( false );
stopRunner.start();
// Wait for the start runner to complete.
if ( m_debug )
{
m_outDebug.println(
"Waiting for WrapperListener.stop runner thread to complete." );
}
while ( ( stopRunner != null ) && ( stopRunner.isAlive() ) )
{
try
{
stopRunner.join();
stopRunner = null;
}
catch ( InterruptedException e )
{
// Ignore and keep waiting.
}
}
// Get the exit code back from the array.
code = codeF[0].intValue();
}
else
{
// This is user code, so don't trust it.
try
{
code = m_listener.stop( code );
}
catch ( Throwable t )
{
m_outError.println( "Error in WrapperListener.stop callback." );
t.printStackTrace( m_outError );
}
}
if ( m_debug )
{
m_outDebug.println( "returned from listener.stop() -> " + code );
}
}
// Crank the priority back up.
Thread.currentThread().setPriority( oldPriority );
}
shutdownJVM( code );
}
private static void signalStarted()
{
sendCommand( WRAPPER_MSG_STARTED, "" );
m_started = true;
}
/**
* Called by the native code when a control event is trapped by native code.
* Can have the values: WRAPPER_CTRL_C_EVENT, WRAPPER_CTRL_CLOSE_EVENT,
* WRAPPER_CTRL_LOGOFF_EVENT, WRAPPER_CTRL_SHUTDOWN_EVENT,
* WRAPPER_CTRL_TERM_EVENT, or WRAPPER_CTRL_HUP_EVENT.
*/
private static void controlEvent( int event )
{
String eventName;
boolean ignore;
switch( event )
{
case WRAPPER_CTRL_C_EVENT:
eventName = "WRAPPER_CTRL_C_EVENT";
ignore = m_ignoreSignals;
break;
case WRAPPER_CTRL_CLOSE_EVENT:
eventName = "WRAPPER_CTRL_CLOSE_EVENT";
ignore = m_ignoreSignals;
break;
case WRAPPER_CTRL_LOGOFF_EVENT:
eventName = "WRAPPER_CTRL_LOGOFF_EVENT";
ignore = false;
break;
case WRAPPER_CTRL_SHUTDOWN_EVENT:
eventName = "WRAPPER_CTRL_SHUTDOWN_EVENT";
ignore = false;
break;
case WRAPPER_CTRL_TERM_EVENT:
eventName = "WRAPPER_CTRL_TERM_EVENT";
ignore = m_ignoreSignals;
break;
case WRAPPER_CTRL_HUP_EVENT:
eventName = "WRAPPER_CTRL_HUP_EVENT";
ignore = m_ignoreSignals;
break;
case WRAPPER_CTRL_USR1_EVENT:
eventName = "WRAPPER_CTRL_USR1_EVENT";
ignore = m_ignoreSignals;
break;
case WRAPPER_CTRL_USR2_EVENT:
eventName = "WRAPPER_CTRL_USR2_EVENT";
ignore = m_ignoreSignals;
break;
default:
eventName = "Unexpected event: " + event;
ignore = false;
break;
}
WrapperControlEvent controlEvent = new WrapperControlEvent( event, eventName );
if ( ignore )
{
// Preconsume the event if it is set to be ignored, but go ahead and fire it so
// user can can still have the oportunity to recognize it.
controlEvent.consume();
}
fireWrapperEvent( controlEvent );
if ( !controlEvent.isConsumed() )
{
if ( ignore )
{
if ( m_debug )
{
m_outDebug.println( "Ignoring control event(" + eventName + ")" );
}
}
else
{
if ( m_debug )
{
m_outDebug.println( "Processing control event(" + eventName + ")" );
}
// This is user code, so don't trust it.
if ( m_listener != null )
{
try
{
m_listener.controlEvent( event );
}
catch ( Throwable t )
{
m_outError.println( "Error in WrapperListener.controlEvent callback." );
t.printStackTrace( m_outError );
}
}
else
{
// A listener was never registered. Always respond by exiting.
// This can happen if the user does not initialize things correctly.
stop( 0 );
}
}
}
}
/**
* Parses a long tab separated string of properties into an internal
* properties object. Actual tabs are escaped by real tabs.
*/
private static char PROPERTY_SEPARATOR = '\t';
private static void readProperties( String rawProps )
{
WrapperProperties properties = new WrapperProperties();
int len = rawProps.length();
int first = 0;
while ( first < len )
{
StringBuffer sb = new StringBuffer();
boolean foundEnd = false;
do
{
int pos = rawProps.indexOf( PROPERTY_SEPARATOR, first );
if ( pos >= 0 )
{
if ( pos > 0 )
{
sb.append( rawProps.substring( first, pos ) );
}
if ( pos < len - 1 )
{
if ( rawProps.charAt( pos + 1 ) == PROPERTY_SEPARATOR )
{
// Two separators in a row, it was escaped.
sb.append( PROPERTY_SEPARATOR );
first = pos + 2;
}
else
{
foundEnd = true;
first = pos + 1;
}
}
else
{
foundEnd = true;
first = pos + 1;
}
}
else
{
// No more separators. The rest is the last property.
sb.append( rawProps.substring( first ) );
foundEnd = true;
first = len;
}
}
while ( !foundEnd );
String property = sb.toString();
// Parse the property.
int pos = property.indexOf( '=' );
if ( pos > 0 )
{
String key = property.substring( 0, pos );
String value;
if ( pos < property.length() - 1 )
{
value = property.substring( pos + 1 );
}
else
{
value = "";
}
properties.setProperty( key, value );
// Process special properties
if ( key.equals( "wrapper.ignore_user_logoffs" ) )
{
m_ignoreUserLogoffs = value.equalsIgnoreCase( "true" );
}
}
}
// Lock the properties object and store it.
properties.lock();
m_properties = properties;
}
private static synchronized Socket openSocket()
{
if ( m_debug )
{
m_outDebug.println( "Open socket to wrapper..." + Thread.currentThread().getName() );
}
InetAddress iNetAddress;
try
{
iNetAddress = InetAddress.getByName( "127.0.0.1" );
}
catch ( UnknownHostException e )
{
// This is pretty fatal.
m_outError.println( "Unable to resolve localhost name: " + e );
m_outError.println( "Exiting JVM..." );
stop( 1 );
return null; //please the compiler
}
// If the user has specified a specific port to use then we want to try that first.
boolean connected = false;
int tryPort;
boolean fixedPort;
if ( m_jvmPort > 0 )
{
tryPort = m_jvmPort;
fixedPort = true;
}
else
{
tryPort = m_jvmPortMin;
fixedPort = false;
}
// Loop until we find a port we can connect using.
SocketException causeException = null;
do
{
try
{
m_socket = new Socket( iNetAddress, m_port, iNetAddress, tryPort );
if ( m_debug )
{
m_outDebug.println( "Opened Socket from " + tryPort + " to " + m_port );
}
connected = true;
break;
}
catch ( SocketException e )
{
String eMessage = e.getMessage();
if ( e instanceof ConnectException )
{
m_outError.println( "Failed to connect to the Wrapper at port " + m_port
+ ". Cause: " + e );
// This is fatal because there is nobody listening.
m_outError.println( "Exiting JVM..." );
stopImmediate( 1 );
}
else if ( ( e instanceof BindException ) ||
( ( eMessage != null ) &&
( ( eMessage.indexOf( "errno: 48" ) >= 0 ) || ( eMessage.indexOf( "Address already in use" ) >= 0 ) ) ) )
{
// Most Java implementations throw a BindException when the port is in use,
// but FreeBSD throws a SocketException with a specific message.
// This happens if the local port is already in use. In this case, we want
// to loop and try again.
if ( m_debug )
{
m_outDebug.println( "Failed attempt to bind using local port " + tryPort );
}
if ( fixedPort )
{
// The last port checked was the fixed port, switch to the dynamic range.
tryPort = m_jvmPortMin;
fixedPort = false;
}
else
{
tryPort++;
}
// Keep this exception around in case we need to log it.
if ( causeException == null )
{
causeException = e;
}
}
else
{
// Unexpected exception.
m_outError.println( "Unexpected exception opening backend socket: " + e );
m_socket = null;
return null;
}
}
catch ( IOException e )
{
m_outError.println( "Unable to open backend socket: " + e );
m_socket = null;
return null;
}
}
while ( tryPort <= m_jvmPortMax );
if ( connected )
{
if ( ( m_jvmPort > 0 ) && ( m_jvmPort != tryPort ) )
{
m_outInfo.println(
"Port " + m_jvmPort + " already in use, using port " + tryPort + " instead." );
}
}
else
{
if ( m_jvmPortMax > m_jvmPortMin )
{
m_outError.println( "Failed to connect to the Wrapper at port " + m_port
+ " by binding to any ports in the range " + m_jvmPortMin + " to "
+ m_jvmPortMax + ". Cause: " + causeException );
}
else
{
m_outError.println( "Failed to connect to the Wrapper at port " + m_port
+ " by binding to port " + m_jvmPortMin + ". Cause: "
+ causeException );
}
// This is fatal because there is nobody listening.
m_outError.println( "Exiting JVM..." );
stopImmediate( 1 );
}
// Now that we have a connected socket, continue on to configure it.
try
{
// Turn on the TCP_NODELAY flag. This is very important for speed!!
m_socket.setTcpNoDelay( true );
// Set the SO_TIMEOUT for the socket (max block time)
if ( m_soTimeout > 0 )
{
m_socket.setSoTimeout( m_soTimeout );
}
}
catch ( IOException e )
{
m_outError.println( e );
}
// Send the key back to the wrapper so that the wrapper can feel safe
// that it is talking to the correct JVM
sendCommand( WRAPPER_MSG_KEY, m_key );
return m_socket;
}
private static synchronized void closeSocket()
{
if ( m_socket != null )
{
if ( m_debug )
{
m_outDebug.println( "Closing socket." );
}
try
{
m_socket.close();
}
catch ( IOException e )
{
}
finally
{
m_socket = null;
}
}
}
private static String getPacketCodeName(byte code)
{
String name;
switch (code)
{
case WRAPPER_MSG_START:
name ="START";
break;
case WRAPPER_MSG_STOP:
name ="STOP";
break;
case WRAPPER_MSG_RESTART:
name ="RESTART";
break;
case WRAPPER_MSG_PING:
name ="PING";
break;
case WRAPPER_MSG_STOP_PENDING:
name ="STOP_PENDING";
break;
case WRAPPER_MSG_START_PENDING:
name ="START_PENDING";
break;
case WRAPPER_MSG_STARTED:
name ="STARTED";
break;
case WRAPPER_MSG_STOPPED:
name ="STOPPED";
break;
case WRAPPER_MSG_KEY:
name ="KEY";
break;
case WRAPPER_MSG_BADKEY:
name ="BADKEY";
break;
case WRAPPER_MSG_LOW_LOG_LEVEL:
name ="LOW_LOG_LEVEL";
break;
case WRAPPER_MSG_PING_TIMEOUT:
name ="PING_TIMEOUT";
break;
case WRAPPER_MSG_SERVICE_CONTROL_CODE:
name ="SERVICE_CONTROL_CODE";
break;
case WRAPPER_MSG_PROPERTIES:
name ="PROPERTIES";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_DEBUG:
name ="LOG(DEBUG)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_INFO:
name ="LOG(INFO)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_STATUS:
name ="LOG(STATUS)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_WARN:
name ="LOG(WARN)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_ERROR:
name ="LOG(ERROR)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_FATAL:
name ="LOG(FATAL)";
break;
case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_ADVICE:
name ="LOG(ADVICE)";
break;
case WRAPPER_MSG_LOGFILE:
name ="LOGFILE";
break;
default:
name = "UNKNOWN(" + code + ")";
break;
}
return name;
}
private static synchronized void sendCommand( byte code, String message )
{
Socket socket = m_socket;
if ( m_debug )
{
if ( ( code == WRAPPER_MSG_PING ) && ( message.equals( "silent" ) ) )
{
//m_outDebug.println( "Send silent ping packet." );
}
else if ( socket == null )
{
m_outDebug.println( "Backend socket not connected, not sending packet "
+ getPacketCodeName( code ) + " : " + message );
}
else
{
m_outDebug.println(
"Send a packet " + getPacketCodeName( code ) + " : " + message );
}
}
if ( m_appearHung )
{
// The WrapperManager is attempting to make the JVM appear hung, so do nothing
}
else
{
// Make a copy of the reference to make this more thread safe.
if ( ( socket == null ) && isControlledByNativeWrapper() && ( !m_stopping ) )
{
// The socket is not currently open, try opening it.
socket = openSocket();
}
if ( ( code == WRAPPER_MSG_START_PENDING ) || ( code == WRAPPER_MSG_STARTED ) )
{
// Set the last ping time so that the startup process does not time out
// thinking that the JVM has not received a Ping for too long.
m_lastPingTicks = getTicks();
}
// If the socket is open, then send the command, otherwise just throw it away.
if ( socket != null )
{
try
{
// It is possible that a logged message is quite large. Expand the size
// of the command buffer if necessary so that it can be included. This
// means that the command buffer will be the size of the largest message.
byte[] messageBytes = message.getBytes();
if ( m_commandBuffer.length < messageBytes.length + 2 )
{
m_commandBuffer = new byte[messageBytes.length + 2];
}
// Writing the bytes one by one was sometimes causing the first byte to be lost.
// Try to work around this problem by creating a buffer and sending the whole lot
// at once.
m_commandBuffer[0] = code;
System.arraycopy( messageBytes, 0, m_commandBuffer, 1, messageBytes.length );
int len = messageBytes.length + 2;
m_commandBuffer[len - 1] = 0;
OutputStream os = socket.getOutputStream();
os.write( m_commandBuffer, 0, len );
os.flush();
}
catch ( IOException e )
{
m_outError.println( e );
e.printStackTrace( m_outError );
closeSocket();
}
}
}
}
/**
* Loop reading packets from the native side of the Wrapper until the
* connection is closed or the WrapperManager class is disposed.
* Each packet consists of a packet code followed by a null terminated
* string up to 256 characters in length. If the entire packet has not
* yet been received, then it must not be read until the complete packet
* has arived.
*/
private static byte[] m_socketReadBuffer = new byte[256];
private static void handleSocket()
{
WrapperPingEvent pingEvent = new WrapperPingEvent();
try
{
if ( m_debug )
{
m_outDebug.println( "handleSocket(" + m_socket + ")" );
}
DataInputStream is = new DataInputStream( m_socket.getInputStream() );
while ( !m_disposed )
{
try
{
// A Packet code must exist.
byte code = is.readByte();
// Always read from the buffer until a null '\0' is encountered.
byte b;
int i = 0;
do
{
b = is.readByte();
if ( b != 0 )
{
if ( i >= m_socketReadBuffer.length )
{
byte[] tmp = m_socketReadBuffer;
m_socketReadBuffer = new byte[tmp.length + 256];
System.arraycopy( tmp, 0, m_socketReadBuffer, 0, tmp.length );
}
m_socketReadBuffer[i] = b;
i++;
}
}
while ( b != 0 );
String msg = new String( m_socketReadBuffer, 0, i );
if ( m_appearHung )
{
// The WrapperManager is attempting to make the JVM appear hung,
// so ignore all incoming requests
}
else
{
if ( m_debug )
{
String logMsg;
if ( code == WRAPPER_MSG_PROPERTIES )
{
// The property values are very large and distracting in the log.
// Plus if any triggers are defined, then logging them will fire
// the trigger.
logMsg = "(Property Values)";
}
else
{
logMsg = msg;
}
// Don't log silent pings.
if ( ( code == WRAPPER_MSG_PING ) && ( msg.equals( "silent" ) ) )
{
//m_outDebug.println( "Received silent ping packet." );
}
else
{
m_outDebug.println( "Received a packet " + getPacketCodeName( code )
+ " : " + logMsg );
}
}
// Ok, we got a packet. Do something with it.
switch( code )
{
case WRAPPER_MSG_START:
startInner( false );
break;
case WRAPPER_MSG_STOP:
// Don't do anything if we are already stopping
if ( !m_stopping )
{
privilegedStopInner( 0 );
// Should never get back here.
}
break;
case WRAPPER_MSG_PING:
m_lastPingTicks = getTicks();
sendCommand( WRAPPER_MSG_PING, msg );
if ( m_produceCoreEvents )
{
fireWrapperEvent( pingEvent );
}
break;
case WRAPPER_MSG_BADKEY:
// The key sent to the wrapper was incorrect. We need to shutdown.
m_outError.println( "Authorization key rejected by Wrapper." );
m_outError.println( "Exiting JVM..." );
closeSocket();
privilegedStopInner( 1 );
break;
case WRAPPER_MSG_LOW_LOG_LEVEL:
try
{
m_lowLogLevel = Integer.parseInt( msg );
m_debug = ( m_lowLogLevel <= WRAPPER_LOG_LEVEL_DEBUG );
if ( m_debug )
{
m_outDebug.println(
"LowLogLevel from Wrapper is " + m_lowLogLevel );
}
}
catch ( NumberFormatException e )
{
m_outError.println( "Encountered an Illegal LowLogLevel from the "
+ "Wrapper: " + msg );
}
break;
case WRAPPER_MSG_PING_TIMEOUT:
try
{
m_pingTimeout = Integer.parseInt( msg ) * 1000;
if ( m_debug )
{
m_outDebug.println(
"PingTimeout from Wrapper is " + m_pingTimeout );
}
}
catch ( NumberFormatException e )
{
m_outError.println( "Encountered an Illegal PingTimeout from the "
+ "Wrapper: " + msg );
}
// Make sure that the so timeout is longer than the ping timeout
if ( m_pingTimeout <= 0 )
{
m_socket.setSoTimeout( 0 );
}
else if ( m_soTimeout < m_pingTimeout )
{
m_socket.setSoTimeout( m_pingTimeout );
}
break;
case WRAPPER_MSG_SERVICE_CONTROL_CODE:
try
{
int serviceControlCode = Integer.parseInt( msg );
if ( m_debug )
{
m_outDebug.println( "ServiceControlCode from Wrapper with code "
+ serviceControlCode );
}
WrapperServiceControlEvent event =
new WrapperServiceControlEvent( serviceControlCode );
fireWrapperEvent( event );
}
catch ( NumberFormatException e )
{
m_outError.println( "Encountered an Illegal ServiceControlCode from "
+ "the Wrapper: " + msg );
}
break;
case WRAPPER_MSG_PROPERTIES:
readProperties( msg );
break;
case WRAPPER_MSG_LOGFILE:
m_logFile = new File( msg );
WrapperLogFileChangedEvent event = new WrapperLogFileChangedEvent( m_logFile );
fireWrapperEvent( event );
break;
default:
// Ignore unknown messages
m_outInfo.println( "Wrapper code received an unknown packet type: "
+ code );
break;
}
}
}
catch ( InterruptedIOException e )
{
int nowTicks = getTicks();
// Unless the JVM is shutting dowm we want to show warning messages and maybe exit.
if ( ( m_started ) && ( !m_stopping ) )
{
if ( m_debug )
{
m_outDebug.println( "Read Timed out. (Last Ping was "
+ getTickAge( m_lastPingTicks, nowTicks ) + " milliseconds ago)" );
}
if ( !m_appearHung )
{
long lastPingAge = getTickAge( m_lastPingTicks, nowTicks );
long eventRunnerAge = getTickAge( m_eventRunnerTicks, nowTicks );
// We may have timed out because the system was extremely busy or
// suspended. Only restart due to a lack of ping events if the
// event thread has been running.
if ( eventRunnerAge < 10000 )
{
// Only perform ping timeout checks if ping timeouts are enabled.
if ( m_pingTimeout > 0 )
{
// How long has it been since we received the last ping
// from the Wrapper?
if ( lastPingAge > m_pingTimeout + 90000 )
{
// It has been more than the ping timeout + 90 seconds,
// so just give up and kill the JVM
m_outInfo.println( "JVM did not exit. Give up." );
safeSystemExit(1);
}
else if ( lastPingAge > m_pingTimeout )
{
// It has been more than the ping timeout since the
// JVM was last pinged. Ask to be stopped (and restarted).
m_outInfo.println( "The Wrapper code did not ping the "
+ "JVM for " + (lastPingAge / 1000) + " seconds. "
+ "Quit and let the Wrapper resynch.");
// Don't do anything if we are already stopping
if ( !m_stopping )
{
// Always send the stop command
sendCommand( WRAPPER_MSG_RESTART, "restart" );
// Give the Wrapper a chance to register the stop
// command before stopping.
// This avoids any errors thrown by the Wrapper because
// the JVM died before it was expected to.
try
{
Thread.sleep( 1000 );
}
catch ( InterruptedException e2 )
{
}
privilegedStopInner( 1 );
}
}
}
}
}
}
}
}
return;
}
catch ( SocketException e )
{
if ( m_debug )
{
if ( m_socket == null )
{
// This error happens if the socket is closed while reading:
// java.net.SocketException: Descriptor not a socket: JVM_recv in socket
// input stream read
}
else
{
m_outDebug.println( "Closed socket: " + e );
}
}
return;
}
catch ( IOException e )
{
// This means that the connection was closed. Allow this to return.
//m_outInfo.println( e );
//e.printStackTrace( m_outInfo );
return;
}
}
private static void startRunner()
{
if ( isControlledByNativeWrapper() )
{
if ( m_commRunner == null )
{
// Create and launch a new thread to manage this connection
m_commRunner = new Thread( m_instance, WRAPPER_CONNECTION_THREAD_NAME );
m_commRunner.setDaemon( true );
m_commRunner.start();
}
// Wait to give the runner a chance to connect.
synchronized( WrapperManager.class )
{
while ( !m_commRunnerStarted )
{
try
{
WrapperManager.class.wait( 100 );
}
catch ( InterruptedException e )
{
}
}
}
}
else
{
// Immediately mark the runner as started as it will never be used.
synchronized( WrapperManager.class )
{
m_commRunnerStarted = true;
WrapperManager.class.notifyAll();
}
}
}
/*---------------------------------------------------------------
* Runnable Methods
*-------------------------------------------------------------*/
public void run()
{
// Make sure that no other threads call this method.
if ( Thread.currentThread() != m_commRunner )
{
throw new IllegalStateException(
"Only the comm runner thread is allowed to call this method." );
}
if ( m_debug )
{
m_outDebug.println( "Communications runner thread started." );
}
// This thread needs to have a very high priority so that it never
// gets put behind other threads.
Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
// Initialize the last ping tick count.
m_lastPingTicks = getTicks();
boolean gotPortOnce = false;
while ( !m_disposed )
{
try
{
try
{
openSocket();
// After the socket has been opened the first time, mark the thread as
// started. This must be done here to make sure that exits work correctly
// when called on startup.
if ( !m_commRunnerStarted )
{
synchronized( WrapperManager.class )
{
m_commRunnerStarted = true;
WrapperManager.class.notifyAll();
}
}
if ( m_socket != null )
{
handleSocket();
}
else
{
// Failed, so wait for just a moment
try
{
Thread.sleep( 100 );
}
catch ( InterruptedException e )
{
}
}
}
finally
{
// Always close the socket here.
closeSocket();
}
}
catch ( ThreadDeath td )
{
m_outError.println( m_warning.format( "SERVER_DAEMON_KILLED" ) );
}
catch ( Throwable t )
{
if ( !isShuttingDown() )
{
// Show a stack trace here because this is fairly critical
m_outError.println( m_error.format( "SERVER_DAEMON_DIED" ) );
t.printStackTrace( m_outError );
}
}
}
// Make sure that noone is ever left waiting for this thread to start.
synchronized( WrapperManager.class )
{
if ( !m_commRunnerStarted )
{
m_commRunnerStarted = true;
WrapperManager.class.notifyAll();
}
}
if ( m_debug )
{
m_outDebug.println( m_info.format( "SERVER_DAEMON_SHUT_DOWN" ) );
}
}
/*---------------------------------------------------------------
* Inner Classes
*-------------------------------------------------------------*/
/**
* Mapping between WrapperEventListeners and their registered masks.
* This is necessary to support the case where the same listener is
* registered more than once. It also makes it possible to reference
* an array of these mappings without synchronization.
*/
private static class WrapperEventListenerMask
{
private WrapperEventListener m_listener;
private long m_mask;
}
private static class WrapperTickEventImpl
extends WrapperTickEvent
{
private int m_ticks;
private int m_tickOffset;
/**
* Returns the tick count at the point the event is fired.
*
* @return The tick count at the point the event is fired.
*/
public int getTicks()
{
return m_ticks;
}
/**
* Returns the offset between the tick count used by the Wrapper for time
* keeping and the tick count generated directly from the system time.
*
* This will be 0 in most cases. But will be a positive value if the
* system time is ever set back for any reason. It will be a negative
* value if the system time is set forward or if the system is under
* heavy load. If the wrapper.use_system_time property is set to TRUE
* then the Wrapper will be using the system tick count for internal
* timing and this value will always be 0.
*
* @return The tick count offset.
*/
public int getTickOffset()
{
return m_tickOffset;
}
}
/**
* When the JVM is being controlled by the Wrapper, stdin can not be used
* as it is undefined. This class makes it possible to provide the user
* application with a descriptive error message if System.in is accessed.
*/
private static class WrapperInputStream
extends InputStream
{
/**
* This method will always throw an IOException as the read method is
* not valid.
*/
public int read()
throws IOException
{
m_out.println( "WARNING - System.in has been disabled by the wrapper.disable_console_input property. "
+ "Calls will block indefinitely." );
// Go into a loop that will never return.
while ( true )
{
synchronized( this )
{
try
{
this.wait();
}
catch ( InterruptedException e )
{
// Ignore.
}
}
}
}
}
}