code
docs
tests
Rickard sent a very interesting HATEOAS Primer to the mailing list in October 2011, as the starting point for the renovation of the ReST Client Library. You should read that to get the full background on the choices made in this library.
Table 41. Artifact
Group ID | Artifact ID | Version |
---|---|---|
org.apache.zest.library | org.apache.zest.library.rest-client | 0 |
This library leverages the Restlet library, so keep its documentation nearby as well.
This library expects the client code to build up handlers on how to react to resources and errors. It is a more declarative approach than a typical ReST client application, which often isn’t HATEOAS at all, and Roy Fielding is upset that ReST now means something else than it was originally intended. We try to be true to Dr. Fielding’s intentions.
The first thing that must be done is to create a ContextResourceClient. Let’s walk through the different steps typically needed.
Client client = new Client( Protocol.HTTP ); ContextResourceClientFactory contextResourceClientFactory = objectFactory.newObject( ContextResourceClientFactory.class, client ); contextResourceClientFactory.setAcceptedMediaTypes( MediaType.APPLICATION_JSON );
Above we create the Client instance and a ContextResourceClientFactory, which takes a client via @Uses annotation. We also set the accepted media type to JSON.
We then create the global handler, which will be set to all ContextResourceClient instances that this factory creates.
contextResourceClientFactory.setErrorHandler( new ErrorHandler().onError( ErrorHandler.AUTHENTICATION_REQUIRED, new ResponseHandler() { boolean tried = false; @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { if (tried) throw new ResourceException( response.getStatus() ); tried = true; client.getContextResourceClientFactory().getInfo().setUser( new User("rickard", "secret") ); // Try again return refresh(); } } ).onError( ErrorHandler.RECOVERABLE_ERROR, new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { // Try to restart return refresh(); } } ) );
Above, we try to handle that autheorization is required by setting user credentials and then try again. The client could do a pop-up box instead, have its own cached entries, contact a credentials server or many other things.
We also added another handler that does a refresh() on any recoverable error.
Note that the ErrorHandler.AUTHENTICATION_REQUIRED and ErrorHandler.RECOVERABLE_ERROR are not enums or constants, but Specifications and it is possible to implement your own.
We then simply proceed to create the ContextResourceClient, by giving the factory the bookmarkable reference of the ReST API.
Reference ref = new Reference( "http://localhost:8888/" ); crc = contextResourceClientFactory.newClient( ref );
Once we have the ContextResourceClient, we can proceed with using it. The general approach is to register handlers for potential results when invoking the method on the ReST resource.
crc.onResource( new ResultHandler<Resource>() { @Override public HandlerCommand handleResult( Resource result, ContextResourceClient client ) { return query( "querywithoutvalue" ); } } ). onQuery( "querywithoutvalue", new ResultHandler<TestResult>() { @Override public HandlerCommand handleResult( TestResult result, ContextResourceClient client ) { Assert.assertThat( result.xyz().get(), CoreMatchers.equalTo( "bar" ) ); return null; } } ); crc.start();
crc.onResource( new ResultHandler<Resource>() { @Override public HandlerCommand handleResult( Resource result, ContextResourceClient client ) { return query( "querywithvalue", null ); } } ).onProcessingError( "querywithvalue", new ResultHandler<TestQuery>() { @Override public HandlerCommand handleResult( TestQuery result, ContextResourceClient client ) { ValueBuilder<TestQuery> builder = valueBuilderFactory.newValueBuilderWithPrototype( result ); builder.prototype().abc().set( "abc" + builder.prototype().abc().get() ); return query( "querywithvalue", builder.newInstance() ); } } ).onQuery( "querywithvalue", new ResultHandler<TestResult>() { @Override public HandlerCommand handleResult( TestResult result, ContextResourceClient client ) { return command( "commandwithvalue", null ); } } ).onProcessingError( "commandwithvalue", new ResultHandler<Form>() { @Override public HandlerCommand handleResult( Form result, ContextResourceClient client ) { result.set( "abc", "right" ); return command( "commandwithvalue", result ); } } ); crc.start();
crc.onResource( new ResultHandler<Resource>() { @Override public HandlerCommand handleResult( Resource result, ContextResourceClient client ) { return query( "commandwithvalue" ); } } ).onQuery( "commandwithvalue", new ResultHandler<Links>() { @Override public HandlerCommand handleResult( Links result, ContextResourceClient client ) { Link link = LinksUtil.withId( "right", result ); return command( link ); } } ).onCommand( "commandwithvalue", new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { System.out.println( "Done" ); return null; } } ); crc.start(); [...snip...] crc.onResource( new ResultHandler<Resource>() { @Override public HandlerCommand handleResult( Resource result, ContextResourceClient client ) { return query( "commandwithvalue" ).onSuccess( new ResultHandler<Links>() { @Override public HandlerCommand handleResult( Links result, ContextResourceClient client ) { Link link = LinksUtil.withId( "right", result ); return command( link ).onSuccess( new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { System.out.println( "Done" ); return null; } } ); } } ); } } ); crc.start();
crc.onResource( new ResultHandler<Resource>() { @Override public HandlerCommand handleResult( Resource result, ContextResourceClient client ) { return query( "commandwithvalue" ).onSuccess( new ResultHandler<Links>() { @Override public HandlerCommand handleResult( Links result, ContextResourceClient client ) { Link link = LinksUtil.withId( "right", result ); return command( link ).onSuccess( new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { System.out.println( "Done" ); return null; } } ); } } ); } } ); crc.start();