Thursday, March 24, 2011

Starting and Verifying SSH Fingerprint of an EC2 instance

Lately I've been doing some experimentation on a temporary Amazon EC2 instance. So far I have been starting each session by running ec2-run-instances on the command line to boot the instance, then running ec2-describe-instances to get the host name, and finally connecting via SSH.

Scott Moser's post inspired me to automate the process as well as verify the SSH fingerprint of the new instance:

#!/bin/sh

# start an EC2 micro instance running the 32-bit, EBS-backed Amazon Linux AMI
RUN=`ec2-run-instances ami-3bc9997e -k KEY -t t1.micro -z us-west-1c`

# retrieve the instance ID from the output
INSTANCE=`echo $RUN | grep -E -o ' i-[a-f0-9]+' | sed 's/INSTANCE  *//'`

# seems to take about 3 to 4 minutes for SSH fingerprints to show
# up in the output. wait for 2 and a half minutes, then start polling output
echo "Waiting 150s for $INSTANCE to boot"

sleep 150

while [ 1 ]
do

FINGERPRINTS=`ec2-get-console-output $INSTANCE | egrep -m 1 -o '([0-9a-f][0-9a-f]:){15}[0-9a-f][0-9a-f]'`

if [ "$FINGERPRINTS" = "" ]
then
  sleep 30
  echo "Booting..."
else
  break
fi

done

echo "Expected fingerprints are $FINGERPRINTS"

# get hostname for the instance
HOST=`ec2-describe-instances | grep -m 1 $INSTANCE | egrep -o 'ec2(-[0-9]+){4}.us-west-1.compute.amazonaws.com'`

echo "Host is $HOST"

ssh-keyscan $HOST 2>/dev/null > host.key

ssh-keygen -lf host.key > host.fingerprint

read len ACTUAL_FINGERPRINTS host rsa < host.fingerprint
echo "Actual fingerprints are $ACTUAL_FINGERPRINTS"

if [ "$ACTUAL_FINGERPRINTS" = "$FINGERPRINTS" ]
then

echo "Fingerprints match, adding to known hosts"

ssh-keygen -q -R "$HOST"

ssh-keygen -q -H -f host.key

cat host.key >> ~/.ssh/known_hosts

echo "Ready to connect"

echo "ssh -i PATH_TO_KEY ec2-user@$HOST"

else

echo "Fingerprints do not match"

fi

shred -u host.key host.fingerprint

Thanks Scott!

Sunday, April 18, 2010

Optimizing GWT for iPhone

I have been working sporadically on my math quiz application for the past several weeks. To complicate matters beyond just learning GWT, I decided to optimize the user interface for the iPhone. That way my daughter could practice her math on the go.

First of all, here is the current version of the application, deployed on Google App Engine:

http://1.latest.mookiemath.appspot.com/

Notice a few things:

  1. After you login, the application shows you the quiz screen directly.
  2. You can click or tap on the buttons at the bottom of the screen to answer the problems.
  3. If you have a keyboard, you can also use the corresponding number keys (capturing the browser key press events is for another post)
  4. After you complete six problems, the application saves the result to the server.
  5. You can click on the little door icon to switch to a history view that lists your quiz results.

Overall, a very minimalist interface designed for a small screen.

Once I mastered how to apply styles to widgets, there were only two specific tweaks I had to make for the iPhone:

Scaling

Since this is a full-screen application rather than a web page, it needs to disable scaling by adding this meta tag to host page header:

<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;">

Scrolling

The history view consists of a fixed header and a scrollable body:

  <g:DockLayoutPanel unit="EM">
    <g:north size="3.2">
      <g:FlowPanel>
        <g:Label ui:field="iphoneHelp">
          Use two fingers to scroll
        </g:Label>
        <g:Button ui:field="quizButton" text="Quiz"/>
      </g:FlowPanel>
    </g:north>
    <g:center>
      <g:ScrollPanel>
        <g:FlowPanel ui:field="history" />
      </g:ScrollPanel>
    </g:center>
  </g:DockLayoutPanel>

A ScrollPanel is basically just a DIV with the CSS overflow property set to auto. This works fine in a full-sized browser, but mobile Safari does not respect the auto setting by showing a scrollbar when the content overflows. Instead, you have to use a two-finger gesture to scroll the content.

I didn't know this until today, and I expect that many other people do not either, so I wanted to add a simple help label that would only appear for iPhone users. GWT provides "deferred binding" as a general technique for substituting client classes (including widgets) based on user agent or other properties, but that seemed like overkill for this one label. Instead I just relied on a simple native method in my HistoryPanel widget to retrieve the user agent and hide the help label for non-iPhone users:

public class HistoryPanel extends Composite {

  ...

  @UiField
  Label iphoneHelp;

  public HistoryPanel(final HandlerManager handlerManager) {

    DockLayoutPanel parent = uiBinder.createAndBindUi(this);
    initWidget(parent);

    if (getUserAgent().indexOf("iphone") == -1) {
      iphoneHelp.removeFromParent();
    }

    ...
  }

  private native String getUserAgent() /*-{
    return navigator.userAgent.toLowerCase();
  }-*/;

  ...
}

Other than those two issues, the layout looks surprisingly consistent on full-sized browsers and the iPhone.

Saturday, February 27, 2010

Dynamic Layouts in GWT

After figuring out how to identify the current user, the next task in building my math quiz application was to build a basic layout for the quiz itself.

For starters, all I need is a header that displays the name of the current user, and a content area that displays the math problems. A user answers a problem by filling in a text box and hitting enter, after which the program should refresh the content area and show a new problem. After they've done a certain number of problems, the content area shows an "All done" message.

Implementing the basic layout and associated dynamic logic was fairly simple. First I added a body area to the layout:

<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:north size="4">
      <g:HTML>
        Hello,
        <span ui:field='nameSpan' />
      </g:HTML>
    </g:north>

    <g:center>
      <g:FlowPanel ui:field="problemPanel">
      </g:FlowPanel>
    </g:center>

  </g:DockLayoutPanel>

</ui:UiBinder>

Then I added a method to my EntryPoint implementation, which clears the problemPanel and shows a new problem up to a fixed limit:

public class Uitest implements EntryPoint {

  ...

