Loading...

May 24, 2009

Wicket component for Java Deployment Toolkit to run applets

Since Java 6 update 10 Sun released the Deployment Toolkit. This kit is a Javascript file that can generate the required HTML to deploy Java applets and Web Start applications. The good thing is the script will take care of all odities between browsers, the applet and embed tags. And we can make sure the minimal Java version for our applet is available on the client's machine. If the required Java version is not available it will be downloaded and installed on the computer.

In this post we see a Wicket component which will replace an applet tag with Javascript. Attributes set for the applet tag are passed on to the Javascript. If we use another HTML element to be replaced, we must programmatically set all attributes.

Let's start with a sample page with the applet tag and a div tag. These tags are replaced with Javascript code by the Wicket component.

<html>
  <head>
  </head>
  <body> 
    <applet wicket:id="applet" code="Applet.class" archive="applet.jar"
      width="100" height="200"/>
    <div wicket:id="appletDiv"/>
  </body>
</html>

We create our Wicket component by extending the org.apache.wicket.markup.html.WebComponent. We then override the onComponentTag and onComponentTagBody methods:

package com.mrhaki.wicket.components;

import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;

public class DeployJava extends WebComponent {
    
    private static final long serialVersionUID = 1L;
    
    public DeployJava(final String id) {
        super(id);
    }

    @Override
    protected void onComponentTag(final ComponentTag tag) {
        super.onComponentTag(tag);
        tag.setName("script");
    }
    
    @Override
    protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
        final StringBuilder script = new StringBuilder();
        replaceComponentTagBody(markupStream, openTag, script.toString());
    }
}

This code doesn't do much yet. The only thing we have achieved is that the component will render a script tag with no content. Now it is time to add more functionality. We want to be able to render Javascript like this:

<script>
var attributes = { code: 'Applet.class', archive: 'applet.jar', width: 100, height: 200 };
var parameters = { text: 'Hello world' };
var version = "1.6";
javaDeploy.runApplet(attributes, parameters, version);
</script>

So we start by adding properties in our component to store attributes, parameters and version text. Next we can implement the code in onComponentTagBody to output the Javascript:

...
import net.sf.json.JSONObject;
import org.apache.wicket.util.value.IValueMap;
import org.apache.wicket.util.value.ValueMap;
...
    private String minimalVersion;
    private IValueMap appletAttributes = new ValueMap();
    private IValueMap appletParameters = new ValueMap();
...
    @Override
    protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
        final StringBuilder script = new StringBuilder();
        if (appletAttributes.size() > 0) {
            final JSONObject jsonAttributes = JSONObject.fromObject(appletAttributes);
            script.append("var attributes = " + jsonAttributes + ";");
        } else {
            script.append("var attributes = {};");
        }
        if (appletParameters.size() > 0) {
            final JSONObject jsonParameters = JSONObject.fromObject(appletParameters);
            script.append("var parameters = " + jsonParameters + ";");
        } else {
            script.append("var parameters = {};");
        }
        if (minimalVersion != null) {
            script.append("var version = \"" + minimalVersion + "\";");
        } else {
            script.append("var version = null;");
        }
        script.append("deployJava.runApplet(attributes, parameters, version);");
        replaceComponentTagBody(markupStream, openTag, script.toString());
    }
...
    public void setMinimalVersion(final String minimalVersion) {
        this.minimalVersion = minimalVersion;
    }
    
    public void addParameter(final String key, final Object value) {
        appletParameters.put(key, value);
    }
    
    public void addAttributes(final String key, final Object value) {
        appletAttributes.put(key, value);
    }
...

We use the JSON-lib library to convert the attributes and parameters map to Javascript. We also add methods to set values for the properties.

Okay, the component displays the correct Javascript, but we need to add a reference to the Deployment Toolkit Javascript file, otherwise the method deployJava.runApplet is not recognized. We extend the Wicket component and implement the org.apache.wicket.markup.html.IHeaderContributor interface. Wicket components that implement this interface must implement the method renderHead. Wicket will invoke this method and add a Javascript reference in the HTML head section of our web page. For now we simply add a reference to http://java.com/js/deployJava.js.

import org.apache.wicket.markup.html.IHeaderResponse;

public class DeployJava extends WebComponent implements IHeaderContributor {

    public void renderHead(IHeaderResponse response) {
        response.renderJavascriptReference("http://java.com/js/deployJava.js");
    }

}

