// 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 net.sourceforge.jnlp; import static net.sourceforge.jnlp.runtime.Translator.R; import java.applet.Applet; import java.awt.Container; 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 java.util.jar.JarFile; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.UpdatePolicy; import net.sourceforge.jnlp.runtime.AppletInstance; import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPClassLoader; import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.services.InstanceExistsException; import net.sourceforge.jnlp.services.ServiceUtil; import javax.swing.SwingUtilities; import javax.swing.text.html.parser.ParserDelegator; import sun.awt.SunToolkit; /** * 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 Launcher {
// defines class Launcher.BgRunner, Launcher.TgThread
/** shared thread group */
/*package*/static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup"));
/** 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 System.exit on fatal errors */
private boolean exitOnFailure = true;
private ParserSettings parserSettings = new ParserSettings();
private Map
*
* 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, Container cont) throws LaunchException {
if (!file.isApplet())
throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
try {
AppletInstance applet = createApplet(file, enableCodeBase, cont);
applet.initialize();
applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance
return applet;
} catch (LaunchException lex) {
throw launchError(lex);
} catch (Exception ex) {
throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
}
}
/**
* Gets an ApplicationInstance, but does not launch the applet.
*/
protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
if (!file.isApplet())
throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
try {
AppletInstance applet = createApplet(file, enableCodeBase, cont);
applet.initialize();
return applet;
} catch (LaunchException lex) {
throw launchError(lex);
} catch (Exception ex) {
throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
}
}
/**
* 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 {
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
*/
protected AppletInstance createApplet(JNLPFile file, boolean enableCodeBase, Container cont) 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");
}
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 appletInstance;
if (cont == null)
appletInstance = new AppletInstance(file, group, loader, null);
else
appletInstance = new AppletInstance(file, group, loader, null, cont);
loader.setApplication(appletInstance);
// Initialize applet now that ServiceManager has access to its
// appletInstance.
String appletName = file.getApplet().getMainClass();
Class appletClass = loader.loadClass(appletName);
Applet applet = (Applet) 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")));
}
}
/**
* 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 Applet createAppletObject(JNLPFile file, boolean enableCodeBase, Container cont) 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);
Applet applet = (Applet) 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);
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) {
ThreadGroup tg = null;
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) {
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
}
/**
* Do hacks on per-application level to allow different AppContexts to work
*
* @see JNLPRuntime#doMainAppContextHacks
*/
private static void doPerApplicationAppContextHacks() {
/*
* With OpenJDK6 (but not with 7) a per-AppContext dtd is maintained.
* This dtd is created by the ParserDelgate. However, the code in
* HTMLEditorKit (used to render HTML in labels and textpanes) creates
* the ParserDelegate only if there are no existing ParserDelegates. The
* result is that all other AppContexts see a null dtd.
*/
new ParserDelegator();
}
/**
* 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 Container cont;
private boolean isPlugin = false;
TgThread(JNLPFile file) {
this(file, null);
}
TgThread(JNLPFile file, Container cont) {
super(createThreadGroup(file), file.getTitle());
this.file = file;
this.cont = cont;
}
TgThread(JNLPFile file, Container cont, boolean isPlugin) {
super(createThreadGroup(file), file.getTitle());
this.file = file;
this.cont = cont;
this.isPlugin = isPlugin;
}
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.
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(), cont);
} else {
if (file.isApplication())
application = launchApplication(file);
else if (file.isApplet())
application = launchApplet(file, true, cont); // 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) {
ex.printStackTrace();
exception = ex;
// Exit if we can't launch the application.
if (exitOnFailure)
System.exit(1);
}
}
public LaunchException getException() {
return exception;
}
public ApplicationInstance getApplication() {
return application;
}
};
/**
* This runnable is used by the launchBackground
* methods to launch a JNLP file from a separate thread.
*/
private class BgRunner implements Runnable {
private JNLPFile file;
private URL location;
BgRunner(JNLPFile file, URL location) {
this.file = file;
this.location = location;
}
public void run() {
try {
if (file != null)
launch(file);
if (location != null)
launch(location);
} catch (LaunchException ex) {
// launch method communicates error conditions to the
// handler if it exists, otherwise we don't care because
// there's nothing that can be done about the exception.
}
}
};
}