A Rich Web Service API for Your Favorite Framework, Part 1: Spring
Join the DZone community and get the full member experience.
Join For FreeThe intent of this how-to series is to demonstrate the development of a rich Web service API on a variety of popular development frameworks. Part 1 (this tutorial) targets Spring Framework.
We're going to be using Enunciate to supply a rich Web service API to the familiar petclinic sample application included in the Spring distribution bundle. By the end of the tutorial the petclinic application will include the following:
- REST endpoints. The petclinic REST endpoints will supply resources in both XML and JSON data formats.
- SOAP endpoints. The petclinic API will be exposed via SOAP and defined by a well-defined and consolidated WSDL.
- Full API Documentation. The petclinic Web service API will be fully-documented.
- Client-side code. The application will provide client-side Java code that will be able to access the Web service API remotely.
Enunciate makes it drop-dead easy to develop this kind of Web service API: a build file, a configuration file, a few minor tweaks to the spring beans definition, and some metadata on the service interface.
Step 1: Set up the Environment
We're going to be using Maven to build our application. Note that Maven was chosen for the sake of simplicity, but you can just as well use Ant or some other build tool.
Here is a POM file that was made to support the latest petclinic application that ships with the spring distribution bundle. At the time of this writing, the latest was spring 2.5.5. Click here to download it, and make sure you download the spring-framework-2.5.5-with-docs.zip file in order to include the petclinic application.
When you unpack the bundle, you'll find the petclinic application under the samples/petclinic directory. We'll refer to this directory as $PETCLINIC_HOME. Just drop the POM file in $PETCLINIC_HOME. Since the petclinic application requires a database instance, we first start up an instance of HSQL DB. So open up a console at $PETCLINIC_HOME and:
mvn exec:java
Next, you'll probably want to populate the database with some data so you can see it working. Since HSQL is running in the last console, open up another console at $PETCLINIC_HOME and:
mvn sql:execute
Okay, you should now be ready to start up the petclinic application now:
mvn jetty:run-exploded
Assuming all went well, you should be able to open up a browser to http://localhost:8080/petclinic to see the petclinic application in all it's glory.
Now let's add a Web service API.
Step 2: Enunciate Configuration
Here is an Enunciate configuration file that Enunciate uses to expose the Web service API for the petclinic sample. Drop that in $PETCLINIC_HOME (next to the POM file). Let's briefly go over the basic parts of this file.
The <api-classes> element simply tells Enunciate what classes are to be used to define the Web service API. By default, Enunciate assumes all classes in the project are a part of the Web service API, but the petclinic application has a lot of classes that are used to drive the web application and were never intended to be part of the Web service API. So we have to tell Enunciate that only the classes in the org.springframework.samples.petclinic package and the org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic is used to define our Web service API:
enunciate.xml:
...
<api-classes>
<include pattern="org.springframework.samples.petclinic.*"/>
<include pattern="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic"/>
</api-classes>
...
The balance of the configuration file is used to define behavior in specific Enunciate modules. Enunciate generates the documentation of our Web service API from the JavaDocs of our API classes. By configuring the docs module, we tell Enunciate to put the documentation in the /api directory of the application and assign the documentation a title.
Enunciate assembles the Web service API in the form of a Spring application, and it needs to be merged with the petclinic application. We do this by merging the Enunciate-generated web.xml file with the petclinic web.xml file and by "importing" the bean definitions of the petclinic application with the bean definitions of the Enunciate application.
enunciate.xml:
...
<modules>
<docs docsDir="api" title="Petclinic API"/>
<spring-app>
<war mergeWebXML="war/WEB-INF/web.xml"/>
<springImport file="war/WEB-INF/applicationContext-jdbc.xml"/>
</spring-app>
</modules>
...
Step 3: Annotate the API Classes
The org.springframework.samples.petclinic.Clinic can be used as-is to publish a nice Web service API, so we don't need to add any other classes to the application. All we have to do is to apply some annotations to the existing classes.
SOAP Metadata
The service interface of our Web service API is defined by the org.springframework.samples.petclinic.Clinic interface and its associated implementation, org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic. To expose a SOAP interface, we just apply some JAX-WS metadata to these classes. Specifically, we apply the @javax.jws.WebService annotation to the Clinic interface and the same annotation to the SimpleJdbcClinic class. The annotation on SimpleJdbcClinic, according to the JAX-WS spec, needs to specify the interface that it implements with the endpointInterface() annotation value.
The Clinic interface, as it is defined, explicitly declares org.springframework.dao.DataAccessException to be thrown by each of its methods. Even though this is an unchecked exception, declaring it as explicitly thrown causes a problem for JAX-WS because it will attempt to expose it as a SOAP fault. Since DataAccessException doesn't conform to the JAX-WS constraints for SOAP faults, Enunciate will fail at compile-time with an error message unless we comment-out the explicit declaration of the DataAccessException. Since this is a runtime exception, this will not present a problem to any consumers of the Clinic interface.
Clinic.java:
@WebService
public interface Clinic {
/**
* Retrieve all <code>Vet</code>s from the data store.
* @return a <code>Collection</code> of <code>Vet</code>s
*/
Collection<Vet> getVets() /* throws DataAccessException */;
/**
* Retrieve all <code>PetType</code>s from the data store.
* @return a <code>Collection</code> of <code>PetType</code>s
*/
Collection<PetType> getPetTypes() /* throws DataAccessException */;
...
}
SimpleJdbcClinic.java:
@WebService (
endpointInterface = "org.springframework.samples.petclinic.Clinic"
)
public class SimpleJdbcClinic implements Clinic, SimpleJdbcClinicMBean {
...
}
REST Metadata
Of course we also want to apply a REST interface to our API. This can be done by applying JAX-RS annotations to our service classes, but it's a bit more complicated because of the added constraints imposed by REST.
You first have to map the service to a URI path by applying the @javax.ws.rs.Path annotation to the implementation class, SimpleJdbcClinic. We'll mount the clinic at the "/clinic" path.
Next, since you're limited to a constrained set of operations, you have to annotate the each method that is to be included in the REST API. The method must specify (1) the HTTP operation that is used to invoke the method and (2) the subpath that is used to locate it. We'll keep it simple by exposing only the loadOwner method and the loadPet methods via the HTTP GET operation using the javax.ws.rs.GET annotation and mounting the methods at the "/owner/{id}" and "/pet/{id}" relative paths, respectively, using the javax.ws.rs.Path annotation. The "{id}" on the path will specify the id of the owner/pet that we want to get. This means that the method parameter must be annotated with the @javax.ws.rs.PathParam annotation that specifies the name of the path parameter to which it is to be mapped.
Of course, you can expose other methods via HTTP POST using other annotations, but we'll refer you to the JAX-RS documentation to learn how to do that. Note also that the method-level JAX-RS annotations can be applied to either the interface or the implementation class, but in this case, we'll apply them to the interface.
Here's what our classes look like fully-annotated. You can also download them here and here.
Clinic.java:
@WebService
public interface Clinic {
...
@GET
@Path ("owner/{id}")
Owner loadOwner(@PathParam ("id") int id);
...
@GET
@Path("pet/{id}")
Pet loadPet(@PathParam("id") int id);
...
}
SimpleJdbcClinic.java:
@Path ( "/clinic" )
@WebService (
endpointInterface = "org.springframework.samples.petclinic.Clinic"
)
public class SimpleJdbcClinic implements Clinic, SimpleJdbcClinicMBean {
...
}
One more thing is required in order to expose a REST API. Since by default the REST endpoints will expose XML data, we have to provide root XML elements for the XML responses. To do this, we simply annotate the org.springframework.samples.petclinic.Pet and org.springframework.samples.petclinic.Owner classes with @javax.xml.bind.annotation.XmlRootElement.
Pet.java:
@XmlRootElement
public class Pet extends NamedEntity {
...
}
Owner.java:
@XmlRootElement
public class Owner extends Person {
...
}
Step 4: Tweak the Spring App XML
Since Enunciate generates documentation for the API, it would be nice to add index.html to the welcome-file-list of the web.xml file for the application (found in the war/WEB-INF directory):
web.xml:
...
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
...
The only thing left is a couple of minor changes to the Spring components definition file. In our case, the components definition is the applicationContext-jdbc.xml file in the war/WEB-INF directory.
In that file, you'll see the declaration of the annotation-driven transaction manager with the <tx:annotation-driven/> XML element. Since the JAX-RS implementation must be tied to an implementation bean, we have to specify that the transaction manager instrument the implementation class (as opposed to instrumenting just its interfaces). We do this by adding proxy-target-class="true" to this XML element.
Finally, since Enunciate declares its own service interface components, we have to disable autowiring of the "clinic" bean. We do this by applying autowire-candidate="false" to the bean definition:
applicationContext-jdbc.xml:
...
<tx:annotation-driven proxy-target-class="true" />
...
<bean id="clinic" class="org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic"
autowire-candidate="false"/>
...
Step 5: Build and Deploy
Enunciate and Maven will do all the dirty work of building and deploying your application. All you have to do is add the Enunciate Maven Plugin to the POM file (it should be already there, you just have to uncomment it):
pom.xml:
...
<plugin>
<groupId>org.codehaus.enunciate</groupId>
<artifactId>maven-enunciate-plugin</artifactId>
<version>1.8.1</version>
<executions>
<execution>
<goals>
<goal>assemble</goal>
</goals>
</execution>
</executions>
</plugin>
...
Back on the command-line at $PETCLINIC_HOME, make sure your database is running:
mvn exec:java
Then make sure your database is populated:
mvn sql:execute
Then just run the application:
mvn jetty:run-exploded
Behold the Glory
Your application is fully-functional at http://localhost:8080/petclinic/.
Check out the documentation for your new Web service API at http://localhost:8080/petclinic/api/:
Everything is documented, scraped from the JavaDoc comments. Here's the documentation for the SOAP API:
And documentation for the REST API:
And you can download client-side libraries that Enunciate generated and can be used to invoke your Web service API:
What about your WSDL? http://localhost:8080/petclinic/api/ns1.wsdl
What about your XML-Schema? http://localhost:8080/petclinic/api/ns0.xsd
Want to see your API in action? Your SOAP endpoints are mounted at the /soap subcontext, and your REST endpoints are mounted at the /rest subcontext. To view an owner, just use the path we defined with JAX-RS annotations relative to the /rest subcontext. So to view the owner identified by id "1", we use http://localhost:8080/petclinic/rest/clinic/owner/1:
As a convenience, the same XML resource can also be found at http://localhost:8080/petclinic/xml/clinic/owner/1. And if you want to get that same resource as JSON, you can use http://localhost:8080/petclinic/json/clinic/owner/1.
And Beyond...
Well, that's how easy it is to add a Web service API to you Spring application. But we've only barely scratched the surface of what Enunciate can do. What about any of this:
- Security (HTTP Auth, OAuth, form-based login, session management, etc.)
- GWT RPC endpoints and client-side JavaScript for accessing them.
- AMF endpoints and client-side ActionScript for accessing them.
- Streaming API for large requests.
- Etc.
At this point, it's only a matter of configuration....
Opinions expressed by DZone contributors are their own.
Trending
-
How To Use an Automatic Sequence Diagram Generator
-
21 Hidden Careers in the AI Revolution: Driving Change in the Tech Industry
-
Test Data Tutorial: A Comprehensive Guide With Examples and Best Practices
-
Payments Architecture - An Introduction
Comments