/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.gebit.integrity.runner.fixtures; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.google.inject.Inject; import de.gebit.integrity.fixtures.FixtureMethod; import de.gebit.integrity.fixtures.FixtureParameter; /** * Generic fixture usable to start, check and kill a Java application class with a static main method. * * @author Rene Schneider - initial API and implementation * */ public class JavaApplicationLaunchFixture { /** * The classloader to use. */ @Inject protected ClassLoader classLoader; /** * The fixture will wait this number of milliseconds before first checking whether an exception occurred during that * time. */ protected static final int WRAPPER_STARTUP_TIME = 1000; /** * The fixture will wait this number of milliseconds for the application thread to die. */ protected static final int WRAPPER_KILL_TIME = 20000; /** * The last application that was started is stored here. If the fixture methods are called without the * {@link ApplicationWrapper} parameter, this instance is used. */ protected static ApplicationWrapper lastApplication; /** * Launches the provided application by calling the static main method of the application class. * * @param aMainClassName * the name of the application class * @param someArguments * the arguments to provide to the main method * @return an application wrapper instance which can optionally be saved to handle multiple applications * @throws Throwable */ @FixtureMethod(description = "Launch Java Application: $mainClass$") public ApplicationWrapper launch(@FixtureParameter(name = "mainClass") String aMainClassName, @FixtureParameter(name = "arguments") String[] someArguments) throws Throwable { return launchInternal(aMainClassName, someArguments); } /** * Launches the provided main class by calling its main method with the provided arguments. * * @param aMainClassName * the main class name * @param someArguments * the arguments * @return the launched application, contained in an {@link ApplicationWrapper} instance * @throws Throwable * if something goes wrong */ protected ApplicationWrapper launchInternal(String aMainClassName, String[] someArguments) throws Throwable { if (aMainClassName == null) { throw new IllegalArgumentException("A class name has to be provided."); } Class<?> tempMainClass = classLoader.loadClass(aMainClassName); Method tempMainMethod = tempMainClass.getMethod("main", new Class[] { String[].class }); String[] tempArguments = someArguments != null ? someArguments : new String[0]; ApplicationWrapper tempApplication = launchMain(tempMainClass, tempMainMethod, tempArguments); lastApplication = tempApplication; return tempApplication; } /** * Checks whether the provided application (if none was provided, the last started application is used) is still * alive. * * @param aWrapper * the application to check (if null, the last started application is used) * @return */ @FixtureMethod(description = "Check if the application is still alive") public boolean isAlive(@FixtureParameter(name = "application") ApplicationWrapper aWrapper) { return isAliveInternal(aWrapper); } /** * Performs a liveliness check on the given application (or the last started one if none is provided). * * @param aWrapper * the application to check * @return true if alive, false if not */ protected boolean isAliveInternal(ApplicationWrapper aWrapper) { ApplicationWrapper tempWrapper = aWrapper != null ? aWrapper : lastApplication; if (tempWrapper == null) { throw new IllegalArgumentException("No application was provided, and none was stored statically"); } return tempWrapper.isAlive(); } /** * Kills the provided application (or the last started one, if none is explicitly provided). * * @param aWrapper * the application to kill * @return true if killing was successful, false otherwise */ @FixtureMethod(description = "Kills the application") public boolean kill(@FixtureParameter(name = "application") ApplicationWrapper aWrapper) { return killInternal(aWrapper); } /** * Actually kills the provided application (or the last started one, if none is explicitly provided). * * @param aWrapper * the application to kill * @return true if killing was successful, false otherwise */ // Deprecation is known, but there's no other way in this case... @SuppressWarnings("deprecation") protected boolean killInternal(ApplicationWrapper aWrapper) { ApplicationWrapper tempWrapper = aWrapper != null ? aWrapper : lastApplication; if (tempWrapper == null) { throw new IllegalArgumentException("No application was provided, and none was stored statically"); } if (!tempWrapper.isAlive()) { // The application is already dead return true; } tempWrapper.stop(); try { tempWrapper.join(WRAPPER_KILL_TIME); } catch (InterruptedException exc) { // ignored } return !tempWrapper.isAlive(); } /** * Here the main method in the given class is actually launched and the wrapper is created. * * @param aMainClass * the main class of the application * @param aMainMethod * the main method * @param someArguments * arguments to be provided to the call * @return the resulting application wrapper * @throws Throwable * if the shit hits the fan */ protected ApplicationWrapper launchMain(Class<?> aMainClass, Method aMainMethod, String[] someArguments) throws Throwable { ApplicationWrapper tempWrapper = new ApplicationWrapper(aMainClass, aMainMethod, someArguments); tempWrapper.start(); try { Thread.sleep(WRAPPER_STARTUP_TIME); } catch (InterruptedException exc) { // ignored } if (!checkWrapper(tempWrapper)) { throw new RuntimeException("The application has not been started successfully"); } return tempWrapper; } /** * Checks whether a provided application wrapper is considered alive. * * @param aWrapper * the wrapped application to check * @return true if it is alive, false otherwise * @throws Throwable */ protected boolean checkWrapper(ApplicationWrapper aWrapper) throws Throwable { if (!aWrapper.isAlive()) { Throwable tempException = aWrapper.getException(); if (tempException != null) { throw new RuntimeException("Failed to invoke application main method", tempException); } else { throw new RuntimeException("The application died immediately"); } } return true; } /** * This wrapper thread is used to run the actual application. * * * @author Rene Schneider - initial API and implementation * */ public class ApplicationWrapper extends Thread { /** * The main class of the application. */ private Class<?> mainClass; /** * The main method. */ private Method mainMethod; /** * The arguments provided to the main method call. */ private String[] arguments; /** * Any exception which has been thrown. */ private Throwable exception; /** * Creates an instance. */ public ApplicationWrapper(Class<?> aMainClass, Method aMainMethod, String[] someArguments) { mainClass = aMainClass; mainMethod = aMainMethod; arguments = someArguments; } @Override public String toString() { return "Wrapper around " + mainClass.getName(); } public Throwable getException() { return exception; } @Override public void run() { try { mainMethod.invoke(null, (Object) arguments); } catch (InvocationTargetException exc) { if (exc.getCause() instanceof ThreadDeath) { // this one is captured and ignored, since it is expected to occur if kill() is called } else { exc.printStackTrace(); exception = exc; } } catch (Throwable exc) { exc.printStackTrace(); exception = exc; } } } }