/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.core.concurrent;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import org.opennms.core.fiber.Fiber;
/**
* <p>VMTaskFiber class.</p>
*
* @author <A HREF="mailto:weave@oculan.com">Brian Weaver </A>
*/
public class VMTaskFiber implements Fiber, Runnable {
/**
* The name of the entry method. This is the same as it is for the JVM,
* which is <code>main</code>.
*/
private static final String MAIN_METHOD_NAME = "main";
/**
* The list of classes that are passed as entry arguments.
*/
private static final String MAIN_PARAMETER_TYPES[] = { "[Ljava.lang.String;" };
/**
* The return type for the entry method.
*/
private static final String MAIN_RETURN_TYPE = "void";
/**
* The name prefixed to the task name to form the name for the thread group.
*/
private static final String THREADGROUP_NAME_PREFIX = "TaskGroup:";
/**
* The name of the VM task.
*/
private String m_taskName;
/**
* The thread group for the task.
*/
private ThreadGroup m_thrGroup;
/**
* The class loader used to resolve classes for the thread group.
*/
private ClassLoader m_classLoader;
/**
* The entry class.
*/
private Class<?> m_entryClass;
/**
* The entry method.
*/
private Method m_entryMethod;
/**
* The entry arguments.
*/
private String[] m_mainArgs;
/**
* The fiber's status.
*/
private int m_fiberStatus;
/**
* <P>
* This method attempts to find the method with the signature <EM>public
* static void main(String[])</EM> if it is part of the passed class. The
* first matching method is returned to the caller.
* </P>
*
* @param c
* The class to search for the main method.
*
* @return The matching method if one is found. If one is not found then a
* null is returned.
*
*/
private static Method findMain(Class<?> c) {
Method[] methods = c.getMethods();
for (int i = 0; i < methods.length; i++) {
Class<?>[] args = methods[i].getParameterTypes();
Class<?> retType = methods[i].getReturnType();
int modifiers = methods[i].getModifiers();
boolean validModifiers = Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers);
if (validModifiers && methods[i].getName().equals(MAIN_METHOD_NAME) && args.length == MAIN_PARAMETER_TYPES.length && retType.getName().equals(MAIN_RETURN_TYPE)) {
// do a looping check to figure out if it
// is the correct ordering of parameters.
//
boolean isOK = true;
for (int x = 0; isOK && x < args.length; x++) {
if (args[x].getName().equals(MAIN_PARAMETER_TYPES[x]) == false)
isOK = false;
}
// it has all the qualifications of being
//
// public static void main(String[] args)
//
if (isOK)
return methods[i];
}
}
return null;
}
/**
* Constructs a new Virtual Macine Task Fiber. The task has a name and is
* passed all the information to invoke the class' main method. When the
* class is loaded it is allocated a new class loader used to locate all of
* it's resources.
*
* @param taskName
* The name of the task
* @param entryClassName
* The name of the entry class.
* @param entryArguments
* The String array passed to main.
* @param searchPaths
* The URL's used to locate resources and classes.
* @throws java.lang.ClassNotFoundException
* Thrown if the entry class is not found.
* @throws java.lang.NoSuchMethodException
* Thrown if the <code>main</code> is not found on the entry
* class.
* @see java.net.URLClassLoader
*/
public VMTaskFiber(String taskName, String entryClassName, String[] entryArguments, URL[] searchPaths) throws ClassNotFoundException, NoSuchMethodException
{
m_taskName = taskName;
m_mainArgs = entryArguments;
m_thrGroup = new ThreadGroup(THREADGROUP_NAME_PREFIX + m_taskName);
m_thrGroup.setDaemon(false);
m_classLoader = new URLClassLoader(searchPaths);
m_entryClass = m_classLoader.loadClass(entryClassName);
m_entryMethod = findMain(m_entryClass);
if (m_entryMethod == null)
throw new NoSuchMethodException("main() method not found for class " + entryClassName);
m_fiberStatus = START_PENDING;
}
/**
* This method invokes the entry method on the main class. The method is
* called after the internal thread starts up, and returns when the entry
* method's thread exits.
*/
public void run() {
Object[] passedArgs = new Object[1];
passedArgs[0] = m_mainArgs;
// Now actually call the entry point method with the
// correct arguments. This should kick start the service
// it may or may not return before the service exits.
//
synchronized (this) {
m_fiberStatus = RUNNING;
}
try {
m_entryMethod.invoke(null, passedArgs);
} catch (Throwable t) {
// do nothing
} finally {
synchronized (this) {
m_fiberStatus = STOPPED;
}
}
}
/**
* Starts the current fiber running.
*/
public synchronized void start() {
m_fiberStatus = STARTING;
Thread t = new Thread(m_thrGroup, this, m_taskName + "-main");
t.setDaemon(false);
t.setContextClassLoader(m_classLoader);
t.start();
}
/**
* Stops the current fiber. Since the JVM does not provide way to kill
* threads, the thread group is interrupted and the status is set to
* <code>STOP_PENDING</code>. When the main thread exits then the service
* is considered stopped!
*/
public synchronized void stop() {
if (m_fiberStatus != STOPPED)
m_fiberStatus = STOP_PENDING;
m_thrGroup.interrupt();
}
/**
* Returns the current status of the fiber.
*
* @return The current status of the fiber.
*/
public synchronized int getStatus() {
return m_fiberStatus;
}
/**
* Returns the name for the virtual machine task.
*
* @return The VM Task's name.
*/
public String getName() {
return m_taskName;
}
}