Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

Hypermedia

249

The client uses the getLink method with the relation type (next or prev), which returns a Link instance. The getUri method then returns an instance of java.net.URI, which can be followed by the client on the next iteration.21

If you would rather put the links in the body of the response, you need a different approach, as described in the next section.

9.5.3Adding structural links

Structural links in JAX-RS are instances of the Link class inside the entity itself. Converting them to XML or JSON then requires a special serializer, which is provided by the API.

Here’s the Person class, expanded to hold the self, next, and prev links as attributes:

@XmlRootElement

@EqualsAndHashCode class Person {

Long id String first String last

@XmlJavaTypeAdapter(JaxbAdapter) Link prev

@XmlJavaTypeAdapter(JaxbAdapter) Link self

@XmlJavaTypeAdapter(JaxbAdapter) Link next

}

The prev, self, and next links are instances of the javax.ws.rs.core.Link class, as before. Link.JaxbAdapter is an inner class that tells JAXB how to serialize the links.

Setting the values of the link references is done in the resource, this time using an interesting Groovy mechanism:

Response findById(@PathParam("id") long id) { Person p = dao.findById(id) getLinks(id).each { link ->

p."${link.rel}" = link

}

}

The same getLinks private method is used as in the headers section, but this time the links are added to the Person instance. By calling link.rel (which calls the getRel method) and injecting the result into a string, the effect is to call p.self, p.next, or p.prev, as the case may be. In each case, that will call the associated setter method and assign the attribute to the link on the right-hand side.

21I have to mention that this is probably one of the only times in the last decade that I really could have used a do/while loop. Ironically, that’s just about the only Java construct not supported by Groovy.

www.it-ebooks.info

250

CHAPTER 9 RESTful web services

A test of the structural links using the RESTClient looks like this:

def 'structural and transitional links for kirk are correct'() { when:

def response = client.get(path: 'people/3')

then:

'James Kirk' == "$response.data.first $response.data.last" response.getHeaders('Link').each { println it }

assert response.data.prev.href == 'http://localhost:1234/people/2' assert response.data.self.href == 'http://localhost:1234/people/3' assert response.data.next.href == 'http://localhost:1234/people/4'

}

The response wraps a Person instance, accessed by calling getData. Then the individual links are retrieved as the prev, self, and next properties. The result is a Link instance whose getHref method can be used to verify the links.

There’s only one problem, and it’s more of a nuisance than anything else. In the Rotten Tomatoes example at the beginning of the hypermedia section, the links were not top-level attributes of the movies. Instead, each movie representation contained a JSON object whose key was links, and which contained the list of individual links and relations. Here’s the snippet from the Rotten Tomatoes response:

"links": {

"self": "http://api.rottentomatoes.com/.../771190753.json", "cast": "http://api.rottentomatoes.com/.../771190753/cast.json", "clips": "http://api.rottentomatoes.com/.../771190753/clips.json",

"reviews": "http://api.rottentomatoes.com/.../771190753/reviews.json", "similar": "http://api.rottentomatoes.com/.../771190753/similar.json"

}

In the JAX-RS approach using the serializer, the relation is the attribute name. What if I want to make a collection of links as shown in the movie example? For that I need to take control of the serialization process.

9.5.4Using a JsonBuilder to control the output

To customize output generation, JAX-RS includes an interface called javax.ws.rs.ext

.MessageBodyWriter<T>. This interface is the contract for converting a Java type into a stream. It contains three methods to be implemented.

The first method is called isWriteable, and it returns true for types supported by this writer. For the Person class the implementation is simple:

boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

type == Person && mediaType == MediaType.APPLICATION_JSON_TYPE

}

The method returns true only for Person instances and only if the specified media type is JSON.

The second method is called getSize, and it’s deprecated in JAX-RS 2.0. Its implementation is supposed to return -1:

www.it-ebooks.info

Using a Groovy JsonBuilder
Nesting links

Hypermedia

251

long getSize(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

return -1;

}

The writeTo method does all the work. Here I use groovy.json.JsonBuilder to generate the output in the form I want, as shown in the following listing.

Listing 9.9 Using a JsonBuilder to produce nested links

void writeTo(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,

WebApplicationException {

def builder = new JsonBuilder() builder {

id t.id

first t.first last t.last

links { if (t.prev) {

prev t.prev.toString()

}

self t.self.toString() if (t.next) {

next t.next.toString()

}

}

}

entityStream.write(builder.toString().bytes)

}

Conversion to bytes to write to the output stream

One special quirk is notable here. The method calls toString on the individual Link instances. As the JavaDocs for Link make clear, the toString and valueOf(String) methods in Link are used to convert to and from strings.

The MessageBodyReader interface is quite similar. In that case there are only two methods: isReadable and readFrom. The implementation of isReadable is the same as the isWriteable method:

public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

type == Person && mediaType == MediaType.APPLICATION_JSON_TYPE

}

The readFrom method uses a JsonSlurper to convert string input into a Person, as shown in the next listing.

Listing 9.10 Parsing a Person instance from a string

public Person readFrom(Class<Person> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders,

www.it-ebooks.info

252 CHAPTER 9 RESTful web services

InputStream entityStream)

throws IOException, WebApplicationException {

def json = new JsonSlurper().parseText(entityStream.text)

Person p = new Person(id:json.id, first:json.first, last:json.last) if (json.links) {

p.prev = Link.valueOf(json.links.prev) p.self = Link.valueOf(json.links.self) p.next = Link.valueOf(json.links.next)

}

return p

}

The readFrom method uses the JsonSlurper’s parseText method to convert the input text data into a JSON object and then instantiates a Person based on the resulting properties. If links exist in the body, they’re converted using the valueOf method.

To use the MessageBodyWriter, I need to add an @Provider annotation to the implementation class and make sure it’s loaded in the application. The latter is done by adding the provider to the MyApplication class:

public class MyApplication extends ResourceConfig { public MyApplication() {

super(PersonResource.class, PersonProvider.class, JacksonFeature.class);

}

}

In this case both the PersonProvider and the JacksonFeature are used. The Person provider converts individual Person instances to JSON, and the JacksonFeature handles collections. A test of the resulting structure looks like this:

def 'transitional links for kirk are correct'() { when:

def response = client.get(path: 'people/3')

then:

'James Kirk' == "$response.data.first $response.data.last" Link.valueOf(response.data.links.prev).uri ==

'http://localhost:1234/people/2'.toURI() Link.valueOf(response.data.links.self).uri == 'http://localhost:1234/people/3'.toURI() Link.valueOf(response.data.links.next).uri == 'http://localhost:1234/people/4'.toURI()

}

The response body now has a links element, which contains prev, self, and next as child elements.

Lessons learned (hypermedia)

1JAX-RS mostly ignores hypermedia but does make some methods available for it.

2Transitional link headers are added by the link and links methods in

ResponseBuilder.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]