Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

 

 

 

{span:style=
Span
Wiki Markup
style
font-size:2em;font-weight:bold
} JAX-RS : Advanced XML {span}XML

 

 

 

Table of Contents

XPath support

...

Here are some examples:

Code Block
java
java

InputStream is = new ByteArrayInputStream("<foo><bar attr=\"3\">barValue</bar></foo>".getBytes());
XMLSource xp = new XMLSource(is);
xp.setBuffering(true);
// query 1
String value = xp.getValue("/foo/bar/@attr");
assertEquals("3", value);

// query 2
Integer intValue = xp.getNode("/foo/bar/@attr", Integer.class);
assertEquals(3, intValue);

// query 3
Node node = xp.getNode("/foo/bar/@attr", Node.class);
assertEquals("3", node.getTextValue());

In the above example a primitive attribute node is accessed in a number of ways, using the same XMLSource instance.
Matched XML complex (element) nodes can be converted in a similar way:

Code Block
java
java

public class Bar {
    @XmlAttribute
    private String attr;  
    public String getAttribute() {
        return attr;
    } 
}

InputStream is = new ByteArrayInputStream("<foo><bar attr=\"3\">barValue</bar></foo>".getBytes());
XMLSource xp = new XMLSource(is);
xp.setBuffering(true);

// query 1
Bar bean = xp.getNode("/foo/bar", Bar.class);
assertEquals("3", bean.getAttribute());
 
// query 2
String value = xp.getValue("/foo/bar");
assertEquals("<bar attr=\"3\">barValue</bar>", value);

...

XMLSource also provides methods for capturing multiple nodes, example:

Code Block
java
java

InputStream is = new ByteArrayInputStream("<foo><bar attr=\"3\">value1</bar><bar attr=\"4\">value2</bar></foo>".getBytes());
XMLSource xp = new XMLSource(is);
xp.setBuffering(true);
// query 1
String[] values = xp.getValue("/foo/bar/text()");
assertEquals("value1", values[0]);
assertEquals("value2", values[1]);

// query 2
Integer[] intValues = xp.getNodes("/foo/bar/@attr", Integer.class);
assertEquals(3, intValues[0]);
assertEquals(4, intValues[1]);

// query 3
Bar[] nodes = xp.getNodes("/foo/bar", Bar.class);

All the above examples have been simplified in that no namespaces have been used. Most real XML instances will have plenty of them and XMLSource has methods accepting optional maps containing prefixes as keys and namespaces as values:

Code Block
java
java

InputStream is = new ByteArrayInputStream("<foo xmlns=\"http://foo\"><ns1:bar xmlns:ns1=\"http://bar\" attr=\"3\">barValue</bar></foo>".getBytes());
XMLSource xp = new XMLSource(is);
xp.setBuffering(true);

Foo foo = xp.getNode("/ps1:foo", Collections.singletonMap("ps1", "http://foo"), Foo.class);
assertNotNull(foo);

Bar foo = xp.getNode("/ps2:bar", Collections.singletonMap("ps2", "http://bar"), Bar.class);
assertNotNull(foo);

...

XMLSource also provides few methods for capturing attribute or text values representing the HTTP links:

Code Block
java
java

String xmlString = "<customers xmlns=\"http://customers\">
                    + "<customer id="1" homepage=\"http://customers/1\"/>"
                    + "</customers>";
InputStream is = new ByteArrayInputStream(xmlString.getBytes());
XMLSource xp = new XMLSource(is);

URI homePage = xp.getLink("/ps1:customer[@id='1']/@homePage", 
                           Collections.singletonMap("ps1", "http://customers"));
WebClient client = WebClient.create(homePage);
// access the home page

...

In some cases, the links are relative. In such cases the base URI is already either known to the application code or it may be specified as the value of the xml:base attribute. In the latter case XMLSource makes it easy to get this base URI:

Code Block
java
java

String xmlString = "<customers xmlns=\"http://customers\" xml:base="http://customers">
                    + "<customer id="1"/><customer id="2"/>"
                    + "</customers>";
InputStream is = new ByteArrayInputStream(xmlString.getBytes());
XMLSource xp = new XMLSource(is);
xp.setBuffering(true);

URI baseURI = xp.getBaseURI();
URI[] relativeURIs = xp.getLinks("/ps1:customer/@id", Collections.singletonMap("ps1", "http://customers"));

WebClient client = WebClient.create(baseURI);
for (URI uri: relativeURIs) {
  client.path(uri);
  // access the home page
  
  // and get back to the base URI
  client.back(true); 
}

