// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. package jogamp.plugin.jnlp; import static net.sourceforge.jnlp.runtime.Translator.R; import java.io.File; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; import jogamp.applet.App3Context; import jogamp.plugin.jnlp.runtime.Applet3Instance; import com.jogamp.plugin.applet.Applet3; import net.sourceforge.jnlp.AppletDesc; import net.sourceforge.jnlp.ApplicationDesc; import net.sourceforge.jnlp.JARDesc; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.LaunchException; import net.sourceforge.jnlp.LaunchHandler; import net.sourceforge.jnlp.ParserSettings; import net.sourceforge.jnlp.PluginBridge; import net.sourceforge.jnlp.PropertyDesc; import net.sourceforge.jnlp.ResourcesDesc; import net.sourceforge.jnlp.StreamEater; import net.sourceforge.jnlp.util.JarFile; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.UpdatePolicy; import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.runtime.JNLPClassLoader; import net.sourceforge.jnlp.services.InstanceExistsException; import net.sourceforge.jnlp.services.ServiceUtil; import net.sourceforge.jnlp.splashscreen.SplashUtils; import net.sourceforge.jnlp.util.logging.OutputController; /** * Launches JNLPFiles either in the foreground or background.

* * An optional LaunchHandler can be specified that is notified of * warning and error condition while launching and that indicates * whether a launch may proceed after a warning has occurred. If * specified, the LaunchHandler is notified regardless of whether * the file is launched in the foreground or background.

