Some Recipes to Improve Your Google Web Toolkit Development

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

Page 3 of 3

Tip 5: Display your PDF Files inside the Browser

Most real web applications give you a way to produce and read a PDF file. In our context, I assume that this PDF file will be produced by the servlet, e.g., by means of JasperReport. The created file can be read later inside the Browser by clicking a specific hyperlink. If you want to test such a feature in both the hosted mode and in web mode I propose you adopt the following procedure:

1. Design your RPC interface to accept a boolean parameter that tells the server whether we are running in hosted mode or in web mode. The interface method should return a string with the name (i.e., the last part of the filename) of your PDF file created by the servlet.

2. Implement the servlet code according to the code presented in listing 4, depending on the boolean parameter "isScript".

3. On the client side: inside your widget code call the createXyzPDF() method with a GWT.isScript() parameter and create an external hyperlink containing the servlet result string.

Listing 4 shows an example where the interface method is called createSummaryPDF(). The string returned from the servlet is "summary.pdf".

Consistent with the Perl philosophy of "There's more than one way to do it", I do not claim that this is the only way to handle this case, but it currently works fine for me. Please note that before starting your application in hosted mode, you have to create at least a dummy "summary.pdf" file (the filename returned from the servlet) in your <ModuleName>-client project's src/.../public folder; otherwise you will get "HTTP 404 - The webpage cannot be found" when the GWT tries to read your PDF when clicking on the hyperlink in your browser ;-)

Tip 6: Aim for a Stateless Server

One crucial question when designing client/server web applications is "how to deal with session and state management?" In the Web 1.0 world the answer is obvious: session and state management is a server issue. But with GWT you have another choice. No longer does the server have to be a "web" service that only supplies HTML content. Using GWT RPC the server now supports services - implemented by servlets in our case - that only supply structured data.

So, "How does GWT influence session & state management?" At last years JAOO conference in Denmark, GWT Tech Lead Bruce Johnson pointed out that with GWT session management should now be a client issue. Figure 5 shows a slide that reviews the changes.

Figure 5: A stateless server with GWT

Figure 5: A stateless server with GWT

In my JUnit2MR GWT application I started with the traditional approach handling session state in my servlets. But it was a rather nasty job, and I looked for another option. So, after seeing Bruce's presentation I decided to redesign the whole application to follow his mantra: "Session state? ... All client...not a server issue". But that step required changing all my RPC interfaces, my caching strategies, and last but not least, all my servlets. Therefore my recommendation is: Consider early on where to put session and state management, and give Bruce Johnson's recipe a chance. It pays at the end.

Because of this decision I had more communication between client objects. So I used the well-known GoF mediator pattern. However, we have some JDK 1.4 and GWT runtime library restrictions on the client side. Therefore I re-implemented the PropertyChangeEvent class and the mediator support to handle listener registration and message broadcast. Please contact me, if you are interested in the mediator part of the system.

Tip 7: Automate your GWT Web Testing with Selenium

Selenium (see Resources) is an open source tool that enables you to easily test web applications that contain rich, client-side, interactive content. So it is an ideal candidate for testing an Ajax application like the one you have created with GWT. Of course, there is still JUnit and the JUnit support inside GWT, especially for the asynchronous parts of your system. That has been described in about every serious publication about GWT. Therefore I will draw your attention to Selenium because I found it easy to use (at least its IDE), powerful, and, last but not least, it has a lot in common with JUnit. You can work with the Selenium IDE to record GUI use-cases, then running the recorded actions with its "Play" feature. Every action may be followed by a JUnit-like "assert" command that verifies some text on a page etc. The IDE is a Firefox extension, but be sure to use the latest Selenium version "Selenium IDE 0 .8 .7" because it contains a major bug fix for the "waitFor ..." commands. These commands -- together with the "pause" commands -- are substantial when it comes to testing Ajax applications. When you are satisfied with your tests you can even generate JUnit Java test classes out of Selenium's proprietary test format. Figure 6 gives you an idea of Selenium's IDE applied to my JUnit2MR example web app that runs with Firefox 2.0. In the IDE screenshot you can see "pause" and "waitFor" commands as well as assertions that could be verified in the test run (marked green) or that failed (marked red). Of course that's only a small part of the tests you can do with Selenium, e.g., you can use XPath expressions to look for text in specific positions.

Figure 6: Example for a Selenium GUI test of a GWT web application

Figure 6: Example for a Selenium GUI test of a GWT web application
Tip 8: Deploy Your Application with a Groovy Gant Script

It's truly great, trying out your application in GWT hosted mode, but the real power of GWT comes when deploying your application to an application server or servlet container like Tomcat. In this step you need to create a "war" file that should be copied automatically to your Tomcat "webapps" destination. Of course, you can do all the necessary preparation, compilation, copy and other tasks with Ant and ant-contrib, and, indeed, that was how I started. But with my Ant script getting more complicated, I found the ant-contrib control structures and property regex processing a little bit cumbersome. After reading "Groovy in Action" with a recommendation for "Gant", I decided to have the best of both worlds: Groovy + Ant = Gant. Installing Groovy and Gant is just an matter of less than 10 minutes. Then you can customize the 'build.gant' script with ordinary properties coming from a 'build.properties' file.

