Sunday, February 5, 2012

Spring 3.1: GWT Maven Plugin and GWTHandler Integration (Part 3)

Review

In the previous section, we have discussed how to generate a simple GWT application using the GWT Maven plugin. In this section, we will study how to integrate Spring and GWT using the GWTHandler library via configuration and modifying existing classes.


Integrating GWTHandler

What is GWTHandler?

The GWTHandler is part of the GWT Server Library--a collection of Java server side components for the Google Web Toolkit AJAX framework with the current focus on the Spring framework by facilitating publishing of Spring beans as RPC services.

The GWTHandler allows you to quickly map multiple RPC service beans to different URLs very similar to the way Spring's SimpleUrlHandlerMapping maps URLs to controllers.

Source: http://code.google.com/p/gwt-sl/

Configuration

The initial step in configuring the GWTHandler is to declare our XML configuration files:
  • web.xml
  • gwt-servlet.xml
  • applicationContext.xml

In the web.xml the part that you need to pay extra attention is the url-pattern. Here we've declared the pattern as /gwtmodule/rpc/*. This means all RPC calls should follow that pattern!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Front Controller for all GWT-Spring based servlets -->
<servlet>
<servlet-name>gwt</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Don't forget to declare a gwt-servlet.xml -->
<servlet-mapping>
<servlet-name>gwt</servlet-name>
<url-pattern>gwtmodule/rpc/*</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub


The gwt-servlet.xml is nothing but a simple bean declaration. Here we've declared a GWTHandler bean, and it contains one mapping. We've mapped all calls to /greet to be processed by a GreetingService implementation. Remember this is an RPC call,sSo if you have plans of calling this RPC mapping, you must also take account the mapping the gwt-servlet.xml! In other words, the complete path is rpc/greet.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- The GWT handler. Pay attention to the mappings -->
<bean class="org.gwtwidgets.server.spring.GWTHandler">
<property name="mappings">
<map>
<entry key="/greet" value-ref="greetingService" />
</map>
</property>
</bean>
</beans>
view raw gwt-servlet.xml hosted with ❤ by GitHub


In the applicationContext.xml we've basically enabled annotation scanning.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />
<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
For example @Controller and @Service. Make sure to set the correct base-package -->
<context:component-scan base-package="org.krams" />
</beans>


Classes

The second step in configuring the GWTHandler is to modify our existing classes to take advantage of the GWTHandler.

We will create a new service class:
  • SpringService

And edit the following classes:
  • GreetingService
  • GreetingServiceImpl

The SpringService basically prints out a Hello *name* from Spring 3.1 message.
package org.krams.tutorial.server;
import org.springframework.stereotype.Service;
@Service
public class SpringService {
public String echo(String input) {
return "Hello " + input + " from Spring 3.1!";
}
}


As discussed earlier, to use the /greet RPC mapping, we have to consider the parent mapping from the gwt-servlet.xml as well. In other words, the complete mapping for this RPC is rpc/greet
package org.krams.tutorial.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
/**
* The client side stub for the RPC service.
*/
@RemoteServiceRelativePath("rpc/greet")
public interface GreetingService extends RemoteService {
String greetServer(String name) throws IllegalArgumentException;
}


Next we modify the GreetingServiceImpl to take advantage of the SpringService implementation. So instead of the original reply that includes server information, we're now returning a simple "hello" message instead.
package org.krams.tutorial.server;
import org.krams.tutorial.client.GreetingService;
import org.krams.tutorial.shared.FieldVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
@Service("greetingService")
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {
@Autowired
private SpringService springService;
public String greetServer(String input) throws IllegalArgumentException {
// Verify that the input is valid.
if (!FieldVerifier.isValidName(input)) {
// If the input is not valid, throw an IllegalArgumentException back to
// the client.
throw new IllegalArgumentException(
"Name must be at least 4 characters long");
}
// Escape data from the client to avoid cross-site script vulnerabilities.
input = escapeHtml(input);
// Delegate to SpringService and return the result
return springService.echo(input);
}
/**
* Escape an html string. Escaping data received from the client helps to
* prevent cross-site script vulnerabilities.
*
* @param html the html string to escape
* @return the escaped string
*/
private String escapeHtml(String html) {
if (html == null) {
return null;
}
return html.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(
">", "&gt;");
}
}


