Ryan Heaton is an engineer, architect, and consultant specializing the Web service design and development. Ryan has architected a wide variety of successful Web service applications and frameworks. One of his projects, Enunciate, has been released as a popular open-source Web service development framework. He has performed technical interviews for scores of candidates probing all aspects of Web service design and implementation. Ryan is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone. View Full User Profile

Providing AMF REST Resources in Java

03.19.2009
| 15163 views |
  • submit to reddit

Action Message Format (AMF) is a high-performance web services protocol that was developed and designed especially for Flash applications by Adobe. AMF is generally considered an RPC protocol (i.e. an optimized alternative to SOAP) because it defines a way to invoke remote methods from a Flash-based client.

Another significant part of the AMF specification, however, is the definition of a data serialization format. This serialization format makes AMF a viable representation (i.e. media type) for REST resources. This offers significant potential for Flash/Flex/ActionScript developers, since the availability of AMF representations for REST resources means that they could work with strongly-typed ActionScript classes without having to translate them to/from XML (or JSON or whatever other format). Instead, the objects could be written and/or read directly to a stream.

This tutorial is intended to show how to provide AMF REST resources in Java using freely-available APIs. How to provide AMF resources on other platforms (e.g. Ruby, .NET, PHP, etc.) is left for another day (and probably another author).

Overview

Here's what we do:

  1. Write the server-side data objects
  2. Write the client-side data objects
  3. Write the server-side resource methods
  4. Write the server-side AMF data provider
  5. Write the client-side invocation code
  6. Compile, build, and deploy
infoThere's the hard way, and then there's...
Enunciate is a free build-time tool designed to make it easy to develop a rich Web-service API. As of version 1.10, Enunciate will automatically deploy your JAX-WS REST endpoints via AMF. It will also generate your client-side ActionScript data objects that can be used to consume these REST endpoints. With Enunciate, you simply write your server-side data objects and resource methods, and Enunciate will take care of the rest (no pun intended).

However, since there's value in knowing what's running under the hood, we continue on with the tutorial...

For this tutorial, we will be writing a simple address book application. We'll provide a contacts resource for reading the list of contacts and we'll provide a contact resource for updating contact information. We'll be using JAX-RS for defining our resource methods and providers, and BlazeDS for (de)serializing our Java objects to/from AMF.

Write the server-side data objects

Pretty basic POJOs:

Contact.java

package net.java.ws.addressbook.domain;

/**
* A contact in the address book.
*/
public class Contact {

private int id;
private String name;
private String phone;
private String address1;
private String address2;
private String city;

/**
* The id of the contact.
*
* @return The id of the contact.
*/
public int getId() {
return id;
}

/**
* The id of the contact.
*
* @param id The id of the contact.
*/
public void setId(int id) {
this.id = id;
}

/**
* The name of the contact.
*
* @return The name of the contact.
*/
public String getName() {
return name;
}

/**
* The name of the contact.
*
* @param name The name of the contact.
*/
public void setName(String name) {
this.name = name;
}

/**
* The phone of the contact.
*
* @return The phone of the contact.
*/
public String getPhone() {
return phone;
}

/**
* TThe phone of the contact.
*
* @param phone The phone of the contact.
*/
public void setPhone(String phone) {
this.phone = phone;
}

/**
* The first address field of the contact.
*
* @return The first address field of the contact.
*/
public String getAddress1() {
return address1;
}

/**
* The first address field of the contact.
*
* @param address1 The first address field of the contact.
*/
public void setAddress1(String address1) {
this.address1 = address1;
}

/**
* The second address field of the contact.
*
* @return The second address field of the contact.
*/
public String getAddress2() {
return address2;
}

/**
* The second address field of the contact.
*
* @param address2 The second address field of the contact.
*/
public void setAddress2(String address2) {
this.address2 = address2;
}

/**
* The city of the contact.
*
* @return The city of the contact.
*/
public String getCity() {
return city;
}

/**
* The city of the contact.
*
* @param city The city of the contact.
*/
public void setCity(String city) {
this.city = city;
}
}