As I mentioned before, I started with a familiar Ant script. It was based on the work of Gautam Shah (see Resources). If you prefer ordinary Ant over Gant, I suggest starting with Gautams's Ant script. If you want the power and flexibility of a scripting language plus the capacity of Ant, then I propose you look at my Gant script, too.

Conclusion

The Google Web Toolkit is really an attractive way of producing Ajax applications in Java where the client part is compiled to JavaScript. Therefore, in general, you do not have to worry about the JavaScript side of things, including browser optimizations. With a solid AWT/Swing/SWT and servlet background I believe you are actually quickly up to speed with GWT, but if you will do more than rapid prototyping, some difficulties and problems remain. So, I hope that the tips and recipes presented in this article were useful to help you in preparing and implementing your GWT-based web applications. Perhaps, after leaving GWT hosted mode and deploying your modified solutions successfully many times to a servlet container, you will enjoy the power of GWT as I did.

Author bio

Klaus Berg has studied electrical engineering and informatics at the University of Karlsruhe in Germany. He was an architect and implementor in some projects at Siemens focused on Java GUI development with Swing and Java Web Start as well as acting on the server side creating Java based intranet applications. Now he works as a senior engineer in the area of functional and performance testing, mainly for J2EE software.

Code Listings

Listing 1: A template for your onModuleLoad() method with improved error handling

/**
                                       * This is a template for your GWT entry point method.
                                       */
                                       public void onModuleLoad() {
                                       DebugUtils.initDebugAndErrorHandling();
                                       Window.setTitle("<Your App Title>");
                                       Window.addWindowCloseListener(new WindowCloseListener() {
                                       /**
                                       * Fired after the browser window closes or navigates to a different site.
                                       * This event cannot be cancelled, and is used mainly to clean up your application
                                       * state and/or save the state to the server.
                                       */
                                       public void onWindowClosed() {
                                       Debug.println("App window closed.");
                                       }
                                       
                                       public String onWindowClosing() {
                                       return "Application closing...";
                                       }
                                       });
                                       // try/catch is necessary here because GWT.setUncaughtExceptionHandler()
                                       // will work not until onModuleLoad() has returned
                                       try {
                                       // create and init your startup widgets (e.g., Login Dialog) here
                                       createStartupWidgets(); 
                                       } catch (RuntimeException e) {
                                       GWT.log("Error in 'onModuleLoad()' method", e);
                                       e.printStackTrace();
                                       }
                                       }
                                       
                                       // DebugUtils class used by 'onModuleLoad()'
                                       //
                                       import asquare.gwt.debug.client.Debug;
                                       import asquare.gwt.debug.client.DebugConsole;
                                       
                                       import com.google.gwt.core.client.GWT;
                                       import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
                                       import com.google.gwt.user.client.Window;
                                       
                                       /**
                                       * Helper class that provides static utility methods to 
                                       * support GWT-client-side error reporting.
                                       */
                                       public class DebugUtils {
                                       
                                       private DebugUtils() {
                                       // hide constructor because we are a utility class with static methods only.
                                       }
                                       
                                       public static void initDebugAndErrorHandling() {
                                       Debug.enable();
                                       DebugConsole.getInstance().disable(); // can be enabled for web mode
                                       GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                                       public void onUncaughtException(final Throwable tracepoint) {
                                       performDefaultErrorHandling(tracepoint);
                                       }
                                       });
                                       }
                                       
                                       public static void performDefaultErrorHandling(final Throwable caught) {
                                       if (caught != null) {
                                       final String stacktrace = DebugUtils.getStacktraceAsString(caught);
                                       Debug.println(stacktrace);
                                       Window.alert(stacktrace);
                                       } else {
                                       final String message = "Error ocuured, but we have no further information
                                       about the cause";
                                       Debug.println(message);
                                       Window.alert(message);
                                       }
                                       }
                                       
                                       public static String getStacktraceAsString(final Throwable tracepoint) {
                                               final StackTraceElement[] trace = tracepoint.getStackTrace();
                                               final StringBuffer sbuf = new StringBuffer(2048);
                                               sbuf.append(tracepoint.toString());
                                               sbuf.append(": at\n");
                                               // I cut the stacktrace at depth 7
                                               final int length = Math.min(7, trace.length);
                                               for (int i = 0; i <= length; i++) {
                                                   sbuf.append(trace[i].toString());
                                                   sbuf.append("\n");
                                               }
                                               if (trace.length > 7) {
                                                   sbuf.append("...");
                                               }
                                               final String stacktrace = sbuf.toString();
                                               return stacktrace;
                                           }
                                       
                                       }
                                     

Listing 2: Patching RemoteServiceServlet class to get better error information

