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.

8 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. Coach Business Bag Outlet, Located near Montreal, Mont Tremblant offers 95 runs serviced by 14 lifts. Besides skiing, winter activities also include snowmobiling, sleigh riding, ice skating, dog sledding and ice fishing. Private ski lessons are available and many of the instructors are experienced in working with special needs children.

    Coach Purse Blue And White, Ads can be annoying. But ads are also how we keep the garage doors open and the lights on here at Autoblog and keep our stories free for you and for everyone. And free is good, right? If you'd be so kind as to whitelist our site, we promise to keep bringing you great content.

    Scored his second and third goals for the Islanders, who swept a three game road trip. Brock Nelson scored his sixth of the season and fifth goal in five games, while Tom Kuhnhackl, who won two Stanley Cups in Pittsburgh, scored his first as an Islander. Andrew Ladd and Matt Martin also scored for New York, which has won four of six after starting the season 2 3.. (Michael Kors Black And Purple Bag)

    Heading south on our way to Kalgoorlie, we detoured into Kookynie, a real 'living ghost town', to visit Margaret in the Hotel. She was still serving icy cold beer in the small front bar and a few visitors called in and local prospectors. Footy was on the tv and we watched Carlton beat Richmond in the eleminary final. (Michael Kors Black Crossbody Purse)

    Blue and Gold recorded a record of 13 12 in the regular season. Maravalli squad defeated Marietta at home in the first round of the OAC Tournament on Monday, Feb. 18 by a score of 60 53. (Michael Kors Sloan Quilted Bag Black)

    Diaper Bags Coach Outlet Store, If you want the real story why doesn't your reporter ask the rank and file of the Social Security Administration what they think of management and leadership? I have been with the agency for 30 years. I pride myself on doing a good job. But management continually puts up road blocks that make it more and more difficult to do a good job in serving the public.

    ReplyDelete
  3. I was the second guy. {tag: Yeezy V2 Cloud White Reflective Release Date}I got a bullet in my stomach they shot him, oh my God, the pregnant woman screams after running from the car to a home in the 2300 block of South Kenneth Avenue in the city's Lawndale section, according to the Chicago Tribune. {tag: Yeezy 350 V3 Triple White}

    Air Jordan 1 Black Royal Blue 2013, Was Trump being with his disinfectant comments? You decideOn Thursday night, at a White House press briefing, Donald Trump appeared to give some pretty unorthodox suggestions about using disinfectant or sunlight to treat the coronavirus. Following a report which showed disinfectant can kill a virus within minutes when applied to different surfaces, the president addressed the press conference with a startling revelation. When pressed by a reporter on the comments in the White House, Trump responded that he was asking the question "sarcastically to reporters like you, just to see what would happen".

    Iranian state television quoted Oil Minister Bijan Zabganeh as saying he discussed the "method of decision making" for the meeting Thursday with OPEC's president, as well as his Kuwaiti and Russian counterparts. {tag: Yeezy Boost 350 V2 Cloud White Retail Price}

    Charles Allen Cary (1861 1935), a veterinary professor at Auburn University shown in the bottom row at far right, is known for his work "in eradication of livestock diseases, establishing food inspection laws, and training veterinarians," according to the Alabama Department of Archives and History. {tag: Yeezy Boost 350 V2 Cloud White Size 6}

    The two arenas would be separated only by the lavish SoFi Stadium complex being built by Los Angeles Rams owner Stan Kroenke.. {tag: White Yeezy Sply 350}The fertilizers cause massive algae blooms, which consume all the oxygen in the water, leaving it too low in oxygen for anything to survive. The size of the Dead Zone varies from year to year, but it generally covers about 6000 7000 square miles and has seriously impacted fisheries in the area, as well as the health of the local ecosystem. Other Dead Zones exist in the Baltic Sea, the Black Sea, Chesapeake Bay, and some parts of the Pacific Ocean, as well as freshwater lakes and rivers around the world.. {tag: White Low Top Yeezys}

    ReplyDelete