|
AdvancedFeatures
Advanced features of the JSR 311 plugin for Grails.
Featured
Domain objectsGrails domain classes can be used in resource methods as return types and parameters out-of-the box. The grails-jaxrs plugin is able to convert domain objects to and from XML and JSON representations. These conversions are done by dedicated domain object providers. This section gives some examples how to use domain classes in resource methods. Single domain objectsLet's assume there's a Person domain class and a PersonResource.update method that responds to HTTP PUT operations. The Person domain class has two properties firstName and lastName. package hello
class Person {
String firstName
String lastName
}The PersonResource.update method
package hello
import javax.ws.rs.Consumes
import javax.ws.rs.Produces
import javax.ws.rs.PUT
class PersonResource {
@PUT
@Consumes(['application/xml','application/json'])
@Produces(['application/xml','application/json'])
Person update(Person person) {
Person result = ... // make some database changes
return result
}
// ...
}Depending on the Content-Type request header the client must either send an XML representation PUT /hello/api/person/1 HTTP/1.1 Content-Type: application/xml Accept: application/xml Host: localhost:8080 Content-Length: 82 <person> <firstName>Sam</firstName> <lastName>Hill</lastName> </person> or a JSON representation PUT /hello/api/person/2 HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: localhost:8080
Content-Length: 58
{"class":"Person","firstName":"Fabien","lastName":"Barel"}In both cases the plugin binds the representation to the person parameter of the update method. The representation of the response entity depends on the Accept request header. If the client sends an Accept=application/xml request header, the Person object returned from the update method is converted to XML. With an Accept=application/json request header, a conversion to JSON is done. Instead of declaring a Person return type one could also declare a javax.ws.rs.core.Response return type and set the response entity during the response building process, as shown in the following example: @PUT
@Consumes(['application/xml','application/json'])
@Produces(['application/xml','application/json'])
Response update(Person person) {
Person result = ... // make some database changes
return Response.ok(result)
}This gives better control over e.g. response header creation and still allows for content negotiation. Domain object collectionsThe grails-jaxrs plugin can also handle domain object collections that are returned from resource methods. In the following example, the readAll resource method returns a list of all Person objects in the database. package hello
import java.util.List
import javax.ws.rs.GET
import javax.ws.rs.Produces
import javax.ws.rs.Path
@Path('/api/person')
class PersonCollectionResource {
@GET
@Produces(['application/xml','application/json'])
List<Person> readAll() {
Person.findAll()
}
// ...
}Depending on the Accept request header either an XML or JSON representation is returned to the client. The generic return type (List<Person>) makes it explicit which domain objects are contained in the collection. But one could equally write: @GET
@Produces(['application/xml','application/json'])
List readAll() {
Person.findAll()
}or @GET
@Produces(['application/xml','application/json'])
def readAll() {
Person.findAll()
}The plugin's domain object writer, however, can be configured to only convert domain objects returned from methods that declare a generic collection return type by adding the following line to grails-app/conf/Config.groovy: org.grails.jaxrs.dowriter.require.generic.collections=true This is useful in situations where resource methods return collections that contain objects other than Grails domain objects (which cannot be handled by the domain object writer). Alternatively, one could disable the plugin's domain object writer completely and write a custom one. As with resource methods that return single domain objects, one can also declare a javax.ws.rs.core.Response return type and set the domain object collection as response entity. @GET
@Produces(['application/xml','application/json'])
Response readAll() {
Response.ok(Person.findAll())
}Using domain object collections for method parameters is not supported (yet). Entity providersEntity providers bind representation formats to Java classes e.g. an XML representation to a Person class as shown in the previous section. They are used to factor out marshalling and unmarshalling code from resource classes. The grails-jaxrs plugin provides some default entity providers that are presented in the following subsections. The custom providers section explains how to implement custom entity providers. Domain object providersDomain object providers convert between Grails domain classes and their XML or JSON representations and support content negotiation. This has already been shown in the sections scaffolding and domain objects.
The behaviour of domain object providers can be customized as described in the custom providers section. XML providersXML providers are superseded by domain object providers since grails-jaxrs version 0.3.
Usage example: import grails.converters.XML
...
class PersonResource {
@PUT
@Consumes('application/xml')
@Produces('application/xml')
XML update(Map dto) {
Person person = new Person(map)
// ... do something with person
return person as XML
}
}JSON providersJSON providers are superseded by domain object providers since grails-jaxrs version 0.3.
Usage example: import grails.converters.JSON
...
class PersonResource {
@PUT
@Consumes('application/json')
@Produces('application/json')
JSON update(Map dto) {
Person person = new Person(map)
// ... do something with person
return person as JSON
}
}Custom providersApplications can implement their own entity providers by placing them into the grails-app/providers directory. In order to be auto-detected by grails-jaxrs they
Custom domain object providersFor customizing the conversion between Grails domain objects and their XML or JSON representations, one has to disable the default domain object providers first. To disable the default domain object reader and writer, the following entries must be added to grails-app/conf/Config.groovy. org.grails.jaxrs.doreader.disable=true org.grails.jaxrs.dowriter.disable=true In the following example a custom domain object writer is implemented, therefore, only the default domain object writer needs to be disabled. A custom XML creation should be done for the Person domain class (see scaffolding example), for all other classes the default XML creation should occur. Here's the custom provider. package hello
import javax.ws.rs.Produces
import javax.ws.rs.ext.Provider
import groovy.xml.MarkupBuilder
import org.grails.jaxrs.support.DomainObjectWriterSupport
@Provider
@Produces(['text/xml', 'application/xml', 'text/x-json', 'application/json'])
class CustomDomainObjectWriter extends DomainObjectWriterSupport {
protected Object writeToXml(Object obj, OutputStream entityStream, String charset) {
if (obj instanceof Person) {
def writer = new OutputStreamWriter(entityStream, charset)
def builder = new MarkupBuilder(writer)
builder.person {
id(obj.id)
fullName("${obj.firstName} ${obj.lastName}")
}
} else {
super.writeToXml(obj, entityStream, charset)
}
}
}The custom provider overrides the writeToXml method and generates custom XML using a MarkupBuilder. To test this provider, create an application as described in the scaffolding example, create a folder grails-app/provider/hello and place this custom provider there. The plugin will auto-detect the provider. To create a new person object in the database, send the following request: POST /hello/api/person HTTP/1.1 Content-Type: application/xml Accept: application/xml Host: localhost:8080 Content-Length: 83 <person> <firstName>Custom</firstName> <lastName>Tester</lastName> </person> The response entity is a custom XML representation created by the custom provider: HTTP/1.1 201 Created Content-Type: application/xml Location: http://localhost:8080/hello/api/person/3 Transfer-Encoding: chunked Server: Jetty(6.1.14) <person> <id>3</id> <fullName>Custom Tester</fullName> </person> There are several other protected DomainObjectWriterSupport methods for customizing the domain object marshalling, for example writeToJson to create custom JSON representations or isWriteable to narrow the set of domain classes that a custom domain object writer accepts. Refer to the the API docs for details. Further entity provider supportFor simple use cases, the grails-jaxrs plugin additionally provides the abstract classes
These base classes can also be used for classes other than domain classes. Implementors define the supported Java type with a type parameter. For example, the following class is a MessageBodyWriter that supports conversions for a Note class. @Provider
@Produces('application/xml')
class NoteWriter extends MessageBodyWriterSupport<Note> {
void writeTo(Note entity, MultivaluedMap httpHeaders, OutputStream entityStream) {
def builder = new MarkupBuilder(new OutputStreamWriter(entityStream))
builder.note {
// create custom XML here ...
}
}
}For details about the MessageBodyWriterSupport and MessageBodyReaderSupport classes refer to the API docs. Alternatively, you may of course write JAX-RS providers from scratch by using the JAX-RS API directly. Using GORMThis section explains what's happening behind the scenes of the scaffolding example and how GORM inside JAX-RS resource classes. PersonCollectionResource.groovyHere's the source code for PersonCollectionResource.groovy. package hello
import static org.grails.jaxrs.response.Responses.*
import javax.ws.rs.Consumes
import javax.ws.rs.GET
import javax.ws.rs.Produces
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.POST
import javax.ws.rs.core.Response
@Path('/api/person')
@Consumes(['application/xml','application/json'])
@Produces(['application/xml','application/json'])
class PersonCollectionResource {
@POST
Response create(Person dto) {
created dto.save()
}
@GET
Response readAll() {
ok Person.findAll()
}
@Path('/{id}')
PersonResource getResource(@PathParam('id') String id) {
new PersonResource(id:id)
}
}It is based based on JSR 311 classes and annotations and uses static methods from org.grails.jaxrs.response.Responses. This is a helper class provided by the plugin that implements a very simple DSL consisting of elements created and ok. Supported content types for requests and responses are application/xml and application/json. This is given by the class-level @Consumes and @Produces annotations. The PersonCollectionResource class responds to HTTP operations that are releated to person lists. The URL of the person list is http://localhost:8080/hello/api/person where the /api/person path is defined by the class-level @Path('/api/person') annotation.
PersonResource.groovyHere's the source code for PersonResource.groovy. package hello
import static org.grails.jaxrs.response.Responses.*
import javax.ws.rs.Consumes
import javax.ws.rs.DELETE
import javax.ws.rs.GET
import javax.ws.rs.Produces
import javax.ws.rs.PUT
import javax.ws.rs.core.Response
import org.grails.jaxrs.provider.DomainObjectNotFoundException
@Consumes(['application/xml','application/json'])
@Produces(['application/xml','application/json'])
class PersonResource {
def id
@GET
Response read() {
def obj = Person.get(id)
if (!obj) {
throw new DomainObjectNotFoundException(Person.class, id)
}
ok obj
}
@PUT
Response update(Person dto) {
def obj = Person.get(id)
if (!obj) {
throw new DomainObjectNotFoundException(Person.class, id)
}
obj.properties = dto.properties
ok obj
}
@DELETE
void delete() {
def obj = Person.get(id)
if (obj) {
obj.delete()
}
}
}The id property is set during construction of the resource and is used for database operations. This class implements the methods read, update and delete to handle GET, PUT and DELETE requests, respectivly. It also uses GORM for database operations and relies on helper methods of org.grails.jaxrs.response.Responses to create repsonses via the JAX-RS API. If there's no person with given id in the database, a DomainObjectNotFoundException is thrown. This exception class generates a custom 404 response using the JAX-RS API (see source code for details). Applying filtersGrails filters can be applied to JAX-RS resources as well. For example, to add a filter for the /api/test/** URL pattern, create a file TestFilters.groovy under grails-app/conf with a content like class TestFilters {
def filters = {
testUris(uri:'/api/test/**') {
before = {
// do some preprocessing
}
after = {
// do some postprocessing
}
}
}
}Service injectionServices can be auto-injected into resource and provider objects by name. Assuming we have a service class named TestService.groovy in grails-app/services package hello
class TestService {
String greet(String name) {
'Hello ' + (name ? name : 'unknown')
}
}then we can auto-inject it by defining a testService property like in the following resource class. package hello
// imports omitted ...
@Path('/api/test')
class TestResource {
def testService // injected
@GET
@Produces('text/plain')
String getTestRepresentation(@QueryParam('name') String name) {
testService.greet(name)
}
}Google App EngineThis section describes how to get the hello world example running on Google App Engine.
org.grails.jaxrs.provider.name='restlet' // replace <application-name> with the // actual App Engine application name google.appengine.application='<application-name>' grails set-version 1
$APPENGINE_HOME/bin/appcfg.sh update ./target/war %APPENGINE_HOME%\bin\appcfg.cmd update .\target\war Please note: Using scaffolding together with the gorm-jpa plugin is not supported at the moment. A related feature request has already been added to the issue tracker. ConfigurationURL mappingsWhen the grails-jaxrs plugin is installed, requests paths matching /api/** are forwarded to a JaxrsController which is part of the plugin. This default URL mapping can be changed via the org.grails.jaxrs.url.mappings property in grails-app/conf/Config.groovy. For example, setting this property to
will create the following URL mappings for the JaxrsController:
@Path annotation values in JAX-RS resource classes must be set accordingly e.g. @Path('/abc/test1')
class TestResource1 {
...
}or @Path('/xyz/test2')
class TestResource2 {
...
}Custom mappings replace the default /api/** mapping. If the default mapping should apply in addition to custom mappings it must be added to the org.grails.jaxrs.url.mappings list.
JAX-RS implementationThe grails-jaxrs plugin allows to choose between Jersey (version 1.2) and Restlet (version 2.0-RC3) as JAX-RS implementations. By default, Jersey is used. If you want to use Restlet instead, add the following line to grails-app/conf/Config.groovy. org.grails.jaxrs.provider.name='restlet' Restlet was added because it supports deployments to Google App Engine (GAE). For instructions how to deploy a grails-jaxrs application to Google App Engine refer to the Google App Engine section. The main obstacle for deploying Jersey to Google App Engine was missing support for JAXB in the past. This has been resolved with the App Engine SDK 1.2.8. However, attempts to deploy Jersey to Google App Engine revealed other issues. We plan to resolve these issues in upcoming versions of this plugin. JAX-RS resource scopeBy default, JAX-RS resource classes are instantiated with every request which corresponds to the following entry in grails-app/conf/Config.groovy. org.grails.jaxrs.resource.scope='prototype' Since this is the default you can omit this entry as well. On the other hand, if you want that your JAX-RS resources are singletons, add the following configuration entry. org.grails.jaxrs.resource.scope='singleton' Domain object providersFrom version 0.3 onwards the grails-jaxrs plugin comes with JAX-RS providers for converting between Grails domain objects and XML/JSON representations. Domain object providers are explained in detail in the domain object providers section. Domain object readers and writers can be disabled by adding the following entries to grails-app/conf/Config.groovy. org.grails.jaxrs.doreader.disable=true org.grails.jaxrs.dowriter.disable=true This is useful in situations where applications implement custom providers. Another domain object provider configuration property, org.grails.jaxrs.dowriter.require.generic.collections, is explained in the domain object collections section. Additional providersBy default the grails-jaxrs plugin scans the grails-app/providers directory for custom providers. JAX-RS provider implementations located elsewhere (e.g. in 3rd party libraries) are ignored. This can be changed by defining extra paths where the plugin should scan for additional providers. For example by adding the following to grails-app/conf/Config.groovy org.grails.jaxrs.provider.extra.paths='com.foo;com.bar' the plugin additionally scans the packages com.foo and com.bar for providers. Please note that this feature is only available when the plugin is configured to use Jersey. You can also define extra paths by setting the corresponding init parameter (see also next section). org.grails.jaxrs.provider.init.parameters=['com.sun.jersey.config.property.packages':'com.foo;com.bar'] Init parametersInit parameters for the servlet of the underlying JAX-RS implementation can be set via the org.grails.jaxrs.provider.init.parameters configuration property in grails-app/conf/Config.groovy, as in the following example. org.grails.jaxrs.provider.init.parameters=[ 'com.sun.jersey.config.property.packages':'com.foo;com.bar', 'another.key':'another.value' ] Integration testingFor integration testing JAX-RS resources and providers, you should extend the plugin's IntegrationTestCase class. The following example is an integration test for the JAX-RS resources created in the scaffolding section. This test
It is located under test/integration in package hello. package hello
import org.grails.jaxrs.itest.IntegrationTestCase
import org.junit.Test
import static org.junit.Assert.*
class PersonIntegrationTest extends IntegrationTestCase {
@Test
void testPostAndGet() {
def headers = ['Content-Type':'application/json', 'Accept':'application/json']
def content = '{"class":"hello.Person","firstName":"Sam","lastName":"Hill"}'
def expected = '{"class":"hello.Person","id":1,"firstName":"Sam","lastName":"Hill"}'
// create new person
sendRequest('/api/person', 'POST', headers, content.bytes)
assertEquals(201, response.status)
assertEquals(expected, response.contentAsString)
assertTrue(response.getHeader('Content-Type').startsWith('application/json'))
// get list of persons
sendRequest('/api/person', 'GET', headers)
assertEquals(200, response.status)
assertEquals("[${expected}]".toString(), response.contentAsString)
assertTrue(response.getHeader('Content-Type').startsWith('application/json'))
}
}For sending requests, the sendRequest method, defined in IntegrationTestCase, is used. The returned response is accessible via the response variable. Query parameters can be set with controller.request.queryString='param=value' prior to calling sendRequest. Tests that extend IntegrationTestCase inherit the following behavior.
Additional configuration options include.
The grails-jaxrs plugin itself is using this test framework for self-testing, such as in JaxrsControllerIntegrationTests. |
I tried to implement a Custom domain object providers for the JSON standard. Unfortunately, I am probably doing some mistake because it is not showing anything in the response... even if the println is correctly shown... Does anyone know where I am wrong?
I have disabled the standard providers org.grails.jaxrs.doreader.disable=true org.grails.jaxrs.dowriter.disable=true
package eu.sytel
import javax.ws.rs.ext.Provider import net.sf.json.util.JSONBuilder import net.sf.json.util.JSONStringer
import org.grails.jaxrs.support.DomainObjectWriterSupport?
@Provider class JSONDomainObjectWriter extends DomainObjectWriterSupport? {
}You additionally need to annotate your writer with @Produces(['text/x-json', 'application/json']). Does that help?
I just fixed that in the docs (http://code.google.com/p/grails-jaxrs/wiki/AdvancedFeatures#Custom_domain_object_providers)
I'm trying to replicate the CustomDomainObjectWriter? example above, but I can't resolve the @Produces annotation:
CustomDomainObjectWriter?.groovy: 8: unable to resolve class Produces , unable to find class for annotation
1 error
javax.ws.rs.Produces is part of jsr311-api-1.1.jar. When you install this plugin it should be on your classpath. Do you see the same error when you run your app from the command-line (e.g. using grails run-app)? If not what IDE are you using? Maybe that's an IDE-related problem. In STS, also try to right-click on your project Grails Tools -> Refresh Dependencies.
Does that help?
No IDE. Everything I've done is on the command line. I see jsr311-api-1.1.jar in C:\Users\lgrey1\.grails\1.3.7\projects\RestfulResource?\plugins\jaxrs-0.4\lib. How do I confirm that it's on the classpath when I do grails run-app? Thanks for your quick response.
You can create a dep. report with 'grails dependency-report' but here I'm not sure if jars in the lib folder are listed as well. Do you have 'plugins.jaxrs=0.4' in application properties?
Anyway, try to use version 0.5-m1 (which has some changes in dep mgnt):
Download http://code.google.com/p/grails-jaxrs/downloads/detail?name=grails-jaxrs-0.5-m1-p1.zip 'grails install-plugin path/to/grails-jaxrs-0.5-m1-p1.zip'
I don't find anything with jax in the dependency reports. The plugin was 0.4 in application.properties. Downloaded and installed 0.5-m1, and application.properties changed. Sadly, I still get the unresolved class error:
-app\providers\restfulresource\CustomDomainObjectWriter?.groovy: 4: unable to res olve class javax.ws.rs.ext.Produces
Compilation error: Compilation FailedYou're using a wrong package: use 'javax.ws.rs' instead of 'javax.ws.rs.ext'. 'javax.ws.rs.ext.Produces' doesn't exist in the jsr311-api.
In the Custom domain object providers section, please add the missing import for Produces. I made a bad assumption by copying the Provider path.
package hello
import javax.ws.rs.ext.Provider import javax.ws.rs.Produces <<<< please add this line import groovy.xml.MarkupBuilder? import org.grails.jaxrs.support.DomainObjectWriterSupport?
@Provider @Produces(['text/xml', 'application/xml', 'text/x-json', 'application/json']) class CustomDomainObjectWriter? extends DomainObjectWriterSupport? {
Sorry about not using the code tag to make my snippets more readable.
Good catch. Added 'import javax.ws.rs.Produces'.
Is it possible not to use command objects? I have made some tests already, i can map a command object, but not his objects i.e. <person>
</person>Telephone won't get populated
Please open a ticket and attach a failing test or app. Thanks.
Is there support for Multipart requests? I have tried it and i'm stuck. I don't thing there is anything wrong with my requests.
import com.sun.jersey.multipart.MultiPart @Consumes('multipart/mixed') @POST Response create(final MultiPart multiPart){ //read out the multiparts }It always results in this error:
javax.ws.rs.WebApplicationException?: org.jvnet.mimepull.MIMEParsingException: Missing start boundary
If multipart requests are properly supported by Jersey then it should also work with the grails-jaxrs plugin. Can you please try to run your example with Jersey alone and see if you also get this exception? If it's running there but not in combination with the plugin then please open a ticket in the issue tracker. Thanks!
I have a problem which you've covered, but not as cleanly as I would like. My domain class has a Date field, for which I need to define custom marshal/unmarshal functionality. I am using grails-jaxrs version 0.6.
I could extend DomainObject?*Support, but that overrides handling for all domain classes, and requires that I work from the I/O stream and deal with all my other object fields as well.
I could extend MessageBody?*Support<MyDomain>, which solves the first problem with using DomainObject?*Support, but not the second one.
What I really want is to use @XmlJavaTypeAdapter?(MyDateAdapter?) on my domain class Date field. But that doesn't seem to work here, nor do other jaxb annotations. Is there a way to use the grails-jaxrs plugin with jaxb annotations? If so, can you provide instructions or an example on how to do that?
Or, perhaps there's another way to solve this problem? Lacking a working jaxb annotation, what do you think would be the best practice here?
And, a second issue - is there any way to not require the "class" element for JSON input? I don't particularly like exposing package and class names in my public API. I would think it wouldn't be necessary, since the method annotated with @POST has an argument of the specific type required.
Thanks in advance.
@ray The grails-jaxrs domain object providers use Grails' default XML marshalling/unmarshalling mechanism which doesn't recognize JAXB annotations. If you want to have JAXB-based XML marshalling/unmarshalling you'll need to disable the domain object providers and use the standard JAXB providers which are part of the of Jersey distribution.