...

Please see this section on how http-centric WebClients can use XPath, and here is an example for the server side:

Code Block
java
java

@Path("/root")
public class Root {
   @POST
   public void post(XMLSource source) {
       String value = source.getProperty("/books/book/@name");
   }    
}

Users have an option to hide XPath expressions, by registering an XPathProvider which is a JAX-RS MessageBodyReader, either on the client or server sides. For example:

Code Block
java
java

XPathProvider provider = new XPathProvider();
provider.setGlobalExpression("/books/book[position() = 1]");
WebClient wc = WebClient.create("http://aggregated/data", Collections.singletonList(provider));
Book b = wc.get(Book.class);

...

For example, given this resource method definition:

Code Block
java
java

@Path("/root")
public class Root {
   @GET
   @Path("{id}") 
   @Produces({"application/xml", "application/json", "text/html"} )
   public Book get(@PathParam("id") String id, @QueryParam("name") String name) {
       return getBook(id, name);
   }    
}

...

Here are some examples of how XSLTJaxbTemplate can be configured:

Code Block
xml
xml

   <map id="outTemplates">
      <entry key="application/xml" value="classpath:/WEB-INF/templates/book-xml.xsl"/>
      <entry key="text/html" value="classpath:/WEB-INF/templates/book-html.xsl"/>
      <entry key="application/json" value="classpath:/WEB-INF/templates/book-json.xsl"/>
  </map>
  
  <bean id="uriResolver" class="org.apache.cxf.systest.jaxrs.URIResolverImpl"/>
  
  <bean id="xsltProvider" class="org.apache.cxf.jaxrs.provider.XSLTJaxbProvider">    
      <property name="outMediaTemplates" ref="outTemplates"/>
      <property name="resolver" ref="uriResolver"/>
  </bean>

...

Here is a simpler configuration example:

Code Block
xml
xml

   <bean id="xsltProvider" class="org.apache.cxf.jaxrs.provider.XSLTJaxbProvider">    
      <property name="outTemplate" value="classpath:/WEB-INF/templates/book-xml.xsl"/>
      <property name="inTemplate" class="classpath:/WEB-INF/templates/fromNewBookToOldBook.xsl"/>
  </bean>

...

Note that when XSLTJaxbProvider is used on the client side, it may not always be possible for template parameters be injected in cases when http-centric clients are used (as opposed to proxies). For example :

Code Block
java
java

WebClient client = WebClient.create("http://books");
client.path("/store/1").get();

it is not possible to deduce that '1' represents a template parameter in the "/store/1" expression. However, one can use the following code instead if '1' needs to be available to XSLT templates :

Code Block
java
java

WebClient client = WebClient.create("http://books");
client.path("/store/{id}", 1).get();

...

One way to get outbound XML transformed to HTML or get an XHTML payload further decorated with CSS tags is to associate an xml-stylesheet processing instruction with the XML payload, for example:

Code Block
xml
xml

<?xml-stylesheet type="text/xsl" href="http://localhost/myapp/stylesheets/toHTML.xsl"?>
<products xmlns="http://products">
   <product id="1"/>
</products>

...

You can also combine XSLTJaxbProvider to create complex HTML on the server and use xml-stylesheet instructions to get the browser to download and cache the CSS stylesheets, for example:

Code Block
xml
xml

<?xml-stylesheet type="text/css" href="http://localhost/myapp/stylesheets/HTMLDecorator.css"?>
<! 
   XSLTJaxbProvider transformed the XML products payload into well-formed XHTML.
   The browser will get HTMLDecorator.css at the next step and apply it to this HTML 
-->
<html>
   <title>The products</title>
   <table>
     <!-- description of products -->
   </table>
</html>

...

Up until CXF 2.5.1 the way to do is to use a JAXBElementProvider 'marshallerProperties' map property, for example:

Code Block
xml
xml

<beans xmlns:util="http://www.springframework.org/schema/util">
<bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
<map>
<entry key="com.sun.xml.bind.xmlHeaders" 
       value="<?xml-stylesheet type='text/xsl' href='/stylesheets/toHTML.xsl'?>"/>
</map>
</bean>
</beans>

...

Starting from CXF 2.5.1 and 2.4.5 one can use an XMLInstruction annotation:

Code Block
java
java

@Path("products")
public class Resource {
   @GET
   @Produces("application/xml")
   @XMLInstruction("<?xml-stylesheet type='text/xsl' href='/stylesheets/toHTML.xsl'?>")
   public Products getProducts() {}
}

