Tapestry 5 GWT Integration

A version of the source as a maven project for Eclipse is available here: t5gwt.jar.

Download the Source code and Project This article is on making use of both of these technologies. http://www.phy6.net/downloads/T5GWT/myapp.png

Reader Requirements

Technologies Used

GWT and Tapestry 5 (Thanks to Martin Kersten)

GWT and Tapestry have in common: Both are Web Techs, Both have a client and server side

GWT vs. Tapestry:

Lets get you set up

You can download the whole project using the link above. Here's the POM for you to look at:

full pom.xml

<project
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.example</groupId>
        <artifactId>myapp</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <packaging>war</packaging><!--
        First use clean compile gwt:compile, and afterwards you can just run the compile.cmd that gets generated.
        Remember to refresh your source directory before serving this, since the GWT compilation outputs into a source dir!
        --><name>myapp Tapestry 5 Application</name>
        <dependencies>
        <!--  GWT deps (from central repo) -->
                <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>provided</scope>
                </dependency>
                <dependency>
                        <groupId>com.google.gwt</groupId>
                        <artifactId>gwt-dev</artifactId>
                        <version>${gwtVersion}</version>
                        <classifier>${platform}-libs</classifier>
                        <type>zip</type>
                        <scope>provided</scope>
                </dependency>
                <dependency>
                        <groupId>com.google.gwt</groupId>
                        <artifactId>gwt-dev</artifactId>
                        <version>${gwtVersion}</version>
                        <classifier>${platform}</classifier>
                        <scope>provided</scope>
                </dependency>
                <dependency>
                        <groupId>log4j</groupId>
                        <artifactId>log4j</artifactId>
                        <version>1.2.14</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                        <version>4.1</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.apache.tapestry</groupId>
                        <artifactId>tapestry-core</artifactId>
                        <version>${tapestry-release-version}</version>
                </dependency>
                <!--
                        A dependency on either JUnit or TestNG is required, or the surefire
                        plugin (which runs the tests) will fail, preventing Maven from
                        packaging the WAR. Tapestry includes a large number of testing
                        facilities designed for use with TestNG (http://testng.org/), so it's
                        recommended.
                -->
                <dependency>
                        <groupId>org.testng</groupId>
                        <artifactId>testng</artifactId>
                        <version>5.1</version>
                        <classifier>jdk15</classifier>
                        <scope>test</scope>
                </dependency>
        </dependencies>
        <build>
                <finalName>myapp</finalName>
                <resources>
                        <resource>
                                <directory>src/main/resources</directory>
                                <includes>
                                        <include>**</include>
                                </includes>
                                <excludes>
                                        <exclude>hibernatecfg/*</exclude>
                                        <exclude>hibernate.cfg.xml</exclude>
                                        <exclude>log4j.properties</exclude>
                                        <exclude>spy.properties</exclude>
                                        <exclude>version.properties</exclude>
                                        <exclude>**/.svn/**</exclude>
                                </excludes>
                        </resource>
                        <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                        <include>**/*</include>
                                </includes>
                                <excludes>
                                        <exclude>**/*.java</exclude>
                                        <exclude>**/.svn/**</exclude>
                                </excludes>
                        </resource>
                </resources>
                <plugins>
<!--  configure the GWT-Maven plugin -->
                        <plugin>
                                <groupId>com.totsp.gwt</groupId>
                                <artifactId>maven-googlewebtoolkit2-plugin</artifactId>
                                <version>2.0-beta26</version>
                                <configuration>
                                        <logLevel>INFO</logLevel>
                                        <compileTargets>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.Sample</value>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.ComplexSample</value>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.StockWatcher</value>
                                        </compileTargets>
                                        <runTarget>/myapp/Start</runTarget>
                                        <style>DETAILED</style>
                                        <noServer>false</noServer>
                                        <extraJvmArgs>-Xmx512m</extraJvmArgs>
                                        <!--
                                                this parameter is VERY important with automatic mode - has to
                                                match the version in your declared deps
                                        -->
                                        <!--
                                                if this is set incorrect, or left out and default does not match
                                                (default is 1.5.3) you will have mysterious errors
                                        -->
                                        <gwtVersion>${gwtVersion}</gwtVersion>
                                        <!-- This puts the GWT output (javascripts) into the source tree -->
                                        <output>src/main/webapp</output >
                                </configuration>
                                <executions>
                                        <execution>
                                                <goals>
                                                        <!--<goal>mergewebxml</goal>
                                                        --><!--<goal>i18n</goal>
                                                        -->
                                                        <goal>compile</goal>
                                                        <!--<goal>test</goal>
                                                -->
                                                </goals>
                                        </execution>
                                </executions>
                        </plugin>
                        <!--  Use the dependency plugin to unpack gwt-dev-PLATFORM-libs.zip -->
                        <!--
                                (this is a replacement for the old "automatic" mode - useful if you
                                don't have GWT installed already, or you just want a maven way to
                                handle gwt deps)
                        -->
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-dependency-plugin</artifactId>
                                <executions>
                                        <execution>
                                                <id>unpack</id>
                                                <phase>compile</phase>
                                                <goals>
                                                        <goal>unpack</goal>
                                                </goals>
                                                <configuration>
                                                        <artifactItems>
                                                                <artifactItem>
                                                                        <groupId>com.google.gwt</groupId>
                                                                        <artifactId>gwt-dev</artifactId>
                                                                        <version>${gwtVersion}</version>
                                                                        <classifier>${platform}-libs</classifier>
                                                                        <type>zip</type>
                                                                        <overWrite>false</overWrite>
                                                                        <outputDirectory>${settings.localRepository}/com/google/gwt/gwt-dev/${gwtVersion}</outputDirectory>
                                                                </artifactItem>
                                                        </artifactItems>
                                                </configuration>
                                        </execution>
                                </executions>
                        </plugin>
                        <!--
                                If you want to use the target/web.xml file mergewebxml produces,
                                tell the war plugin to use it. Also, exclude what you want from the
                                final artifact here.
                        -->
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-war-plugin</artifactId>
                                <configuration><!--
                                        <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
                                        --><warSourceExcludes>.gwt-tmp/**</warSourceExcludes>
                                </configuration>
                        </plugin>
                        <!--  tell the compiler we can use 1.5 -->
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <configuration>
                                        <source>1.5</source>
                                        <target>1.5</target>
                                </configuration>
                        </plugin>
                </plugins>
        </build>
<!--  profiles (with activation per platform) -->
        <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>
                                <activeByDefault>false</activeByDefault>
                                <os>
                                        <family>mac</family>
                                </os>
                        </activation>
                </profile>
                <profile>
                        <id>gwt-dev-linux</id>
                        <properties>
                                <platform>linux</platform>
                        </properties>
                        <activation>
                                <activeByDefault>false</activeByDefault>
                                <os>
                                        <name>linux</name>
                                </os>
                        </activation>
                </profile>
        </profiles>
        <reporting>
                <!--
                        Adds a report detailing the components, mixins and base classes
                        defined by this module.
                -->
                <plugins>
                        <plugin>
                                <groupId>org.apache.tapestry</groupId>
                                <artifactId>tapestry-component-report</artifactId>
                                <version>${tapestry-release-version}</version>
                                <configuration>
                                        <rootPackage>org.example.myapp</rootPackage>
                                </configuration>
                        </plugin>
                </plugins>
        </reporting>
        <repositories>
                <!--
                        This can be commented out when the tapestry-release-version is a not
                        a snapshot. The non-snapshot Tapestry artifacts are distributed
                        through the central repository at ibiblio.
                -->
                <repository>
                        <id>tapestry-snapshots</id>
                        <url>http://tapestry.formos.com/maven-snapshot-repository/
                        </url>
                </repository>
                <repository>
                        <id>codehaus.snapshots</id>
                        <url>http://snapshots.repository.codehaus.org
                        </url>
                </repository>
                <repository>
                        <id>OpenQA_Release</id>
                        <name>OpenQA Release Repository</name>
                        <url>http://archiva.openqa.org/repository/releases/
                        </url>
                </repository>
                <repository>
                        <id>gwt-maven</id>
                        <url> http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
                </repository>
        </repositories>
        <pluginRepositories>
                <!--
                        As above, this can be commented out when access to the snapshot
                        version of a Tapestry Maven plugin is not required.
                -->
                <pluginRepository>
                        <id>tapestry-snapshots</id>
                        <url>http://tapestry.formos.com/maven-snapshot-repository/
                        </url>
                </pluginRepository>
                <pluginRepository>
                        <id>gwt-maven-plugins</id>
                        <url> http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
                </pluginRepository>
        </pluginRepositories>
        <properties>
                <gwtVersion>1.5.3</gwtVersion>
                <tapestry-release-version>5.0.18</tapestry-release-version>
        </properties>
</project>

For now, I manually make additions to the web.xml if a servlet mapping is needed. So far this example only uses one, for the Stock Prices.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
        <session-config>
                <session-timeout> 30 </session-timeout>
        </session-config>
        <welcome-file-list>
                <welcome-file>Start</welcome-file>
        </welcome-file-list>
        <display-name>Tapestry 5 With GWT</display-name>
        <context-param>
                <param-name>tapestry.app-package</param-name>
                <param-value>se.pmdit.tutorial.t5gwt.tapestry</param-value>
        </context-param>
        <filter>
                <filter-name>app</filter-name>
                <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
        </filter>
        <filter-mapping>
                <filter-name>app</filter-name>
                <url-pattern>/*</url-pattern>
        </filter-mapping>
        <servlet>
                <servlet-name>stock</servlet-name>
                <servlet-class>se.pmdit.tutorial.t5gwt.gwt.server.StockPriceServiceImpl</servlet-class>
        </servlet>
        <servlet-mapping>
                <servlet-name>stock</servlet-name>
                <url-pattern>/se.pmdit.tutorial.t5gwt.gwt.StockWatcher/stockPrices</url-pattern>
        </servlet-mapping>
</web-app>

Adding GWT Targets in the POM

Notice that for each entrypoint you want to have compiled, you add it here.

...
                                        <compileTargets>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.Sample</value>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.ComplexSample</value>
                                                <value>se.pmdit.tutorial.t5gwt.gwt.StockWatcher</value>
                                        </compileTargets>
...

The StockWatcher Entrypoint

package se.pmdit.tutorial.t5gwt.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.i18n.client.Dictionary;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.RootPanel;
/**
 * 
 * @author djue
 *
 */
public class StockWatcher implements EntryPoint {

        private static final int REFRESH_INTERVAL = 5000; // ms

        public void onModuleLoad() {

                // Find all the instances in this DOM where this entry point should be
                // created.
                // The dictionary is created by
                Dictionary gwtComponents = Dictionary.getDictionary("gwtComponents");
                if (gwtComponents != null) {
                        String str[] = gwtComponents.get("stockwatcher").split(",");
                        for (int i = 0; i < str.length; i++) {
                                createUniqueModule(str[i]);
                        }
                }

        }

        /**
         * @param string
         */
        private void createUniqueModule(String string) {

                // // add the main panel to the HTML element with the id "stockList"
                // RootPanel.get("stockList"+string).add(mainPanel);

                final StockWatcherPanel swp = new StockWatcherPanel();
                // add the main panel to the HTML element with the id "stockList"
                RootPanel.get("stockList" + string).add(swp);

                // setup timer to refresh list automatically
                Timer refreshTimer = new Timer() {
                        public void run() {
                                swp.refreshWatchList();
                        }
                };
                refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

                // move cursor focus to the text box
                swp.newSymbolTextBox.setFocus(true);

        }

}

The StockWatcherComponent

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package se.pmdit.tutorial.t5gwt.tapestry.components;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.RenderSupport;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.IncludeJavaScriptLibrary;
import org.apache.tapestry5.annotations.Path;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;

/**
 * 
 * @author djue
 */
@IncludeJavaScriptLibrary("context:se.pmdit.tutorial.t5gwt.gwt.StockWatcher/se.pmdit.tutorial.t5gwt.gwt.StockWatcher.nocache.js")
public class StockWatcherComponent extends GWTEntryPointComponent {
        @Environmental
        private RenderSupport renderSupport;
        @Inject
        private ComponentResources resources;

        @Inject
        @Path("context:se.pmdit.tutorial.t5gwt.gwt.StockWatcher/images/GoogleCode.png")
        @Property
        private Asset banner;

        void setupRender() {
                renderSupport.addScript("GWTComponentController.add('stockwatcher','" + resources.getCompleteId() + "')");
        }
        // If you wanted you could output the DIV from here, then you would not
        // need the template file.
}

Make special note of this line:

...
renderSupport.addScript("GWTComponentController.add('stockwatcher','" + resources.getCompleteId() + "')");
...

It's kind of like adding it to a HashMap<String,String>. (It's not really a HashMap, but later your Entrypoint will look up values associated with a key; The key will be 'stockwatcher', and the values will be the T5 generated ids)

The StockWatcherComponent.tml

Notice the div where the id becomes stockList+component id. This allows a unique and independent stock list to be created.

<div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
Stock Watcher Component
<div id="${resources.completeId}"></div>
<img src="${banner}" />
<h1>Stock Watcher</h1>
<div id="stockList${resources.completeId}"></div>
</div>

...
<div id="${resources.completeId}"></div>
...

Is the same id that you added in the "HashMap" above.

General Tips

In general it can be very easy to get GWT working with Tapestry. The complexity comes in when you want GWT to be a component, and you consider that a component may exist many times on one page. (think of dialog boxes). In order for the components to behave properly, you must somehow provide a unique ID to the Javascript object that GWT will create.

In your GWT code, make the Entrypoint class do as little as possible. I've read this elsewhere as a GWT mini-pattern, but it helps alot here! Even references to your GWT services can be put into the separate class that your Entrypoint will instantiate. In fact the Entrypoints I show here do pretty much the same thing:

For example, there will be a String like this:

mydialog,mydialog1,mydialog2

These are the unique id's that Tapestry will create for you. Some included Javascript (from the other GWT tutorial) will copy these unique id's into a Javascript variable. The compiled GWT Javascript will then read that list of id's in your Entrypoint (as shown later)

So instead of having all kinds of code in your entrypoint class, refactor it into a separate class that your entrypoint will add to the RootPanel.

The other tip is that when developing GWT by itself, you usually have a HTML file that runs your Javascript. You can still have those if that's what you want, but when using a GWT entrypoint as a compnent, you put the HTML into a Tapestry component/template.

The best way to understand this is to get this sample app working and play with it. You'll see that the component TML is minimal, and in some cases, could be done away with altogether.

Some unusual things about this

The GWT is configured in the POM to output it's compiled directories into your src/webapp. (If someone knows a way to use Maven Assembly or an Ant copy that will work better, please add it to this wiki) This will require a refresh of your source dir after compilation, or Eclipse won't see the GWT compiled code. If Eclipse does not see the new files, then your Tomcat instance won't see it either if you are launching Tomcat from Eclipse.

I have not included the gwt compiled directories in the Zip file, because they should be generated for you when you run:

mvn clean compile gwt:compile

clean will delete the Target directory compile will take care of Java compilation like your T5 code gwt:compile will take care of your GWT generated code (the Javascript)

You could also launch tomcat afterwards using Maven, but I find this less thank friendly.

You can leave "Build Automatically" checked in your Eclipse project settings, and Tomcat should restart when you make changes on the Tapestry side. But you'll need to restart Tomcat otherwise.

Tapestry5GWTIntegration (last edited 2010-06-26 05:47:00 by GeoffCallender)