Hello All
New year new me thing :). I decided to start sharing some of the more interesting things I am doing so that they remain somewhere, plus that I could use them myself afterwards if the need arises.
So, here it goes.
I was looking into a way to document the REST services these days. While there are some worthy tools such as enunciate, swagger and others, I found a more simple and effective way to do it.
We're using Jersey ATM, and this post refers to configuring Jersey in this regard.
In short the trick is to extend the generated WADL to contain the documentation (javadoc) taken from your service java classes.
Here's how it goes in a few short steps:
1. Document your services and request/responses (javadoc
style)
EX:
/**
* Use
this method to subscribe fx market rates from the price
feed.
* @param tradingBranchId branch
ID
* @param userId branch ID 2
*
@param customerId 33
*
@response.representation.200.q
name {
http://www.example.com}item
*
@response.representation.200.m
ediaType application/xml
* @response.representation.200.d
oc This is the representation returned by
default (if we have an even number of millis since
1970...:)
* @response.representation.503.m
ediaType
text/plain
* @response.representation.503.e
xample
You'll get some explanation why this service is not
available
* @response.representation.503.d
oc You'll
get some explanation why this service is not available -
return
* @return a JAX-RS Response containing the pricing
information
*/
@GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_XML,
MediaType.APPLICATION_JSON })
@Produces({
MediaType.TEXT_HTML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON
})
@Path("/subscribe/branches/{tr
ading-branch-id}/cust/{cust-id
}/user/{user-id}/")
public Response subscribeFXLimitOrder(
@PathParam("trading-branch-id"
) String tradingBranchId,
@PathParam("userId") String userId,
@PathParam("cust-id") String customerId,
@QueryParam("channel-id") String channelId,
@Context Request request,
@Context UriInfo
uriInfo)
{ ...
2. Use the Jersey build doclet features to generate documentation file.
EX:
<target name="generateRestDoc"
description="Generates a resource desc file with the javadoc from resource
classes">
<antelope:call target="createPathReferences"
/>
<javadoc
classpathref="lbbw.service_api.alldeps.jars.path.id">
<fileset dir="${lbbw.service_api.rest.dir}/src/java"
defaultexcludes="yes">
<include
name="com/ibm/vd/otr/tradingapi/rest/resources/**"
/>
</fileset>
<doclet
name="org.glassfish.jersey.wadl.doclet.ResourceDoclet"
pathref="lbbw.service_api.alldeps.jars.path.id">
<param name="-output"
value="${lbbw.service_api.rest.dir}/${class.dir}/resourcedoc.xml"
/>
</doclet>
</javadoc>
</target>
3. Configure the Jersey framework to generate an extended WADL to include the documentation file generated at build time. Details about Jersey config:
Create a custom Jersey custom WADL generator config class, then add it to Jersey in the same manner with the rest of the services. Make sure you have the 3 XML files in the classpath (they are going to be used by the Jersey custom generator, further below).
application-doc.xml example:
<applicationDocs targetNamespace="http://wadl.dev.java.net/2009/02">
<doc xml:lang="en" title="The doc for your API">
This is a paragraph that is added to the start of the generated application.wadl
</doc>
<doc xml:lang="de" title="Die Dokumentation fuer Ihre API">
Dies in ein Abschnitt, der dem Beginn der generierte application.wadl hinzugefugt wird - in deutscher Sprache.
</doc>
</applicationDocs>
application-grammars.xml example:
<grammars xmlns="http://wadl.dev.java.net/2009/02"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xi="http://www.w3.org/1999/XML/xinclude">
<include href="schema.xsd" />
</grammars>
resource-doc.xml is generated by the doclet ant task above.
Custom Jersey WADL generator class example:
public class DocWadlGeneratorConfig extends WadlGeneratorConfig
{
@Override
public List<WadlGeneratorDescription> configure()
{
List<WadlGeneratorDescription> someList = generator(WadlGeneratorApplicationDoc.class)
.prop("applicationDocsStream", "application-doc.xml")
.generator(WadlGeneratorGrammarsSupport.class)
.prop("grammarsStream", "application-grammars.xml")
.prop("overrideGrammars", true)
.generator(WadlGeneratorResourceDocSupport.class)
.prop("resourceDocStream", "resourcedoc.xml")
.descriptions();
return someList;
}
}
Add the config for this class to the Jersey servlet in the web.xml file:
<init-param>
<param-name>jersey.config.server.wadl.generatorConfig</param-name>
<param-value>com.ibm.vd.otr.tradingapi.rest.DocWadlGeneratorConfig</param-value>
</init-param>
At this point, if you deploy and hit: http://localhost:9080/myapp/application.xml, you will see an extended WADL containing the documentation taken from your REST service java files.
EX of WADL file:
4. In order to see a pretty HTML with documentation in addition to the extended WADL xml file, you need to do the following:
- Configure Jersey to apply this XSL to the extended WADL it generates, which was configured in the previous steps:
Create a Jersey WADL resource generator class, used to direct the browser to apply the new XSL transformation to the extended WADL file, by inserting the preprocessing directive <?xml-stylesheet type="text/xsl" href="statics/wadl.xsl"?> (note that I am using the application2.wadl url in order to have both WADL files, the one without pretty styles and the one with styles available at the same time):
@Produces({ "application/vnd.sun.wadl+xml", "application/xml" })
@Singleton
@Path("application2.wadl")
public final class WadlResource
{
private WadlApplicationContext wadlContext;
private Application application;
private byte[] wadlXmlRepresentation;
public WadlResource(@Context WadlApplicationContext wadlContext)
{
this.wadlContext = wadlContext;
}
@GET
public synchronized Response getWadl(@Context UriInfo uriInfo)
{
if (wadlXmlRepresentation == null)
{
// String styleSheetUrl = uriInfo.getBaseUri().toString() + "wadl.xsl";
this.application = wadlContext.getApplication(uriInfo).getApplication();
try
{
Marshaller marshaller = wadlContext.getJAXBContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(os);
writer.write("<?xml version='1.0'?>\n");
writer.write("<?xml-stylesheet type=\"text/xsl\" href=\"statics/wadl.xsl\"?>\n");
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(application, writer);
writer.flush();
writer.close();
wadlXmlRepresentation = os.toByteArray();
os.close();
}
catch (Exception e)
{
e.printStackTrace();
return Response.ok(application).build();
}
}
return Response.ok(new ByteArrayInputStream(wadlXmlRepresentation)).build();
}
}
You configure this class in Jersey in the same manner as the rest of your services. Also make sure the XSL file you downloaded is accessible at the URL indicated in the WADL. This is because the browser itself will apply the XSL transformation to the WADL, so it needs to locate the XSL file used for it, as directed by the XML WADL file).
Now if you deploy and hit http://localhost:9080/myapp/application2.xml you should see something like this (this is just a fragment):
5. Congrats! Now you have a way to expose in a nice way your REST API javadoc to others :)