Sunday, February 14, 2010

Thinking in GWT

The next task in building my math quiz application was to figure out how to identify visitors so I can track their quiz points. I figure most people either have a Google account or don't mind getting one, so I can just leverage the Google Accounts service that is provided with App Engine.

First I added the security constraint to web.xml:

<security-constraint>
  <web-resource-collection>
      <url-pattern>/Uitest.html</url-pattern>
  </web-resource-collection>
  <auth-constraint>
      <role-name>*</role-name>
  </auth-constraint>
</security-constraint>

After adding that and restarting my dev server, the application redirected to the dummy login page as expected.

The next challenge was to display the identity of the user on the page. Following one of the sample programs, I updated the template to include a dynamic field:

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
  xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <g:DockLayoutPanel unit='EM'>

    <g:center>
      <g:HTML>
        <p>Hello, <span ui:field='nameSpan'/>.</p>
      </g:HTML>
    </g:center>

  </g:DockLayoutPanel>

</ui:UiBinder>

Then I hit the wall. My instinct from years of writing servlets and JSPs was to get the user principal from the request and use it to set the value of the nameSpan field. But none of the documentation and samples and examples I could find on the web seemed to show how to do that.

Unsure how to proceed, I created a new project and took another look at the GreetingService that the GWT plugin generates. I noticed that the server implementation of the service extends RemoteServiceServlet, which provides a method for accessing the current request. At first I wasn't sure how I was going to use it, but I modified the server implementation to simply return the current user:

public class GreetingServiceImpl extends RemoteServiceServlet implements
  GreetingService {

  public String greeting() throws IllegalArgumentException {

    return getThreadLocalRequest().getUserPrincipal().getName();
  }
}

Then I updated the client interface accordingly:

@RemoteServiceRelativePath("greet")
public interface GreetingService extends RemoteService {
  String greeting() throws IllegalArgumentException;
}

and, following the errors reported by Eclipse, lastly I updated the async version of the interface:

public interface GreetingServiceAsync {
  void greeting(AsyncCallback callback)
      throws IllegalArgumentException;
}

So now I had a service that returns the current user name, but I wasn't sure how to use it yet. After looking at the samples a bit more it finally dawned on me that I needed to change my whole perspective. Writing Java code no longer automatically means that the code is executing on the server. GWT "compiles" the Java code into JavaScript to run on the client. Admittedly, this is all explained well in the documentation but it had been a while since I read it.

Any information that I needed to retrieve from the server needed to be obtained via a callback:

public class Uitest implements EntryPoint {

  interface Binder extends UiBinder {
  }

  private static final Binder binder = GWT.create(Binder.class);

  private final GreetingServiceAsync greetingService =
    GWT.create(GreetingService.class);

  @UiField
  SpanElement nameSpan;

  public void onModuleLoad() {

    DockLayoutPanel outer = binder.createAndBindUi(this);

    Window.enableScrolling(false);
    Window.setMargin("0px");

    RootLayoutPanel root = RootLayoutPanel.get();
    root.add(outer);

    greetingService.greeting(new AsyncCallback() {

      @Override
      public void onSuccess(String result) {
        nameSpan.setInnerText(result);
      }

      @Override
      public void onFailure(Throwable caught) {
        // TODO implement a dialog box to show error
        nameSpan.setInnerText("ERROR");
      }
    });
  }
}

This didn't work the first time I tried to run the application, but then I realized that I needed to add the Greeting servlet to web.xml:

  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>org.sample.uitest.server.GreetingServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/uitest/greet</url-pattern>
  </servlet-mapping>

Now I was able to run the application and see the user name show up.