Java Code Geeks

Tuesday, October 9, 2018

GWT with Spring Boot

Intro

I've been investigating the options for writing UI code in Java recently. In my previous post I investigated Eclipse RAP and found that it can be integrated with Spring Boot in one executable jar. This time I wanted to do the same trick with GWT. 
Everyone likes Spring Boot. It makes a lot of things so much cleaner and easier. But historically the frameworks for creating the UI in the web browser had their own methods to do some of the things Spring Boot does. Unfortunately in many cases their methods look old and outdated. So the goal was to use Spring Boot as much as possible and use GWT only for UI. 
I have to warn the readers this post is actually a classic example of TL;DR :-)). 

GWT Approach

  GWT uses a special compiler to generate Javascript code from Java code. The method is to create a module description file .gwt.xml, use it to import some other modules and write the code in Java with GWT widgets. Then their compiler will generate a lot of javascript code that needs to be included on the html page. They have a tutorial at www.gwtproject.org that explains the basics. 
  They use GWT RPC to call methods on the backend. This approach requires an interface that is shared between the client and the server. The client uses the interface to call the RPC method. The server-side implementation of the method is registered in web.xml as a Servlet with the appropriate URL pattern. 
  One major issue in my opinion is debugging. GWT in latest versions adopted a radical approach of source maps. This means Java code debugging occurs in the browser with source maps enabled and not in Eclipse (or maybe I couldn't make it work in Eclipse). I tried this in Chrome and it actually works but it looks like a bit of a kludge. GWT does not even generate source maps by default. In order to use them one must start the code server and load a different javascript in the html page from this code server. Most people in this case add an option to the compiler.
  I really mean no offence to the GWT team and the supporters of this technology but it looks a little outdated in general. They do not spend too much time developing new features. Even the build plugins are maintained by enthusiasts.

Goals

Here is what I wanted to achieve in my investigation:
  1. Use GWT only for generating the Javascript code that is to be archived together with everything else into the executable jar.
  2. Use Spring Boot for REST endpoints and avoid GWT RPC completely
  3. Use Spring Boot's executable jar to start the application and service the GWT html files with the embedded Tomcat. This also means all the other great Spring Boot features can be used. 

Build Tool

In order to achieve goal #1 we need a good build tool. I've created the sample project from the tutorial with the Maven plugin. Here is the complete configuration that worked for me:
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.0-rc-6</version>
<executions>
<execution>
<goals>
<goal>import-sources</goal>
<goal>compile</goal>
<goal>import-test-sources</goal>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<moduleName>org.example.gwt.StockWatcher</moduleName>
<moduleShortName>StockWatcher</moduleShortName>
<failOnError>true</failOnError>
<!-- GWT compiler 2.8 requires 1.8, hence define sourceLevel here if you use
a different source language for java compilation -->
<sourceLevel>1.8</sourceLevel>
<!-- Compiler configuration -->
<compilerArgs>
<!-- Ask GWT to create the Story of Your Compile (SOYC) (gwt:compile) -->
<arg>-compileReport</arg>
<arg>-XcompilerMetrics</arg>
</compilerArgs>
<!-- DevMode configuration -->
<warDir>${project.build.directory}/${project.build.finalName}</warDir>
<classpathScope>compile+runtime</classpathScope>
<!-- URL(s) that should be opened by DevMode (gwt:devmode). -->
<startupUrls>
<startupUrl>StockWatcher.html</startupUrl>
</startupUrls>
</configuration>
</plugin>

With the GWT Eclipse plugin I made it work and even debugging was working in Chrome because the GWT plugin for Eclipse starts the code server automatically and somehow updates the html file to load the javascript from the code server. 

The bottom line is: the GWT Maven plugin works :-)). But integrating Spring Boot and GWT will be a complex task. I'll need to run the GWT compilation first, then add the resulting javascript to the executable Jar. Maybe it is possible to do this with Maven but for this task I decided to use Gradle

Gradle is a rapidly developing build tool. The DSL and API are not stable yet but it offers substantial flexibility. While Maven has a fairly straight line of build phases Gradle can execute tasks in any order. This flexibility is what I need.
After some digging I found one working Gradle plugin for GWT: de.esoco.gwt. It is a fork of the Putnami plugin. The documentation is good enough to make this plugin work. I didn't notice any major issues. The configuration in build.gradle is inside the gwt block:

gwt {
gwtVersion = gwtVersion

module("org.example.gwt.StockWatcher2", "de.richsource.gradle.plugins.gwt.example.Example")
        // other configuration options
}

This plugin adds some tasks to the gradle build. The most important of them is gwtCompile. This task actually generates the javascript code and puts it in ${buildDir}/gwt/outThese values (both gwt and out) are hardcoded in the Gradle GWT plugin. 
It is important to remember that the code that is compiled into javascript is specified in the GWT module file like this:
<source path='client'/>
<source path='shared'/>

REST and Resty

