JAX-RS / RESTful services

Rice now allows allows RESTful (JAX-RS) services to be exported and consumed on the Kuali Service Bus (KSB). For some background on REST, see http://en.wikipedia.org/wiki/Representational_State_Transfer.

For details on JAX-RS, see JSR-311.

Caveats

  • The KSB does not currently support "busSecure" (digital signing of requests & responses) REST services. Attempting to set a REST service's "busSecure" property to "true" will result in a RiceRuntimeException being thrown. Rice can be customized to expose REST services in a secure way, e.g. using SSL and an authentication mechanism such as client certificates, but that is beyond the scope of this documentation.

  • If the JAX-RS annotations on your resource class don't cover all of its public methods, then accessing the non-annotated methods over the bus will result in an Exception being thrown.

A Simple Example

To expose a simple JAX-RS annotated service on the bus, you can follow this recipe for your spring configuration (which comes from the Rice unit tests):

<!-- The service implementation you want to expose -->

<bean id="baseballCardCollectionService" class="org.kuali.rice.ksb.testclient1.BaseballCardCollectionServiceImpl"/>


<!-- The service definition which tells the KSB to expose our RESTful service -->
<bean class="org.kuali.rice.ksb.messaging.RESTServiceDefinition">
    <property name="serviceNameSpaceURI" value="test" />


    <!-- as noted earlier, the servicePath property of RESTServiceDefinition can't be set here  -->


    <!-- The service to expose.  Refers to the bean above -->
    <property name="service" ref="baseballCardCollectionService" />


    <!-- The "Resource class", the class with the JAX-RS annotations on it.  Could be the same as the  -->
    <!-- service implementation, or could be different, e.g. an interface or superclass    -->
  
    <property name="resourceClass" 
value="org.kuali.rice.ksb.messaging.remotedservices.BaseballCardCollectionService" />


    <!-- the name of the service, which will be part of the RESTful URLs used to access it -->
    <property name="localServiceName" value="baseballCardCollectionService" />
</bean>

The following java interface uses JAX-RS annotations to specify its RESTful interface:

// … eliding package and imports

@Path("/")
public interface BaseballCardCollectionService {
    @GET
    public List<BaseballCard> getAll();


    /**
      * gets a card by it's (arbitrary) identifier
      */
    @GET
    @Path("/BaseballCard/id/{id}")
    public BaseballCard get(@PathParam("id") Integer id);
    /**
      * gets all the cards in the collection with the given player name
      */
    @GET
    @Path("/BaseballCard/playerName/{playerName}")
    public List<BaseballCard> get(@PathParam("playerName") String playerName);


    /**
      * Add a card to the collection.  This is a non-idempotent method
      * (because you can add more than one of the same card), so we'll use @POST
      * @return the (arbitrary) numerical identifier assigned to this card by the service
      */
    @POST
    @Path("/BaseballCard")
    public Integer add(BaseballCard card);


    /**
      * update the card for the given identifier.  This will replace the card that was previously
      * associated with that identifier.
      */
    @PUT
    @Path("/BaseballCard/id/{id}")
    @Consumes("application/xml")
    public void update(@PathParam("id") Integer id, BaseballCard card);


    /**
      * delete the card with the given identifier.
      */
    @DELETE
    @Path("/BaseballCard/id/{id}")
    public void delete(@PathParam("id") Integer id);

    /**
      * This method lacks JAX-RS annotations
      */
    public void unannotatedMethod();
}

Acquisition and use of this service over the KSB looks just like that of any other KSB service. In the synchronous case:

BaseballCardCollectionService baseballCardCollection = (BaseballCardCollectionService) GlobalResourceLoader.getService(new QName("test", "baseballCardCollectionService");
);


List<BaseballCard> allMyMickeyMantles = baseballCardCollection.get("Mickey Mantle");
// baseballCardCollection.<other service method>(...)
// etc

Composite Services

It is also possible to aggregate multiple Rice service implementations into a single RESTful service where requests to different sub-paths off of the base service URL can be handled by different underlying services. This may be desirable to expose a RESTful service that is more complex than could be cleanly factored into a single java service interface.

The configuration for a composite RESTfull service looks a little bit different, and as might be expected given the one-to-many mapping from RESTful service to java services, there are some caveats to using that service over the KSB. Here is a simple example of a composite service definition (which also comes from the Rice unit tests):

<bean class="org.kuali.rice.ksb.messaging.RESTServiceDefinition">
    <property name="serviceNameSpaceURI" value="test" />
    <property name="localServiceName" value="kms" />
    <property name="resources">
        <list>
            <ref bean="inboxResource"/>
            <ref bean="messageResource"/>
        </list>
    </property>
    <property name="servicePath" value="/" />
</bean>


<!-- the beans referenced above are just JAX-RS annotated Java services -->
<bean id="inboxResource" class="org.kuali.rice.ksb.testclient1.InboxResourceImpl">
    <!-- ... eliding bean properties ... -->
</bean>
<bean id="messageResource" class="org.kuali.rice.ksb.testclient1.MessageResourceImpl">
    <!-- ... eliding bean properties ... -->

</bean>

As you can see in the bean definition above, the service name is kms, so the base service url would by default (in a dev environment) be http://localhost:8080/kr-dev/remoting/kms/. Acquiring a composite service such as this one on the KSB will actually return you a org.kuali.rice.ksb.messaging.serviceconnectors.ResourceFacade, which allows you to get the individual java services in a couple of ways, as shown in the following simple example:

ResourceFacade kmsService =
 (ResourceFacade) GlobalResourceLoader.getService(
new QName(NAMESPACE, KMS_SERVICE));


// Get service by resource name (url path)
InboxResource inboxResource = kmsService.getResource("inbox");
// Get service by resource class
MessageResource messageResource = kmsService.getResource(MessageResource.class);

Additional Service Definition Properties

There are some properties on the RESTServiceDefinition object that let you do more advanced configuration:

Providers

JAX-RS Providers allow you to define:

  • ExceptionMappers which will handle specific Exception types with specific Responses.

  • MessageBodyReaders and MessageBodyWriters that will convert custom Java types to and from streams.

  • ContextResolver providers allow you to create special JAXBContexts for specific types, which will gives you fine control over marshalling, unmarshalling, and validation.

The JAX-RS specification calls for classes annotated with @Provider to be automatically used in the underlying implementation, but the CXF project which Rice uses under the hood does not (at the time of this writing) support this configuration mechanism, so this configuration property is currently necessary.

Extension Mappings

Ordinarily you need to set your ACCEPT header to ask for a specific representation of a resource. ExtensionMappings let you map certain file extensions to specific media types for your RESTful service, so your URLs can then optionally specify a media type directly. For example you could map the .xml extension to the media type text/xml, and then tag .xml on to the end of your resource URL to specify that representation.

Language Mappings

language mappings allow you a way to control the the Content-Language header, which lets you specify which languages your service can accept and provide.

Additional Information

For more information on what these properties provide, it may be helpful to consult the JAX-RS specification, or the CXF documentation.