And that is it for a basic component which renders the correct Javascript. The following code shows a complete component, which can read attributes set on the applet tag and use them in the Javascript. We can also choose between adding the Javascript source file as a Wicket resource or as a reference to the public Sun website URL.

package com.mrhaki.wicket.components;

import net.sf.json.JSONObject;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.resources.CompressedResourceReference;
import org.apache.wicket.util.value.IValueMap;
import org.apache.wicket.util.value.ValueMap;

/**
 * <p>Wicket component to add the 
 * <a href="http://java.sun.com/javase/6/docs/technotes/guides/jweb/deployment_advice.html">Sun's Deployment Toolkit</a>
 * Javascript.
 * The markup can be defined as applet. Attributes defined for the applet are
 * copied to the Javascript. The markup can also be another element, for example
 * a div element.</p>
 * <p>
 * Suppose we have the following applet markup:
 * <pre>
 * &lt;applet wicket:id="applet" width=200 height=120 code="SignatureApplet.class"
 *   archive="codesign.jar"&gt;&lt;/applet&gt;
 * </pre>
 * In a Wicket page we can create this component and add it to the page:
 * <pre>
 * final JavaDeploy javaDeploy = new JavaDeploy("applet");
 * add(javaDeploy);
 * </pre>
 * We get the following output:
 * <pre>
 * &lt;html&gt;
 *   &lt;head&gt;
 *     &lt;script type="text/Javascript" src="http://java.com/js/deployJava.js"&gt;&lt;/script&gt;
 *   &lt;/head&gt;
 *   &lt;body&gt;
 *     &lt;script&gt;
 *     var attributes = { "width":200,"height":120,"code":"SignatureApplet.class","archive":"codesign.jar"};
 *     var parameters = {};
 *     var version = null;
 *     deployJava.runApplet(attributes, parameters, version);
 *     &lt;/script&gt;
 *   &lt;/body&gt;
 * &lt;/html&gt;
 * </pre>
 * </p>
 */
public class DeployJava extends WebComponent implements IHeaderContributor {

    private static final long serialVersionUID = 1L;

    /**
     * Javascript URL on Sun's website for deployJava Javascript. (={@value})
     */
    private static final String JAVASCRIPT_URL = "http://java.com/js/deployJava.js";

    /**
     * Attribute to set the width of the applet. (={@value})
     */
    private static final String ATTRIBUTE_WIDTH = "width";

    /**
     * Attribute to set the height of the applet. (={@value})
     */
    private static final String ATTRIBUTE_HEIGHT = "height";

    /**
     * Attribute to set the applet classname. (={@value})
     */
    private static final String ATTRIBUTE_CODE = "code";

    /**
     * Attribute to set the codebase for the applet. (={@value})
     */
    private static final String ATTRIBUTE_CODEBASE = "codebase";

    /**
     * Attribute to set the archive neede by the applet. (={@value})
     */
    private static final String ATTRIBUTE_ARCHIVE = "archive";

    /**
     * Minimal Java version needed for the applet.
     */
    private String minimalVersion;

    /**
     * If true we use a local resource otherwise the URL from the Sun site.
     * For the local resource we must add the file deployJava.js next to 
     * this class in our package structure.
     */
    private boolean useJavascriptResource;

    /**
     * Attributes for the javaDeploy.runApplet Javascript method.
     */
    private IValueMap appletAttributes = new ValueMap();

    /**
     * Parameters for the javaDeploy.runApplet Javascript method.
     */
    private IValueMap appletParameters = new ValueMap();

    /**
     * Default constructor with markup id.
     *
     * @param id Markup id for applet.
     */
    public DeployJava(String id) {
        super(id);
    }

    /**
     * Minimal Java version for the applet. E.g. Java 1.6 is "1.6".
     *
     * @param version Minimal Java version needed by the applet.
     */
    public void setMinimalVersion(final String version) {
        this.minimalVersion = version;
    }

    /**
     * Width of the applet.
     *
     * @param width Width of the applet on screen.
     */
    public void setWidth(final Integer width) {
        appletAttributes.put(ATTRIBUTE_WIDTH, width);
    }

    /**
     * Height of the applet.
     *
     * @param height Height of the applet on screen.
     */
    public void setHeight(final Integer height) {
        appletAttributes.put(ATTRIBUTE_HEIGHT, height);
    }

    /**
     * Applet classname.
     *
     * @param code Applet classname.
     */
    public void setCode(final String code) {
        appletAttributes.put(ATTRIBUTE_CODE, code);
    }

    /**
     * Codebase for the applet code.
     *
     * @param codebase Codebase for the applet code.
     */
    public void setCodebase(final String codebase) {
        appletAttributes.put(ATTRIBUTE_CODEBASE, codebase);
    }

    /**
     * Archive path for the applet.
     *
     * @param archive Archive location for the applet.
     */
    public void setArchive(final String archive) {
        appletAttributes.put(ATTRIBUTE_ARCHIVE, archive);
    }

    /**
     * Add a parameter to the applet.
     *
     * @param key Name of the parameter.
     * @param value Value for the parameter.
     */
    public void addParameter(final String key, final Object value) {
        appletParameters.put(key, value);
    }

    /**
     * Indicate if deployJava Javascript must be loaded from the Sun site or as local resource.
     *
     * @param useResource True local resource is used, otherwise Sun site.
     */
    public void setUseJavascriptResource(final boolean useResource) {
        this.useJavascriptResource = useResource;
    }

    /**
     * Get the applet attributes already set and assign them to the attribute
     * list for the Javascript code. And we change the tag name to "script".
     *
     * @param tag De current tag which is replaced.
     */
    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        if ("applet".equalsIgnoreCase(tag.getName())) {
            final IValueMap tagAttributes = tag.getAttributes();
            // Save wicket:id so we can add again to the list of attributes.
            final String wicketId = tagAttributes.getString("wicket:id");
            appletAttributes.putAll(tagAttributes);
            tagAttributes.clear();
            tagAttributes.put("wicket:id", wicketId);
        }
        tag.setName("script");
    }

    /**
     * Create Javascript for deployJava.runApplet.
     *
     * @param markupStream MarkupStream to be replaced.
     * @param openTag Tag we are replacing.
     */
    @Override
    protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
        final StringBuilder script = new StringBuilder();
        if (appletAttributes.size() > 0) {
            final JSONObject jsonAttributes = JSONObject.fromObject(appletAttributes);
            script.append("var attributes = " + jsonAttributes + ";");
        } else {
            script.append("var attributes = {};");
        }
        if (appletParameters.size() > 0) {
            final JSONObject jsonParameters = JSONObject.fromObject(appletParameters);
            script.append("var parameters = " + jsonParameters + ";");
        } else {
            script.append("var parameters = {};");
        }
        if (minimalVersion != null) {
            script.append("var version = \"" + minimalVersion + "\";");
        } else {
            script.append("var version = null;");
        }
        script.append("deployJava.runApplet(attributes, parameters, version);");
        replaceComponentTagBody(markupStream, openTag, script.toString());
    }

    /**
     * Add Javascript src reference in the HTML head section of the web page.
     *
     * @param response Header response.
     */
    public void renderHead(IHeaderResponse response) {
        if (useJavascriptResource) {
            response.renderJavascriptReference(new CompressedResourceReference(JavaDeploy.class, "deployJava.js"));
        } else {
            response.renderJavascriptReference(JAVASCRIPT_URL);
        }
    }
}

If we look at the beginning of the post we see the HTML for a web page. We create the code for the page as follows:

package com.mrhaki.wicket.page;

public class AppletPage extends WebPage {

    public AppletPage() {
        final DeployJava applet = new DeployJava("applet");
        add(applet);
        
        final DeployJava div = new DeployJava("appletDiv");
        div.setWidth(100);
        div.setHeight(200);
        div.setCode("NewApplet.class");
        div.setCodeBase("../classes");
        div.addParameter("text", "Hello world");
        div.setMinimalVersion("1.6");
        add(div);
    }
}

When we run the page we get the following HTML:

<html>
  <head>
    <script type="text/javascript" src="http://java.com/js/deployJava.js"></script>
  </head>
  <body>
    <script>
      var attributes = { "code":"Applet.class", "archive":"applet.jar", "width":100, "height":200 };
      var parameters = {};
      var version = null;
      deployJava.runApplet(attributes, parameters, version);
    </script>
    <script>
      var attributes = { "code":"NewApplet.class", "codebaSE":"../classes", "width":100, "height":200 };
      var parameters = { "text":"Hello world" };
      var version = "1.6";
      deployJava.runApplet(attributes, parameters, version);
    </script>
  </body>
</html>