...

The above relative href value will be converted to "http://localhost/myapp/stylesheets/toHTML.xsl" thus making it easy to resources not 'covered' by CXFServlet. What if you prefer not to even list 'stylesheets' in href='/stylesheets/toHTML.xsl' given that the name of the resource folder may change ?
Use an 'xmlResourceOffset' property of JAXBElementProvider:

Code Block
xml
xml

<bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
<property name="xmlResourceOffset" value="stylesheets"/>
</bean>
</beans>

and only have href='toHTML.xsl'. You can also use xmlResourceOffset to make sure the absolute URI will be covered by CXFServlet if preferred.

XSLTTransform

XSI Schema Location

 

XSLTTransform is a new annotation introduced in CXF 3.0.0. It can support either server or client driven transformations.

Single XSLTTransform annotation can also support both client and server transformations.

Its 'mediaTypes' property, if enabled, is used to restrict the server-based transformations to the listed media types only and expected to be a subset of JAX-RS Produces value.

 

XSLTTransform can be used in SERVER, CLIENT or BOTH modes.

For it to be effective for the server side transformations (SERVER, BOTH), it has to be used in conjunction with XSLTJaxbProvider.

Default JAXBElementProvider will support XSLTransform CLIENT mode independently.

When it is used either in CLIENT or BOTH modes to let the clients run the transformations it is processed by JAXBElementProvider and is converted into XML XSL instruction.

Using it in BOTH mode effectively supports a transformation 'negotiation': the clients which can do the transformation themselves will have an XML XSL instruction added to the response XML payloads,

and in other case the server will run the transformations.

Examples:

 

Code Block
java
java
// server based Products to XML transformation
@Path("products")
public class Resource {
   @GET
   @Produces("application/xml")
   @XSLTTransform("template.xsl")
   public Products getProducts() {}
}

// server based Products to HTML transformation, 
// Product to XML is not affected
@Path("products")
public class Resource {
   @GET
   @Produces("application/xml", "text/html")
   @XSLTTransform(value = "template.xsl", mediaTypes = {"text/html"})
   public Products getProducts() {}
}

// client based Products XML to XML transformation
@Path("products")
public class Resource {
   @GET
   @Produces("application/xml")
   @XSLTTransform(value = "template.xsl", type = CLIENT)
   public Products getProducts() {}
}

// client based Products XML to HTML transformation, 
// Product to XML is not affected
@Path("products")
public class Resource {
   @GET
   @Produces("application/xml", "text/html")
   @XSLTTransform(value = "template.xsl", type = CLIENT, mediaTypes = {"text/html"})
   public Products getProducts() {}
}

// clients with "Accept: application/xml" will do Products XML to HTML transformation 
// clients with "Accept: text/html" will have server-based Products to HTML transformation
@Path("products")
public class Resource {
   @GET
   @Produces("application/xml", "text/html")
   @XSLTTransform(value = "template.xsl", type = BOTH, mediaTypes = {"text/html"})
   public Products getProducts() {}
} 





 

 

Typically you do not need to configure either XSLT or JAXB providers with the additional properties for XSLTTransform be effective unless BOTH mode is used, in this case simply set XSLTJaxbProvider "supportJaxbOnly":

Code Block
xml
xml
<bean id="xsltProvider" class="org.apache.cxf.jaxrs.provider.XSLTJaxbProvider">
<property name="supportJaxbOnly" value="true"/>
</bean>

The above configuration is sufficient to have XSLTTransform BOTH mode supported.

XSI Schema Location

Some tools such as Microsoft Excel can do Some tools such as Microsoft Excel can do WEB queries and import the XML payload but this payload is expected to use an xsi:schemaLocation attribute pointing to the XML schema document describing this XML, for example:

Code Block
xml
xml

<products xmlns="http://products"
   xmlns:xsi=""http://www.w3.org/2000/10/XMLSchema-instance""
   xsi:schemaLocation="http://localhost/myapp/schemas/product.xsd">
   <product id="1"/>
</products>

In order to get this attribute set with JAXB, one needs to use a "javax.xml.bind.Marshaller.JAXB_SCHEMA_LOCATION" property which can be set on JAXBElementProvider directly (similarly to the way XML Processing Instructions set, see the previous section) or, starting with CXF 2.5.1, with the help of a new XSISchemaLocation annotation:

Code Block
java
java

@Path("products")
public class Resource {
   @GET
   @Produces("application/xml")
   @XSISchemaLocation("schemas/products.xsd")
   public Products getProducts() {}
}

...