Friday, January 26, 2007

Applets

I had a need to write an applet for a client that had to allow the user to upload a file, then based on the server's response, to redirect to a success page or a failure page. I'm using Tapestry as the view technology, so I had to figure out how to hook this into Tapestry's framework.

First thing I ran into was getting the applet to show up at all. I discovered the Java console, which is your friend in these situations. It appears as an icon in the windows tray, but can be made visible in other ways too. The console told me there were class-not-found problems. I discovered you have to list all the jars your applet depends on in the codebase field, separated by commas. I found I had to package the applet in its own jar to make this easiest. So the archive attribute ended up looking like this (most of these jars are related to web services):

ARCHIVE = "MyApplet.jar, swing-layout-1.0.jar, UpekCommon.jar, axis.jar, jaxrpc.jar, commons-logging.jar, commons-discovery.jar, saaj.jar, wsdl4j-1.5.1.jar, xstream-1.1.3.jar, dom4j-1.6.1.jar"

It still didn't work. My jars were in the WEB-INF/lib directory, and I tried mightily to get the archive attribute to reference jars in that directory, but in the end I had to duplicate them in the webapp directory (the parent of WEB-INF). In netbeans, which I'm using, the way to do this was in Project Properties-->Build-->Packaging, click on Add Library, then in the "Path in WAR" field, type "." (without quotes).

After I did this, the applet showed up, but I was getting permission problems because the file upload requires going outside the applet's sandbox. So I discovered that policytool was my friend (http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/policytool.html). For testing (this wouldn't work for production), and using policytool, I created the file C:\Documents and Settings\<user>\.java.policy and granted java.security.AllPermission to the codeBase "http://localhost:8084/-". The trailing dash is important because it means "all directories under localhost:8084". For production you need to sign the applet; I didn't have to do that yet.

To pass parameters to the applet, I used http://wiki.apache.org/tapestry/TapestryAndApplets as a model. The key piece that worked for me was:

<applet name="myApplet" code="Applet.class" archive="somejar.jar" width="310" height="335">
<span jwcid="@Insert" value="ognl:getAppletParameters()" raw="true"/>
</applet>

Then I had problems with browser compatibility until I discovered the HTML Converter (http://java.sun.com/j2se/1.5.0/docs/guide/plugin/developer_guide/html_converter.html). It was easy to upgrade my html page that contains the applet from <applet> to <object>.

Lastly, I had to redirect to a different page depending on the success of the upload. I discovered the JSObject class (first I tried com.sun.java.browser.dom.DOMService, which didn't work right).

JSObject is in <jre>\lib\plugin.jar. To use it, I created a form on the page with one hidden field and no submit button. I used JSObject from within the applet to fill in the field and submit the form. The form looks like this (I'm using Tapestry):

<form jwcid="appletForm@Form" listener="listener:doSubmit">
<input jwcid="samlResult@Hidden" value="ognl:samlResult">
</form>

and the applet code looks like this:

JSObject appletWindow = JSObject.getWindow(this);
JSObject mainForm = (JSObject) appletWindow.eval("document.forms[0]");
JSObject hiddenField = (JSObject) mainForm.getMember("samlResult");
hiddenField.setMember("value", this.getSamlResult());
appletWindow.eval("document.forms[0].submit()");

Note that it doesn't work to say:
mainForm.eval("submit()");

Hope this helps someone out there.

No comments: