Deploying with Java Web Start - Part 1: Minimize the Differences
In the ideal world packaging and deploying your product should not have to be difficult. Even when you have the best product in the world, packaging and deploying may be your most important task and it can be hard to do well. An organic farmer may grow the best apples in the world, but they are going to be hard to sell next to apples with no exterior blemishes.
A software product is just like an apple, and the user experience of bringing home your software product should be sweet for Macintosh, Windows, and Linux users. Java Web Start allows applications to be deployed with a single click and from the user perspective that is sweet. From the developer perspective actually using Java Web Start can be less palatable.
Ideally the developer plans, designs, codes, compiles, and tests in an environment which minimizes or hides the differences between the development environment and the deployment environment. Better yet, an infrastructure to support deployment should hide and centralize any reference to Java Web Start. Such an infrastructure should allow applications to just start and just work in the same way for the developer, the build tester, and for the end user.
What follows is Part 1 in a 3 Part series that explores hiding and centralizing references to Java Web Start. All three parts of this series assume that the application to be deployed will be a signed application running in the "all-permissions" mode. Part 1 focuses on accessing Java Web Start services and providing a development-deployment-environment-independent mechanism for launching a URL. Part 2 focuses on providing a Preferences implementation for storing a small amount of configuration data for a Java Web Start application. Part 3 explores the Java Web Start DownloadService. With minimal use of the java.lang.reflect package all three parts can be compiled without reference to the javaws.jar.
Listing 1 provides access to common JNLP objects and services and is needed for the code in all three parts of this article. Listing 2 provides a URL launching mechanism which uses reflection to first attempt launching using the javax.jnlp.BasicService object's showDocument method. If the BasicService object is not present then a fall back launch is performed based on the Bare Bones Browser Launch for Java (http://www.centerkey.com/java/browser).
package ca.ansir.jnlp;
import java.lang.reflect.Method;
import java.net.URL;
/**
* The <code>JnlpServices</code> provides package protected static methods for
* accessing common jnlp objects and services. This code can be compiled without
* referencing the javaws.jar.
* <p>
* Please feel free to use this code snippet (<code>JnlpServices</code>)
* in developing your own commercial or non-commercial applications, but please
* credit the author, Dan Andrews, for any portion of this code that you use,
* and provide a reference to <a href="http://www.ansir.ca">Ansir</a>. Thank
* you.
* </p>
*
* @author Dan Andrews
*/
public final class JnlpServices {
/** Class name for the basic service. */
private static final String JNLP_BASIC_SERVICE = "javax.jnlp.BasicService";
/** Class name for the persistence service. */
private static final String JNLP_PERSISTENCE_SERVICE =
"javax.jnlp.PersistenceService";
/** Class name for the download service. */
private static final String JNLP_DOWNLOAD_SERVICE = "javax.jnlp.DownloadService";
/** Lookup method name of the service manager. */
private static final String LOOKUP = "lookup";
/** Class name for the service manager. */
private static final String JNLP_SERVICE_MANAGER = "javax.jnlp.ServiceManager";
/** Method name of getCodeBase method of the basic service. */
private static final String GET_CODE_BASE = "getCodeBase";
/**
* Constructor - do not construct.
*/
private JnlpServices() {
// do not instantiate this class.
}
/**
* Gets the <code>javax.jnlp.BasicService</code> object.
*
* @return The <code>javax.jnlp.BasicService</code> object.
* @throws Exception
* If invoked outside of a web start application.
*/
static Object getBasicService() throws Exception {
return getService(JNLP_BASIC_SERVICE);
}
/**
* Gets the <code>URL</code> for the code base.
*
* @return The <code>URL</code> for the code base.
* @throws Exception
* If invoked outside of a web start application.
*/
static URL getCodeBase() throws Exception {
Object bs = JnlpServices.getBasicService();
Method method = bs.getClass().getMethod(GET_CODE_BASE, new Class[0]);
return (URL) method.invoke(bs, new Object[0]);
}
/**
* Gets the <code>PersistenceService</code> object.
*
* @return The <code>javax.jnlp.PersistenceService</code> object.
* @throws Exception
* If invoked outside of a web start application.
*/
static Object getPersistenceService() throws Exception {
return getService(JNLP_PERSISTENCE_SERVICE);
}
/**
* Gets the <code>DownloadService</code> object.
*
* @return The <code>javax.jnlp.DownloadService</code> object.
* @throws Exception
* If invoked outside of a web start application.
*/
static Object getDownloadService() throws Exception {
return getService(JNLP_DOWNLOAD_SERVICE);
}
/**
* Gets the given service object.
*
* @return The given service object.
* @throws Exception
* If invoked outside of a web start application.
*/
private static Object getService(String className) throws Exception {
Class serviceManagerClass = Class.forName(JNLP_SERVICE_MANAGER);
Method lookupMethod = serviceManagerClass.getMethod(LOOKUP,
new Class[] { String.class });
Object ps = lookupMethod.invoke(null, new Object[] { className });
return ps;
}
}
Listing 2: JnlpLaunchUrl class
package ca.ansir.jnlp;
import java.lang.reflect.Method;
import java.net.URL;
/**
* The goal of this class is to provide a mechanism for launching a URL from an
* application. The application may be a standalone rich Java GUI or a signed
* Java Web Start application. A secondary goal is hide environment differences
* that include deployment versus development differences, and OS differences.
* The <code>JnlpLaunchUrl</code> class launches a url using the jnlp basic
* service if it can be found via reflection, otherwise, the URL is launched in
* a native fashion if possible.
* <p>
* <b>Usage:</b>
* </p>
* <p>
* <code>JnlpLaunchUrl.launch(url);</code>
* </p>
*
* Please feel free to use this code snippet (<code>JnlpLaunchUrl</code>) in
* developing your own commercial or non-commercial applications, but please
* credit the author, Dan Andrews, for any portion of this code that you use,
* and provide a reference to <a href="http://www.ansir.ca">Ansir</a>. Thank
* you.
*
* @author Dan Andrews
*/
public final class JnlpLaunchUrl {
/** Method name of showDocument method of the basic service. */
private static final String SHOW_DOCUMENT = "showDocument";
/**
* Constructor - do not construct.
*/
private JnlpLaunchUrl() {
// No state or member data required so don't let anyone instantiate this
// class.
}
/**
* Launch the url.
*
* @param url
* The <code>URL</code> object.
* @return True if launched.
*/
public static boolean launch(URL url) {
boolean rv = false;
try {
Object basicServiceObject = JnlpServices.getBasicService();
if (basicServiceObject != null) {
rv = true;
}
if (rv) {
// Note: Joshua Bloch (author of Effective Java) recommends
// interfaces over reflection where possible. A touch of
// reflection, in this case, allows us to compile without
// referencing the javaws.jar.
Method method = basicServiceObject.getClass().getMethod(
SHOW_DOCUMENT, new Class[] { URL.class });
Boolean result = (Boolean) method.invoke(basicServiceObject,
new Object[] { url });
rv = result.booleanValue();
}
} catch (Exception ex) {
rv = false;
}
if (!rv) {
rv = openURL(url.toString());
}
return rv;
}
/**
* Launch the url if the jnlp services cannot be found. Implementation
* modified from Bare Bones Browser Launch for Java:<br>
* http://www.centerkey.com/java/browser
*
* @param url
* The URL string.
* @return True if launched.
*/
private static boolean openURL(String url) {
boolean rv = false;
String osName = System.getProperty("os.name");
try {
if (osName.startsWith("Mac OS")) {
Class fileMgr = Class.forName("com.apple.eio.FileManager");
Method openURL = fileMgr.getDeclaredMethod("openURL",
new Class[] { String.class });
{
openURL.invoke(null, new Object[] { url });
rv = true;
}
} else if (osName.startsWith("Windows")) {
Runtime.getRuntime().exec(
"rundll32 url.dll,FileProtocolHandler " + url);
rv = true;
} else {
// assume Unix or Linux
String[] browsers = { "firefox", "opera", "konqueror",
"epiphany", "mozilla", "netscape" };
String browser = null;
for (int count = 0; count < browsers.length && browser == null; count++) {
if (Runtime.getRuntime().exec(
new String[] { "which", browsers[count] })
.waitFor() == 0) {
browser = browsers[count];
}
}
if (browser == null) {
rv = false;
} else {
Runtime.getRuntime().exec(new String[] { browser, url });
rv = true;
}
}
} catch (Exception e) {
rv = false;
}
return rv;
}
}