public String processCall(String
                                       payload) throws SerializationException {
                                           ...
                                       ... String myServletException = null; // added by KPB
                                       try {
                                       .....
                                               } catch (InvocationTargetException e) {
                                                   // Try to serialize the caught exception if the client is expecting it,
                                                   // otherwise log the exception server-side.
                                                   caught = e;
                                                   Throwable cause = e.getCause();
                                                   
                                                   // added by KPB
                                                   final StringWriter stringWriter = new StringWriter(1000);
                                                   e.printStackTrace(new PrintWriter(stringWriter));
                                                   final StringBuffer sbuf = stringWriter.getBuffer();
                                                   int startIndexCause = sbuf.indexOf("Caused by:");
                                                   if (startIndexCause != -1) {
                                                       myServletException = "\n" + sbuf.substring(startIndexCause) + "\n";
                                                   }
                                                   // end additional code
                                                   
                                                   ......
                                       
                                               // Let subclasses see the serialized response.
                                               //
                                               onAfterResponseSerialized(responsePayload);
                                       
                                        // added by KPB
                                               if (myServletException != null) {
                                                   responsePayload = myServletException + responsePayload;
                                               }
                                            // end additional code
                                       
                                               return responsePayload;
                                       }
                                     

Listing 3: Enhancing web.xml to access servlet init parameters in hosted mode

<?xml version="1.0"
                                       encoding="UTF-8"?>
                                       <web-app>
                                           <!-- GWTShellServlet mapping for embedded Tomccat -->
                                           <servlet>
                                               <servlet-name>shell</servlet-name>
                                               <servlet-class>com.google.gwt.dev.shell.GWTShellServlet</servlet-class>
                                           </servlet>
                                           <servlet-mapping>
                                               <servlet-name>shell</servlet-name>
                                               <url-pattern>/*</url-pattern>
                                           </servlet-mapping>
                                           
                                           <!-- Display name -->
                                           <display-name>JUnit2MR</display-name>
                                           
                                       <!-Context info for embedded Tomccat to allow access to servlet's init parameters in hosted mode, too-->
                                           <context-param>
                                               <param-name>host</param-name>
                                               <param-value>localhost</param-value>
                                           </context-param>
                                           <context-param>
                                               <param-name>port</param-name>
                                               <param-value>3306</param-value>
                                           </context-param>
                                           <context-param>
                                               <param-name>db</param-name>
                                               <param-value>fts_db</param-value>
                                           </context-param>
                                           <context-param>
                                               <param-name>cq_db</param-name>
                                               <param-value><![CDATA[SYM_API_7.0.0/SYM]]></param-value>
                                           </context-param>
                                       
                                           <servlet>
                                               ... your module servlet's information goes here...
                                       }
                                     

Listing 4: PDF file creation on the server and file usage on the client

// Client-Code (Widget)
                                       String pdfLink;
                                       dataService.createSummaryPDF(GWT.isScript() ,new AsyncCallback() {
                                          public void onFailure(final Throwable caught) {
                                             DebugUtils.performDefaultErrorHandling(caught);
                                          }
                                          public void onSuccess(final Object result) {
                                             pdfLink = (String)result;
                                             final String infoAsHtml = "..." + "<P/> + "<A href=\"" + GWT.getModuleBaseURL()
                                       +
                                                pdfLink + "\" target=\"_blank\">Summary (PDF)</A>";
                                             final HTML htmlText = new HTML(infoAsHtml);
                                             ...
                                          }
                                       });
                                       
                                       // Corresponding Server-Code (Servlet)
                                       private static final String SUMMARY_PDF_NAME = "summary.pdf";
                                       public String createSummaryPDF(final boolean isScript) {
                                         try {
                                           if (isScript) {
                                             FileCopy.copy(PDF_FILE_TO_COPY, getGwtServletContext().getRealPath(
                                              "/" + SUMMARY_PDF_NAME)); // FileCopy is a replacement for your real PDF creation
                                           } else {
                                             // the GWTShell servlet expects resources coming from the src.../public folder
                                             String destinationPathname = System.getProperty("user.dir")
                                                + "/src/" + getClass().getName().replace('.', '/').replace("server", "public");
                                             destinationPathname = destinationPathname.substring(0,
                                                destinationPathname.lastIndexOf('/')) + SUMMARY_PDF_NAME;
                                             generatePdfIinPath(destinationPathname);
                                           }
                                         } catch (final IOException e) {
                                             e.printStackTrace();
                                         }
                                         return SUMMARY_PDF_NAME;
                                       }
                                       
                                       public final getGwtServletContext() {
                                         final HttpServletRequest request = this.getThreadLocalRequest();
                                         return request.getSession().getServletContext();
                                       }
                                     

Learn more about this topic

Google Web Toolkit home page

GWT Unofficial Wiki page

Introduction to GWT development: Ease AJAX development with the Google Web Toolkit

GWT add-on for debugging, developed by Mat Gessel.

GUI-based GWT testing with Selenium

For a good Ant script to deploy your GWT applications to Tomcat see: Google Web Toolkit: AJAX Buzz Meets Real World Development

Groovy Gant

To get the Gant script and the corresponding build.properties file for easy GWT deployment to Tomcat, download sample code.

| 1 2 3 Page 3