Monday, January 11, 2010

A JWS Installer for BugRunner









A JWS Installer for BugRunner


BugRunner is a basic 2D arcade game (see Chapter 11. I'll work with the version that uses the J3DTimer class, which means the deployment file must install the relevant Java 3D library. I can't assume it's present on the user's machine.



Write the Application


The installer version of the application shouldn't rely on nonstandard extensions or native libraries being present on the client machine. However, BugRunner uses the Java 3D timer, which is part of the Java 3D extension. The OpenGL Windows version of Java 3D is implemented across seven files:



j3daudio.jar
j3dcore.jar
j3dutils.jar
vecmath.jar

JAR files
J3D.dll
j3daudio.dll
J3DUtils.dll

Native libraries



The native libraries will vary across different platforms, and the JAR versions may also vary.



Only j3dutils.jar and J3DUtils.dll are needed for the timer functionality as explained in Appendix A. They should be placed in the BugRunner/ directory to be locally accessible to the application. Java 3D should not be installed on your test machine.


Figure B-1 shows the BugRunner/ directory prior to compilation. It contains all the Java files (unchanged from Chapter 11), j3dutils.jar, and J3DUtils.dll. The batch files are optional but reduce the tedium of typing long command lines.


Since Java 3D isn't installed in a standard location checked by javac and java, the calls to the compiler and JVM must include additional classpath information. The compileBR.bat batch file contains this line:



javac -classpath "%CLASSPATH%;j3dutils.jar" *.java



The BugRunner.bat batch file has this:



java -cp "%CLASSPATH%;j3dutils.jar" BugRunner



There's no need to mention J3DUtils.dll, which will be found by the JAR as long as it's in the local directory.


Once the program has been fully debugged, it should be packaged as a JAR. The BugRunner application consists of various classes, and the subdirectories Images/ and Sounds/. These should be thrown together into a single BugRunner.jar file. The makeJar.bat batch file contains the line:



jar cvmf mainClass.txt BugRunner.jar *.class Images Sounds




Figure B-1. The BugRunner/ directory




The manifest details in mainClass.txt are:



Main-Class: BugRunner
Class-Path: j3dutils.jar



The manifest specifies the class location of main( ) and adds j3dutils.jar to the classpath used when BugRunner.jar is executed. This setup assumes that it's in the same directory as BugRunner.jar.


The required DLLs (only J3DUtils.dll in this case) aren't added to BugRunner.jar.



The application now consists of three files: BugRunner.jar, j3dutils.jar, and J3DUtils.dll. These should be moved to a different directory on a different machine and tested again. Double-clicking on BugRunner.jar should start it running. Alternatively, type:



java -jar BugRunner.jar





Modify the Application for Deployment


Since the native library J3DUtils.dll is utilized by BugRunner, two tasks must be carried out. The DLL must be placed inside its own JAR:



jar cvf J3DUtilsDLL.jar J3DUtils.dll



There's no need for additional manifest information.



The main( ) method of BugRunner, in BugRunner.java, must be modified to call System.loadLibrary( ) for each DLL:



public static void main(String args[])
{
// DLL used by Java 3D J3DTimer extension
String os = System.getProperty("os.name");
if (os.startsWith("Windows")) {
System.out.println("Loading '" + os + "' native libraries...");
System.out.print(" J3DUtils.dll... ");
System.loadLibrary("J3DUtils"); // drop ".dll"
System.out.println("OK");
}
else {
System.out.println("Sorry, OS '" + os + "' not supported.");
System.exit(1);
}

long period = (long) 1000.0/DEFAULT_FPS;
new BugRunner(period*1000000L); // ms --> nanosecs
}



If several libraries are loaded, the load order will matter if dependencies exist between them.



The checking of the os.name property string gives the program a chance to report an error if the application is started by an OS that doesn't support the library. This coding style also allows the application to load different libraries depending on the OS name. For instance:



String os = System.getProperty("os.name");
System.out.println("Loading " + os + " native libraries...");
if (os.startsWith("Windows")) {
System.loadLibrary("J3DUtils"); // drop ".dll"
: // other libraries loaded
}
else if (os.startsWith("Linux")) {
System.loadLibrary("J3DUtils"); // drop "lib" prefix & ".so"
:
}
else if (os.startsWith("Mac")) {
System.loadLibrary("J3DUtils"); // drop ".jnilib"
:
}
else {
System.out.println("Sorry, OS '" + os + "' not supported.");
System.exit(1);
}



A longer example of this kind can be found in "Marc's Web Start Kamasutra," a JWS and JNLP forum thread at http://forum.java.sun.com/thread.jsp?forum=38&thread=166873.


A lengthy list of the possible os.name values is presented in http://www.vamphq.com/os.html.



These changes to BugRunner.java mean it must be recompiled and re-JARed:



javac -classpath "%CLASSPATH%;j3dutils.jar" *.java
jar cvmf mainClass.txt BugRunner.jar *.class Images Sounds





Create a Public/Private Keypair for Signing the Application


The digital signing of a JAR requires two of Java's security tools: keytool and jarsigner. They're described in the security tools section of the J2SE documentation in <JAVA HOME>/docs/tooldocs/tools.html#security.


keytool generates and manages keypairs, collected together in a keystore. Each keypair is made up of a public key and private key, and an associated public-key certificate. A simplified diagram showing a typical keypair is shown in Figure B-2.



Figure B-2. The elements of a keypair




The two keys can be used to encrypt documents. Something encrypted with a private key can only be decrypted with the corresponding public key; if the encryption uses the public key, then only the private key can unlock it. The intention is that the public key is widely distributed, but users keep their private key secret. Determining the private key from examining the public key is impossible.


A message sent to users can be encrypted with the public key, so only they can read it by applying their private key. A message from users to another person can be encrypted with the private key. The fact that the user's public key can decrypt the message means that it must have come from that user. The private key is being used as a digital signature.


One of the problems with the public/private keys approach is how to distribute a public key safely. For instance, if I receive an email from "Stan Lee" giving me his public key, how do I know that it is from the famous Atlas/Timely editor? This is where the public-key certificate comes into play.


A certificate is a digitally signed statement from a third party, perhaps my respected friend "Alan Moore," that is "Stan Lee's public key." Of course, the question of authenticity still applies, but now to the "Alan Moore" signature, which can be combated by signing it with the certificate of yet another person. This process leads to a chain of certificates, ending with a certificate that can be proved genuine in some unforgeable way (for example, by visiting the person and asking them).


Whenever keytool generates a new keypair it adds a self-signed certificate to the public key. In effect, all my public keys contain certificates signed by me saying they're genuine. This is useless in a real situation, but it's sufficient for these demos. You'll see that JWS issues a dire warning when it sees a self-signed certificate but will let it pass if the client gives the okay. I'll discuss how to obtain better certificates later in this appendix.


A new keypair is written to the keystore called MyKeyStore by typing the following:



keytool -genkey -keystore MyKeyStore -alias BugRunner



The user is prompted for the keystore password, a lengthy list of personal information, and a password for the new keypair (see Figure B-3). The keypair's alias (name) is BugRunner in this example, though any name could be used.


Better passwords should be thought up than those used in my examples; a good password uses letters, numbers, and punctuation symbols, and should be at least eight characters long.


The keystore's contents can be examined with this command:



keytool -list -keystore MyKeyStore





Sign Everything with the Private Key


You're ready to use the jarsigner tool to start signing the JARs in the BugRunner application. Figure B-4 presents a simple diagram of what jarsigner does to a JAR.



Figure B-3. Generate a new keypair





Figure B-4. A JAR file signed with jarsigner




jarsigner digitally signs a JAR with a private key. A digital signature has many useful characteristics:


  • Its authenticity can be checked by seeing if it matches the public key stored with the JAR. This relies on the public key being trusted, which depends on the certificates attached to it.

  • The digital signature cannot be forged since the private key is only known to the sender of the JAR.

  • Unlike a real signature, the digital signature is partly derived from the data it's attached to (i.e., the JAR file). This means that it cannot be removed from its original JAR, stuck on a different one, and still be authenticated successfully.


The actual mechanics of creating a signed JAR are simple:



jarsigner -keystore MyKeyStore foo.jar BugRunner



This signs foo.jar using the BugRunner keypair stored in MyKeyStore. jarsigner will prompt the user for the keystore and keypair passwords.


A variant of this is to create a new signed JAR file rather than modify the existing one:



jarsigner -keystore MyKeyStore -signedjar foo_signed.jar foo.jar BugRunner



This leaves foo.jar unchanged and creates a signed version called foo_signed.jar.


The name of the new JAR can be anything you choose.



For the BugRunner application, there are three JARs: BugRunner.jar, j3dutils.jar, and J3DUtilsDLL.jar. The latter two are signed this way:



jarsigner -keystore MyKeyStore -signedjar j3dutils_signed.jar
j3dutils.jar BugRunner
jarsigner -keystore MyKeyStore J3DUtilsDLL.jar BugRunner



The creation of a new JAR for the signed version of j3dutils.jar is to avoid any confusion with the original JAR created by Sun.



This process means yet another reJARing of BugRunner since the manifest information in mainClass.txt must be changed to reference the signed filenames:



Main-Class: BugRunner
Class-Path: j3dutils_signed.jar



The jar command in makeJar.bat is unchanged:



jar cvmf mainClass.txt BugRunner.jar *.class Images Sounds



After BugRunner.jar is regenerated and then it's signed:



jarsigner -keystore MyKeyStore BugRunner.jar BugRunner





Create a Deployment File


The deployment file for the BugRunner application, BugRunner.jnlp, is shown in Example B-2.



Example B-2. JNLP deployment file for BugRunner


<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+"
<!-- codebase="http://fivedots.coe.psu.ac.th/~ad/jws/BugRunner/"-->
codebase="file:///D:/Teaching/Java Games/Code/JWS/BugRunner/"
href="BugRunner.jnlp"
>

<information>
<title>BugRunner</title>
<vendor>Andrew Davison</vendor>
<homepage href="http://fivedots.coe.pcu.ac.th/~ad/jg"/>
<description>BugRunner</description>
<description kind="short">BugRunner: a 2D arcade-style game,
using the J3DTimer class</description>
<icon href="bug32.gif"/>
<icon kind="splash" href="BRBanner.gif"/>
<offline-allowed/>
</information>

<security>
<all-permissions/>
</security>

<resources os="Windows">
<j2se version="1.4+"/>
<jar href="BugRunner.jar" main="true"/>
<jar href="j3dutils_signed.jar"/>
<nativelib href="J3DUtilsDLL.jar"/>
</resources>

<application-desc main-class="BugRunner"/>
</jnlp>




The URL codebase value is commented out at this stage; instead, use a path to the local development directory.


The information tag contains two forms of textual description: a one-line message and a longer paragraph (confusingly labeled with the attribute value short). The icon and splash screen images are named; they should be located in the BugRunner directory. The icon is the default size of 32 x 32 pixels, but other sizes are possible and several icons with different resolutions can be supplied. GIF or JPEG images can be used. Unfortunately, transparent GIF are rendered with black backgrounds, at least on Windows.


The offline-allowed tag states that the application can run when JWS detects that the network is unavailable. all-permissions security is used, which requires that all the JARs named in the resources section are signed. The resources will be downloaded only if the client-side OS matches the os attribute. There's also an optional arch attribute to further constrain the installation.


For example, the following is able to retrieve any one of five different versions of j3daudio.jar depending on the OS and architecture:



<resources os="Windows">
<jar href="jars/j3d/windows/j3daudio.jar"/>
</resources>

<resources os="Linux" arch="x86"> <!-- Linux IBM -->
<jar href="jars/j3d/linux/i386/j3daudio.jar"/>
</resources>

<resources os="Linux" arch="i386"> <!-- Linux Sun -->
<jar href="jars/j3d/linux/i386/j3daudio.jar"/>
</resources>

<resources os="Solaris" arch="sparc">
<jar href="jars/j3d/solaris/j3daudio.jar"/>
</resources>

<resources os="Mac OS X" arch="ppc">
<jar href="jars/j3d/osx/j3daudio.jar"/>
</resources>



The jars/ directory should be in the same directory as the deployment file.




It may seem rather silly to have five different JARs when their contents should be identical because they're coded in Java. In practice, however, this approach avoids incompatibles that may have crept into the different versions.


More details on how the Java 3D libraries can be divided into multiple resource tags are given in the "Marc's Web Start Kamasutra" forum thread (http://forum.java.sun.com/thread.jsp?forum=38&thread=166873).


The j2se version tag in BugRunner.jnlp specifies that any version of J2SE or JRE from 1.4.0 on can execute the application. JWS will abort if it detects an earlier version when it starts the program. It's possible to specify initial settings for the JRE when it starts and to trigger an automatic download of a JRE if the client's version is incompatible. The following tags illustrate these features:



<j2se version="1.4.2" initial-heap-size="64m"/>
<j2se version="1.4.2-beta" href="http://java.sun.com/products/autodl/j2se"/>



BugRunner.jnlp specifies that three JARs should be retrieved:



<jar href="BugRunner.jar" main="true"/>
<jar href="j3dutils_signed.jar"/>
<nativelib href="J3DUtilsDLL.jar"/>



BugRunner.jar contains the application's main( ) method and J3DUtilsDLL.jar holds the native library. The JARs must be in the same directory as the JNLP file and must be signed.


The resources tag is considerably more versatile than this example shows. For instance, it's possible to request that resources be downloaded lazily. This may mean that a resource is only retrieved when the application requires it at runtime (but JWS may choose to download it at installation time):



<jar href="sound.jar" download="lazy"/>



Resources may be grouped together into parts and subdivided into extensions. Each extension is in its own deployment file.


Property name/value pairs, which can be accessed in code with System.getProperty( ) and System.getProperties( ) calls may appear inside the resources section. For example:



<property name="key" value="overwritten"/>



The application-desc tag states how the application is to be called and may include argument tags:



<application-desc main-class="Foo">
<argument>arg1</argument>
<argument>arg2</argument>
</application-desc>



The development guide in the J2SE documentation explains many of these tags (<JAVA_HOME>/docs/guide/jws/developersguide/contents.html). An extensive JNLP tag reference page is located at http://lopica.sourceforge.net/ref.html.



Deployment testing

Deployment testing should be carried out by moving the relevant files to a different directory on a different machine. For BugRunner, there are six files:


BugRunner.jnlp


The deployment file


bug32.gif


BRBanner.gif


The installer icon and splash


BugRunner.jar


j3dutils_signed.jar


J3DUtilsDLL.jar


The resource JARs


The chosen directory must match the one used in the codebase attribute at the start of the deployment file.


Double-clicking on BugRunner.jnlp should initiate JWS and the installation process, which is shown in Figure B-5.



Figure B-5. BugRunner installation




The dialog box in Figure B-6 appears at the point when the application should start executing.


Since BugRunner has requested <all-permissions/> access and the digital signature uses a self-signed public-key certificate, then JWS reports that "it is highly recommended not to install and run this code." This sort of message is deeply worrying to novice users. It can only be removed if you replace the self-signed certificate by a third-party certificate as outlined at the end of this appendix.


Clicking on Details will show information obtained from the certificate, or chain of certificates, attached to the JAR. Clicking on Exit will stop JWS without executing



Figure B-6. Execute at your peril




the application. Start will start the application and, depending on how JWS is configured, add a BugRunner item to the Windows Start menu and a BugRunner icon to the desktop.


For more details about the application and how to configure JWS the application manager should be started. Figure B-7 shows the manager for JWS 1.4.2.



Figure B-7. The JWS 1.4.2. application manager




This manager has been pensioned off in Version 5.0, replaced by a control panel and a cache manager.






Place Everything on a Server


It's time to move the following six BugRunner files to a server:


  • BugRunner.jnlp

  • bug32.gif and BRBanner.gif

  • BugRunner.jar, j3dutils_signed.jar, and J3DUtilsDLL.jar


BugRunner.jnlp must be modified to have its codebase use the server's URL:



codebase="http://fivedots.coe.psu.ac.th/~ad/jws/BugRunner/"



The BugRunner/ directory is placed below jws/, which holds a JWS portal page called index.html. The directory structure is shown in Figure B-8.



Figure B-8. The server directories used for JWS




The portal page (loaded from http://fivedots.coe.psu.ac.th/~ad/jws/) appears as shown in Figure B-9.


Clicking on the BugRunner link will cause the application to be downloaded, with JWS showing the same dialogs as in Figures B-5 and B-6.


Before starting this phase of the testing, any previous client-side installation of BugRunner should be removed via the JWS application manager.










    No comments: