Wednesday, February 10, 2010

Tutorial: GWT RPC Stub modified with UIBinder

GWT RPC is a very significant reason to use GWT. GWT RPC sets up a http channel between a web page with the server so that the web page and the web server could transmit data to each other without the web page being refreshed or flipped.

The Google plugin for Eclipse creates a stub GWT RPC example whenever we choose to create a new GWT project. This tutorial modifies that stub in a few points.
  • Use of GWT UIBinder by providing the UI definition in Z.ui.xml
  • GreetingService.java is renamed to Z.java.
  • Use of HashMap instead of String in client-server RPC communication.
  • Change of servicing class name from GreetServiceImpl to SendGreeting.
  • Change of package namespace to com.blessedgeek.gwt.examples.greet.
The code for this tutorial is found in
http://code.google.com/p/synthfuljava/source/browse/#svn/apps/UIBGreet,
where the actual code of the service and async interfaces are derived from genericized interfaces, and therefore differs a little from this tutorial but is functionally equivalent.
The genericized interfaces used in the actual code is found at
http://code.google.com/p/synthfuljava/source/browse/#svn/trunk/gwt/http/org/synthful/gwt/http/servlet/client.

The GWT module is Z and the module file is /com/blessedgeek/gwt/examples/greet/Z.gwt.xml. According to the rename-to attribute, the module is to be made available by the web server through the context URI /blessed.
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='blessed'>
  <inherits name='com.google.gwt.user.User'/>
  <inherits name='org.synthful.gwt.http.servlet.OriginServerCall'/>
  <inherits name='com.google.gwt.user.theme.standard.Standard'/>
  <entry-point class='com.blessedgeek.gwt.examples.greet.client.Z'/>
  <source path='client'/>
</module>


GWT RPC requires a web service to be created on the server, with which the async client communicates. As in the original stub-example, the service needs to be declared in war/WEB-INF/web.xml.
<?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>
  <!-- Servlets -->
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>
      com.blessedgeek.gwt.examples.greet.server.SendGreeting
    </servlet-class>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/blessed/greet</url-pattern>
  </servlet-mapping>
 
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>Z.html</welcome-file>
  </welcome-file-list>
</web-app>

According to the above web.xml, sitting in the namespace com.blessedgeek.gwt.examples.greet.server, the class that provides the web service is SendGreeting (was named GreetServiceImpl in the original stub-example code). SendGreeting service is made available by the web server through the URI /blessed/greet/.

An interface to be implemented by the web service has to be defined, wherein the relative path to the URI /blessed/greet/ should be declared. Since the GWT module and the RPC web service will sit on the same parent URL /blessed, only the relative path greet needs to be defined. Hence the annotation @RemoteServiceRelativePath("greet").
package com.blessedgeek.gwt.examples.greet.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("greet")
public interface GreetingService
{
  HashMap<String, String> doServiceResponse(HashMap<String, String> input);
}

A matching interface, to be implemented by the GWT client, has to be defined. It's name and path must be similar to the service interface but suffixed by Async. Therefore, GWT client interface is GreetingServiceAsync.
package com.blessedgeek.gwt.examples.greet.client;

import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* The async counterpart of <code>GreetingService</code>.
*/
public interface GreetingServiceAsync
{
  void doServiceResponse(
    HashMap<String, String> parameters,
    AsyncCallback<HashMap<String, String>> callback);
}

The client async interface must define the method doServiceResponse as defined in the service interface, but with pattern

if service interface method is
ReturnType serviceMethodName (ArgType)

then the async interface method must have the pattern
void serviceMethodName(
    ArgType,
    AsyncCallback<ReturnType>);

where AsyncCallback is a GWT RPC method which would facilitate the calling-back of the client side serviceMethodName.
Instead of String objects being transmitted in the original example code, the code of this tutorial effects the transmission of HashMap objects between the server and the client.

This is the service servlet addressed by the URI /blessed/greet, which implements GreetingService interface. Rather than constructing the whole text to be displayed by the client as in the original example, this sends only the parameters.
package com.blessedgeek.gwt.examples.greet.server;

import java.util.HashMap;
import java.util.Map;
import com.blessedgeek.gwt.examples.greet.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class SendGreeting
  extends RemoteServiceServlet
  implements GreetingService
{

  @Override
  public Map<String, String> doServiceResponse(
    Map<String, String> input){
    HashMap<String, String> params = new HashMap<String, String>();
    params.put("serverInfo", getServletContext().getServerInfo());
    params.put("userAgent", getThreadLocalRequest().getHeader("User-Agent"));
    params.put("name", input.get("name"));
    return params;
  }
}

This is UIBinder Z.ui.xml, which defines the dialog box.
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui">
  <ui:style>
    .sendButton {
      display: block;
      color: blue;
      font-size: 16pt;
    }
  </ui:style>
  <g:VerticalPanel>
    <g:HorizontalPanel ui:field="hPanel">
      <g:Button ui:field="sendButton" text="Send"
       styleName="{style.sendButton}" />
      <g:TextBox ui:field="nameField" text="GWT User" />
    </g:HorizontalPanel>
    <g:DialogBox ui:field="dialogBox"
     text="Remote Procedure Call"
     animationEnabled="true">
      <g:VerticalPanel horizontalAlignment="ALIGN_RIGHT">
        <g:Label>Sending name to the server:</g:Label>
        <g:Label ui:field="textToServerLabel" />
        <g:Label>Server replies:</g:Label>
        <g:HTML ui:field="serverResponseLabel" />
        <g:Button ui:field="closeButton" text="Close" />
      </g:VerticalPanel>
    </g:DialogBox>
  </g:VerticalPanel>
</ui:UiBinder>

/com/blessedgeek/gwt/examples/greet/public/Z.html the GWT launching HTML file:
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="Z.css">
    <title>Web Application Starter Project</title>
    <script type="text/javascript" language="javascript"
     src="blessed.nocache.js"></script>
  </head>
  <body>
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    <h1>Web Application Starter Project</h1>

    <center>
    Please enter your name and click [Send]:
    <div id="here"/>
    </center>
  </body>
</html>
Take note of id here, which would be used as the root panel launch pad.

The GWT Java-source bean that captures the ui definition in Z.ui.xml must be named Z.java. The Java-source bean need not implement EntryPoint, but in this case it is made an EntryPoint. The GWT Java-source bean will be compiled into client-side script by the GWT compiler.

The bean defines an arbitrarily named interface LilyUiBinder which extends UIBinder, which is to be bound to the instance of class Z.
Notice GWT.create deferred binding for LilyUiBinder.
And GWT.create deferred binding for GreetingServiceAsync using GreetingService.class.
public class Z
  implements EntryPoint
{
  interface LilyUiBinder
  extends UiBinder<Widget, Z>{}
  private static LilyUiBinder uiBinder = GWT.create(LilyUiBinder.class);
  /**
   * Create a remote service proxy to talk to the server-side Greeting service.
   */
  private final GreetingServiceAsync greetingService =
    GWT.create(GreetingService.class);

The bean variables bound to fields in Z.ui.xml.
@UiField
  HorizontalPanel hPanel;
  @UiField
  Button sendButton;
  @UiField
  TextBox nameField;

  //Fired when user clicks send Button
  @UiHandler("sendButton")
  public void sendOnClick(ClickEvent event){
    sendNameToServer();
  }

  //Fired when user types in the nameField.
  @UiHandler("nameField")
  public void nameOnKeyUp(KeyUpEvent event){
    if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER){
      sendNameToServer();
    }
  }

  @UiField
  DialogBox dialogBox;
  @UiField
  Label textToServerLabel;
  @UiField
  HTML serverResponseLabel;
  @UiField
  Button closeButton;

  @UiHandler("closeButton")
  public void closeOnClick(ClickEvent event){
    dialogBox.hide();
    sendButton.setEnabled(true);
    sendButton.setFocus(true);
  }

Normally, the UIBinder deferred binding is done at the bean constructor, but it may be done at onModuleLoad of an EntryPoint object that also happens to be a UIBinder bean. Notice  the method sendNameToServer which would be called by the uihandlers defined earlier. callBack is the client-side routine that would be called-back when the server returns data to the client.
public void onModuleLoad(){
    uiBinder.createAndBindUi(this);
    nameField.setText("GWT User");
    RootPanel.get("here").add(hPanel);
    ....
   
  }

  private void sendNameToServer(){
    HashMap<String, String> params = new HashMap<String, String>();
    String textToServer = nameField.getText();
    params.put("name", textToServer);
    textToServerLabel.setText(textToServer);
    serverResponseLabel.setText("");
    sendButton.setEnabled(false);

    greetingService.doServiceResponse(params,callBack);
  }

  private final AsyncCallback<Map<String, String>> callBack =
    new AsyncCallback<Map<String, 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(Map<String, String> result){
        dialogBox.setText("Remote Procedure Call");
        serverResponseLabel
          .removeStyleName("serverResponseLabelError");
        String s = "Hello, "
        + result.get("name") + "!<br><br>I am running " + result.get("serverInfo")
        + ".<br><br>It looks like you are using:<br>" + result.get("userAgent");

        serverResponseLabel.setHTML(s);
        dialogBox.center();
        closeButton.setFocus(true);
      }
    };
    ....

Notice that sendNameToServer sends the text field value to the server in a hash rather than as a string. Sending in a hash would be necessary when there is more than one parameter to send to the server. Notice that at onSuccess, it also receives data from the server in a hash rather than a string.

It should be noted that due to restrictions of any pre-version 5 HTML, GWT is only half-async. The client has to initiate communication with the service. Therefore, to masquerade as full-async, the GWT client has to set up a polling loop to let the service the opportunity to pseudo-push data to the client. I recommend a polling sleep of no shorter than 5 seconds or if acceptable - 20 seconds.

5 comments:

  1. Thanks for your wonderful post, you made my life easier :) you put everything included file names and that makes a huge difference

    ReplyDelete
  2. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Java developer learn from Java Training in Chennai. or learn thru Java Online Training India . Nowadays Java has tons of job opportunities on various vertical industry.

    ReplyDelete