590 likes | 827 Views
JAX-RS 2.0: New and Noteworthy in the RESTful Web Services API. John Clingan Java EE and GlassFish Product Manager john.clingan@oracle.com. JAX-RS Review Client API Common Configuration Asynchronous Processing Filters/Interceptors Hypermedia Support Server-side Content Negotiation.
E N D
JAX-RS 2.0: New and Noteworthy in the RESTful Web Services API John Clingan Java EE and GlassFish Product Manager john.clingan@oracle.com
JAX-RS Review • Client API • Common Configuration • Asynchronous Processing • Filters/Interceptors • Hypermedia Support • Server-side Content Negotiation What’s New in JAX-RS 2.0
JAX-RS – Java API for RESTful Services Standard annotation-driven API that aims to help developers build RESTful Web services and clients in Java • POJO-Based Resource Classes • HTTP Centric Programming Model • Entity Format Independence • Container Independence • Included in Java EE
JAX-RS Example ... @POST @Path("/withdrawal") @Consumes("text/plain") @Produces("application/json") public Money withdraw( @PathParam("card") String card, @QueryParam("pin") String pin, String amount) { return getMoney(card, pin, amount); } }
Client API Motivation • HTTP client libraries too low level • Leveraging providers/concepts from JAX-RS 1.x API • Proprietary APIs introduced by major implementations
Client API // Get instance of Client Client client = ClientBuilder.newClient(); // Get account balance String bal = client.target("http://.../atm/{cardId}/balance") .resolveTemplate("cardId", "111122223333") .queryParam("pin", "9876") .request("text/plain").get(String.class);
Client API // Withdraw some money Moneymoney =client.target("http://.../atm/{cardId}/withdrawal") .resolveTemplate("cardId", "111122223333") .queryParam("pin", "9876") .request("application/json") .post(text("50.0"), Money.class);
Client API Invocationinvocation1 = client.target("http://.../atm/{cardId}/balance")… .request(“text/plain”).buildGet(); Invocationinvocation2 = client.target("http://.../atm/{cardId}/withdraw")… .request("application/json") .buildPost(text("50.0"));
Client API Collection<Invocation> invocations =Arrays.asList(inv1, inv2); Collection<Response> responses = Collections.transform( invocations, new F<Invocation, Response>() { public Responseapply(Invocationinvocation) { return invocation.invoke(); } });
Client API // Create client and register MyProvider1 Client client = ClientBuilder.newClient(); client.register(MyProvider1.class); // Create atm target; inherits MyProvider1 WebTargetatm = client.target("http://.../atm"); // Register MyProvider2 atm.register(MyProvider2.class); // Create balance target; inherits MyProvider1, MyProvider2 WebTarget balance = atm.path(”{cardId}/balance"); // Register MyProvider3 balance.register(MyProvider3.class);
Common Configuration - Motivation Client-side client .register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(“jsonp.callback.name”, “callback”) .property(“jsonp.callback.queryParam”, “true”) ...
Common Configuration - Motivation Server-side public class MyApp extends javax.ws.rs.core.Application { public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<…>(); ... classes.add(JsonMessageBodyReader.class); classes.add(JsonMessageBodyWriter.class); classes.add(JsonpInterceptor.class); ... return classes; } }
Common Configuration - Solution Client-side client .register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(“jsonp.callback.name”, “callback”) .property(“jsonp.callback.queryParam”, “true”) ... JsonFeaturejf= new JsonFeature().enableCallbackQueryParam(); client.register(jf);
Common Configuration - Solution Server-side public Set<Class<?>> getClasses() { ... classes.add(JsonMessageBodyReader.class); classes.add(JsonMessageBodyWriter.class); classes.add(JsonpInterceptor.class); ... } public Set<Class<?>> getClasses() { ... classes.add(JsonFeature.class); ... }
Common Configuration public interface Configurable { Configuration getConfiguration(); Configurable property(String name, Object value); Configurable register(...); } public interface Configuration { Set<Class> getClasses(); Map<Class,Integer> getContracts(Class componentClass); Set<Object> getInstances(); Map<String,Object> getProperties(); Object getProperty(String name); Collection<String> getPropertyNames(); boolean isEnabled(Feature feature); boolean isRegistered(Object component); ... }
Common Configuration public interface Feature { boolean configure(FeatureContext context); }
A Feature Example public void JsonFeature implements Feature { public boolean configure(FeatureContext context) { context.register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(CALLBACK_NAME, calbackName) .property(USE_QUERY_PARAM, useQueryParam); return true; } }
Dynamic Feature Server-side only public interface DynamicFeature { void configure(ResourceInfo ri, FeatureContext context); } public interface ResourceInfo { Method getResourceMethod(); Class<?> getResourceClass(); }
Asynchronous Processing • Server API • Off-load I/O container threads • Efficient asynchronous event processing • Leverage Servlet 3.x async support (if available) • Client API • Asynchronous request invocation API
Asynchronous Processing @Stateless @Path("/async/longRunning") public class MyResource { @GET @Asynchronous public void longRunningOp(@SuspendedAsyncResponse ar) { ar.setTimeoutHandler(new MyTimoutHandler()); ar.setTimeout(15, SECONDS); final String result = executeLongRunningOperation(); ar.resume(result); } }
Asynchronous Processing: Server Side public interface AsyncResponse { public void resume(Object/Throwable response); public void cancel(); public void cancel(int/Date retryAfter); public boolean isSuspended(); public boolean isCancelled(); public boolean isDone(); public void setTimeout(long time, TimeUnit unit); public void setTimeoutHandler(TimeoutHandler handler); public Collection<Class<?>> register(Class<?> callback); public Map<Class<?>,Collection<Class<?>>> register(Class<?> callback, Class<?>... callbacks); public Collection<Class<?>> register(Object callback); public Map<Class<?>,Collection<Class<?>>> register(Object callback, Object... callbacks); }
Asynchronous Processing: Server Side @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Suspended { } public interface TimeoutHandler { void handleTimeout(AsyncResponse asyncResponse); }
Asynchronous Processing: Server Side public interface CompletionCallback { public void onComplete(Throwable throwable); } public interface ConnectionCallback { public void onDisconnect(AsyncResponse disconnected); }
Asynchronous Processing: Client Side WebTarget target = client.target("http://.../balance”)… // Start async call and register callback Future<?> handle = target.request().async().get( new InvocationCallback<String>() { void complete(String balance) { … } void failed(InvocationException e) { … } }); // After waiting for too long… if (!handle.isDone()) handle.cancel(true);
Filters & Interceptors Motivation • Customize JAX-RS request/response processing • Use Cases: Logging, Compression, Security, etc. • Introduced for client and server APIs • Replace existing proprietary support
Filters & Interceptors Filter each incoming/outgoing message • Request Request • ContainerRequestFilter, ClientRequestFilter • Response Response • ContainerResponseFilter, ClientResponseFilter • Server-side specialties • @PreMatching, DynamicFeature • Non-wrapping filter chain • Filters do not invoke next filter in the chain directly • Managed by the JAX-RS runtime • Each filter decides to proceed or break the chain
Filters & Interceptors A Logging Filter Exampe public class RequestLoggingFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) { log(requestContext); // non-wrapping => returns without invoking the next filter } ... }
Filters & Interceptors Intercept entity providers • Invoked ONLY when/if entity processing occurs • Performance boost • Wrapping interceptors chain • Each interceptor invokes the next one in the chain via context.proceed() • MessageBodyReader interceptor • ReaderInterceptorinterface • MessageBodyWriter interceptor • WriterInterceptorinterface
Filters & Interceptors A Gzip Reader Interceptor Example public class GzipInterceptor implements ReaderInterceptor { @Override Object aroundReadFrom(ReaderInterceptorContext ctx) { InputStream old = ctx.getInputStream(); ctx.setInputStream(new GZIPInputStream(old)); // wrapping => invokes the next interceptor Object entity = ctx.proceed(); ctx.setInputStream(old); return entity; } }
Filters & Interceptors write(…) … Transport Writer Interceptor Writer Interceptor MBW Response Network • Application … Request Filter Filter … Filter Filter read(…) - optional … MBR Reader Interceptor Reader Interceptor
Filters & Interceptors read(…) - optional … Reader Interceptor Reader Interceptor MBR @PreMatching Network Resource Matching … Application Request Filter Filter Filter Request Filter … Filter Filter Response Response Filter Filter write(…) … MBW Writer Interceptor Writer Interceptor
Bindings & Priorities • Binding • Associating filters and interceptors with resource methods • Server-side concept • Priority • Declaring relative position in the execution chain • @Priority(int priority) • Shared concept by filters and interceptors
Bindings @NameBinding @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Logged {} @Provider @Logged @Priority(USER) public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter { … }
Bindings @Path("/greet/{name}") @Produces("text/plain") public class MyResourceClass { @Logged @GET public String hello(@PathParam("name") String name) { return "Hello " + name; } }
A DynamicFeatureExample Server-side only public void SecurityFeature implements DynamicFeature { public boolean configure(ResourceInfo ri, FeatureContext context) { String[] roles = getRolesAllowed(ri); if (roles != null) { context.register(new RolesAllowedFilter(roles)); } } ... }
Hypermedia Support • REST Principles • Identifiers and Links • HATEOAS (Hypermedia As The Engine Of App State) • Link types: • Structural Links • Transitional Links
Hypermedia Support Transitional Links Link: <http://.../orders/1/ship>; rel=ship, <http://.../orders/1/cancel>; rel=cancel ... <order id="1"> <customer>http://.../customers/11</customer> <address>http://.../customers/11/address/1</address> <items> <item> <product>http://.../products/111</product> <quantity>2</quantity> </item> <items> ... </order> Structural Links
Hypermedia • Link and LinkBuilder classes • RFC 5988: Web Linking • Support for Link in ResponseBuilder and filters • Transitional links (headers) • Support for manual structural links • Via Link.JaxbAdapter & Link.JaxbLink • Create a resource target from a Link in Client API
Hypermedia // Producer API (server-side) Link self= Link.fromMethod(MyResource.class, ”handleGet”) .build(); Link update= Link.fromMethod(MyResource.class, “handlePost”) .rel(”update”) .build(); ... Response res = Response.ok(order) .link("http://.../orders/1/ship", "ship") .links(self, update) .build();
Hypermedia Response order = client.target(…).request("application/xml").get(); // Consumer API (client-side) Link shipmentLink = order.getLink(“ship”); if (shipmentLink != null) { Response shipment = client.target(shipmentLink).post(null); … }
Server-side Content Negotiation GET http://.../widgets2 Accept: text/*; q=1 … Path("widgets2") public class WidgetsResource2 { @GET @Produces("text/plain", "text/html") public Widgets getWidget() {...} }