GWT and Maven - Playing Nicely Together Since 2008

Posted by Sumit Chandel, Developer Programs Engineer - Monday, May 04, 2009 at 1:55:00 PM

Maven is a great resource that allows developers to enable dependency management within their GWT web applications. While a complete description of Maven's dependency management is beyond the scope of this article, we encourage you to read more here . Essentially, Maven allows you to modularize your GWT project, separating the reusable pieces of code (i.e. Custom Widgets or Data Transfer Objects) into their own projects while maintaining versioning. In addition, Maven allows you to incorporate several different modules without having to write/maintain a complex Ant build file.

Modularization

When developing your application you may quickly realize that it would be beneficial to separate large pieces into separate modules for easier management and reusability. We have multiple internal applications that use the same Data Transfer Objects and Custom Widgets. In our case, we found that separating these two pieces into separate modules made everyone's life easier. In order to accomplish this, we created two separate Maven modules, studyblue-data and studyblue-widgets.

Your resulting project hierarchy will look like this once its all hooked up:

Diagram illustrating a StudyBlue sample project hierarchy

Each of the new modules (data and widgets) look similar to the main project because they contain a gwt.xml file, however their project structure looks like this:

+ studyblue-data/
  + src/
    + main/
      + java/
        + com.studyblue.data/
          Data.gwt.xml
        + com.studyblue.data.client/
          Data.java
          ...
      + resources/
    + test
      + java/
      + resources/
  pom.xml

Your Data.gwt.xml file would look like this:

<module>
  <!-- Inherit the core Web Toolkit stuff. -->
  <inherits name="'com.google.gwt.user.User'/">     
  <source path="client">
</module>

Your Data.java file would be empty in most cases:

package com.studyblue.data;

import com.google.gwt.core.client.EntryPoint;

public class Data implements EntryPoint {
  
  public void onModuleLoad() {
  
  }
}

As per usual, your source code goes under the com.studyblue.data.client package. We created an additional project for studyblue-widgets similar to the one above. At this point, you should have three projects open in your Workspace (your main web app, your newly created data module, and your newly created widget module). Once you've organized your code, it's time to hook it up with your core web application via a few hooks with Maven and the gwt.xml files.

Connecting to Maven

So what is that pom.xml file all about? The first step is downloading the GWT-Maven plugin for Eclipse (which takes care of most of the heavy lifting) http://gwt-maven.googlecode.com/svn/docs/maven-googlewebtoolkit2-plugin/index.html . Once you've installed the plugin, you're ready to organize your pom.xml files (one each for: data, widgets, web app).

Our studyblue-data pom.xml file looks like the combination of the following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelversion>4.0.0</modelversion>
  <groupid>studyblue</groupid>
  <artifactid>studyblue-data</artifactid>
  <version>1.1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>studyblue gwt data</name>

This is the portion that indicates how the jar should be created. The jar's title is "[artifactId]-[version].[packaging]". The parts in red, you would subsitute with your own information. *Note: We use -SNAPSHOT to notify Maven that the jar should be updated on every build (see below).

  <repositories>
    <repository>
      <id>gwt-maven</id>
      <url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
    </repository>
  </repositories>

  <!--  include pluginRepository and repository for GWT-Maven -->
  <pluginrepositories>
    <pluginrepository>
      <id>gwt-maven-plugins</id>
      <url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
    </pluginrepository>
  </pluginrepositories>

The repositories tag tells your project where to download the gwt jars (servlet, user, etc). The pluginRepositories tag tells your project where to download the Maven-GWT plugin.

  <build>
    <plugins>
      <plugin>
        <artifactid>maven-compiler-plugin</artifactid>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactid>maven-eclipse-plugin</artifactid>
        <version>2.5.1</version>
        <configuration>
          <additionalprojectnatures>
            <projectnature>org.maven.ide.eclipse.maven2Nature</projectnature>
          </additionalprojectnatures>
          <additionalbuildcommands>
            <buildcommand>org.maven.ide.eclipse.maven2Builder</buildcommand>
          </additionalbuildcommands>
        </configuration>
      </plugin>
    </plugins>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/client/**</include>
          <include>**/*.gwt.xml</include>
        </includes>
      </resource>
    </resources>
  </build>

The build tag tells Maven what Java version you want your jar to be compiled into (1.6 in this case). The resources tag lets your project compiler know which java files should be included in the jar (i.e. ALL YOUR CODE).

  <properties>
    <gwtversion>1.5.3</gwtversion>
  </properties>

The properties tag allows us to set a variable (gwtVersion) so Maven knows which version of GWT it needs to download when compiling. When it's time to upgrade, just change the version and the rest takes care of itself.

  <dependencies>
    <dependency>
      <groupid>com.google.gwt</groupid>
      <artifactid>gwt-user</artifactid>
      <version>${gwtVersion}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

The dependencies tag tells your project which jars you need to compile. In the case of the studyblue-data and studyblue-widgets modules, we reference the GWT user jar, so we need to include it. Notice it takes use of the gwtVersion variable. There are six different types of scope. The three most important are "runtime", "provided" and "compile". Runtime means "I don't need it during compilation of the jar, but I do need it when the jar is executed." Provided means "The project that depends on this module will provide the jar". Compile means "This module needs the jar immediately for compilation".

In this case, we plan on connecting the studyblue-data and studyblue-widget modules to our main web app, which will "provide" the necessary GWT jars for these modules.

    <dependency>
      <groupid>studyblue</groupid>
      <artifactid>studyblue-data</artifactid>
      <version>1.1.0-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>

If one module needs to reference an additional module (i.e. studyblue-widgets depends on studyblue-data), you could insert the above code into studyblue-widget dependencies.

  </project>

Don't forget to close your project tag :) Make sure you generate the appropriate pom.xml for your widgets module as well.

Deployment

Alright, so you've separated your data and widgets from your main app. You built two projects and included a pom.xml file in each. Now it's time to build studyblue-data and studyblue-widgets into jars so you (and your teammates) can use them as necessary.

This part is super easy, just open command prompt or terminal and navigate to the project's root folder. Type the following:

  mvn clean deploy

That's it. Your project will be compiled with all the necessary libraries and is available for consumption by you and your teammates under the title entered above (studyblue-data-1.1.0-SNAPSHOT.jar and studyblue-widget-1.1.0-SNAPSHOT.jar).

Putting It All Together

So, you now have the two jars available, and you could just simply add them to your main web app's classpath. However, if you enable Maven on your main web app, you'll be able to take advantage of Maven's dependency management. This way you'll be able to download updated jars instantly and automatically. Additionally, assuming your main web app project is Mavenized you can include the GWT jar dependencies (servlet, user, etc) in the web app automatically.

To do this, you need to insert the following into your main web apps pom.xml below the properties tag:

  <profiles>
    <profile>
      <id>gwt-dev-windows</id>
      <properties>
        <platform>windows</platform>
      </properties>
      <activation>
        <activebydefault>true</activebydefault>
        <os>
          <family>Windows</family>
        </os>
      </activation>
    </profile>
    <profile>
      <id>gwt-dev-mac</id>
      <properties>
        <platform>mac</platform>
      </properties>
      <activation>
        <os>
          <family>mac</family>
        </os>
      </activation>
    </profile>
    <profile>
      <id>gwt-dev-linux</id>
      <properties>
        <platform>linux</platform>
      </properties>
      <activation>
        <os>
          <name>Linux</name>
        </os>
      </activation>
    </profile>
  </profiles>

The profiles tag allows our developers to use whichever GWT jars are necessary for their operating system (takes care of Windows vs. Mac vs. Linux automatically :) ). No more keeping copies of all GWT jars and native libraries in your project folder.

Finally, modify the dependencies section to look like this:

  <dependencies>
    <dependency>
      <groupid>com.google.gwt</groupid>
      <artifactid>gwt-servlet</artifactid>
      <version>${gwtVersion}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupid>com.google.gwt</groupid>
      <artifactid>gwt-user</artifactid>
      <version>${gwtVersion}</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>com.google.gwt</groupid>
      <artifactid>gwt-dev</artifactid>
      <version>${gwtVersion}</version>
      <classifier>${platform}-libs</classifier>
      <type>zip</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>com.google.gwt</groupid>
      <artifactid>gwt-dev</artifactid>
      <version>${gwtVersion}</version>
      <classifier>${platform}</classifier>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>studyblue</groupid>
      <artifactid>studyblue-data</artifactid>
      <version>1.1.0-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>studyblue</groupid>
      <artifactid>studyblue-widgets</artifactid>
      <version>1.1.0-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

Notice here that the <scope> for the GWT jars is "compile", which ensures they are downloaded during compilation of the web app and "provided" to the dependent modules studyblue-data and studyblue-widgets. The GWT servlet jar is only needed when the web app is executed, so we choose "runtime" here.

Don't forget your gwt.xml

In order to make sure that our main web app knows about studyblue-data and studyblue-widgets we have to include the following lines in our main app's gwt.xml file:

<inherits name='com.studyblue.data.Data' />
<inherits name='com.studyblue.widgets.Widgets' />

Reflecting Updates In Your Modules

During development, we want our main web app to reflect changes that we make in the modules (studyblue-data and studyblue-widgets), everytime we build the project. This way every developer who is working on a project that references the modules (including our main web app), can get the latest build of the jars. We use the Maven keyword "-SNAPSHOT" when versioning our modules during development (i.e. studyblue-data-1.1.0-SNAPSHOT). Snapshot is a special version that indicates a current development copy. With a SNAPSHOT version, Maven will automatically fetch the latest SNAPSHOT every time you build your project. For rapidly moving code, this can be a necessity, particularly in a team environment. When you are ready to release the code, you finalize the version by removing "-SNAPSHOT".

To update your SNAPSHOTS before executing hosted mode or compilation, run this command in your project's root directory:

mvn clean --update-snapshots

Running Hosted Mode and Compiling

As long as your Maven dependencies are added to your project's classpath, you can execute hosted mode and GWT-Compile without worry. At StudyBlue, we use the Maven plugin for Eclipse (http://m2eclipse.sonatype.org/update/ ) to add all our Maven dependencies to our classpath, update snapshots and for editing the pom.xml files.

Conclusion

Maven can be tricky to setup, but once you've got it connected to your GWT project, you'll be amazed at how efficient and modular your application can become.

2 comments:

R G said...

Hey, this is a nice post. Helped me lot. But, I have an issue. According your sample project structure, there is src/main/resources (I have src/main/locales). Maven builds does not copy this to WEB-INF/classes. Any directions to do this?

Thanks!

David Chandler said...

This post is very old. For up to date info, see http://code.google.com/p/google-web-toolkit/wiki/WorkingWithMaven. To answer your question, though, src/main/resources is part of standard Maven structure. If you want to copy resources from another directory, you must configure the Maven resources plugin in your POM.