For completion purposes, we will also show the contents of the gwtmodule class, though nothing has been changed.
package org.krams.tutorial.client;
import org.krams.tutorial.shared.FieldVerifier;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class gwtmodule implements EntryPoint {
/**
* The message displayed to the user when the server cannot be reached or
* returns an error.
*/
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";
/**
* Create a remote service proxy to talk to the server-side Greeting service.
*/
private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
private final Messages messages = GWT.create(Messages.class);
/**
* This is the entry point method.
*/
public void onModuleLoad() {
final Button sendButton = new Button( messages.sendButton() );
final TextBox nameField = new TextBox();
nameField.setText( messages.nameField() );
final Label errorLabel = new Label();
// We can add style names to widgets
sendButton.addStyleName("sendButton");
// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(nameField);
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("errorLabelContainer").add(errorLabel);
// Focus the cursor on the name field when the app loads
nameField.setFocus(true);
nameField.selectAll();
// Create the popup dialog box
final DialogBox dialogBox = new DialogBox();
dialogBox.setText("Remote Procedure Call");
dialogBox.setAnimationEnabled(true);
final Button closeButton = new Button("Close");
// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");
final Label textToServerLabel = new Label();
final HTML serverResponseLabel = new HTML();
VerticalPanel dialogVPanel = new VerticalPanel();
dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);
dialogBox.setWidget(dialogVPanel);
// Add a handler to close the DialogBox
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
dialogBox.hide();
sendButton.setEnabled(true);
sendButton.setFocus(true);
}
});
// Create a handler for the sendButton and nameField
class MyHandler implements ClickHandler, KeyUpHandler {
/**
* Fired when the user clicks on the sendButton.
*/
public void onClick(ClickEvent event) {
sendNameToServer();
}
/**
* Fired when the user types in the nameField.
*/
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
sendNameToServer();
}
}
/**
* Send the name from the nameField to the server and wait for a response.
*/
private void sendNameToServer() {
// First, we validate the input.
errorLabel.setText("");
String textToServer = nameField.getText();
if (!FieldVerifier.isValidName(textToServer)) {
errorLabel.setText("Please enter at least four characters");
return;
}
// Then, we send the input to the server.
sendButton.setEnabled(false);
textToServerLabel.setText(textToServer);
serverResponseLabel.setText("");
greetingService.greetServer(textToServer, new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
// Show the RPC error message to the user
dialogBox.setText("Remote Procedure Call - Failure");
serverResponseLabel.addStyleName("serverResponseLabelError");
serverResponseLabel.setHTML(SERVER_ERROR);
dialogBox.center();
closeButton.setFocus(true);
}
public void onSuccess(String result) {
dialogBox.setText("Remote Procedure Call");
serverResponseLabel.removeStyleName("serverResponseLabelError");
serverResponseLabel.setHTML(result);
dialogBox.center();
closeButton.setFocus(true);
}
});
}
}
// Add a handler to send the name to the server
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
nameField.addKeyUpHandler(handler);
}
}
view raw gwtmodule.java hosted with ❤ by GitHub


Next

In the next section, we will build and run the application using Maven, and show how to import the project in Eclipse. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3.1: GWT Maven Plugin and GWTHandler Integration (Part 3) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

8 comments:

  1. This is a perfect executed tutorial...
    what are the features that make GWT convenient for using in a web project?

    gwt seems way overengineered and extremely heavy weight. Is there other way to get same results? I wonder.

    Thank you in advance
    jD

    ReplyDelete
  2. @Findings, thanks for the comments. I do feel GWT is too complicated just to achieve a simple task. But it works. It is somewhat a heavyweight indeed.

    And that's why I've completely eradicated GWT from our development framework and instead opted for Spring MVC and jQuery because they are simpler to build and debug. They are more malleable than GWT. Of course, GWT has some niceties that you won't get or will require extra work with Spring MVC and jQuery combination, but most of the time I don't need those. In addition, I can use Backbone.js to achieve an MVC architecture on the presentation layer.

    If you look at my tutorial list, most of them use jQuery and jqGrid.

    ReplyDelete
    Replies
    1. @krams,
      I totally, fundamentally, completely agree with you on the GWT opinion...
      On the other hand: the problem that I see when using backbone.js or similar js libraries is that you ending replicating much of the server side plumbing on the client side. Besides it will increase the loading time of your initial pages.

      When you have time, I invite you to visit my blog too. I am in the same neighborhood. Blog at http://pragmatikroo.blogspot.com.

      Thank you
      jD

      Delete
  3. @Findings,
    Probably a repetition of patterns but with different concerns. It's true that it will increase the initial loading of all affected pages. But there are ways to optimize and improve the loading experience, i.e Wro4j and following the tips from YSlow and alike.

    I've visited your blog. It's the first time I've seen a blog that puts heavy focus on Spring Roo and its various uses. It's something I've tried before but never found the inspiration to use it. Your blog showcases Spring Roo on a different way--something that intrigues my mind. I will definitely take a second look. But I'm gonna read first your articles. Thanks

    ReplyDelete
    Replies
    1. @krams,

      Thank you for visiting my blog...
      Telling the true, I think currently Spring Roo is in "transitioning mode". The thing is I don't know what would be the ending state. Anyway I'll be good to try it.
      Any question on my stuff... please let me know neighbor.

      Delete
  4. Maybe you should also remove "extends RemoteServiceServlet" from GreetingServiceImpl.java since it is not a servlet anymore and getServletContext() returns null. That's why I was stuck here for an hour or so. Odd: Exceptions are not logged with mvn gwt:run, it just returns 500. BTW, how you debug the mvn gwt:run process?
    Otherwise: a nice introduction, thanks.

    ReplyDelete
    Replies
    1. also stuck for hours on this!! Is there any way to debug server side code?

      Delete
  5. Great tutorial - I found this very helpful. Many Thanks.

    ReplyDelete