The next goal is to use Spring Boot's REST endpoints. I found RestyGWT that helped me do just that. They have a simple how-to on the front page. 
I added the required dependencies to build.gradle:

implementation("javax.ws.rs:javax.ws.rs-api:2.0.1")
compileOnly group: "org.fusesource.restygwt", name: "restygwt", version: "2.2.0"
implementation group: "com.fasterxml.jackson.jaxrs", name: "jackson-jaxrs-json-provider", version: "2.8.9"

The JAX-RS dependencies are necessary because RestyGWT uses the annotation from JAX-RS to declare the endpoints. As far as I understood Jackson is also necessary to parse the JSON. 
I added the dependency in the GWT module as well:

<inherits name="org.fusesource.restygwt.RestyGWT"/>

Here is the service I created with RestyGWT:

public interface TestService extends RestService {

    @GET
    @Path("test"void test1(@QueryParam("input") String inp,    
        MethodCallback<TestResultcallback);
}

I call this service in a ClickHandler (I mostly used the code from the original GWT tutorial):

private final TestService testSrv = GWT.create(TestService.class);
btnCallServer.addClickHandler(clkEvent -> {
    testSrv.test1("TestString"new MethodCallback<TestResult>() {

        @Override
        public void onSuccess(Method method, TestResult response) {
            testLabel.setText("Srv success " + response.getStr1());
        }

        @Override
        public void onFailure(Method method, Throwable exception) {
            testLabel.setText("Srv failure " + exception.getMessage());
        }
    });
});

This service calls this simple method in a Spring Boot controller:

@GetMapping("/test")
public TestResult test1(@RequestParam(name="input", required=false) String inp) {
return new TestResult(inp + " qqq");
}

The good thing is all this code is part of one executable jar.

Executable Jar

The third goal is to actually bundle all that into one executable fat jar. In this section I can finally make use of Gradle's flexibility. 
First I put the html files to /src/main/resources/static
I created a task to copy the generated javascript into the static folder in ${buildDir} during the build:

task copyGWTCode(dependsOn: ["gwtCompile"], type: Copy) {
    from file("${buildDir}/gwt/out")
    into file("${buildDir}/resources/main/static")
}

Next I made the bootJar task dependent on this task and copied the jar to the more traditional target directory:

bootJar {
    dependsOn copyGWTCode
    doLast {
        mkdir "${buildDir}/target"
        setDestinationDir(file("${buildDir}/target"))
        copy()
    }
}

Debugging in GWT

One extra chapter on GWT debugging.
I found a fairly simple way to debug GWT UI in Chrome (Chrome can handle it better than Firefox). Here are the steps to make it work. I used the project from the GWT tutorial but renamed it to "stockwatcher2".
1. Add a new html file for debugging to src/main/resources/static. If the original file was for example  StockWatcher2.html the new file should be  StockWatcher2debug.html. In this new file replace the line

<script type="text/javascript" src="stockwatcher2/stockwatcher2.nocache.js"></script>

with this line (javascript from the code server):

<script src="http://localhost:9876/recompile-requester/stockwatcher2"></script>


2. Execute the task bootJar and run it. 
3. Start the code server from the projects folder with "gradle gwtCodeServer".
4. Open http://<host>:<port>/<somepath>/StockWatcher2debug.html in Chrome
5. Now you can find the source maps in Developer Tools -> Sources under 127.0.0.1:9876. The breakpoint can be set and hit in Chrome directly.

The idea with a separate file is to exclude it from production builds but keep it in developer builds. It is easy with Gradle. There is only one issue with this approach and that is the REST endpoints that are called from the debug sources are different from the endpoints that are called from the "normal" sources. Adding one more mapping solves the problem.

Conclusion

I congratulate the heroic persons who have reached this conclusion! You are true programmers and those who gave up are miserable cowards! 
But the bottom line is that working with GWT is quite tough. The build tools are quite unwieldy and lack important features. There is practically no integration (for example with Spring Boot). Debugging is unnecessary complicated. 
If someone were to choose between GWT and Eclipse RAP I would recommend Eclipse RAP. 
No happy ending :-(.

Friday, September 21, 2018

Eclipse RAP with Spring Boot

Intro

I like Eclipse :-). I've been using it for many years. I think the IDE is great and it is free! But Eclipse is much more than just the IDE. It is more like an ecosystem of applications, plugins and projects. Anyone can use the Eclipse Framework to create his/her own Eclipse-based application. It is called Eclipse RCP - Rich Client Application. Any RCP is based on the same principles as the Eclipse IDE: the product includes some features, any feature includes some plugins. 


I heard it is possible to include a plugin directly into the product but this is deprecated.
The UI in Eclipse Framework is based on 2 things:
  • The Application model
  • SWT - Standard Widget Toolkit
The Application Model is like a big "Mediator' (remember the pattern?) that handles interactions of many UI components at a high level. It is defined in an XML-like format. It defines windows, parts, commands, handlers, menus etc. 
SWT is a collection of widgets: buttons, labels, dialogs. These widgets are used to create the UI at the lower level than the Application Model. For example the UI within a part (MPart class) is created with SWT.

The Eclipse Framework can also be used to create web applications. It is called Eclipse RAP - Remote Application Platform. The idea is simple and impressive at the same time. The UI is created in Java with SWT widgets. In fact the UI is created with SWT widgets adapted for RAP. Then it is rendered in the browser. There is a special servlet that does the rendering RWTServlet (RWT may stand for Remote Widget Toolkit). Here is the link to the Developer's Guide for RAP.

And finally to the point. We all know that Spring Boot is awesome :-))! It must be the dominant platform for creating web applications. It offers some technologies to create the UI like JSP or Thymeleaf. But JSP is a little old and Thymeleaf could be a little less interactive than RAP. It would be great to make the two work together.