Contact.java

package net.java.ws.addressbook.domain;

import java.util.Collection;

/**
* A list of contacts.
*/
public class ContactList {

private Collection<Contact> contacts;

/**
* The contact list.
*
* @return The contact list.
*/
public Collection<Contact> getContacts() {
return contacts;
}

/**
* The contact list.
*
* @param contacts The contact list.
*/
public void setContacts(Collection<Contact> contacts) {
this.contacts = contacts;
}
}

Write the client-side data objects

The associated ActionScript classes are a reflection of the Java server-side classes. Of course, you have to do some translation, since not all Java types are ActionScript types and vice-versa. You can view some of the rules at Converting data from Java to ActionScript. Note also that if the name of your ActionScript object is different from that of your Java object, you need to register a "class alias" with the registerClassAlias method. (Here's where a code generator, like Enunciate, comes in really handy.)

Contact.as

package net.java.ws.addressbook.domain {

import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
import flash.net.registerClassAlias;


/**
* A contact in the address book.
*/
[Bindable]
[RemoteClass(alias="net.java.ws.addressbook.domain.amf.Contact")]
public class Contact {

private var _id:int;
private var _name:String;
private var _phone:String;
private var _address1:String;
private var _address2:String;
private var _city:String;

public function Contact() {
}

/**
* The id of the contact.
*/
public function get id():int {
return _id;
}

/**
* The id of the contact.
*/
public function set id(id:int):void {
_id = id;
}

/**
* The name of the contact.
*/
public function get name():String {
return _name;
}

/**
* The name of the contact.
*/
public function set name(name:String):void {
_name = name;
}

/**
* The phone of the contact.
*/
public function get phone():String {
return _phone;
}

/**
* The phone of the contact.
*/
public function set phone(phone:String):void {
_phone = phone;
}

/**
* The first address field of the contact.
*/
public function get address1():String {
return _address1;
}

/**
* The first address field of the contact.
*/
public function set address1(address1:String):void {
_address1 = address1;
}

/**
* The second address field of the contact.
*/
public function get address2():String {
return _address2;
}

/**
* The second address field of the contact.
*/
public function set address2(address2:String):void {
_address2 = address2;
}

/**
* The city of the contact.
*/
public function get city():String {
return _city;
}

/**
* The city of the contact.
*/
public function set city(city:String):void {
_city = city;
}
}
}


ContactList.as

package net.java.ws.addressbook.domain {

import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
import flash.net.registerClassAlias;
import net.java.ws.addressbook.domain.Contact;
import mx.collections.ArrayCollection;

//register the item type of the collection for the "contacts" property.
registerClassAlias("net.java.ws.addressbook.domain.amf.Contact", Contact);

/**
* A list of contacts.
*/
[Bindable]
[RemoteClass(alias="net.java.ws.addressbook.domain.amf.ContactList")]
public class ContactList {

private var _contacts:ArrayCollection;

public function ContactList() {
}

/**
* The contact list.
*/
public function get contacts():ArrayCollection {
return _contacts;
}

/**
* The contact list.
*/
public function set contacts(contacts:ArrayCollection):void {
_contacts = contacts;
}
}
}

Write the server-side resource methods

We just write a pair of simple JAX-RS resource methods. We'll refer you to other resources to learn more about JAX-RS. Note that the resource method produces and/or consumes media type "application/x-amf".

AddressBook.java

package net.java.ws.addressbook.services;

import net.java.ws.addressbook.domain.Contact;
import net.java.ws.addressbook.domain.ContactList;

import java.util.*;

import javax.ws.rs.*;

/**
* The address book resource.
*/
@Path ("/contacts")
public class AddressBook {

@GET
@Produces ( "application/x-amf" )
public ContactList getContacts() {
ContactList list = ...; //load the contacts
return list;
}

@POST
@Path ("/contact")
@Consumes ( "application/x-amf" )
public void postContact(Contact contact) throws AddressBookException {
//store the contact...
}

}

Write the server-side AMF data provider

Again, we use JAX-RS to supply a provider for AMF. Here's what it might look like:

AMFProvider.java

package net.java.ws.providers;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;

/**
* A JAX-RS provider for data that is serialized/deserialized according to the AMF specification for serialization of objects.
* E.g. mime type "application/x-amf".
*/
@Provider
@Produces ("application/x-amf")
@Consumes ("application/x-amf")
public class AMFProvider implements MessageBodyReader, MessageBodyWriter {

public boolean isReadable(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true; //we'll assume everything is readable, for now.
}

public Object readFrom(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream stream)
throws IOException, WebApplicationException {
SerializationContext context = new SerializationContext();
Amf3Input input = new Amf3Input(context);
input.setInputStream(stream);
return input.readObject();
}

public boolean isWriteable(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true; //we'll assume everything is writeable, for now.
}

public void writeTo(Object o, Class realType, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream stream) throws IOException, WebApplicationException {
SerializationContext context = new SerializationContext();
Amf3Output output = new Amf3Output(context);
output.setOutputStream(stream);
output.writeObject(obj);
}

public long getSize(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1; //we don't know the size
}

}

Write the client-side invocation code

What you do with the data is up to you. But here's some sample code that shows how one might make a request to a REST resource and (de)serialize the data:

myapp.mxml

import net.java.ws.addressbook.domain.ContactList;
import net.java.ws.addressbook.domain.Contact;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.net.URLRequestMethod;
import flash.utils.ByteArray;
import flash.events.*;
import flash.errors.*;

private function getContact(id:String):void {
var request:URLRequest = new URLRequest("/contacts");
request.method = URLRequestMethod.GET;
var contactStream:URLStream = new URLStream();
contactStream.addEventListener("complete", handleContacts)
contactStream.addEventListener("ioError", handleError)
try {
contactStream.load(request);
}
catch (error:Error) {
Alert.show(error.toString());
}
}

private function handleContacts(event:Event):void {
var contactStream:URLStream = URLStream( event.target );
var contacts:ContactList = (contactStream.readObject() as ContactList);
//handle the contacts...
}

private function postContact():void {
var request:URLRequest = new URLRequest("/contacts/contact");
request.method = URLRequestMethod.POST;
var ba:ByteArray = new ByteArray();
var c:Contact = new Contact();
c.id = 12345;
c.name = "new name";
c.phone = "new phone";
c.address1 = "new address1";
c.address2 = "new address2";
c.city = "new city";
ba.writeObject(c);
request.data = ba;
request.contentType = "application/x-amf";
var contactStream:URLStream = new URLStream();
contactStream.addEventListener("complete", handleSuccessfulUpdate)
contactStream.addEventListener("ioError", handleError)
try {
contactStream.load(request);
}
catch (error:Error) {
Alert.show(error.toString());
}
}

private function handleSuccessfulUpdate(event:Event):void {
//do something to show the update was successful...
}

private function handleError(event:Event):void {
//handle the error...
}

Compile, build, and deploy

This one's up to you. How you build and deploy is very environment-specific. It also depends on the JAX-RS implementation you're using. And of course, you've got to compile your Flex app, too...

Again, if you're using Enunciate, it's easy--Enunciate will package up everything in a war file for you, which you can drop in your favorite servlet container.

Original post here. Used by permission.
Published at DZone with permission of Ryan Heaton, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mike P(Okidoky) replied on Thu, 2009/03/19 - 8:20pm

Wauw, that's verbose. Can't this be done with like 10 times less code?

Perhaps we can drop the obsession with getters and setters for one, and just let people access the fields directly. Taboo? Why? It *is* a POJO after all, no logic in it. It's a data carrier...
Or else how about a POM (Plain Old Map).

Have meta data (or annotations) describe what maps to what.

etc etc. I'm *sure* we can a *lot* better than we're seeing here...

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.