Another Approach To Use GWT With NetBeans

GWT is one of my favorite tools since it was born, but unfortunately I never had a chance to use it at work. I tested it some time ago and I loved it, but version 1.5 stands even above. I can't keep ignoring it, so I'm going to use it in my free time. I'm a GWT newbie, keep this in mind while reading, nevertheless I've organized a simple setup that I like and I wanted to share it hoping for comments and suggestions.

Most Java developers use Maven, as I do, although not always. I tried the GWT Maven plugin, it does everything, but I found it to be slow, even after it downloads everything. Since I've not found an easy way to make it run fast, I went back to Ant. The setup I'm going to describe runs faster than the Maven based one.

I tried the GWT4NB plugin for NetBeans, it works very well, but when I tested GWT for the first time, I used the tools created by Joakim Recht (GWT Task for Ant and GWT XDoclet). It looked smarter to me than using the wizards provided by an IDE. Now another utility has been added to Joakim's GWT swiss knife: a GWT XDoclet replacement for GWT 1.5 that uses annotations instead of XDoclet.

I have another goal: building a JAR instead of a WAR. Why? Because it would make upgrades easier: when a new version is out, I'd simply replace the JAR, without touching the web application that might have been customized in the meanwhile. I'm thinking of my good old MeshCMS, where pages are created inside the webapp: I'd like to write a new admin interface using GWT, and then distribute the whole CMS as a JAR file.

But let's go to the setup. Since the goal is to build a JAR, create a new Java Class Library project instead of a web application:

New Java Class Library project: step 1

New Java Class Library project: step 2

Some sources will be generated automatically, so add another source directory to avoid mixing them with handwritten code:

Add a source directory: step 1

Add a source directory: step 2

Add a source directory: step 3

We need some libraries: GWT itself and the two Joakim's tools: GWT Task for Ant and gwt-apt. This is the list of JARs, together with other GWT files:

List of JARs

This is how the libraries are imported in NetBeans (you might want to create some libraries in the IDE instead of adding JAR files like I did):

Imported JARs

Note that you need to add src and gensrc to the libraries, since GWT wants to read the sources when compiling.

It's better to avoid building the JAR every time the project is compiled:

Packaging

Now it's time to edit the Ant build script to automate the code generation and GWT compilation. Open build.xml and add this code:

<target name="-post-clean">
  <delete dir="${basedir}/tomcat"/>
  <delete dir="${basedir}/www"/>
  <delete dir="${basedir}/.gwt-tmp"/>
  <delete includeemptydirs="true">
    <fileset dir="${basedir}/gensrc" includes="**/*"/>
  </delete>

  <path id="gcp">
    <path path="${run.classpath}"/>
  </path>
  <taskdef resource="dk/contix/gwt/apt/ant-apt.xml" classpathref="gcp" />
  <gwtgenerate srcdir="${src.dir}" classpathref="gcp"
      gendir="${basedir}/gensrc"
      serviceFactory="my.gwtsetup.client.ServiceFactory"
      prefix=""
  />
</target>

<target name="-pre-jar">
  <mkdir dir="${basedir}/www"/>
  <taskdef resource="dk/contix/ant/gwt/ant-gwt.xml" classpath="${run.classpath}" />
  <gwtcompile destdir="${basedir}/www" optimize="true">
    <fileset dir="${basedir}/gensrc">
      <include name="**/*.gwt.xml"/>
    </fileset>
  </gwtcompile>

  <mkdir dir="${build.classes.dir}/www"/>
  <copy todir="${build.classes.dir}/www">
    <fileset dir="${basedir}/www"/>
  </copy>
</target>

build.xml

Please note that this code should be optimized by defining new parameters. This is a work in progress and is good for now.

I used -post-clean and -pre-jar, but you could use some custom targets, or add them to the build target (using -pre-compile and -post-compile, for example). I like this approach because when I build the project I don't generate the additional code (I use "Clean and Build" for that), and I skip the final GWT compilation that is not needed to run the project in hosted mode. It will be generated when building the JAR.

We need some sample code to run: I created a dummy project with the applicationCreator tool provided by GWT, then I took the Module class containing the entry point and the HTML file that will load the generated script. I modified them slightly and then added the @Module annotation to the Java class:

package my.gwtsetup.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import dk.contix.gwt.annotations.Module;

@Module(packageName = "my.gwtsetup",
        inherits = {"com.google.gwt.user.User",
                    "com.google.gwt.user.theme.standard.Standard"})
public class Main implements EntryPoint {
  public void onModuleLoad() {
    Button button = new Button("Click me");

    VerticalPanel vPanel = new VerticalPanel();
    vPanel.setWidth("100%");
    vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
    vPanel.add(button);

    RootPanel.get().add(vPanel);

    final DialogBox dialogBox = new DialogBox();
    dialogBox.setText("Welcome to GWT!");
    dialogBox.setAnimationEnabled(true);
    Button closeButton = new Button("close");
    VerticalPanel dialogVPanel = new VerticalPanel();
    dialogVPanel.setWidth("100%");
    dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
    dialogVPanel.add(closeButton);

    closeButton.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        dialogBox.hide();
      }
    });

    dialogBox.setWidget(dialogVPanel);

    button.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        dialogBox.center();
        dialogBox.show();
      }
    });
  }
}

Main.java

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
      <title>Main</title>
      <script type="text/javascript" language="javascript"
              src="my.gwtsetup.Main.nocache.js">
      </script>
  </head>

  <body>
    <iframe src="javascript:''" id="__gwt_historyFrame" tabindex='-1'
            style="position:absolute;width:0;height:0;border:0">
    </iframe>
  </body>
</html>

Main.html

Now that the code is in place, clean and build your project to get the generated code:

Clean and build

We got the Main.gwt.xml module definition for free, and also a ServiceFactory that will be useful when adding GWT services to the project:

Current project structure

Now it's time to run the project. Go back to its properties, and set the Run parameters as follows:

Run parameters

com.google.gwt.dev.GWTShell is the hosted shell main class, while my.gwtsetup.Main/Main.html is the path of our HTML file in the GWT compiled application. We can finally run the application now:

First run

Now let's add a simple service to have the popup print the current time as returned by the server. Since gwt-apt will generate the remaining code for us, we just need to create the concrete service implementation. The service will be named TimeService, so our implementation is TimeServiceImpl and goes into the server package. Use the class wizard to create the new class:

Class wizard

You need to annotate the generated class to have it recognized as a GWT service:

@Service(path = "/timeservice", service = "my.gwtsetup.client.TimeService")

Annotated class

Now clean (or clean and build) the project to generate the client interface:

Clean

This is the updated project structure. We got the TimeService and TimeServiceAsync classes, while the ServiceFactory was updated with the new service.

Updated project structure

Now let the TimeServiceImpl class extend RemoteServiceServlet and implement the generated TimeService interface, then add a method that returns the current date, and annotate it with @ServiceMethod:

@ServiceMethod
public String getCurrentTime() {
  return new Date().toString();
}

Updated TimeServiceImpl.java

Clean the project again, so that the generated files get updated with the new method.

Now we must make some use of the new service. As you can easily guess, we're going to replace the message shown by the popup with the text returned by this service. Edit Main.java and change the button click listener as follows:

TimeServiceAsync timeService = ServiceFactory.getTimeService();

timeService.getCurrentTime(new AsyncCallback<String>() {
  public void onFailure(Throwable caught) {
    dialogBox.setText(caught.getMessage());
  }

  public void onSuccess(String result) {
    dialogBox.setText(result);
  }
});

dialogBox.center();
dialogBox.show();

Updated Main.java

Now clean, build and run to see the current time in our popup.

Last run

If you look at the development shell, you'll see that GWT complains about how the service is called. It depends on how gwt-apt builds the service URL and might change in a future release (I used the current version, which is gwt-apt-1941.jar). If you want, you can fix it by unpacking gwt-apt-1941.jar, editing dk/contix/gwt/apt/servicefactory.tmpl and then rebuilding the JAR (it's just a template, there's no need to compile anything). You must edit line #16 and change it from

prefix = base.substring(0, base.lastIndexOf('/') ) + "/..";

to

prefix = base.substring(0, base.lastIndexOf('/') );

I'm going to test this setup more extensively and I'll report any issue with it. I hope to add more articles soon, as my GWT experience grows. For example, a second part of this article might deal with integrating the JAR in a web application project and run the final code in client mode.

You can download the sample project, it's just 14 Kbytes.

Robert

Mar 17, 2009 5:20 PM

Luciano,

Again well done on MeshCMS, its great. Like you Ive been trying various AJAX options, the one that impressed me is ZK. (www.zkoss.org)
Very simple and fast to use, all javascript and comms is automaticly handled. Ive built several sites now, and its very powerful
Rob

Luciano

Mar 18, 2009 5:08 AM

Yes, there's a lot of good products. I'm also trying out SmartClient (www.smartclient.com) and it looks great. It's LGPL, has lots of widgets, examples and documentation. I'm pleased with its DataSource model, that seems very logical and productive.

Hanumant

Dec 22, 2010 5:48 PM

Most of the plugins are tied to specific versions of either the IDE or GWT. Since both are quite dynamic in terms of new releases, I found this approach explained on http://javaworkshop.wordpress.com that doesn't use any plugins to be a lot transparent and scalable for large projects if you are using Maven as your build system. Direct link is http://javaworkshop.wordpress.com/2010/12/22/debugging-gwt-project-using-netbeans-and-maven-without-the-damn-plugins/

Comments are closed.

Search