Posting complex forms with RESTEasy - Part 1

Maarten Winkels

RESTEasy is a Framework for building RESTful applications in Java. In this blog I will show how to easily build RESTful webservices that accept data from an HTML Form. We will also explore the possibilities to extend RESTEasy to handle more complex cases.

Note: All source code in this post is attached as a zipped Maven project.

Simple Example

So let's look at a very simple example:

public class Person {
	private String firstName;
	private String lastName;
}

@Path("/person")
public class PersonResource {
	private PersonRepository repository;
	
	@POST
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	@Produces(MediaType.APPLICATION_JSON)
	public Person savePerson(Person person) {
		return repository.save(person);
	}
}

The intent of these two classes should be pretty clear: We want to expose the savePerson method as a WebService, accept data from a HTML form and produce JSON. The annotations used on the PersonResource are JAX-RS annotations.

Now notice that we both accept and return a Person object in the service method. We expect the RESTEasy framework to handle the unmarshalling from the Form fields and the marshalling to JSON. Luckily JAX-RS also comes with some handy annotations for this: The @FormParam annotation lets you define which Form field to map to which parameter or field. Unfortunately the JAX-RS specification does not talk about mapping forms to rich objects, so the signature for our service method would become

	public Person savePerson (
			@FormParam("firstName") String firstName,
			@FormParam("lastName") String lastName,
			...
			) {

which would become rather large and unwieldy rather soon, when the form has to include all the usual name and address fields.

This is where RESTEasy comes to the rescue with the @Form annotation. This annotation tells the RESTEasy framework to map the entire form onto a parameter object, relieving us from the task to specify all form fields in the parameter list of the service method. The code thus becomes:

public class Person {
	@FormParam("firstName") private String firstName;
	@FormParam("lastName") private String lastName;
}

@Path("/person")
public class PersonResource {
	private PersonRepository repository;
	
	@POST
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	@Produces(MediaType.APPLICATION_JSON)
	public Person savePerson(@Form Person person) {
		return repository.save(person);
	}
}

How to test this?

With these two simple classes, we're done implementing our simple requirements. But how do we test this? RESTEasy comes with a JUnit extension that is very useful to test Resources.

@RunWith(MockitoJUnitRunner.class)
public class PersonResourceTest extends BaseResourceTest{

	private PersonResource resource;
	@Mock private PersonRepository repository;

	@Before
	public void setupResource() {
		MockitoAnnotations.initMocks(getClass());
		resource = new PersonResource();
		resource.setRepository(repository);
(1)		dispatcher.getRegistry().addSingletonResource(resource);
	}

	@After
	public void removeResource() {
		dispatcher.getRegistry().removeRegistrations(PersonResource.class);
	}	

	@Test
	public void shouldSavePerson() throws Exception {
(2)		MockHttpRequest request = post("/person");
		request.addFormHeader("firstName", "Maarten");
		request.addFormHeader("lastName", "Winkels");
		
(3)		ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass(Person.class);
		when(repository.save(personCaptor.capture())).thenAnswer(new ArgumentAnswer<Person>(0));

		MockHttpResponse response = new MockHttpResponse();
(4)		dispatcher.invoke(request, response);

		verify(repository).save(any(Person.class));
(5)		Person person = capturePerson.getLastValue();
		assertEquals("Maarten", person.getFirstName());
		assertEquals("Winkels", person.getLastName());
		
(6)		assertEquals(200, response.getStatus());
		assertEquals("{\"firstName\":\"Maarten\",\"lastName\":\"Winkels\"}", response.getContentAsString());
	}
}

The test above is a true integration test as far as RESTEasy is concerned, in the sense that an HttpRequest is processed by the framework and triggers our PersonResource to produce a JSON response.

Mockito is used to be mock out the PersonRepository (which is not yet implemented), and to be able to check some behavior of the code in the test. Stepping through the code:

  1. The PersonResource with the mock for the repository is registered with the framework in the test. We need to register it as a singleton here, wince we have to prepare the resource with a mock. Notice that the class is removed in the @After method to ensure that the tests have no side-effects.
  2. A MockHttpRequest is prepared to be processed with certain values.
  3. The mock repository is setup to capture the argument coming into the request and to return the same argument as response. The ArgumentAnswer is a small extension of the Mockito/Hamcrest framework to enable the mock to return the parameter passed into the call on the mock as the return value.
  4. The request is executed on the dispatcher, capturing the response in a MockHttpResponse.
  5. The Person we captured is inspected to see that the values are copied from the request.
  6. The response is inspected for return value and content.

That was easy! We've integration tested our entire RESTful application without having to start a container!

A little more complex: nested object structures and forms

Let's try something little more complex: Let's assume the person has an address. We'll code the test first:

	@Test
	public void shouldSavePersonWithAddress() throws Exception {
		MockHttpRequest request = post("/person");
		request.addFormHeader("firstName", "Maarten");
		request.addFormHeader("lastName", "Winkels");
		request.addFormHeader("address.street", "Dorpsstraat");
		request.addFormHeader("address.houseNumber", "19a");
		
		ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass(Person.class);
		when(repository.save(personCaptor.capture())).thenAnswer(new ArgumentAnswer<Person>(0));

		MockHttpResponse response = new MockHttpResponse();
		dispatcher.invoke(request, response);

		verify(repository).save(any(Person.class));
		Person person = capturePerson.getLastValue();
		assertEquals("Maarten", person.getFirstName());
		assertEquals("Winkels", person.getLastName());
		assertEquals("Dorpsstraat", person.getAddress().getStreet());
		assertEquals("19a", person.getAddress().getHouseNumber());
		
		assertEquals(200, response.getStatus());
		assertEquals("{\"firstName\":\"Maarten\",\"lastName\":\"Winkels\",\"address\":{\"street\":\"Dorpsstraat\",\"houseNumber\":\"19a\"}}", response.getContentAsString());
	}

As you can see from the test, a person now has an address and we expect the form fields that start with "address." to be mapped to the fields in the Address class. In a sense, the form is now mapped to the nested object structure of person and address. Unfortunately neither JAX-RS nor RESTEasy has support for this. (RESTEasy does support a @Form annotation in a @Form mapped parameter class, but it does not support specifying a prefix, thus only supporting a single nested object of the same type.)

So we introduce a new annotation to indicate that a field is mapped to a part of the form fields:

public class Person {
...
	@NestedFormParams("address")
	private Address address;
...
}

To the RESTEasy framework, this new annotation means a new way of injecting values into objects, which is rather similar to how values are injected into @Form annotated parameters. *Injectors are created by an InjectorFactory. The ExtendedInjectorFactory below extends the RESTEasy InjectorFactoryImpl to make RESTEasy aware of the new annotation.

public class ExtendedInjectorFactory extends InjectorFactoryImpl{

	private final ResteasyProviderFactory factory;

	public ExtendedInjectorFactory(ResteasyProviderFactory factory) {
		super(factory);
		this.factory = factory;
	}
	
	@Override
	public ValueInjector createParameterExtractor(Class injectTargetClass, AccessibleObject injectTarget, Class type, Type genericType, Annotation[] annotations, boolean useDefault) {
		NestedFormParams param = FindAnnotation.findAnnotation(annotations, NestedFormParams.class);
		if (param != null) {
			return new NestedFormInjector(type, factory, param.value());
		}
		return super.createParameterExtractor(injectTargetClass, injectTarget, type, genericType, annotations, useDefault);
	}

}

In the test, the new InjectorFactory is registered with the ProviderFactory:

	@Before
	public void setupResource() {
		getProviderFactory().setInjectorFactory(new ExtendedInjectorFactory(getProviderFactory()));
		...
	}

Whenever a NestedFormParams annotation is found, a NestedFormInjector is created to handle the injection. Since the functionality is so similar to the FormInjector, it simply is an extension of that class.

public class NestedFormInjector extends FormInjector {

	private final String prefix;

	public NestedFormInjector(Class<?> type, ResteasyProviderFactory factory, String prefix) {
		super(type, factory);
		this.prefix = prefix;
	}

	@Override
	public Object inject(HttpRequest request, HttpResponse response) {
		if (!containsPrefixedName(request.getDecodedFormParameters().keySet())) {
			return null;
		}
		return super.inject(new PrefixedFormFieldsHttpRequest(prefix, request), response);
	}

	private boolean containsPrefixedName(Set<String> keys) {
		for (String key : keys) {
			if (key.startsWith(prefix)) {
				return true;
			}
		}
		return false;
	}
}

To understand how the NestedFormInjector works, one has to understand how the FormInjector works. The form injector is created for a specific type. It uses a ConstructorInjector to create an instance of that type and a PropertyInjector to inject other values into this instance. The injection of these values is carried out by other injectors. If a field in the class is annotated with an @FormParam annotation, the FormInjector is used to inject the value from the form parameter into the field.

The NestedFormInjector works by restricting the access of any form injectors that are used for it's properties to form fields that start with the given prefix (and a dot). This is achieved by wrapping the original HttpRequest in an PrefixedFormFieldHttpRequest. Objects of this class wrap the original form fields in a map that will add the prefix on each look up. The code can be found in the attached ZIP file.

Conclusion

By extending a few of the core classes of the RESTEasy framework, we have added the functionality to map a HTTP form to a complex association of two classes. This can be really useful when complex forms are posted.
A next step would be to map form fields to collections, lists or maps. I'll write about that in a next post.

Comments (7)

  1. yves amsellem - Reply

    March 18, 2011 at 11:49 pm

    Maybe, we need the HTML form to fully understand this.
    Managing those lists of mapping (address.name, etc) seems very hard to maintain through time.
    Passing an already initialized complete object (person with an address field) with JAXB fells more easy.
    Is this impossible with a HTML form? Have we to solely rely on separates fields?
    JAXB handle all primitives and collections.

  2. Maarten Winkels - Reply

    March 19, 2011 at 1:09 pm

    @Yves: Well, JAXB can only marshall and unmarshall XML (or other structural formats like JSON), not form fields. A form field is simply a key value pair. Both are strings. The value could be a list. Therefor we need a mapping from the names on the client side to the field on the server side.

    This mapping is always necessary, with or without associated objects or collections. If we want to pass the "firstName" and the "lastName" field as a combined structure to the service method, some where a mapping will have to be made between these (unrelated) form fields and fields on a Java object that captures their relation.

    Another option would be to construct XML (or JSON) on the client side in JavaScript and post that as the message body to the server. This way we can use JAXB or some other technology to parse the posted structure into Java. To me constructing the complicated structure on the client side, keeping in mind that the mapping between the XML or JSON structure and the Java objects is also name based, is as fragile as maintaining the mapping between the form field names and the Java structure (if not more so).

  3. Solomon Duskis - Reply

    March 28, 2011 at 1:45 pm

    It's awesome that you used the extensible RESTEasy InjectorFactory. I'm really glad it came in handy 🙂

  4. VJ - Reply

    October 4, 2011 at 9:44 pm

    Whats the configuration that needs to be done in the web.xml for this provider?

  5. VJ - Reply

    October 5, 2011 at 8:31 pm

    I use spring so I cannot add rest.scan true, so I added the annotation @Provider
    annotation to ExtendedInjectorFactory class.

    and I have

    <context:component-scan

    The annotation is not being picked up.
    Any idea why it does not pick up?

  6. VJ - Reply

    October 5, 2011 at 8:51 pm

    Hi
    I got it working
    by adding the below.

  7. Subhrajit Roy - Reply

    November 10, 2011 at 6:54 pm

    Cool work ! Am I free to use the source code ?

Add a Comment