* * @author Jon A. Maxwell (JAM) - initial author * @version $Revision: 1.22 $ */ public class App3Launcher { // defines class Launcher.BgRunner, Launcher.TgThread /** shared thread group */ /*package*/static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup")); public static ThreadGroup getMainGroup() { return mainGroup; } /** the handler */ private LaunchHandler handler = null; /** the update policy */ private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy(); /** whether to create an AppContext (if possible) */ private boolean context = true; /** If the application should call JNLPRuntime.exit on fatal errors */ private boolean exitOnFailure = true; private ParserSettings parserSettings = new ParserSettings(); private Map extra = null; /** * Create a launcher with the runtime's default update policy * and launch handler. */ public App3Launcher() { this(null, null); if (handler == null) { handler = JNLPRuntime.getDefaultLaunchHandler(); } } /** * Create a launcher with the runtime's default update policy * and launch handler. * * @param exitOnFailure Exit if there is an error (usually default, but false when being used from the plugin) */ public App3Launcher(boolean exitOnFailure) { this(null, null); if (handler == null) { handler = JNLPRuntime.getDefaultLaunchHandler(); } this.exitOnFailure = exitOnFailure; } /** * Create a launcher with the specified handler and the * runtime's default update policy. * * @param handler the handler to use or null for no handler. */ public App3Launcher(LaunchHandler handler) { this(handler, null); } /** * Create a launcher with an optional handler using the * specified update policy and launch handler. * * @param handler the handler to use or null for no handler. * @param policy the update policy to use or null for default policy. */ public App3Launcher(LaunchHandler handler, UpdatePolicy policy) { if (policy == null) policy = JNLPRuntime.getDefaultUpdatePolicy(); this.handler = handler; this.updatePolicy = policy; } /** * Sets the update policy used by launched applications. */ public void setUpdatePolicy(UpdatePolicy policy) { if (policy == null) { throw new IllegalArgumentException(R("LNullUpdatePolicy")); } this.updatePolicy = policy; } /** * Returns the update policy used when launching applications. */ public UpdatePolicy getUpdatePolicy() { return updatePolicy; } /** * Sets whether to launch the application in a new AppContext * (a separate event queue, look and feel, etc). If the * sun.awt.SunToolkit class is not present then this method * has no effect. The default value is true. */ public void setCreateAppContext(boolean context) { this.context = context; } /** * Returns whether applications are launched in their own * AppContext. */ public boolean isCreateAppContext() { return this.context; } /** * Set the parser settings to use when the Launcher initiates parsing of * a JNLP file. * @param settings */ public void setParserSettings(ParserSettings settings) { parserSettings = settings; } /** * Set a map to use when trying to extract extra information, including * arguments, properties and parameters, to be merged into the main JNLP * @param input a map containing extra information to add to the main JNLP. * the values for keys "arguments", "parameters", and "properties" are * used. */ public void setInformationToMerge(Map input) { this.extra = input; } /** * Launches a JNLP file by calling the launch method for the * appropriate file type. The application will be started in * a new window. * * @param file the JNLP file to launch * @return the application instance * @throws LaunchException if an error occurred while launching (also sent to handler) */ public ApplicationInstance launch(JNLPFile file) throws LaunchException { TgThread tg; mergeExtraInformation(file, extra); JNLPRuntime.markNetxRunning(); //First checks whether offline-allowed tag is specified inside the jnlp //file. if (!file.getInformation().isOfflineAllowed()) { try { //Checks the offline/online status of the system. //If system is offline do not launch. InetAddress.getByName(file.getSourceLocation().getHost()); } catch (UnknownHostException ue) { OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "File cannot be launched because offline-allowed tag not specified and system currently offline."); return null; } catch (Exception e) { OutputController.getLogger().log(e); } } if (file instanceof PluginBridge) { tg = new TgThread(file, true); } else { tg = new TgThread(file); } tg.start(); try { tg.join(); } catch (InterruptedException ex) { //By default, null is thrown here, and the message dialog is shown. throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo"))); } if (tg.getException() != null) { throw tg.getException(); } // passed to handler when first created if (handler != null) { // FIXME handler.launchCompleted(tg.getApplication()); } return tg.getApplication(); } /** * Launches a JNLP file by calling the launch method for the * appropriate file type. * * @param location the URL of the JNLP file to launch * location to get the pristine version * @throws LaunchException if there was an exception * @return the application instance */ public ApplicationInstance launch(URL location) throws LaunchException { return launch(fromUrl(location)); } /** * Merges extra information into the jnlp file * * @param file the JNLPFile * @param extra extra information to merge into the JNLP file * @throws LaunchException if an exception occurs while extracting * extra information */ private void mergeExtraInformation(JNLPFile file, Map extra) throws LaunchException { if (extra == null) { return; } String[] properties = extra.get("properties"); if (properties != null) { addProperties(file, properties); } String[] arguments = extra.get("arguments"); if (arguments != null && file.isApplication()) { addArguments(file, arguments); } String[] parameters = extra.get("parameters"); if (parameters != null && file.isApplet()) { addParameters(file, parameters); } } /** * Add the properties to the JNLP file. * @throws LaunchException if an exception occurs while extracting * extra information */ private void addProperties(JNLPFile file, String[] props) throws LaunchException { ResourcesDesc resources = file.getResources(); for (int i = 0; i < props.length; i++) { // allows empty property, not sure about validity of that. int equals = props[i].indexOf("="); if (equals == -1) { throw launchError(new LaunchException(R("BBadProp", props[i]))); } String key = props[i].substring(0, equals); String value = props[i].substring(equals + 1, props[i].length()); resources.addResource(new PropertyDesc(key, value)); } } /** * Add the params to the JNLP file; only call if file is * actually an applet file. * @throws LaunchException if an exception occurs while extracting * extra information */ private void addParameters(JNLPFile file, String[] params) throws LaunchException { AppletDesc applet = file.getApplet(); for (int i = 0; i < params.length; i++) { // allows empty param, not sure about validity of that. int equals = params[i].indexOf("="); if (equals == -1) { throw launchError(new LaunchException(R("BBadParam", params[i]))); } String name = params[i].substring(0, equals); String value = params[i].substring(equals + 1, params[i].length()); applet.addParameter(name, value); } } /** * Add the arguments to the JNLP file; only call if file is * actually an application (not installer). */ private void addArguments(JNLPFile file, String[] args) { ApplicationDesc app = file.getApplication(); for (int i = 0; i < args.length; i++) { app.addArgument(args[i]); } } /** * Launches the JNLP file in a new JVM instance. The launched * application's output is sent to the system out and it's * standard input channel is closed. * * @param vmArgs the arguments to pass to the new JVM. Can be empty but * must not be null. * @param file the JNLP file to launch * @param javawsArgs the arguments to pass to the javaws command. Can be * an empty list but must not be null. * @throws LaunchException if there was an exception */ public void launchExternal(List vmArgs, JNLPFile file, List javawsArgs) throws LaunchException { List updatedArgs = new LinkedList(javawsArgs); if (file.getFileLocation() != null) { updatedArgs.add(file.getFileLocation().toString()); } else if (file.getSourceLocation() != null) { updatedArgs.add(file.getFileLocation().toString()); } else { launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo"))); } launchExternal(vmArgs, updatedArgs); } /** * Launches the JNLP file in a new JVM instance. The launched * application's output is sent to the system out and it's * standard input channel is closed. * * @param url the URL of the JNLP file to launch * @throws LaunchException if there was an exception */ public void launchExternal(URL url) throws LaunchException { List javawsArgs = new LinkedList(); javawsArgs.add(url.toString()); launchExternal(new LinkedList(), javawsArgs); } /** * Launches the JNLP file at the specified location in a new JVM * instance. The launched application's output is sent to the * system out and it's standard input channel is closed. * @param vmArgs the arguments to pass to the jvm * @param javawsArgs the arguments to pass to javaws (aka Netx) * @throws LaunchException if there was an exception */ public void launchExternal(List vmArgs, List javawsArgs) throws LaunchException { try { List commands = new LinkedList(); // this property is set by the javaws launcher to point to the javaws binary String pathToWebstartBinary = System.getProperty("icedtea-web.bin.location"); commands.add(pathToWebstartBinary); // use -Jargument format to pass arguments to the JVM through the launcher for (String arg : vmArgs) { commands.add("-J" + arg); } commands.addAll(javawsArgs); String[] command = commands.toArray(new String[] {}); Process p = Runtime.getRuntime().exec(command); new StreamEater(p.getErrorStream()).start(); new StreamEater(p.getInputStream()).start(); p.getOutputStream().close(); } catch (NullPointerException ex) { throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo"))); } catch (Exception ex) { throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); } } /** * Returns the JNLPFile for the URL, with error handling. */ private JNLPFile fromUrl(URL location) throws LaunchException { try { JNLPFile file = new JNLPFile(location, parserSettings); boolean isLocal = false; boolean haveHref = false; if ("file".equalsIgnoreCase(location.getProtocol()) && new File(location.getFile()).exists()) { isLocal = true; } if (file.getSourceLocation() != null) { haveHref = true; } if (isLocal && haveHref) { file = new JNLPFile(file.getSourceLocation(), parserSettings); } return file; } catch (Exception ex) { if (ex instanceof LaunchException) { throw (LaunchException) ex; // already sent to handler when first thrown } else { // IO and Parse throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo"))); } } } /** * Launches a JNLP application. This method should be called * from a thread in the application's thread group. */ protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException { if (!file.isApplication()) { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo"))); } try { try { ServiceUtil.checkExistingSingleInstance(file); } catch (InstanceExistsException e) { OutputController.getLogger().log("Single instance application is already running."); return null; } if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) { List netxArguments = new LinkedList(); netxArguments.add("-Xnofork"); netxArguments.addAll(JNLPRuntime.getInitialArguments()); launchExternal(file.getNewVMArgs(), netxArguments); return null; } handler.launchInitialized(file); ApplicationInstance app = createApplication(file); app.initialize(); String mainName = file.getApplication().getMainClass(); // When the application-desc field is empty, we should take a // look at the main jar for the main class. if (mainName == null) { JARDesc mainJarDesc = file.getResources().getMainJAR(); File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); if (f != null) { final JarFile mainJar = new JarFile(f); try { mainName = mainJar.getManifest().getMainAttributes().getValue("Main-Class"); } finally { mainJar.close(); } } } if (mainName == null) { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass"), R("LCantDetermineMainClassInfo"))); } Class mainClass = app.getClassLoader().loadClass(mainName); Method main = mainClass.getMethod("main", new Class[] { String[].class }); String args[] = file.getApplication().getArguments(); setContextClassLoaderForAllThreads(app.getThreadGroup(), app.getClassLoader()); handler.launchStarting(app); main.setAccessible(true); OutputController.getLogger().log("Invoking main() with args: " + Arrays.toString(args)); main.invoke(null, new Object[] { args }); return app; } catch (LaunchException lex) { throw launchError(lex); } catch (Exception ex) { throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); } } /** * Set the classloader as the context classloader for all threads in * the given threadgroup. This is required to make some applications * work. For example, an application that provides a custom Swing LnF * may ask the swing thread to load resources from their JNLP, which * would only work if the Swing thread knows about the JNLPClassLoader. * * @param tg The threadgroup for which the context classloader should be set * @param classLoader the classloader to set as the context classloader */ private void setContextClassLoaderForAllThreads(ThreadGroup tg, ClassLoader classLoader) { /* be prepared for change in thread size */ int threadCountGuess = tg.activeCount(); Thread[] threads; do { threadCountGuess = threadCountGuess * 2; threads = new Thread[threadCountGuess]; tg.enumerate(threads, true); } while (threads[threadCountGuess - 1] != null); for (Thread thread : threads) { if (thread != null) { OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Setting " + classLoader + " as the classloader for thread " + thread.getName()); thread.setContextClassLoader(classLoader); } } } /** * Launches a JNLP applet. This method should be called from a * thread in the application's thread group.

* * The enableCodeBase parameter adds the applet's codebase to * the locations searched for resources and classes. This can * slow down the applet loading but allows browser-style applets * that don't use JAR files exclusively to be run from a applet * JNLP file. If the applet JNLP file does not specify any * resources then the code base will be enabled regardless of * the specified value.

* * @param file the JNLP file * @param enableCodeBase whether to add the codebase URL to the classloader */ protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { if (!file.isApplet()) { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); } if (handler != null) { handler.launchInitialized(file); } Applet3Instance applet = null; try { ServiceUtil.checkExistingSingleInstance(file); applet = createApplet(file, enableCodeBase); applet.initialize(); applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance return applet; } catch (InstanceExistsException ieex) { OutputController.getLogger().log("Single instance applet is already running."); throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists")), applet); } catch (LaunchException lex) { throw launchError(lex, applet); } catch (Exception ex) { throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")), applet); }finally{ if (handler != null) { handler.launchStarting(applet); } } } /** * Gets an ApplicationInstance, but does not launch the applet. */ protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { if (!file.isApplet()) { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); } Applet3Instance applet = null; try { ServiceUtil.checkExistingSingleInstance(file); applet = createApplet(file, enableCodeBase); applet.initialize(); return applet; } catch (InstanceExistsException ieex) { OutputController.getLogger().log("Single instance applet is already running."); throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists")), applet); } catch (LaunchException lex) { throw launchError(lex, applet); } catch (Exception ex) { throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")), applet); } } /** * Launches a JNLP installer. This method should be called from * a thread in the application's thread group. */ protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException { // TODO Check for an existing single instance once implemented. // ServiceUtil.checkExistingSingleInstance(file); throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo"))); } /** * Create an AppletInstance. * * @param enableCodeBase whether to add the code base URL to the classloader */ //FIXME - when multiple applets are on one page, this method is visited simultaneously //and then appelts creates in little bit strange manner. This issue is visible with //randomly showing/notshowing spalshscreens. //See also PluginAppletViewer.framePanel protected Applet3Instance createApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { Applet3Instance appletInstance = null; try { JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); if (enableCodeBase) { loader.enableCodeBase(); } else if (file.getResources().getJARs().length == 0) { throw new ClassNotFoundException("Can't do a codebase look up and there are no jars. Failing sooner rather than later"); } ThreadGroup group = Thread.currentThread().getThreadGroup(); // appletInstance is needed by ServiceManager when looking up // services. This could potentially be done in applet constructor // so initialize appletInstance before creating applet. appletInstance = new Applet3Instance(file, group, loader, null); loader.setApplication(appletInstance); // Initialize applet now that ServiceManager has access to its // appletInstance. String appletName = file.getApplet().getMainClass(); Class appletClass = loader.loadClass(appletName); Applet3 applet = (Applet3) appletClass.newInstance(); // Finish setting up appletInstance. appletInstance.setApplet(applet); appletInstance.getAppletEnvironment().setApplet(applet); setContextClassLoaderForAllThreads(appletInstance.getThreadGroup(), appletInstance.getClassLoader()); return appletInstance; } catch (Exception ex) { throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")), appletInstance); } } /** * Creates an Applet object from a JNLPFile. This is mainly to be used with * gcjwebplugin. * @param file the PluginBridge to be used. * @param enableCodeBase whether to add the code base URL to the classloader. */ protected Applet3 createAppletObject(JNLPFile file, boolean enableCodeBase) throws LaunchException { try { JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); if (enableCodeBase) { loader.enableCodeBase(); } else if (file.getResources().getJARs().length == 0) { throw new ClassNotFoundException("Can't do a codebase look up and there are no jars. Failing sooner rather than later"); } String appletName = file.getApplet().getMainClass(); Class appletClass = loader.loadClass(appletName); Applet3 applet = (Applet3) appletClass.newInstance(); return applet; } catch (Exception ex) { throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); } } /** * Creates an Application. */ protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException { try { JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); ThreadGroup group = Thread.currentThread().getThreadGroup(); ApplicationInstance app = new ApplicationInstance(file, group, loader, App3Context.getAppContext()); loader.setApplication(app); return app; } catch (Exception ex) { throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo")); } } /** * Create a thread group for the JNLP file. * * Note: if the JNLPFile is an applet (ie it is a subclass of PluginBridge) * then this method simply returns the existing ThreadGroup. The applet * ThreadGroup has to be created at an earlier point in the applet code. */ protected ThreadGroup createThreadGroup(JNLPFile file) { final ThreadGroup tg; if (file instanceof PluginBridge) { tg = Thread.currentThread().getThreadGroup(); } else { tg = new ThreadGroup(mainGroup, file.getTitle()); } return tg; } /** * Send n launch error to the handler, if set, and also to the * caller. */ private LaunchException launchError(LaunchException ex) { return launchError(ex, null); } private LaunchException launchError(LaunchException ex, Applet3Instance applet) { if (applet != null) { SplashUtils.showErrorCaught(ex, applet); } if (handler != null) { handler.launchError(ex); } return ex; } /** * Send a launch error to the handler, if set, and to the * caller only if the handler indicated that the launch should * continue despite the warning. * * @return an exception to throw if the launch should be aborted, or null otherwise */ private LaunchException launchWarning(LaunchException ex) { if (handler != null) { if (!handler.launchWarning(ex)) // no need to destroy the app b/c it hasn't started return ex; } // chose to abort return null; // chose to continue, or no handler } /** * This runnable is used to call the appropriate launch method * for the application, applet, or installer in its thread group. */ private class TgThread extends Thread { // ThreadGroupThread private JNLPFile file; private ApplicationInstance application; private LaunchException exception; private boolean isPlugin = false; TgThread(JNLPFile file) { super(createThreadGroup(file), file.getTitle()); this.file = file; } TgThread(JNLPFile file, boolean isPlugin) { super(createThreadGroup(file), file.getTitle()); this.file = file; this.isPlugin = isPlugin; } @Override public void run() { try { // Do not create new AppContext if we're using NetX and icedteaplugin. // The plugin needs an AppContext too, but it has to be created earlier. /** FIXME: Non plugin context if (context && !isPlugin) { SunToolkit.createNewAppContext(); } doPerApplicationAppContextHacks(); */ if (isPlugin) { // Do not display download indicators if we're using gcjwebplugin. JNLPRuntime.setDefaultDownloadIndicator(null); application = getApplet(file, ((PluginBridge)file).codeBaseLookup()); } else { if (file.isApplication()) { application = launchApplication(file); } else if (file.isApplet()) { application = launchApplet(file, true); } // enable applet code base else if (file.isInstaller()) { application = launchInstaller(file); } else { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotLaunchable"), R("LNotLaunchableInfo"))); } } } catch (LaunchException ex) { OutputController.getLogger().log(OutputController.Level.ERROR_ALL, ex); exception = ex; // Exit if we can't launch the application. if (exitOnFailure) { JNLPRuntime.exit(1); } } catch (Throwable ex) { OutputController.getLogger().log(OutputController.Level.ERROR_ALL, ex); throw new RuntimeException(ex); } } public LaunchException getException() { return exception; } public ApplicationInstance getApplication() { return application; } }; }