/* * Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The * University of Hong Kong (HKU). All Rights Reserved. * * This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1] * * [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt */ package hk.hku.cecid.piazza.commons.swallow; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Map.Entry; import org.tanukisoftware.wrapper.WrapperActionServer; import static org.tanukisoftware.wrapper.WrapperActionServer.*; import hk.hku.cecid.piazza.commons.module.ActiveModule; import hk.hku.cecid.piazza.commons.module.ModuleException; import hk.hku.cecid.piazza.commons.util.StringUtilities; /** * The JSWActionServerModule is an integrated module with JSW (Java service wrapper) to provide functionality same as WrapperActionServer defined in JSW. * * In simple, this module is acting as a Back-door tool enabling the operator to control the behavior of the JVM running inside JSW. * * NOTE: The JVM must run under JSW before using the module !. * * Quoted from the JAVADOC of WrapperActionServer: * * If an application instantiates an instance of this class, the JVM will listen on the specified port for connections. * * When a connection is detected, the first byte of input will be read from the socket and then the connection will be immediately closed. * * An action will then be performed based on the byte read from the stream. * * The easiest way to invoke an action manually is to Telnet to the specified port and then type the single command key. telnet localhost 9999, for example. * * Valid commands include: * <ul> * <li>S : Shutdown cleanly.</li> * <li>H : Immediate forced shutdown.</li> * <li>R : Restart</li> * <li>D : Perform a Thread Dump</li> * <li>U : Unexpected shutdown. (Simulate a crash for testing)</li> * <li>V : Cause an access violation. (For testing)</li> * <li>G : Make the JVM appear to be hung. (For testing)</li> * </ul> * * @author Twinsen Tsang * @version 1.0.0 * @since JDK5.0 */ public class JSWActionServerModule extends ActiveModule { /* * The JSW wrapper action server instance. */ private WrapperActionServer wrappingActionServer; /* * The listening port of the action server, default is 9998. */ private int listenPort = 9998; /* * The The flag indicate whether the server accept local connection only. * * Note, setting this value to false indicate everyone can access and terminate the JVM. * * Take your own risk to set this value! */ private boolean localConnectionOnly = true; private boolean isServerStarted = false; public static final int MAX_ACTION_ALLOWED = 255; private boolean[] actionEnabled = new boolean[MAX_ACTION_ALLOWED]; /** * The <code>COMMAND</code> is the enumeration of default action server command set. */ public static enum COMMAND { SHUTDOWN (COMMAND_SHUTDOWN , "shutdownEnabled" , true , null), FORCE_HALT (COMMAND_HALT_EXPECTED , "forceHaltEnabled" , true , null), RESTART (COMMAND_RESTART , "restartEnabled" , true , null), THREADDUMP (COMMAND_DUMP , "threadDumpEnabled" , true , null), ACCESS_VIOLATION(COMMAND_ACCESS_VIOLATION, "stimulateAccessViolationEnabled", false, null), JVM_HANG (COMMAND_APPEAR_HUNG , "stimulateJVMHangEnabled" , false, null), UNEXPECT_HALT (COMMAND_HALT_UNEXPECTED , "stimulateUnexpectedHaltEnabled" , false, null); private byte c; private String property; private boolean enabled; private Runnable r; private COMMAND(byte c, String property, boolean defaultEnabled, Runnable r) { this.c = c; this.property = property; this.enabled = defaultEnabled; this.r = r; } public boolean getEnabled() { return this.enabled; } public String getPropertyKey() { return this.property; } public byte getCode() { return this.c; } } /** * Creates a new instance of <code>JSWActionServerModule</code>. * * @param descriptorLocation the module descriptor. * @throws ModuleException if errors encountered when loading the module descriptor. */ public JSWActionServerModule(String descriptorLocation, boolean shouldInitialize) { super(descriptorLocation, shouldInitialize); } /** * Creates a new instance of <code>JSWActionServerModule</code>. * * @param descriptorLocation the module descriptor. * @param shouldInitialize true if the module should be initialized. * @throws ModuleException if errors encountered when loading the module descriptor. */ public JSWActionServerModule(String descriptorLocation, ClassLoader loader, boolean shouldInitialize) { super(descriptorLocation, loader, shouldInitialize); } /** * Creates a new instance of <code>JSWActionServerModule</code>. * * @param descriptorLocation the module descriptor. * @param loader the class loader for this module. * @throws ModuleException if errors encountered when loading the module descriptor. */ public JSWActionServerModule(String descriptorLocation, ClassLoader loader) { super(descriptorLocation, loader); } /** * Creates a new instance of <code>JSWActionServerModule</code>. * * @param descriptorLocation the module descriptor. * @param loader the class loader for this module. * @param shouldInitialize true if the module should be initialized. * @throws ModuleException if errors encountered when loading the module descriptor. */ public JSWActionServerModule(String descriptorLocation) { super(descriptorLocation); } /** * Initialize the action server configuration from the property defined in the xml. */ @Override public void init() { super.init(); Properties p = super.getParameters(); this.listenPort = StringUtilities.parseInt(p.getProperty("listenPort"), 9998); this.localConnectionOnly = StringUtilities.parseBoolean(p.getProperty("localConnectionOnly"), true); for (COMMAND e: COMMAND.values()) { this.actionEnabled[e.getCode()] = StringUtilities.parseBoolean( p.getProperty(e.getPropertyKey()), e.getEnabled()); } /* this.actionEnabled[COMMAND_SHUTDOWN] = StringUtilities.parseBoolean(p.getProperty("shutdownEnabled")); this.actionEnabled[COMMAND_HALT_EXPECTED] = StringUtilities.parseBoolean(p.getProperty("forceHaltEnabled")); this.actionEnabled[COMMAND_RESTART] = StringUtilities.parseBoolean(p.getProperty("restartEnabled")); this.actionEnabled[COMMAND_DUMP] = StringUtilities.parseBoolean(p.getProperty("threadDumpEnabled")); this.actionEnabled[COMMAND_ACCESS_VIOLATION] = StringUtilities.parseBoolean(p.getProperty("stimulateAccessViolationEnabled")); this.actionEnabled[COMMAND_APPEAR_HUNG] = StringUtilities.parseBoolean(p.getProperty("stimulateJVMHangEnabled")); this.actionEnabled[COMMAND_HALT_UNEXPECTED] = StringUtilities.parseBoolean(p.getProperty("stimulateUnexpectedHaltEnabled")); */ if (this.localConnectionOnly) { try { /* * Start the server which bind the local-host address only. */ this.wrappingActionServer = new WrapperActionServer(this.listenPort, InetAddress.getByName("localhost")); } catch(UnknownHostException uhex) { this.getLogger().error("[JSW ActServer] Unable to find localhost", uhex); } } else { /* * Start the server which bind any of address. */ this.wrappingActionServer = new WrapperActionServer (this.listenPort); } this.wrappingActionServer.enableShutdownAction (this.actionEnabled[COMMAND_SHUTDOWN]); this.wrappingActionServer.enableHaltExpectedAction (this.actionEnabled[COMMAND_HALT_EXPECTED]); this.wrappingActionServer.enableRestartAction (this.actionEnabled[COMMAND_RESTART]); this.wrappingActionServer.enableThreadDumpAction (this.actionEnabled[COMMAND_DUMP]); this.wrappingActionServer.enableAccessViolationAction(this.actionEnabled[COMMAND_ACCESS_VIOLATION]); this.wrappingActionServer.enableAppearHungAction (this.actionEnabled[COMMAND_APPEAR_HUNG]); this.wrappingActionServer.enableHaltUnexpectedAction (this.actionEnabled[COMMAND_HALT_UNEXPECTED]); } /** * Return true if the command action is enabled in this action server. false otherwise. * * @param c The action command to check whether it is enabled. * @return true if the command action is enabled in this action server. false otherwise. */ public boolean isActionEnabled(COMMAND c) { return this.actionEnabled[c.getCode()]; } /** * Get whether the action server accepts local connection only. * * @return Get whether the action server accepts local connection only. */ public boolean getIsLocalConnectionOnly() { return this.localConnectionOnly; } /** * Get the listen port of the action server. * * @return Get the listen port of the action server. */ public int getListenPort() { return this.listenPort; } /** * Start the JSW Action server by calling {@link WrapperActionServer#start()}. * * In this module, the ActiveModule thread will act as control thread for monitoring the wrapper * action server thread. So it do nothing. * * @see #execute() */ @Override public synchronized void start() { // Return immediate after the server has been started. if (this.isServerStarted) return; super.start(); super.getThread().setName("JSW-Control-ActionServer@" + Integer.toHexString(this.hashCode())); if (this.wrappingActionServer == null) { throw new IllegalStateException("The 'JSW action server' has not initialized. Please call init()"); } try { this.wrappingActionServer.start(); this.isServerStarted = true; } catch(IOException ioex) { this.getLogger().error("[JSW ActServer]: Start Error" + ioex.getMessage(), ioex); } } /** * Stop the JSW Action server by calling {@link WrapperActionServer#stop()}. * * This method does not guarantee the JSW Action Server has been switched off after the invocation of * this method. It just spawn a new thread for closing the action server. */ @Override public synchronized void stop() { // Return immediate when the server has yet to start. if (!this.isServerStarted) return; // Stop the control thread super.stop(); if (this.wrappingActionServer != null) { /* * The program will suffer deadlock when the action server is shutdown by the current thread under * JSW * * WrapperManager -> lock@ WrapperStartStopApp.stop * WrapperStartStopApp -> lock@ JSWActionServerModule.stop * WrapperActionServer -> lock@ WrapperManager.stop */ Thread stopServerHelper = new Thread("JSW-Stop-ActionServer@" + Integer.toHexString(this.hashCode())) { public void run() { try { wrappingActionServer.stop(); } catch(Exception ex) { getLogger().error("[JSW ActServer]: Stop Error" + ex.getMessage(), ex); } } }; stopServerHelper.setDaemon(true); stopServerHelper.start(); this.isServerStarted = false; } } /** * This method does nothing, just sleep forever. This module thread is acting as the control * thread for the wrapper action server. */ @Override public boolean execute() { try { while(true) { Thread.sleep(Integer.MAX_VALUE); } } catch(Throwable t){} return true; } // =============================================== // Helper method // =============================================== private static Field actionsMapField; static { try { actionsMapField = WrapperActionServer.class.getDeclaredField("m_actions"); actionsMapField.setAccessible(true); } catch (NoSuchFieldException e) { String error = "Unable to find \"m_actions\" in the JSW ActServer class, incompatible JSW version"; System.err.println(error); e.printStackTrace(); } } /** * Dump all action enabled in the java service wrapper action server. */ public void dumpEnabledAction() { this.dumpEnabledAction(System.out); } /** * Dump all action enabled in the java service wrapper action server to the specified <code>os</code>. * * @param os The output stream dumping to. */ @SuppressWarnings("unchecked") public void dumpEnabledAction(OutputStream os) { if (os == null) { throw new NullPointerException("Missing 'os' in the arguments."); } Set<Entry<Byte, Runnable>> actionsMapEntries = null; try { actionsMapEntries = ((Map<Byte, Runnable>) actionsMapField.get(this.wrappingActionServer)).entrySet(); for (Entry<Byte, Runnable> e : actionsMapEntries) { os.write(String.format("COMMAND '%c' : Runnable <%s>\n", e.getKey(), e.getValue().toString()).getBytes()); } } catch(Exception ex) { this.getLogger().error("Unable to dump enabled action due to:", ex); } } }