Thursday, May 7, 2009

MVC the XML way with CXF JAX-RS

For some reasons, the 'V' bit in a MVC pattern is often associated by Java users with Java Server Pages (JSP). Personally I've never been a fan of JSP - I don't understand why would users want to mix Java code into HTML pages or pretend they don't do it by spending time on writing tag libraries in Java.

Fortunately, technologies like XSLT and well-known techniques for decoupling the presentation from content exist and in CXF we did a bit of work for users be able to refresh their XML skills and say goodbye to another legacy Java technology which is JSP and never look back.

If you work with CXF and do like JSP then please read on anyway - hope you will appreciate that XSLT (or technology like XQuery) can do the job too. As a side note, even if you won't want to use XSLT for generating HTML pages, in CXF you will be able to use it for generating all types of formats, doing micro-transformation routes by dealing with either legacy (backward-compatibilty) or newer (forward compatibilty) XML requests and responses. With XSLT you can pretty much just produce any format you need, starting from text and ending with RDF - you won't need RDF-specific libraries for it :-)

So we introduced an XSLTJaxbProvider. It's a very flexible provider. You can tell it to transform input or output data (on either client or server sides) . You can specify a stylesheet which will apply to all incoming data, or you can tell the provider to invoke a stylesheet a.xsl for application/xml and b.xsl for application/atom+feed formats. You can also tell it which Java classes are supported : for example, you may want JAXB to directly deal with Book class but you may also want to pre/post transform Book2 XML instances.

For a moment JAXP Templates are used to preprocess/compile XSLT templates and SAX events are used to drive in and out transformations. The base class, JAXBElementProvider does all the work and the XSLT provider only deals with the final marshal/unmarshal invocations by wrapping input or output streams as needed. For the record, JAXBElementProvider has been updated to deal with Stax XMLStreamReaders/Writers which gives yet another option of pre/post transforming the data and we will update the XSLT provider to pick them up, when they're available, which will essentially create 2-level transformation chains.

Template instances will get all the JAX-RS Path template variables, query and last past segment matrix name-value pairs as xsl:param parameters (note : you will only need to declare the parameters you need, in XSLT unused passed-in parameters will be ignored). Additionally UriInfo.getAbsolutePath(), UriInfo.getPath() and UriInfo.getBasePath() will be available as 'absolute.path', 'relative.path' and 'base.path' respectively. We'll update the provider as needed to push more useful information to templates.

If you don't use JAXB but deal with XML then please feel free just to create a custom provider. You'll just probably need to remove the 'extends JAXBElementProvider' bit from a class definition with few other minor updates.

I added a simple system test which shows how to merge the (JAXB-produced) XML into HTML. It's well-known technique, described in articles like

Style-free XSLT stylesheets
Integrating data at runtime with XSLT stylesheets

The idea is not to go the traditional JSP way by combining XSLT instructions and presentation tags into a single source which makes the source unreadable and difficult to maintain. Rather let people who understand how to do proper HTML work on HTML templates, Java developers produce Java code (possibly dealing with either Java classes for JAXB to handle or JAXP Sources ) and those who like XSLT write XSLT templates which sources both presentation HTML and content XML inputs and transforms them as needed. HTML templates have special xml tags which XSLT templates replace with actual data. Trust me, this seperation of roles does work in practice quite well, I can vouch for it based on my previous experience, though I guess we didnt do the complicated stuff - but it worked nonetheless.

Now lets get back to the test. Look at the jaxrs endpoint with 'bookservice5' id. It registers an XSLTJaxbProvider which is told to handle Book classes only and invoke a template.xsl for application/xhtml+xml and template2.xsl for application/xml. (For the record, this endpoint also registers CXF JAX-RS Request/Response filters which install custom Stax stream readers/writers which perform namespace translations - as advised by one of CXF users recently). The provider is also injected with URIResolverImpl, a systemId property is also supported.

The template.xsl template sources in an xhtml template book.xhtml and imports template2.xsl. Thus template2.xsl can be used on its own too. A URIResolver instance registered earlier on takes care of resolving those relative references. Now, the book.xhtml has a special tag books:bookTag which is what a template.xsl reacts upon : the flow is to copy the html tags and pull in the xml content as needed when special tags get encountered. Note that fundamental XSLT style is to do a free flow and rely on matches which makes the templates code much more readable and easier to understand.

In our test we simply copy the whole XML content blob into the resulting HTML, by preprocessing a Book instance as needed (see the imported template2.xsl). Perhaps it would be better to add special tags to book.xhtml corresponding to Book id and Book name, with HTML table tags if needed.

The server test code is in BookStoreSpring.getBookXSLT(). Note that JAX-RS Path, Query and Matrix parameter values are available to template2.xsl, when used on its own or when being imported.

See testGetBookXSLTXml() and testGetBookXSLTHtml() here. Here is the one which extract the Book instance from an HTML document :




WebClient wc = WebClient.create(endpointAddress);
wc.accept("application/xhtml+xml")
.path(666)
.matrix("name2", 2)
.query("name", "Action - ");
XMLSource source = wc.get(XMLSource.class);
Map namespaces = new HashMap();
namespaces.put("xhtml", "http://www.w3.org/1999/xhtml");
Book2 b = source.getNode(
"xhtml:html/xhtml:body/xhtml:ul/xhtml:Book",
namespaces,
Book2.class);


In reality you wont see a Book element in an xhtml namespace though :-)

So it is the XML all the way. I believe it's great fun to be able easily and explicitly control how the XML looks like and to mix a much looser XPath style with the Java code. So give it a try and have fun.

Update. I mentioned above that XSLTJaxbProvider can choose which template to use depending on the media type, such as application/atom+feed for example. Note that even though this provider has static Produces and Consumes values, you can still tell to handle other media types for the runtime to view it as an eligible provider, look at the bean with 'jsonProvider' id on how to overwrite the static Produces and Consumes values on a given message body provider. So for example you can easily tell XSLTJaxbProvider to actually convert a given JAXB-produced XML into JSON by registering an application/json specific template (which will transform XML into JSON) with it and registering application/json as a custom Produces value.

4 comments:

Unknown said...

Looks really cool. Now just need to find time to try it out.

Sergey Beryozkin said...

Give it a try please, the xslt provider is available in CXF 2.2.2-SNAPSHOT only

anon_anon said...

you might also want to look at vtd-xml, the latest and most advanced XML processing API available today

http://vtd-xml.sf.net

l-carnitin said...

The main aim of the MVC architecture is to separate the business logic and application data from the presentation data to the user.