Some Recipes to Improve Your Google Web Toolkit Development

Experiences, tips and techniques to assist you in creating GWT Web applications.

Page 2 of 3

GWT Development Lessons Learned

After writing several hundred lines of GWT- and GWT-related servlet code for Tomcat I would not claim that I'm a GWT guru, but as outlined in the abstract, I try to present some problems and solutions that are not covered by "getting started" tutorials and articles. I will assume that you are familiar with Java, GWT and servlet basics and with tools like Eclipse, Ant, and JUnit.

Of course, I cannot cover every possible GWT topic here, that would break the context of such an article, and - to be honest - I have not used all major GWT features in my case study; so, e.g., I have no practice with GWT security aspects or internationalization. However, the following eight tips will address some typical areas in developing, testing and deploying your GWT applications, assuming your server part is covered by servlet technology.

Tip 1: Divide and conquer

We all know, GWT applications are Java applications. However, the point is "which Java"? We have to keep in mind that GWT compiles Java sources that are compatible with J2SE 1.4.2 or earlier! Furthermore, only a subset of the J2SE 1.4.2 APIs is supported, namely the java.lang and java.util. package. Even when working with these packages you should study Google's remarks on runtime library support (to be found at the GWT home page) very carefully and take the corresponding tip to heart: "You'll save yourself a lot of frustration if you make sure that you use only translatable classes in your client-side code from the very beginning. To help you identify problems early, your code is checked against the JRE emulation library whenever you run in hosted mode. As a result, most uses of unsupported libraries will be caught the first time you attempt to run your application. So, run early and often."

Now, my new tip is "divide et impera", a Latin phrase meaning, in our context, divide your application code from the beginning into three different parts for client-side code, RPC-related code, and server-side code, and create corresponding Eclipse projects to conquer your task. In doing so, you can make use of different Java language versions for your client and server part. I created the server part of the application (the servlet code) with Java 5, but in the following list you can replace Java 5 by Java 6, if you are using the Mustang release right now. Even if you are still using J2SE 1.4.2 on the server-side, such a division offers you more flexibility for the future and clearly separates your code ("separation of concerns"), without limiting your debugging actions in GWT hosted mode. If you have all sections in only one Eclipse project, you will need to be very disciplined, particularly on the server-side. Otherwise, compile or runtime problems will occur. My advice is to use a special naming convention that clearly identifies your distinct projects and that will make the deployment script easier. You can use an Eclipse working set named, e.g., GWT-<ModuleName> to include all three projects. Here, "ModuleName" is the name of the GWT module that identifies your web application; in my environment it was called "JUnit2MR", because the web application connected JUnit error messages to ClearQuest modification requests (MRs) by means of IBM Rational's new Java teamapi. Using a working set, everything you need is close together.

  • Client-Side code: Contains your UI related code that will be translated to JavaScript. Therefore you are limited to J2SE 1.4.2 and GWT runtime support. Enable Eclipse Java compiler settings and "Java Compiler Errors/Warnings" per project, adjust Java compliance level to "1.4" and source and class file compatibility to "1.4" (assuming you do not use JDK versions prior to 1.4). The name of this project is <ModuleName>-client, e.g., "JUnit2MR-client", and it depends on the <ModuleName>-rpc project in your build path settings. The package name is something like <com.company.project>.gwt.<ModuleName>.client.

  • RPC-related code: Contains your RPC related code that will be translated to JavaScript. This project follows the same guidelines as the client-side code project above. The project name is <ModuleName>-rpc, e.g., "JUnit2MR-rpc", and it depends on no other project. The package name is the same as for the <ModuleName>-client project. The RPC-project contains remote interfaces on the client-side, data transfer objects that are serialized by GWT during RPC, and global constant classes that will contain public constants used by client and server code (e.g., to handle input form data that is identified by unique names). Just a new remark on data containers: GWT forces your data containers used by RPC to implement the IsSerializable interface. The obvious problem with this approach is that on the server side you might have the same classes implement java.io.Serializable, which is not allowed by the GWT Java-to-JavaScript compiler. One possible solution is to have a JavaBean class on the server side implementing Serializable only, with exactly the same member variables as the data container used by RPC. Then you call PropertyUtils.copyProperties(serverBean, rpcDataContainer)or PropertyUtils.copyProperties(rpcDataContainer, serverBean) to copy your values back and forth. PropertyUtils is part of the Jakarta Commons BeanUtils project.

  • Server-side code: Contains your servlet code, if your server-side is made up by Java servlets. If you are using Tomcat 5.5 or Tomcat 6, you can use the full power of Java 5+ here. Enable Eclipse compiler settings per project, and use the Java 5 compiler settings with compliance level set to "5.0". You can also enable all of the Eclipse "Compiler->Error/Warnings" features that meet your needs, e.g., to address missing @override annotations or the like. If you have Eclipse 3.2.2, then its new "Source->Clean up" feature is also worth configuring. The name of this project is <ModuleName>-server', e.g., "JUnit2MR-server", and it depends on the <ModuleName>-rpc project in your build path settings. The package name is <com.company.project>.gwt.<ModuleName>.server, if you are programming according to GWT's default package proposals.

You can see the resulting directory structure for our example application in figure 1. If you look inside the JUnit2MR-client directory, you can see additional directories created during the deployment process, as well as an Eclipse launch configuration to start our GWT application "JUnit2MR" in hosted mode. My tip is to collect project-specific jar files in a directory called "lib", e.g., inside JUnit2MR-client/lib we have GWT add-on libraries like "gwt-tk" or "gwt-login". You can find a good list of third-party client-side GWT add-ons at the GWT Unofficial Wiki site (see Resources). The RPC-project does not need a lib directory in my case statudy, but the server-side project's lib directory will contain only jar files needed by your servlet code.

Using this three-projects GWT structure, the Eclipse launch configuration for your application in the GWT hosted mode is a little bit more complicated than having only one project. Therefore, I provide a screenshot in figure 2 that shows the necessary settings for my case study.

Figure 1: Eclipse project directory structure for client, rpc, and server-part

Figure 1: Eclipse project directory structure for client, rpc, and server-part

Figure 2: Eclipse launch configuration for GWT hosted mode

Figure 2: Eclipse launch configuration for GWT hosted mode

You can do even more on structuring your code by establishing a separate Java "rpc" package inside the client and rpc project. In the rpc project it will just replace the "client" package still containing your transfer objects and the asynchronous interface definitions. In the client project the "rpc" package must be added, and it will include your synchronous interface definitions (without the callback parameter) moved from the client packag e. The only thing left is to make the new rpc package known to GWT by adding the <source path="client"/><source path="rpc"/> statements to your GWT module description.

Tip 2: Debugging and Error Reporting: there is more than Window.alert ()

It's true: you can actually use your IDE's full debugging capabilities when creating GWT applications. But before digging into where the error may have occurred, you need solid exception reporting on both the client and the server side of your code. This is typically done with try/catch blocks. Inside a client-side catch block you should pay attention to the fact that the default method call e.printStackTrace() is not a suitable solution in all situations. It will work when running your application in GWT hosted mode, printing text to your Eclipse console. However, for web mode ask yourself: "Where will my stacktrace and error messages that I have sent to stdout or stderr be displayed?" One possible solution is to use Mat Gessel's debug utility classes (see Resources) but you need a browser JavaScript console to see the results in web mode.

One thing I recommend to do on the client-side is to provide your own exception handler for any uncaught exceptions using the GWT.setUncaughtExceptionHandler() method. After having caught these kind of exceptions you have several alternatives: GWT.log(message, caught), Debug.println (message_with_stacktrace), when working with Mat Gessel's' Debug class, or - coming closer to JavaScript - Window.alert(message_with_stacktrace), or your own customized error reporting. Figure 3 shows the GWT console output if you do not catch all exceptions.

Figure 3: GWT console output when not catching all exceptions on the client side

Figure 3: GWT console output when not catching all exceptions on the client side

Depending on the origin, you will either get "Failure to load module" (if the exception was thrown inside your onModuleLoad() method) or "Uncaught exception escaped", if it has been thrown elsewhere in your client code. Of course, you can click on the error message in the GWT console to get the full stacktrace but I recommend to use the startup sequence shown in listing 1 as a template for your onModuleMoad() method. It makes use of a small DebugUtility class that provides a default client-side error handling that can easily be customized.

On the server-side you have the broad power of the java.util.logging API or of log4j, depending on your personal preferences or on the project's constraints. However, if you do not patch GWT's com.google.gwt.user.server.rpc.RemoteServiceServlet class, for uncaught unchecked exceptions you will only get a hint inside the stacktrace that points to your server-side class that produced the error; for checked exceptions that are covered and reported inside your catch() block, everything is fine.

To give you an idea of what I mean, please look at figure 4 that shows two screenshots of error messages caught in hosted mode with Window.alert (you will get the same messages on your Eclipse console):

  • The first alert window contains the original message from the RemoteServiceServlet class pointing to a proxy.

  • The second alert window shows the output with the patch mentioned above, pointing to the concrete line of servlet code that forced the error (marked with a blue border).

Figure 4: Server-side exception reporting enhanced

Figure 4: Server-side exception reporting enhanced

The code presented in listing 2, an addition to the processCall() method of the RemoteServiceServlet class does the magic (my statements are marked as bold).

The only thing to pay attention to is to incorporate the patched class so that it is searched before the original GWT code from gwt-user.jar in your classpath. But note: all this patching will not help you when you call a remote method with a wrong target in your setServiceEntryPoint(GWT.getModuleBaseURL() + "/wrongTarget") method. This can happen, e.g., with copy/paste actions when you tried to "reuse" server access code, but you forgot to adapt the target string constant.

Tip 3: Beware of the GWT Shell's "Refresh" Button Pitfalls

When you start your application in hosted mode, you will see a "Refresh" button in your browser's toolbar. When you press this button, GWT will recompile your modified Java client sources to Java bytecode (as .tmp files in the .gwt.-cache/bytecode directory) and reload your module. You can use this button to shorten your edit-compile-debug cycle, but there are some things you should keep in mind when using this nice feature:

  • Only modified sources are recompiled, i.e., no new bytecode is generated for files that depend on your modified code. So, when you change the value of a global constant, let's say the value of a public final int field, you will not see this change in dependent files immediately.

  • Only modified sources are recompiled by GWT. That means that even a "Project clean" inside your Eclipse IDE does not help. You have to touch all dependent sources, e.g., by adding a new empty line.

    Because this would be quite a cumbersome procedure, my advice is to follow these 4 steps when modifying global constants:

    1. Change your public final constant value in the corresponding source file.
    2. Recompile the changed source.
    3. Delete the GWT cache by removing the complete <ModuleName>-client/.get-cache/bytecode directory.
    4. Create a new GWT cache with recompiled bytecode by starting your application from scratch using "Run as" inside Eclipse. It's better to ignore the Refresh button in such cases, although there are situations when deleting the complete <ModuleName>-client/.get-cache/bytecode directory would allow you to use the Refresh button.
  • When modifying server-side code, the GWT bytecode cache is not affected. However, the embedded Tomcat instance will cache it, and therefore only your first code change after starting from scratch will be acknowledged when you use the Refresh button. So again, to be on the safe side, it's better to start your application from scratch after changing server-side code.

Tip 4: Read Servlet Init Parameters in Hosted Mode too

When you are working with a database system, you generally do not want to have hard-coded database connection parameters in your servlet source. Typically you will read them from a property file, or even better, you will supply them as init parameters to your servlet (as a part of your application's web.xml file). This works fine when running your application in web mode, but it fails in hosted mode. This is due to restrictions in the GWT hosted mode servlet processing. The good news is: you can overcome this obstacle by modifying the web.xml file that will be used by the embedded Tomcat instance. To do so, modify (or create if necessary) the web.xml file in the <ModuleName>-client/tomcat/webapps/ROOT/WEB-INF directory: in addtion to the GWTShellServlet mapping for the embedded Tomcat add a context section with the init parameters as shown in listing 3 for my JUnit2MR example. Because the context information is "global", and not targeted to a specific servlet, you have either only one section of init parameter information here, or you have to use a special naming scheme to relate parameters to different servlets. When using this new web.xml file, you can delete the old one in your src/web/WEB-INF folder.

In your servlet code you access the init parameters the same way they are read in web mode, e.g., final String host = getInitParameter("host")

The hack I used to make this happen is to modify GWT's RemoteServiceServlet in the same way as was done in tip 2. Now you simply have to override GenericServlet's getInitParameter() method in order to use getServletContext() instead of getServletConfig(). That's all!

                                   // new method added to RemoteServiceServlet
                                       public String getInitParameter(String name) {
                                       return getServletContext().getInitParameter(name);
                                       }
                                     

My classpath settings guarantee that the modified RemoteServiceServlet class is executed instead of the one in gwt-user.jar. For the GWT web mode, the modified class is not deployed. When deploying your application to Tomcat, an Ant script (or my Gant script) can help you to deal with the different web.xml versions.

An additional tip: When testing different server code behavior in hosted and web mode, I found it saved time to skip the GWT compile part in my Gant script and to copy the pre-compiled JavaScript code from a "temp" location instead. This made sense with compile times of up to 10 minutes when having more than trivial client-side code. With Gant it is very easy to incorporate a switch in the script that controls whether you are compiling or reusing older JavaScript files. The parameter for this switch is provided on the command-line, e.g., as ">gant -D 'compileGWT=yes' deploy". For details on this Gant script see tip 8.

| 1 2 3 Page 2