RAP Application with Spring Boot in a WAR file

The first option is to create a WAR with Spring Boot and RAP and deploy it to Tomcat. It is the option described in the Developer's Guide. Here is the link. I copied the important files.
The web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

  <context-param>
    <param-name>org.eclipse.rap.applicationConfiguration</param-name>
    <param-value>com.example.HelloWorldConfiguration</param-value>
  </context-param>

  <listener>
    <listener-class>org.eclipse.rap.rwt.engine.RWTServletContextListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>rwtServlet</servlet-name>
    <servlet-class>org.eclipse.rap.rwt.engine.RWTServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>rwtServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app> 

The application configuration:
public class HelloWorldConfiguration implements ApplicationConfiguration {
  public void configure( Application application ) {
    application.addEntryPoint( "/hello", HelloWorld.class, null );
  }
}
And the class:
public class HelloWorld extends AbstractEntryPoint {
  public void createContents( Composite parent ) {
    Label label = new Label( parent, SWT.NONE );
    label.setText( "Hello RAP World" );
  }
}
In order to make it work with the Maven WAR packaging put the web.xml into src/main/webapp/WEB-INF. The entry point in the application configuration must be configured for rwtServlet in web.xml.

For Spring Boot add the following:
@SpringBootApplication
public class SpringApp extends SpringBootServletInitializer {
  public SpringApplicationBuilder configure( SpringApplicationBuilder builder ) {
    return builder.web(WebApplicationType.NONE).sources(<configurations>);
  }
}

In pom.xml set the packaging to war.
<packaging>war</packaging>

There is though one important issue. Eclipse jars are not available in the Maven Central. Maybe it is possible to somehow use the p2 repository but I didn't try that. After all only one jar is necessary. I used the latest available version RAP 3.5. The jar is org.eclipse.rap.rwt_3.5.0.20180613-1038.jar.
I installed it manually to the local Maven repository and used the dependency in the pom.

This was enough to build a WAR and use it in Tomcat 9. No hacks necessary.

RAP Application with Spring Boot in an executable jar

This option looks more convenient. It would be really great to be able to use the RWTServlet inside Spring Boot. Then all the functionality like the executable jar and embedded Tomcat will work out of the box.
First return the main Spring Boot class to normal:

@SpringBootApplication
public class SpringApp {
    public static void main(String[] args) {
        SpringApplication.run(SpringApp.classargs);
    }
}
In fact Spring Boot can add a servlet with a ServletRegistrationBean:

@Bean
public ServletRegistrationBean<RWTServlet> servletRegistrationBean() {
    ServletRegistrationBean<RWTServlet> rwt = new 
ServletRegistrationBean<RWTServlet>(new RWTServlet(),"/hello""/hello2");
    return rwt;
}

But unfortunately this is not enough. This RWTServlet requires a special context to be bound to the ServletContext. Previously in the WAR example the RWTServletContextListener handled this :-(.

But Spring Boot even in this desperate situation showed was it was worth :-). It can bind custom listeners by declaring them as beans! The default functionality of RWTServletContextListener  required some rework because as it turned out binding this listener from inside Spring Boot had some limitations. This is what I ended up with:

public class RWTSCLSimple extends RWTServletContextListener {

    private ApplicationRunner applicationRunner;

    @Override
    public void contextInitialized( ServletContextEvent event ) {
        ServletContext servletContext = event.getServletContext();
        ApplicationConfiguration configuration = new 
HelloWorldConfiguration();
        applicationRunner = new ApplicationRunner( configuration
servletContext );
        applicationRunner.start();
    }

    @Override
    public void contextDestroyed( ServletContextEvent event ) {
        applicationRunner.stop();
        applicationRunner = null;
    }
}

This class simplifies the initial functionality of RWTServletContextListener and removes some method calls which were "not acceptable for a listener not declared in web.xml" :-)). That error message was very funny.

Now this class must be declared as a bean and that's it:
@Bean
public RWTSCLSimple rwtSCListener() {
    return new RWTSCLSimple();
}

All Spring Boot functionality works out of the box, even server.servlet.context-path. If the reader doesn't mind the little workaround this option looks better than the first one.