  private int problemCount = 0;

  public void onModuleLoad() {

    ...
    showProblem();
  }
  
  private void showProblem() {
    
    problemPanel.clear();
    
    long a = Math.round(Math.random() * 10);
    long b = Math.round(Math.random() * 10);

    problemPanel.add(new InlineLabel(Long.toString(a)));
    problemPanel.add(new InlineLabel("+"));
    problemPanel.add(new InlineLabel(Long.toString(b)));
    problemPanel.add(new InlineLabel("="));
    
    final TextBox textBox = new TextBox();
    textBox.setWidth("2em");
    
    textBox.addChangeHandler(new ChangeHandler() {

      @Override
      public void onChange(ChangeEvent event) {
        if (problemCount++ < 5) {
          showProblem();
        } else {
          problemPanel.clear();
          problemPanel.add(new Label("All done!"));
        }
      }
    });
    
    problemPanel.add(textBox);

    textBox.setFocus(true);    
  }
}

I am assuming that it is acceptable for the class to be stateful (keep track of the count of problems for a single user), since this code runs on the client.

I ran the project in Eclipse and it worked as expected...almost. The positioning of the problem was oddly shifted on first viewing the page, and then it seemed to straighten itself out after the first answer. By process of elimination I discovered that the setFocus was causing this. First I verified that this method was not changing the DOM in any way; it was not. After looking through the docs I finally tried deferring the focus command until after all other pending operations were complete:

DeferredCommand.addCommand(new Command() {

      @Override
      public void execute() {
        textBox.setFocus(true);
      }
    });

This solved the display problem. Now I was ready to implement the remote service to record the user's score.

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.

Saturday, February 13, 2010

The simplest possible GWT application using UiBinder

I've been thinking about building a application to help my daughters practice math. The idea is to generate a simple daily quiz that they take online. The application should send them an SMS or email reminder to take the quiz, and then track their results. Each completed quiz should earn them points (number of correct answered problems) that they can redeem with me (in exchange for something to-be-decided). Each day they fail to get over some threshold or fail to take the quiz at all, they lose some number of number of points.

Google App Engine seems like a good fit for implementing this app, and anyway I've been wanting to try it out and also learn how to use GWT. I just installed the latest Eclipse plugin (App Engine 1.3.1 and GWT 2.0.2).

Where to begin? Whenever I learn a new technology, I find it helpful to start with the simplest possible scenario and then iterate, making sure I understand each additional layer of complexity. A logical starting point was just to build an app that outputs static HTML.

GWT 2.0 introduced a new declarative technique for defining the UI in XML markup rather than code. The UiBinder documentation has examples but doesn't have a complete tutorial that assumes no previous knowledge of GWT. I started with the Mail sample app and pared it down to the simplest possible example.

The first step was to create a new Web Application Project in Eclipse using org.sample.uitest as the default project package. Then I deleted all of the boilerplate code that gets generated for the new project, leaving a src directory with empty client, server and shared packages. I also removed the servlet mapping from war/WEB-INF/web.xml.

The first file that was clearly necessary was the 'module definition' in src/org/sample/uites/Uitest.gwt.xml. The simplest possible module definition inherits the basic toolkit functionality and stylesheet and defines an entry point into the application:

<module rename-to='uitest'>

  <inherits name='com.google.gwt.user.User'/>

  <inherits name='com.google.gwt.user.theme.standard.Standard'/>

  <entry-point class='org.sample.uitest.client.Uitest'/>

</module>

Next I needed to implement the entry point class. I started by copying the entry point from the Mail sample app and paring it down:

package org.sample.uitest.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;

public class Uitest implements EntryPoint {

  interface Binder extends UiBinder { }

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

