@GET
@Consumes("application/json")
@Produces("application/json")
@ManagedAsync
public void addBook(Book book, @Suspended final AsyncResponse response) {
ListenableFuture<Book> bookFuture = dao.addBookAsync(book);
Futures.addCallback(bookFuture, new FutureCallback<Book>() {
public void onSuccess(Book addedBook) {
response.resume(addedBook);
}
public void onFailure(Throwable thrown) {
response.resume(thrown);
}
});
}
Last active
September 27, 2017 07:59
-
-
Save vicly/3a9e7cc4a1ce6c7a2d9237568a6e5880 to your computer and use it in GitHub Desktop.
[Jersey note] #REST #Jersey
@Context Request request;
@GET
@Path("{id}")
@Produces("application/json")
public Response getBook(@PathParam("id") String id) {
Book book = dao.getBookById(id);
if (book != null) {
EntityTag etag = EntityTag(String.valueOf(book.getLastModifiedMillis()));
Response.ResponseBuilder rb = request.evaluatePreconditions(entityTag);
if (rb != null) {
// not changed
return rb.build();
} else {
// changed
return Response.ok().tag(entityTag).entity(book).build();
}
} else {
// 404
...
}
}
Request
EntityTag etag = new EntityTag("the-last-entity-etag");
Response response = target("books").path(book_id).request().header("If-Match", entityTag).get();
Pre-Matching Request Filter
[resource method matching]
Post-Matching Request Filter
Reader Interceptor
[resource method executed]
Response Filter
Writer Interceptor
Filters
- modify headers, the entity and other request/response parameters
- executed regarless of whether there is an entity to read or write
- can abort a request, preventing the resource method from executing
Interceptors
- primarily used to manipulate the entity (via input/output stream)
- change properties to affect choice of Message Body Reader/Writer
- ONLY executed when there is an entity to read or write
Jersey built-in filters
- logging
- jaxrs to spring bridge
- cross-site request forgery protection
- HTTP method overrides
- URI-based content negotiation
// enable it
register(HttpMethodOverrideFilter.class);
// test code: _method=PATCH + POST
Response response = target("books").path(book_id).queryParam("_method", "PATCH").request().post();
Register filter
Map<String, MediaType> map = ...;
map.put("xml", MediaType.APPLICATION_XML);
map.put("json", MediaType.APPLICATION_JSON);
UriConnegFilter ucnFilter = new UriConnegFilter(map, null);
register(ucnFilter);
Make the call
Response response = target("books.json").request().get();
Request Filter
@Provider // if you use auto-scan
public class MyRequestFilter implements ContainerRequestFilter {
// @PreMatching if you want a pre-matching filter
public void filter(ContainerRequestContext reqCtx) throws IOException {
...
}
}
Response filter
@Provider // if you use auto-scan
public class PoweredByFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext reqCtx,
ContainerResponseContext resCtx)
throws IOException {
resCtx.getHeaders().add("X-Powered-By", "Vic");
}
}
Define how your filter/interceptor applies.
- define annotation
@javax.ws.rs.NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PoweredBy {
String value() default "";
}
- annotate filter class
@Provider
@PoweredBy
public class PoweredByFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext)
throws IOException {
for (Annotation a : responseContext.getEntityAnnotations()) {
if (a.annotationType() == PoweredBy.class) {
String value = ((PoweredBy) a).value();
responseContext.getHeaders().add("X-Powered-By", value);
}
}
}
}
- annotate resource method to apply the fiter
@GET
@PoweredBy("Vic")
public List<Book> getBooks() {...
PATH
: partical update
Create Annotation
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {
}
Resource
@PATCH
@PATH("{id}")
public void updateBookNameTitle(@PathParam("id") String id, Book update) {
// transaction start
Book book = dao.getById(id);
if (book == null) // 404
if (update.getTitle() != null) book.setTitle(update.getTitle());
if (update.getName() != null) book.setTitle(update.getName());
return book;
// transaction auto commit
}
If JerseyTest shows not support PATCH
// use grizzly
org.glassfish.jersey.test-framework.providers:jersey-test-framework-providers-grizzly2
org.glassfish.jersey.connectors:jersey-grizzly-connector
clientConfig.connectorProvider(new GrizzlyConnectorProvider());
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
</dependency>
// enable this to return error message in body
property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
// special case
BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK
public class Book {
@NotNull(message = "title is required")
private String title;
...
}
@Valid // validate output
public Book addBook(@Valid @NotNull Book book) { // validate input
...
return addedBook;
}
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>2.3.0</version>
</dependency>
JacksonXMLProvider xml = new JacksonXMLProvider()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.INDENT_OUTPUT, true);
register(xml);
@Produces({MediaType.APPLICATION_XML}}
public Response top10Cars() {
...
}
Given Collection<Book> top10Books();
, we want below output rather than change method to BooksWrapper top10Books();
<books>
<book>
<id>abc</id>
<name>xyz</name>
</book>
</books>
Add custom MessageBodyWriter
@Provider
@Produces({MediaType.APPLICATION_XML})
public class BooksMessageBodyWriter implements MessageBodyWriter<Collection<Book>> {
@JacksonXmlRootElement(localName = "books")
public class BooksWrapper {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "book")
public Collection<Book> books;
BooksWrapper(Collection<Book>) {
this.books = books;
}
}
@Context Providers providers;
public long getSize(Collection<Book> books, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
public isWritable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return Collection.class.isAssignableFrom(type);
}
public void writeTo(Collection<Book> books, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivalueMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
providers.getMessageBodyWriter(BooksWrapper.class, generictype, annotation, mediaType)
.writeTo(new BooksWrapper(books), type, genericType, annotations, mediaType, httpHeaders, entityStream);
}
}
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-xml</artifactId>
<version>0.7.8</version>
<scope>test</scope>
</dependency>
@Test
public void top10Books() {
String output = target("books/top10").request("application/xml").get().readEntity(String.class);
XML xml = new XMLDocument(output);
assertEquals("author1", xml.xpath("books/book[@id=' + book1_id + ']).get(0));
assertEquals(10, xml.xpath("//book/author/text()").size());
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment