/*
* 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.net.URLClassLoader;
import java.util.Properties;
import hk.hku.cecid.piazza.commons.module.Module;
import hk.hku.cecid.piazza.commons.module.ModuleException;
import hk.hku.cecid.piazza.commons.swallow.ShutdownHookEmailThread;
import hk.hku.cecid.piazza.commons.util.Instance;
import hk.hku.cecid.piazza.commons.util.StringUtilities;
/**
* The ShutdownHookEmailModule is a piazza common module which send email to the specified when the JVM
* is terminated. It is typical used to notify the operator for the termination of long-running java process
* like application server (for instance, Tomcat) through email.
*
* Background about this: This component is emerged when project swallow requires the detection of
* application crashes and therefore we want a reusable generic component for doing such.
* <br/><br/>
* Sample Module Descriptor
* <pre>
* <module id="shutdown.email.module" name="Email shutdown hook module">
* <parameters>
* <parameter name="host" value="intraflow2.cs.hku.hk"/>
* <parameter name="protocol" value="smtp"/>
* <parameter name="username" value=""/>
* <parameter name="password" value=""/>
* <parameter name="from" value="yourDaemon@cecid.hku.hk"/>
* <parameter name="to" value="yourEmailAddress"/>
* <parameter name="cc" value="yourCCEmailAddress"/>
* <parameter name="verbose" value="false"/>
* </parameters>
* </module>
* </pre>
*
* @author Twinsen Tsang
* @version 1.0.0
* @since JDK5.0
*/
public class ShutdownHookEmailModule extends Module
{
private Thread shutdownThread;
/**
* Creates a new instance of <code>ShutdownHookEmailModule</code>.
*
* @param descriptorLocation the module descriptor.
* @throws ModuleException if errors encountered when loading the module descriptor.
*/
public ShutdownHookEmailModule(String descriptorLocation, boolean shouldInitialize)
{
super(descriptorLocation, shouldInitialize);
}
/**
* Creates a new instance of <code>ShutdownHookEmailModule</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 ShutdownHookEmailModule(String descriptorLocation, ClassLoader loader, boolean shouldInitialize)
{
super(descriptorLocation, loader, shouldInitialize);
}
/**
* Creates a new instance of <code>ShutdownHookEmailModule</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 ShutdownHookEmailModule(String descriptorLocation, ClassLoader loader)
{
super(descriptorLocation, loader);
}
/**
* Creates a new instance of <code>ShutdownHookEmailModule</code>.
*
* @param descriptorLocation the module descriptor.
*/
public ShutdownHookEmailModule(String descriptorLocation)
{
super(descriptorLocation);
}
/**
* Invoked for initialization.
*
* Wire up all property from the XML.
*/
@Override
public void init()
{
super.init();
try
{
synchronized(this)
{
if (this.shutdownThread == null)
{
this.shutdownThread = this.createShutdownHookWorker();
/*
* Add it to the runtime shutdown sequence.
*/
Runtime.getRuntime().addShutdownHook(this.shutdownThread);
}
}
// Old implementation
/*
* Class c = Class.forName(ShutdownHookEmailThread.class.getName(), true, threadCtxClassLoader);
* Constructor con = c.getConstructor(String.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class);
* Thread shutdownThread = (Thread) con.newInstance(protocol, host, username, password, from, tos, ccs, subject);
* shutdownThread.setContextClassLoader(threadCtxClassLoader);
*/
}
catch(Throwable t)
{
throw new ModuleException("Unable to initialize mail alert shutdown hook", t);
}
}
/**
* A special hack for remove the shutdown hook registered during the {@link #init()} phrase
* of this module. This is used for testing the module only. You rarely call this in your application.
*/
public synchronized void stop()
{
if (this.shutdownThread != null)
{
try
{
Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
}
catch(Throwable t)
{
// Ignore, A hook that can't be removed is because the shutdown
// sequence is already started.
}
finally
{
this.shutdownThread = null;
}
}
}
/**
*
* @return
*/
protected Thread getThread()
{
/*
* TODO: Share the implementation with #init.
*/
try
{
synchronized(this)
{
if (this.shutdownThread == null)
{
this.shutdownThread = this.createShutdownHookWorker();
}
}
}
catch(Throwable t)
{
throw new ModuleException("Unable to retrieve mail alert shutdown hook", t);
}
return this.shutdownThread;
}
/**
* Create the thread executed during JVM shutdown. By default, it create
* an new instance of ShutdownHookEmailThread associated with the property
* defined in this module descriptor.
*
* The thread created must conform the rule specified in the Java API. For detail, read
* <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread)">here</a>
*
* @return The thread executed during JVM shutdown
* @throws Throwable any kind of execution for setup the shutdown hook worker.
*
* @{@link java.lang.Runtime#addShutdownHook(Thread)}
*/
@SuppressWarnings("unchecked")
protected Thread createShutdownHookWorker() throws Throwable
{
Properties p = super.getParameters();
final String host = super.getRequiredParameter("host");
final String protocol = p.getProperty("protocol", "smtp");
final String username = p.getProperty("username");
final String password = p.getProperty("password");
final String from = p.getProperty("from", "commonDaemon@cecid.hku.hk");
final String tos = super.getRequiredParameter("to");
final String ccs = p.getProperty("cc");
final String subject = p.getProperty("subject", ShutdownHookEmailThread.DEFAULT_SHUTDOWN_MAIL_SUBJECT);
final boolean verbose = StringUtilities.parseBoolean(p.getProperty("verbose"), false);
/*
* TODO: super-class provide helper method to throw if null get property method
*/
/*if (host == null || host.equals(""))
{
throw new ModuleException("Missing required property \"host\" in " + this.toString());
}
else if (tos == null || tos.equals(""))
{
throw new ModuleException("Missing required property \"to\" in " + this.toString());
}*/
/*
* In certain circumstance (mostly in servlet container), the class loader
* that create this thread may reject loading / be unloaded all classes
* during the JVM is terminating.
*
* Thus we need to clone the current class loader in advance to
* guarantee this thread are able to use the JAF/JavaMail or other class
* when executing. This is achieved by setting the context class loader in this thread.
*/
ClassLoader threadCtxClassLoader = this.copyClassLoader();
/*
* Okay now we have cloned the class loader, then let create the shutdown hook
* through reflection API so that the hook is loaded through the class loader
* cloned in the previous step.
*/
Class [] argsType = {String.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class, Boolean.TYPE};
Object[] initargsType = {protocol, host, username, password, from, tos, ccs, subject, verbose};
Instance shutdownThreadIns = new Instance(
ShutdownHookEmailThread.class.getName(),
threadCtxClassLoader,
argsType,
initargsType);
Thread shutdownThread = (Thread) shutdownThreadIns.getObject();
this.getLogger().debug("[SHUTDOWN] Email Hook: Delegating classloader: " + threadCtxClassLoader);
/*
* IMPORTANT, The context class loader must match with the class loader that load the
* shutdownHookEmailThread. Otherwise it cause numerous error throwing because class
* loading conflict (i.e. same class-name with different class loader).
*/
shutdownThread.setContextClassLoader(threadCtxClassLoader);
return shutdownThread;
}
/*
* Clone a class loader from the current class loader. Return the identical
* if the current class loader is not a URL class loader.
*
* TODO: re-factor to provide utility function to copy class-loader.
*/
private ClassLoader copyClassLoader()
{
ClassLoader ret = null;
ClassLoader cl = this.getClass().getClassLoader();
if (cl instanceof URLClassLoader)
{
URLClassLoader ucl = (URLClassLoader) cl;
ret = URLClassLoader.newInstance(ucl.getURLs(), ucl.getParent());
}
else
{
ret = cl;
}
return ret;
// Old implementation
/*StringBuilder sb = new StringBuilder();
sb.append('.').append(File.pathSeparatorChar);
ClassLoader cl = this.getClass().getClassLoader();
if (cl instanceof URLClassLoader)
{
URLClassLoader ucl = (URLClassLoader) cl;
URL[] urlclasspath = ucl.getURLs();
threadCtxClassLoader = URLClassLoader.newInstance(urlclasspath, ucl.getParent());
for (URL u : urlclasspath)
{
sb.append(u.getFile()).append(File.pathSeparatorChar);
}
}*/
}
}
/*
* Keep the POC development until the module is claimed as fully reliable.
*/
/*
public void run()
{
try
{
ProcessBuilder pb = new ProcessBuilder("java", "-cp", this.processClasspath, ShutdownHookEmailThread.class.getName());
Map<String, String> env = pb.environment();
env.put("SHOOK_USERNAME", username);
env.put("SHOOK_PASSWORD", password);
env.put("SHOOK_FROM" , from);
env.put("SHOOK_TO" , tos);
env.put("SHOOK_CC" , ccs);
env.put("SHOOK_SUBJECT" , subject);
env.put("SHOOK_PROTOCOL", protocol);
env.put("SHOOK_HOST" , host);
pb.redirectErrorStream(true);
try
{
Process p = pb.start();
byte[] bs = IOHandler.readBytes(p.getInputStream());
System.out.println(new String(bs, "UTF-8"));
p.waitFor();
p.destroy();
}
catch(IOException ioex)
{
ioex.printStackTrace();
}
catch(InterruptedException inex)
{
inex.printStackTrace();
}
}
catch(Throwable t)
{
t.printStackTrace();
}
}
};*/
/*System.out.println("Installed Signal Handler..");
SignalHandlerExample.install("TERM");
SignalHandlerExample.install("INT");
SignalHandlerExample.install("ABRT");
System.out.println("Installed Signal Handler Done..");*/
//Runtime.getRuntime().addShutdownHook(shutdownThread);
//System.exit(0);
//SignalHandlerExample.install("QUIT");
//System.out.println("Register shutdown hook");
/*
try
{
Class c2 = Class.forName("hk.hku.cecid.piazza.commons.util.DateUtil", true, ret);
System.out.printf("c2=%s\n", c2.newInstance().getClass().getClassLoader());
//System.out.println(ShutdownHookEmailThread.class.getName());
//System.out.println(ret);
Class c = ret.loadClass("hk.hku.cecid.piazza.commons.module.ShutdownHookEmailModule$ShutdownHookEmailThread");
System.out.println(c.getClassLoader());
Instance ins = new Instance(
"hk.hku.cecid.piazza.commons.module.ShutdownHookEmailModule$ShutdownHookEmailThread", ret,
new Class[]{String.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class},
new Object[]{protocol, host, username, password, from, tos, ccs, subject});
Thread t = (Thread) ins.getObject();
System.out.println(t.getClass().getClassLoader());
//Runtime.getRuntime().addShutdownHook(t);
}
catch(Throwable t)
{
t.printStackTrace();
}
//new ShutdownHookEmailThread(protocol, host, username, password, from, tos, ccs, subject)
*/