Dual-purpose (end-to-end) interfaces

A common coding practice is to use the same Java interface to define both your server and client side REST interfaces. The advantage to this approach is that changes that you make to your REST interface can be reflected in both places at the same time, reducing the chances for compatibility mistakes.

What makes this possible is that method-level annotations such as @RestMethod and parameter-level annotations such as @Query are inherited from parent classes. This normally isn't possible, but the framework will spider up the parent hierarchy of classes to find method and parameter level annotations defined on overridden methods.

The general approach is to define your {@link oaj.http.remote.Remote @Remote}-annotated interface first. The following example is pulled from the PetStore app:

@Remote(path="/petstore") public interface PetStore { @RemoteMethod(method=GET, path="/pet") public Collection<Pet> getPets() throws NotAcceptable; @RemoteMethod(method=DELETE, path="/pet/{petId}") public Ok deletePet( @Header( name="api_key", description="Security API key", required=true, example="foobar" ) String apiKey, @Path( name="petId", description="Pet id to delete", example="123" ) long petId ) throws IdNotFound, NotAcceptable; ...

Next you define the implementation of your interface as a normal Juneau REST resource:

@Rest( path="/petstore", title="Petstore application", ... ) public class PetStoreResource extends BasicRestJena implements PetStore { ... @Override /* PetStore */ @RestMethod( name=GET, path="/pet", summary="All pets in the store", ... ) public Collection<Pet> getPets() throws NotAcceptable { return store.getPets(); } @Override /* PetStore */ @RestMethod( name=DELETE, path="/pet/{petId}", summary="Deletes a pet", ... ) public Ok deletePet(String apiKey, long petId) throws IdNotFound, NotAcceptable { store.removePet(petId); return OK; }

Then use the interface as a remote resource like so:

try (RestClient rc = RestClient.create().json().rootUrl("http://localhost:10000").build()) { PetStore ps = rc.getRemote(PetStore.class); for (Pet x : ps.getPets()) { ps.deletePet("my-special-key", x.getId()); System.err.println("Deleted pet: id=" + x.getId()); } }

In the example above, we chose to add the @RestMethod annotation to the implementation class. However, they could have been added to the interface instead.

Note how we didn't need to use the @Header and @Path annotations in our implementation since the annotations were inherited from the interface.