Since some time ago, there seems to be a fight between two "philosophies" as regards web applications. Each philosophy has its own representatives amongst the various frameworks or platforms we can select to help us develop our app. On one hand there are the page-oriented web applications, some of whose representatives are ASP.NET and JSP (and JSF, Struts, etc.). On the other hand there are component-oriented web applications supported by technologies such as GWT, Flash, Silverlight and so on.
We know there are some requirements which are better suited by page-oriented (for example, the classic shopping cart solution) apps, whereas some others are ideal for a component-oriented approach (GMail being the most remarkable example). However, there are scenarios where both kind of requirements coexist for the same web application. So, why would we be forced to choose only one approach and discard the other?
In this article, we'll try to show how we managed to integrate a GWT component into the JSF page-oriented life cycle with the addition of JBoss RichFaces to support Ajax calls (and partial HTML rerenders), and trying to minimize the number of hardcoded JavaScript lines.
Before going into deeper implementation details, let's see what problems we had to work out.
First, we needed to communicate with the GWT component, so it had to expose a JavaScript API. Furthermore, we didn't want to stand on a JavaScript API, which would have implied having to write non-refactorable (does that word even exist?) JavaScript code. We wanted the GWT component to be driven by a JSF bean through the API, based on a server side model. So in fact, the JavaScript client code would be generated at the server side rather than hardcoded into a HTML or JS file. That's why it seemed nice to have the JavaScript API's client code generated somehow by Java code, so that it was able to automatically adapt to changes in the JavaScript API methods' signature.
Second, we needed to implement some mechanism to let GWT raise events, and the JSF page respond to them. The constraint of having as little hardcoded JavaScript as possible is also present here.
Let's see how we addressed the first problem in this part of the article, and leave the second for the next part.
We assume basic knowledge from the reader about Java, JavaScript, JSP, JSF, RichFaces, Ajax and GWT.
Exposing a JavaScript API from a GWT module
Well, we want to be able to call our GWT component from the outside, so, since we are in the "browser world", the only way to do it is through an API which the component itself exposes. The problem is that the GWT Java-to-JavaScript compiler performs obfuscation. This particular issue was really easy to solve, since we didn't do it :). There's a very nice library called gwt-exporter which did the work for us.
Say we have the HelloWorld GWT example and we want to expose a JavaScript API method "helloWorld()" to invoke the alert message "Hello world" from outside the GWT module.
With gwt-exporter, we only have to write a class which looks as the one below:
/* @gwt.export */ public class HelloWorldAPI implements Exportable { public void helloWorld(){ Window.alert("Hello world!"); } }
If we want to call the helloWorld from JavaScript, we just have to execute the following code:
var helloWorldAPI = new HelloWorldAPI(); helloWorldAPI.helloWorld();
Generating the JavaScript code from server-side Java code
Thanks to gwt-exporter, we're half way there. But we still have to hardcode JavaScript code.
Suppose we're using Eclipse, we know how easy it is to say "Uhm, I don't like how 'helloWorld' sounds, I'll refactor it to 'goodMorningWorld'". Eclipse performs the refactoring in less than a second within the Java code, but the hardcoded JavaScript remains untouched, thus breaking the calls to the GWT component.
Typically, the client code to call the API will look always the same: create an instance of the API class (if we still haven't created one) and call the API method we want to execute. So, since the JavaScript API method signature is generated from the Java class method signature, we could reflect in some way over the latter to generate that typical piece of code. This way, when we refactor a method in HelloWorldAPI, our generated code we'll adapt to the new method name. We can achieve this with the help of Java's Proxy class (java.lang.reflect.Proxy), following the steps below:
1) Extract the API methods into an interface:
public interface IHelloWorldAPI { void helloWorld(); }
2) Make the original API class implement the new interface:
public class HelloWorldAPI implements Exportable, IHelloWorldAPI { public void helloWorld(){ Window.alert("Hello world!") } }
3) In the bean's code, create a proxy instance of the new interface:
InvocationHandler helloWorldAPIInvocationHandler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { StringWriter stringWriter = new StringWriter(); stringWriter.append("if (helloWorldAPI == null) var helloWorldAPI = new HelloWorldAPI(); helloWorldAPI."); stringWriter.append(method.getName()); stringWriter.append("("); for (int i = 0; i < method.getParameterTypes().length; i++) { if (method.getParameterTypes()[i].equals(String.class)){ stringWriter.append("'" + args[i] + "'"); } else { stringWriter.append(args[i].toString()); } if (i != method.getParameterTypes().length - 1) stringWriter.append(", "); } stringWriter.append(")"); stringWriter.append(";"); queue.add(stringWriter.toString()); return null; } }; ClasshelloWorldAPIProxy = (Class ) Proxy.getProxyClass( IHelloWorldAPI.class.getClassLoader(), new Class[] { IHelloWOrldAPI.class }); IHelloWorldAPI helloWorldAPI = (IHelloWorldAPI) helloWorldAPIProxy.getConstructor( new Class[] {InvocationHandler.class}) .newInstance(new Object[] {helloWorldAPIInvocationHandler });
Then, to call the JS API, we only have to use the helloWorldAPI instance and call the method we want to.
If you pay attention to the code above, you'll see that the invocation handler is trapping all the calls to the helloWorldAPI object and generating the typical JS code we were talking about, getting the method name and param types by reflection. Then, it queues the generated JavaScript. We didn't say anything about that queue.
Remember that this code is server-side and the GWT component is ultimately a piece of JavaScript rendered in the browser, so we need some way of getting the newly generated JavaScript code with the calls to the GWT module API from the server to the client browser.
To do this, we wrote a custom RichFaces component which is binded to a queue of String, and when rendered gets the queue and iterates over its elements to write them into a <script>
tag. This code is executed on the components reRender event. So, if we want a RichFaces action to let the server make calls to the GWT component, we have to add to its reRender attribute the custom component's id.
We won't discuss the implementation details of this custom component, since we think it's not important to show the integration we are trying to achieve, and anyone with some experience using JSF and RichFaces could write something similar with ease.
I hope the article was clear enough to let you understand the basic ideas behind the solution.
In part 2 we'll show how we implemented a way of letting GWT raise events and JSF-RichFaces respond to them.
Martín
<SCRIPT> tag. This code is executed on the components reRender event. So, if we want a RichFaces action to let the server make calls to the GWT component, we have to add to its reRender attribute the custom component's id.
We won't discuss the implementation details of this custom component, since we think it's not important to show the integration we are trying to achieve, and anyone with some experience using JSF and RichFaces could write something similar with ease.
In part 2 we'll show how we implemented a way of letting GWT raise events and JSF-RichFaces respond to them.
Martín