  public void onModuleLoad() {

    DockLayoutPanel outer = binder.createAndBindUi(this);

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

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

The Binder requires a matching layout file named Uitest.ui.xml:

<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>UI Test</p>
      </g:HTML>
 
    </g:center>

  </g:DockLayoutPanel>

</ui:UiBinder>

Finally, an HTML page named Uitest.html is required in the war directory to 'host' the application:

<!doctype html>

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>UI Test</title>
    <script type="text/javascript" language='javascript' src='uitest/uitest.nocache.js'></script>
  </head>
  <body>
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color:white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>
  </body>
</html>

At this point my project consisted of five files, including WEB-INF/web.xml. I ran the project in Eclipse and was able to see the message in the browser.

Out of curiosity, I looked at the requests in Firebug when the page loads. The initial response is simply an unmodified version of the 'host' page, which loads uitest.nocache.js to actually render the page. It appears that uitest.nocache.js executes an onload handler that in turn triggers a request for hosted.html?uitest. I didn't try very hard to decipher the obfuscated JavaScript, but it appears that it is writing a SCRIPT tag to load the dynamic content.

Sunday, August 30, 2009

Log4J and Spring

I just spent a half hour trying to increase the logging for a unit test that uses SpringJUnit4ClassRunner. First I thought I could just add a log4j.xml configuration file at the root of the classpath, but that didn't work. After looking around the Spring code a bit it dawned on me that I didn't even have log4j on the classpath for the project, so Commons Logging was using the standard java.util.logging package instead. I added log4j as a dependency to the project, and now log4j configuration works as expected.

Saturday, August 15, 2009

Getting started with Spring Web Services

I needed to quickly implement a simple (document/literal wrapped) web service, so I thought I would try Spring Web Services. Unfortunately, just following the tutorial did not result a working webapp. I had to do some searching and reading of the source code to figure out a few problems. Here are my notes.

Like every other part of the Spring portfolio, Spring Web Services provides a wide variety of ways to accomplish the same task. I chose the following options:

  • Use the @Endpoint and @PayloadRoot annotations to map methods to SOAP operations.
  • Use JAXB 2 for XML binding.

This combination seemed to require the least amount of coding and configuration directly related to web services, allowing me to focus on the actual service implementation.

Set up your environment

There are only two things you absolutely need to get started with Spring Web Services:

  1. JDK 6, including the xjc command-line tool for generating XML binding classes from your schema.
  2. Maven (http://maven.apache.org) for managing dependencies and building the project

Maven eliminates the need to download Spring Web Services or any of its dependencies directly.

Generate the project skeleton

Maven makes it really easy to create the basic skeleton for the project:

mvn archetype:create \
  -DarchetypeGroupId=org.springframework.ws \
  -DarchetypeArtifactId=spring-ws-archetype \
  -DgroupId=org.xocoatl.ws \
  -DartifactId=web-service

This command downloads the required dependencies to your local repository and builds the following directory structure:

$ ls -R web-service/
web-service/:
pom.xml  src

web-service/src:
main

web-service/src/main:
resources  webapp

web-service/src/main/resources:

web-service/src/main/webapp:
WEB-INF

web-service/src/main/webapp/WEB-INF:
spring-ws-servlet.xml  web.xml

This structure just follows the Maven conventions for a webapp.

Write the web service contract

Spring Web Services only supports a contract-first service development model, so the next task is to write the XML schema that defines your service. Create web-service/src/main/webapp/WEB-INF/web-service.xsd and write your contract:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:tns="urn:ws.xocoatl.org" elementFormDefault="qualified"
  targetNamespace="urn:ws.xocoatl.org">

  <xsd:element name="EchoRequest">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Message" type="xsd:string" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="EchoResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Message" type="xsd:string" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

</xsd:schema>

At minimum, your schema will include types representing the payload of the request and response messages for each operation in your service. Spring-WS includes the ability to automatically generate a WSDL that includes your schema. As I discovered, however, the default WSDL generator requires that your payload types end in Request or Response. This seems to go against the typical convention of having your request payload elements match the operation name (i.e. Echo rather than EchoRequest.

Generate the XML binding classes

Once you have defined your contract, you can use the xjc command-line tool to generate the source code for the XML binding classes that your service will use. The simplest thing is to just add this to the maven build configuration. Here is how my pom.xml wound up:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.xocoatl.ws</groupId>
  <artifactId>web-service</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Web Service</name>

  <build>
    <plugins>
      <!-- use JDK 6 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <!-- use the command-line xjc tool that comes with JDK 6 
           to generate XML binding classes from schema -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>LATEST</version>
        <executions>
          <execution>
            <id>mkdir</id>
            <phase>generate-sources</phase>
            <goals>
              <goal>exec</goal>
            </goals>
            <configuration>
              <executable>mkdir</executable>
              <arguments>
                <argument>-p</argument>
                <argument>target/generated-sources/xjc</argument>
              </arguments>
            </configuration>
          </execution>
          <execution>
            <id>xjc</id>
            <phase>generate-sources</phase>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <executable>xjc</executable>
          <arguments>
            <argument>-p</argument>
            <argument>org.xocoatl.schema</argument>
            <argument>-d</argument>
            <argument>target/generated-sources/xjc</argument>
            <argument>src/main/webapp/WEB-INF/web-service.xsd</argument>
          </arguments>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>LATEST</version>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ws</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>1.5.7</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.ws</groupId>
      <artifactId>spring-oxm-tiger</artifactId>
      <version>1.5.7</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.ws</groupId>
      <artifactId>spring-ws-core</artifactId>
      <version>1.5.7</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.ws</groupId>
      <artifactId>spring-ws-core-tiger</artifactId>
      <version>1.5.7</version>
    </dependency>
  </dependencies>
</project>

There is also a maven plugin to invoke xjc, but maven reported errors while trying to download it, and with JDK 6 it is easy to just execute the command-line tool directly. Note that it is necessary to create the output directory first, for some reason xjc will not do this for you. Windows users may need to be tweak this step.

This task is bound to the generate-sources phase of the build lifecycle, so if necessary it will always run before compiling your own code.

While editing the POM file, I made a few other additions:

  1. Plugin configuration for building the project for Java 6
  2. Dependencies required for using the annotation features of Spring-WS
  3. Plugin configuration for running the webapp in Jetty during development

Now you are ready to actually generate the XML binding classes:

$ mvn generate-sources
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Web Service
[INFO]    task-segment: [generate-sources]
[INFO] ------------------------------------------------------------------------
[INFO] [exec:exec {execution: mkdir}]
[INFO] [exec:exec {execution: default}]
[INFO] parsing a schema...
[INFO] compiling a schema...
[INFO] org/xocoatl/schema/EchoRequest.java
[INFO] org/xocoatl/schema/EchoResponse.java
[INFO] org/xocoatl/schema/ObjectFactory.java
[INFO] org/xocoatl/schema/package-info.java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Sat Aug 15 21:07:19 PDT 2009
[INFO] Final Memory: 7M/79M
[INFO] ------------------------------------------------------------------------

The generated source code is written to target/generated-sources/xjc, where it is available for building with your own code.

Implement the endpoint

The next step is to implement the endpoint itself. Maven requires that your source code reside in src/main/java. I created a file at src/main/java/org/xocoatl/ws/EchoEndoint.java for my implementation:

package org.xocoatl.ws;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.xocoatl.schema.EchoRequest;
import org.xocoatl.schema.EchoResponse;

@Endpoint
public class EchoEndpoint {

  @PayloadRoot(localPart = "EchoRequest", namespace = "urn:ws.xocoatl.org")
  public EchoResponse echo(EchoRequest echoRequest) {

    EchoResponse echoResponse = new EchoResponse();
    echoResponse.setMessage(echoRequest.getMessage());
    
    return echoResponse;
  }
}

Usually your endpoint will delegate to an underlying service to perform some logic. The code in the endpoint simply extracts data from the request, passes it to the service, and then builds an appropriate response.

Write the Spring configuration

By convention, the Spring configuration resides in src/main/webapp/WEB-INF/spring-ws-servlet.xml. The samples in the Spring-WS distribution are a tossed salad of various approaches, but this is the minimal configuration that seems to be necessary for the annoation + JAXB approach:

<?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:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
                      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                      http://www.springframework.org/schema/web-services 
                      http://www.springframework.org/schema/web-services/web-services-1.5.xsd">

  <bean id="echoEndpoint" class="org.xocoatl.ws.EchoEndpoint" />

  <bean
    class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
    <constructor-arg ref="marshaller" />
    <constructor-arg ref="marshaller" />
  </bean>

  <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
    <property name="contextPath" value="org.xocoatl.schema" />
  </bean>

  <bean
    class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping" />

  <bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
    <property name="xsd" value="/WEB-INF/web-service.xsd" />
  </bean>

  <bean id="echo"
    class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
    <property name="schema" ref="schema" />
    <property name="portTypeName" value="Echo" />
    <property name="locationUri" value="http://localhost:8080/" />
    <property name="targetNamespace" value="urn:ws.xocoatl.org" />
  </bean>

</beans>

Every endpoint requires two form of configuration:

  1. some way to map methods to the payload of the request message. In our case the job is handled by PayloadRootAnnotationMethodEndpointMapping in conjunction with the PayloadRoot annotation.
  2. an adapter for reading the request and writing the response messages. In our case we are using JAXB marshalling and unmarshalling for these tasks.

Note that the id of the DefaultWsdl11Definition bean winds up in the URL of the WSDL.

Run the application

Once the configuration is complete, you can run the application from the command line:

$ mvn jetty:run

At this point you should be able to browse to http://localhost:8080/echo.wsdl. You should also be able to load the WSDL into a tool such as soapUI and try out the operation.

Saturday, February 23, 2008

Recipe for Building a Maven Plugin

Recently I've become a maven convert. One of the hardest parts of making the switch from Ant was the fact that I couldn't implement custom logic directly in pom.xml the way I could in an Ant build file. I had to make the mental shift to writing custom plugins instead, which at first glance seemed a lot more complicated than writing Ant targets.

This weekend I finally sat down and worked through the process of writing a custom plugin. It's actually pretty easy, and the end result is a nice reusable component.

I found that I had to refer to both the "Better Builds with Maven" book as well as the plugin developers' guide on the maven web site to get the gist of writing a plugin. Here is the general recipe I came up with.

A mojo implements a maven goal. If you want to be able to type: mvn my:mygoal

and have something happen, then you need to implement a single mojo.

To do this, first use maven to generate the boilerplate for a plugin project:


mvn archetype:create -DgroupId=com.mycompany.maven.plugins \
  -DartifactId=maven-my-plugin \
  -DarchetypeArtifactId=maven-archetype-mojo

Edit pom.xml:

  • Change name and artifact ID
  • Set build source and target to Java 5:
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>very
            <configuration>
              <source>1.5</source>
              <target>1.5</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    
  • Add dependencies

Rename the generated MyMojo.java file to the desired name of your mojo, and edit the file.

First change the class-level mojo annotations. For an explicitly invoked task (not automatically bound to a lifecycle phase), remove the @phase annotation and only specify the @goal annotation.

Update the field-level annotations. Use @parameter to set a field value based on the plugin configuration.

Finish implementing and testing mojo.

Install the finished plugin to your local repository using mvn install. If you look in target/classes/META-INF/maven/plugin.xml, you'll see that maven has extracted all the annotations from your mojo into a plugin descriptor file. Maven uses this file at runtime to map configuration properties onto mojo fields.

Next switch over to a module where you want to use your newly implemented goal. Add your new plugin to the module's pom.xml:


  <build>
    <plugins>
      <plugin>
        <groupId>com.mycompany.maven.plugins</groupId>
        <artifactId>maven-my-plugin</artifactId>
        <executions>
          <execution>
            <id>mygoal</id>
            <goals>
              <goal>mygoal</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <myfield>myvalue</myfield>
        </configuration>
      </plugin>
    </plugins>
  </build>

Now you can execute the goal: mvn my:mygoal

Certainly not as easy as adding an Ant target, but the nice thing about maven is that you rarely have implement custom build logic if you just follow the maven conventions.

Sunday, February 10, 2008

Multiple Persistence Units with Spring and JPA: Part II

In my last post I explained how I'd like to implement a persistence unit as a self-contained module that I could drop into another application that already has its own persistence unit. Both persistence units should talk to their respective database schemas without problems.

The two basic parts to the solution I worked out are: (1) ensure that the location of all configuration files and the names of all Spring-managed beans are unique; and (2) always specify which persistence unit and transaction manager a class requires.

To implement the solution, I established a set of conventions based on the persistence unit name:

First convention. A reusable code module that contains a persistence unit should have three configuration files located in META-INF/persistence-unit-name. If the persistence unit is named myunit, here is the structure of the jar file that contains the module:

  • com/**/**/*.class (Java classes related to the persistence unit
  • META-INF
    • myunit
      • META-INF/myunit/applicationContext.xml
      • META-INF/myunit/database.properties
      • META-INF/myunit/persistence.xml

The persistence.xml only has to specify the persistence unit name:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
                     http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
 version="1.0">

 <persistence-unit name="myunit" transaction-type="RESOURCE_LOCAL">
 </persistence-unit>

</persistence>

The database.properties file contains a set of overridable connection settings:

database.url=jdbc:oracle:thin:@localhost:1521:ORCL
database.user=mydata
database.password=mydata

Second convention. The Spring configuration file for the reusable module should define a database connection pool, entity manager factory and transaction manager for the module's persistence unit. To avoid naming conflicts, all bean names and properties should be prefixed with the persistence unit name:

<?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:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
                     http://www.springframework.org/schema/beans/spring-beans.xsd 
                     http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                        http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:annotation-config />
  
  <context:component-scan base-package="com.mycompany.myunit" />

  <bean id="myunitDatabaseConfigurer"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <value>classpath:/META-INF/myunit/database.properties</value>
    </property>
    <property name="systemPropertiesModeName">
      <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
    </property>
    <property name="placeholderPrefix" value="$MYUNIT{"/>
  </bean>

  <bean id="myunitDataSource"
    class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass"
      value="oracle.jdbc.driver.OracleDriver" />
    <property name="jdbcUrl" value="$MYUNIT{database.url}" />
    <property name="user" value="$MYUNIT{database.user}" />
    <property name="password" value="$MYUNIT{database.password}" />
  </bean>

  <bean id="myunitEntityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="myunitDataSource" />
    <property name="jpaVendorAdapter" ref="myunitJpaAdapter" />
    <property name="persistenceXmlLocation" value="classpath:/META-INF/myunit/persistence.xml"/>
  </bean>

  <bean id="myunitTransactionManager"
    class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="myunitEntityManagerFactory" />
  </bean>

  <tx:advice id="myunitTxAdvice" transaction-manager="myunitTransactionManager">
    <tx:attributes>
      <tx:method name="*" read-only="true"/>
    </tx:attributes>
  </tx:advice>
  
  <aop:config>
    <aop:pointcut id="myunitTxPointcut" expression="execution(* com.mycompany.mypackage.*.*(..))"/>
    <aop:advisor advice-ref="myunitTxAdvice" pointcut-ref="myunitTxPointcut"/>
  </aop:config>

  <bean id="myunitJpaAdapter"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="database" value="ORACLE" />
    <property name="showSql" value="true" />
  </bean>

</beans>

Third convention. Always specify the persistence unit name when using the PersistenceContext annotation to inject an entity manager into a class that is part of the module:

  @PersistenceContext(unitName = "myunit")
  private EntityManager entityManager;

Fourth convention. Use explict AOP configuration in the Spring configuration file for declaring transaction boundaries. The Transactional annotation does not support multiple local transaction managers tied to different persistence units, so it cannot be used in the context of a reusable module.

Note that the module should not publicly expose any non-transactional methods involving database access. This ensures that the correct transaction manager is used for the persistence unit.

With these conventions, I was able to build the module as a jar file that can be tested in isolation and also deployed to other applications in three steps:

  1. add the jar file to the classpath
  2. add classpath:/META-INF/myunit/applicationContext.xml to the list of configuration locations for the application's Spring container.
  3. (optional) override database settings via system properties as necessary

The conventions may be too restrictive in some cases but they work well for my situation.

Saturday, February 09, 2008

Multiple Persistence Units with Spring and JPA

Recently I've been building an application with Spring, Hibernate and JPA. The application uses a single persistence unit with a number of entities stored in a single database schema.

Next I need to add some functionality that queries data from another database schema. I'd like to be able to implement this as a self-contained module packaged as a jar file that could be dropped into any application, not just the one I'm currently implementing. The module therefore needs to configure its own persistence unit and all supporting objects, namely a database connection pool and transaction manager. I don't need global transaction support since the module is only reading information from the second schema.

To meet these criteria, the jar file for the module needs to include:

  1. Annotated classes reponsible for executing queries using some combination of JPA and Hibernate APIs.
  2. JPA configuration file for defining the persistence unit
  3. Spring configuration file that declares beans for the database connection pool, entity manager factory and transaction manager and initializes the persistence unit.

Applications should be able to integrate the module in three steps:

  1. Add the jar file to the classpath
  2. Add the module's Spring configuration file to the list of files used to configure the application's Spring container.
  3. Define system properties to override the database connection information (url, user name, password) for the module

The Spring documentation has a brief section on dealing with multiple persistence units. The instructions there seemed relevant but the solution I ultimately came up with did not require defining my own PersistenceUnitManager. I'll explain the solution in my next post.

Saturday, February 17, 2007

Removing nodes from an XML document in Flex 2

E4X is the ECMAScript language extension for accessing and manipulating XML. One odd thing about E4X is that it has no methods for removing nodes. This has stumped me for a while, but I just discovered that you simply use the delete operator:

var doc:XML = <folder label="Parent" name="parent">
   <folder label="Child A" name="childa"/>
   <folder label="Child B" name="childb"/>
 </folder>;

// remove the child folders
delete doc.folder;

trace(doc.toXMLString());

Easy once you know how to do it.

Saturday, February 10, 2007

Debugging in Flex 2

Until now I have been adding Alert.show() statements in ActionScript to help me debug my Flex application. This is very cumbersome, so I tried to figure out how to enable trace() statements that would write messages to a local log file.

I couldn't find any up-to-date instructions, but here is what I was able to piece together for Firefox on Windows at least:

  1. Download and install the latest debug version of the Flash plugin.
  2. Create or edit the mm.cfg file (located in C:\Documents and Settings\<user>\ in Windows). The file should have these three entries:
    TraceOutputFileEnable=1
    ErrorReportingEnable=1
    TraceOutPutFileName=c:\Documents and Settings\<user>\Application Data\Macromedia\Flash Player\Logs\flashlog.txt
  3. Install the FlashTracer extension for Firefox.
  4. Restart Firefox

Prior to the latest player revision it was necessary to set the HOMEDRIVE and HOMEPATH environment variables, but apparently not anymore. Also note that it is not possible to simply change TraceOutPutFileName to another location.

You do not need to compile your Flex application with the -debug option to use the trace method.

Saturday, February 03, 2007

Flex 2: Adding a toolbar to the list pane

I wanted to add a toolbar to the top of the list pane of my remote file explorer. The toolbar would contain icon buttons for some basic file operations like add, move, copy and delete.

After some poking around, it seemed that Flex does not have a built-in component for this purpose. The closest container for this purpose seemed to be ApplicationControlBar. I was able to create something that looks and acts like a typical desktop application toolbar with this declaration:

<mx:ApplicationControlBar dock="true" width="100%" 
  paddingTop="2" paddingRight="2"
  paddingLeft="2" paddingBottom="2" horizontalGap="2">
  <mx:PopUpButton id="newItem" label="" height="36" 
    styleName="toolbarButton" toolTip="Add Item..."
    icon="@Embed(source='assets/24x24/document_add.png')"/>
  <mx:Spacer width="8"/>
  <mx:Button id="newFolder" label="" height="32" 
    styleName="toolbarButton" toolTip="Add Folder..."
    icon="@Embed(source='assets/24x24/folder_add.png')"/>
  <mx:Spacer width="8"/>
  <mx:Button id="copyItems" label="" height="32" 
    styleName="toolbarButton" toolTip="Copy Selected Items"
    enabled="false" alpha="0.4"
    icon="@Embed(source='assets/24x24/copy.png')"/>
  <mx:Button id="moveItems" label="" height="32" 
    styleName="toolbarButton" toolTip="Move Selected Items"
    enabled="false" alpha="0.4"
    icon="@Embed(source='assets/24x24/document_into.png')"/>
  <mx:Button id="deleteItems" label="" height="32" 
    styleName="toolbarButton" toolTip="Delete Selected Items"
    enabled="false" alpha="0.4"
    icon="@Embed(source='assets/24x24/delete.png')"/>
</mx:ApplicationControlBar>

One additional puzzle was how to disable the copy, move and delete buttons until the user selects one or more items. The enabled flag on the Button component controls user interaction, but setting this flag to false does not change a button's appearance unless you specify the disabledColor, disabledIcon, or disabledSkin properties. I didn't want to create a second set of disabled icons. Eventually I figured out I can use the alpha property to dim out the button when disabled. I added this code to the change handler for the item list:

copyItems.enabled = moveItems.enabled = deleteItems.enabled = true;
copyItems.alpha = moveItems.alpha = deleteItems.alpha = 1.0;

I haven't written handlers for clicking on any of the buttons yet.

Saturday, January 27, 2007

Sorting a DataGrid in Flex 2

The default sorting behavior of DataGrid needs some customization to suit the list pane of my remote file explorer. Rather than a simple alphabetical sort in every case, the behavior depends on the sort column:

  1. The last modified column should sort in chronological order.
  2. The file size column should sort by actual size (i.e. 1 KB, 500 KB and 1.5 MB should sort correctly).
  3. Text columns should sort in case-insenstive order.

The documentation for DataGridColumn mentions the optional sortCompareFunction property, which specifies a comparison function with the contract:

private function compare(a:Object, b:Object):int {
   return (a < b) ? -1 : (a == b) ? 0 : 1;
}

However, the documentation doesn't say what the arguments a and b actually are. Turns out that they are the data for the rows being compared, not the column values.

This is convenient for writing generic functions that can be applied to multiple columns. First I wrote a function to do a case-insensitive comparison for text columns:

/**
  * Compares items by the case-insensitive value of the current sort
  * column.
  **/
private function compareCaseInsensitive(a:Object, b:Object):int {

  var sortColumn:String = itemData.sort.fields[0].name;

  var valueA:String = (a as XML)[sortColumn].toString().toLowerCase();
  var valueB:String = (b as XML)[sortColumn].toString().toLowerCase();

  return (valueA < valueB) ? -1 : (valueA == valueB) ? 0 : 1;
}

The sort.fields collection indicates the current sort column. Note that the < and > operators work for strings as well as numbers in ActionScript.

For the Last Modified and Size columns of the list pane, I added a sortable value attribute to the XML data supplied by the server, i.e.:

<item>
  <title>clone</title>
  <size value="1024">1 KB</size>
  <type>Web Page</type>
  <lastModified value="200606241611">24 Jun 06 04:11 PM PDT</lastModified>
</item>

This convention allowed me to use a single comparison function for all columns that do not sort alphabetically:

/**
  * Compares items by the value attribute of the current sort column.
  **/
private function compareByValue(a:Object, b:Object):int {

  var sortColumn:String = itemData.sort.fields[0].name;

  var sizeA:int = (a as XML)[sortColumn].@value;
  var sizeB:int = (b as XML)[sortColumn].@value;

  return (sizeA < sizeB) ? -1 : (sizeA == sizeB) ? 0 : 1;
}

I applied these functions to the corresponding columns by modifying their declarations in the MXML file:

<mx:DataGridColumn dataField="title" headerText="Title" 
                   sortCompareFunction="compareCaseInsensitive"/>
<mx:DataGridColumn dataField="size" headerText="Size" 
                   sortCompareFunction="compareByValue"/>
<mx:DataGridColumn dataField="type" headerText="Type" 
                   sortCompareFunction="compareCaseInsensitive"/>
<mx:DataGridColumn dataField="lastModified" headerText="Modified" 
                   sortCompareFunction="compareByValue"/>

I tested this arrangement and it worked fine.

Sunday, January 21, 2007

Incremental Loading of a Tree in Flex 2

In my last post I started building a remote file explorer in Flex 2. Implementing the list pane was simple. The next task was to implement the tree pane on the left side of the explorer window.

The requirements for the tree pane were:

  1. The tree must load incrementally. When the explorer first loads it should only retrieve the top-level directories from the server. The explorer should retrieve additional subdirectories one parent directory at a time, when the user expands a tree node for the first time.
  2. The explorer must accept the path to a nested subdirectory and automatically expand to the tree down to that subdirectory when the explorer loads.

I couldn't find any good examples to meet these requirements. After some trial and error with XMLListCollection, XMLList and XML data providers, I came up with a solution that seems to work.

First I added an HTTPService, data provider and Tree component to my initial MXML file:

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  creationComplete="initApp();">

  <mx:Script source="explorer.as"/>

  <mx:HTTPService id="folderService" url="/flex/folders.jsp" 
    resultFormat="e4x" showBusyCursor="true"
    result="addFolders(event)"
    fault="handleFault(event)">
  </mx:HTTPService>

  <mx:XML id="folderData"/>

  <mx:HTTPService id="itemService" url="/flex/items.jsp" 
    resultFormat="e4x" showBusyCursor="true"
    fault="handleFault(event)">
  </mx:HTTPService>

  <mx:XMLListCollection id="itemData" 
      source="{itemService.lastResult.item}"/>

  <mx:HDividedBox width="100%" height="100%">

    <mx:Tree id="folderTree" 
      width="40%" height="100%" 
      labelField="@label"
      showRoot="false" 
      dataProvider="{folderData}" 
      folderOpenIcon="@Embed(source='assets/folder.png')"
      folderClosedIcon="@Embed(source='assets/folder_closed.png')"
      defaultLeafIcon="@Embed(source='assets/folder_closed.png')"
      itemOpening="loadChildFolders(event)"
      change="loadList(event)"/>

    <mx:DataGrid id="itemGrid" width="60%" height="100%" 
      dataProvider="{itemData}">
      <mx:columns> 
 <mx:DataGridColumn dataField="title" headerText="Title"/>
 <mx:DataGridColumn dataField="size" headerText="Size"/>
 <mx:DataGridColumn dataField="type" headerText="Type"/>
 <mx:DataGridColumn dataField="lastModified" headerText="Modified"/>
      </mx:columns>
    </mx:DataGrid>

  </mx:HDividedBox>

</mx:Application>

Note a couple significant differences between the data provider for the Tree and the DataGrid components:

  1. The data provider for the list pane is automatically bound to the corresponding HTTPService. Each time the itemService is called, the DataGrid clears and displays the last data received from the server. Since the Tree needs to load its data incrementally, it seemed that Flex requires manual handling of the folderService response using a result event handler.
  2. Because the directory tree represents a single hierarchy, the data provider can be a simple XML object rather than a XMLList or XMLListCollection. I was more or less able to get the tree to work using the latter two types of data providers as well, but the code was messier. I also could not convince the root node to hide itself that way, in spite of the showRoot attribute being set to false.

The event handlers for the tree pane required much more ActionScript than the list pane (comments inline):

import mx.controls.Alert;
import mx.events.TreeEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

/**
 * Handles the creationComplete event for the application.
 *
 * Expects a comma-separated list of folder IDs representing the
 * initial expansion path for the explorer.
 **/
private function initApp():void {
  
  var expandIDs:Array = Application.application.parameters.folderID.split(",");

  var lastIndex:Number = expandIDs.length - 1;

  var parameters:Object = {folderID:expandIDs[lastIndex]};
  itemService.send(parameters); 

  parameters.folderID = expandIDs.shift();
  var token:Object = folderService.send(parameters);
  token.expandIDs = expandIDs;
}

private function handleFault(e:FaultEvent):void {
  Alert.show("Failed to contact the server.");
}

/**
 * Handles the change event (selection of a directory) for the
 * explorer tree.
 *
 * Expands the selected directory to show subdirectories.  Initiates
 * retrieval a list of subdirectories from the server if user is
 * selecting this directory for the first time.
 *
 * Also retrieves a list of directory files for display in the list
 * pane.
 **/
private function loadList(e:Event):void {

  var node:XML = Tree(e.target).selectedItem as XML;

  if (loadChildFolderNode(node, e.type) && ! folderTree.isItemOpen(node)) {
    var open:Boolean = true;
    var animate:Boolean = true;
    folderTree.expandItem(folderTree.selectedItem, open, animate);
  }

  var parameters:Object = {folderID:node.@id};
  
  itemService.send(parameters);
}

/**
 * Handles the itemOpening event (clicking the expand arrow next to a
 * directory) for the explorer tree.
 *
 * Initiates retrieval of a list of subdirectories from the server and
 * cancels immediate opening if user is selecting this directory for
 * the first time.  The explorer then opens the directory after
 * receiving the server response and adding the subdirectories to the
 * tree.
 **/
private function loadChildFolders(e:TreeEvent):void {

  var node:XML = e.item as XML;

  if (! loadChildFolderNode(node, e.type)) {
    e.stopPropagation();
    e.preventDefault()
  }
}

/**
 * Checks if a tree node is loaded and initiates retrieval if not.
 **/
private function loadChildFolderNode(node:XML, eventType:String):Boolean {

  if (node.@isBranch && node.children().length() == 0) {

    var parameters:Object = {folderID:node.@id};
    var token:Object = folderService.send(parameters);
    token.parent = node;
    token.eventType = eventType;
    return false;

  } else {

    return true;
  }
}

/**
 * Handles the result event for folderService by inserting
 * subdirectories into the tree under the parent node and then
 * expanding the tree to display the newly inserted nodes.
 *
 * Initiates retrieval of the next level in the starting expansion
 * path if appropriate.
 **/
private function addFolders(e:ResultEvent):void {

  var node:XML = e.result as XML;
  var parent:XML = e.token.parent;

  if (! parent) {
    // insert root node here to work around apparent problem with
    // showRoot property of the Tree component
    parent = folderData = ;
  }

  // add nodes from server response
  for each (var child:XML in node.children()) {
    parent.appendChild(child);    
  }

  folderTree.expandItem(parent, true, true);
  // only update tree selection for change events
  if (folderTree.selectedItem != parent && 
      e.token.eventType == "change") {
    folderTree.selectedItem = parent;
  }

  // expandIDs tracks the expansion path
  var expandIDs:Array = e.token.expandIDs;
  if (expandIDs != null && expandIDs.length > 0) {

    var folderID:String = expandIDs.shift();

    var list:XMLList = parent.folder.(@id == folderID);

    if (list.length() > 0) {
      var parameters:Object = {folderID:folderID};
      var token:Object = folderService.send(parameters);
      token.expandIDs = expandIDs;
      token.parent = list[0];
      token.eventType = (expandIDs.length > 1) ? "init" : "change";
    }
  }
}

I took advantage of the Asynchronous Completion Token (ACT) pattern to keep track of the parent directory when retrieving its subdirectories from the server, as well as for completing the initial expansion of the tree over a series of retrievals. It seems a little odd to set the token properties after making the request, but that's the way it seems to be done.

On the server side, I wrote a simple JSP that accepts a directory ID and returns a list of subdirectories in the form:

<folder id="parent ID">
  <folder id="first child ID" label="first child label" isBranch="true or false"/>
  <folder id="second child ID" label="second child label" isBranch="true or false"/>
  ...
</folder>

Maybe there's an easier way to do it, but this is still a very small amount of code and markup compared to what would be required to implement anything close to the equivalent functionality using DHTML.

Sunday, January 14, 2007

Implementing a Remote File Explorer in Flex 2: List View

The first application I want to implement in Flex is a standard split-pane file explorer, with a folder tree in the left pane and a list of folder contents in the right pane.

A basic implementation of the list pane turned out to be very simple:

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  creationComplete="initApp(); itemsService.send();">

  <mx:Script source="explorer.as"/>

  <mx:HTTPService id="itemsService" url="/flex/items.jsp" 
    resultFormat="e4x" showBusyCursor="true"
    fault="handleFault(event)">
    <mx:request>
      <folderID>{folderID}</folderID>
    </mx:request>
  </mx:HTTPService>

  <mx:XMLListCollection id="itemsList" 
      source="{itemsService.lastResult.item}"/>

  <mx:DataGrid id="itemGrid" width="100%" height="100%" 
 dataProvider="{itemsList}">
    <mx:columns> 
      <mx:DataGridColumn dataField="title" headerText="Title"/>
      <mx:DataGridColumn dataField="size" headerText="Size"/>
      <mx:DataGridColumn dataField="type" headerText="Type"/>
      <mx:DataGridColumn dataField="lastModified" headerText="Last Modified"/>
    </mx:columns>
  </mx:DataGrid>
</mx:Application>

Working from the bottom of the file up:

4. The DataGrid is a scrollable, sortable table component. Viewers can resize and reposition columns by dragging the column headers.

3. The XMLListCollection is the data source for the table. Flex automatically updates the table when the data source changes.

2. The HTTPService defines how to retrieve data from the server as a simple XML document, in this case returned by a JSP. Flex automatically unmarshals the XML into the data source whenever it receives a successful response. The request element defines the dynamic query parameters that should be added to each request.

3. The Script references a small bit of ActionScript to handle initialization and request faults:

import mx.controls.Alert;
import mx.rpc.events.FaultEvent;

[Bindable]
public var folderID:String;

private function initApp():void {
  folderID = Application.application.parameters.folderID;
}

private function handleFault(e:FaultEvent):void {
  Alert.show("Failed to contact the server.");
}

The XML document returned by the server corresponds to the rows and columns in the table:

<items>
  <item>
    <title>myscript.as</title>
    <size>10.1K</size>
    <type>ActionScript</type>
    <lastModified>8:34 AM 01/15/07</lastModified>
  </item>
  ...
</items>

The only other thing I had to do was modify the wrapper HTML page to pass a folder ID to Flex:

<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>File Explorer</title>
      <script src="AC_OETags.js" language="javascript"></script>
      <style>
 body { margin: 0px; overflow:hidden }
      </style>
  </head>

  <body scroll='no'>
    <script language="JavaScript" type="text/javascript">
      <!--
      AC_FL_RunContent(
      "src", "explorer",
      "width", "100%",
      "height", "100%",
      "align", "middle",
      "id", "flexstore",
      "quality", "high",
      "bgcolor", "#869ca7",
      "name", "explorer",
      "flashVars", "folderID=<%=folderID%>",
      "allowScriptAccess","sameDomain",
      "type", "application/x-shockwave-flash",
      "pluginspage", "http://www.adobe.com/go/getflashplayer"
 );
      // -->
    </script>
    <noscript>
      <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
 id="flexstore" width="100%" height="100%"
 codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
 <param name='flashVars' value='folderID=<=folderID%>'></param>
 <param name="movie" value="explorer.swf"></param>
 <param name="quality" value="high"></param>
 <param name="bgcolor" value="#869ca7"></param>
 <param name="allowScriptAccess" value="sameDomain"></param>
 <embed src="explorer.swf" quality="high" bgcolor="#869ca7"
   width="100%" height="100%" name="flexstore" align="middle"
   flashVars="folderID=<%=folderID%>"
   play="true"
   loop="false"
   quality="high"
   allowScriptAccess="sameDomain"
   type="application/x-shockwave-flash"
   pluginspage="http://www.adobe.com/go/getflashplayer">
 </embed>
      </object>
    </noscript>
  </body>
</html>

I compiled the MXML file and was able to view the file list in the browser. Very nice!

Friday, January 12, 2007

Getting Started with Flex 2

After reading about Flex 2, I wanted to give it a try for myself. Adobe provides a free command-line compiler in the Flex SDK, downloadable with registration from their surprisingly clunky web site.

The download is just a zip file that you can extract and immediately begin using. Within a few minutes I was able to compile and test the sample applications, and from there it wasn't too hard to figure out the minimum setup I needed to create my own first application:

  1. Create an empty directory under the document root of your web server.
  2. Copy samples/explorer/AC_OETags.js to the new directory.
  3. Copy samples/explorer/explorer.html to the new directory. Rename it first.html
  4. Edit first.html and replace all instances of explorer with first
  5. Copy samples/explorer/build.sh to the new directory.
  6. Edit build.sh and change to the relative path to mxmlc so it points to wherever you extracted the SDK.

Once that was done, I created a new main layout file named first.mxml to declare my application:

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
</mx:Application>

I used mxmlc to compile this into first.swf. I was then able to view first.html in a browser and successfully access my first (empty) Flex application.

Building user interfaces with Flex 2

After several years in the trenches, I am very sick of building rich browser-based user interfaces using DHTML. Even relying on fancy libraries like scriptaculous or the Yahoo User Interface Library, the interface never acts quite as nice as a desktop application. Loading can be slow. Uneditable labels and components are selectable. Objects can stutter and flicker or get stuck on unexpected boundaries when dragged. Different combinations of browsers and operating systems yield different results and bugs.

The whole programming model is also tedious. Not so much because of JavaScript, but because HTML is awkward for declaring layouts and wiring together components and data sources.

Adobe Flex 2 seems to offer a very compelling alternative to DHTML for rich browser-based application interfaces. With Flex you build interfaces in three parts:

  1. XML files that declare layouts, components and other static objects. Flex includes a full set of standard interface components such as trees, lists, menus and form controls. You can also create custom components.
  2. Script files (ActionScript) for implementing event handlers and other dynamic behavior.
  3. Server-side data sources and callbacks to handle data flow to and from the client.

Besides being compelling from a programming perspective, the standard components look and feel beautiful and responsive on all browsers and platforms. This demo is my favorite since it shows off each component along with the code required to use it.

Sunday, December 17, 2006

Solving 403 Forbidden Error in Apache on Fedora Core 6

I am trying out Fedora Core 6 in a VMWare image downloaded from thoughtpolice. I started apache using apachectl, created a test file in /var/www/html/test.html, tried to access it from a browser, and received a 403 Forbidden error.

This incantation solved the problem:

# chcon -R -t httpd_sys_content_t /var/www/html/

Sunday, November 26, 2006

Naming Exceptions

When defining a WebMethod for JAX-WS 2.0, it appears that the best naming convention is to leave off the word Exception when defining method-specific exceptions. For example:
  @WebMethod(operationName = "GetMember", action = "GetMember")
  public void getMember(@WebParam(name = "MemberID")
                        Long memberID,
                        @WebParam(name = "Member",
                                  mode = Mode.OUT)
                        Holder member) 
    throws InvalidMemberID {

    if (! isValid(memberID)) {
      throw new InvalidMemberID(memberID);
    }

    ...
  }

I originally named the exception InvalidMemberIDException, but this results in a SOAP fault with the same name in the WSDL, rather than just InvalidMemberID. The client tool wsimport will then use this WSDL to generate an exception named InvalidMemberIDException_Exception.