The {@link org.apache.juneau.jena.RdfSerializer} class is the top-level class for all Jena-based serializers.
Language-specific serializers are defined as inner subclasses of the RdfSerializer
class:
- {@link org.apache.juneau.jena.RdfSerializer.Xml}
- {@link org.apache.juneau.jena.RdfSerializer.XmlAbbrev}
- {@link org.apache.juneau.jena.RdfSerializer.NTriple}
- {@link org.apache.juneau.jena.RdfSerializer.Turtle}
- {@link org.apache.juneau.jena.RdfSerializer.N3}
Static reusable instances of serializers are also provided with default settings:
- {@link org.apache.juneau.jena.RdfSerializer#DEFAULT_XML}
- {@link org.apache.juneau.jena.RdfSerializer#DEFAULT_XMLABBREV}
- {@link org.apache.juneau.jena.RdfSerializer#DEFAULT_TURTLE}
- {@link org.apache.juneau.jena.RdfSerializer#DEFAULT_NTRIPLE}
- {@link org.apache.juneau.jena.RdfSerializer#DEFAULT_N3}
Abbreviated RDF/XML is currently the most widely accepted and readable RDF syntax, so the examples shown here will use that format.
For brevity, the examples will use public fields instead of getters/setters to reduce the size of the examples.
In the real world, you'll typically want to use standard bean getters and setters.
To start off simple, we'll begin with the following simplified bean and build it up.
public class Person {
// Bean properties
public int id;
public String name;
// Bean constructor (needed by parser)
public Person() {}
// Normal constructor
public Person(int id, String name) {
this.id = id;
this.name = name;
}
}
The following code shows how to convert this to abbreviated RDF/XML:
// Create a new serializer with readable output.
RdfSerializer s = new RdfSerializer.XmlAbbrev().setProperty(RdfProperties.RDF_rdfxml_tab, 3);
// Create our bean.
Person p = new Person(1, "John Smith");
// Serialize the bean to RDF/XML.
String rdfXml = s.serialize(p);
It should be noted that serializers can also be created by cloning existing serializers:
// Create a new serializer with readable output by cloning an existing serializer.
RdfSerializer s = RdfSerializer.DEFAULT_XMLABBREV.clone().setProperty(RdfProperties.RDF_rdfxml_tab, 3);
This code produces the following output:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/">
<rdf:Description>
<jp:id>1</jp:id>
<jp:name>John Smith</jp:name>
</rdf:Description>
</rdf:RDF>
Notice that we've taken an arbitrary POJO and converted it to RDF.
The Juneau serializers and parsers are designed to work with arbitrary POJOs without requiring
any annotations.
That being said, several annotations are provided to customize how POJOs are handled to produce usable RDF.
2.1 - Namespaces
You'll notice in the previous example that Juneau namespaces are used to represent bean property names.
These are used by default when namespaces are not explicitly specified.
The juneau
namespace is used for generic names for objects that don't have namespaces associated with them.
The juneaubp
namespace is used on bean properties that don't have namespaces associated with them.
The easiest way to specify namespaces is through annotations.
In this example, we're going to associate the prefix 'per'
to our bean class and all
properties of this class.
We do this by adding the following annotation to our class:
@Rdf(prefix="per")
public class Person {
In general, the best approach is to define the namespace URIs at the package level using a package-info.java
class, like so:
// RDF namespaces used in this package
@RdfSchema(
prefix="ab",
rdfNs={
@RdfNs(prefix="ab", namespaceURI="http://www.apache.org/addressBook/"),
@RdfNs(prefix="per", namespaceURI="http://www.apache.org/person/"),
@RdfNs(prefix="addr", namespaceURI="http://www.apache.org/address/"),
@RdfNs(prefix="mail", namespaceURI="http://www.apache.org/mail/")
}
)
package org.apache.juneau.sample.addressbook;
import org.apache.juneau.xml.annotation.*;
This assigns a default prefix of "ab" for all classes and properties within the project,
and specifies various other prefixes used within this project.
Now when we rerun the sample code, we'll get the following:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/">
<rdf:Description>
<per:id>1</per:id>
<per:name>John Smith</per:name>
</rdf:Description>
</rdf:RDF>
Namespace auto-detection ({@link org.apache.juneau.xml.XmlSerializerContext#XML_autoDetectNamespaces}) is enabled
on serializers by default.
This causes the serializer to make a first-pass over the data structure to look for namespaces.
In high-performance environments, you may want to consider disabling auto-detection and providing an explicit list of namespaces to the serializer
to avoid this scanning step.
// Create a new serializer, but manually specify the namespaces.
RdfSerializer s = new RdfSerializer.XmlAbbrev()
.setProperty(RdfProperties.RDF_rdfxml_tab, 3)
.setProperty(XmlSerializerContext.XML_autoDetectNamespaces, false)
.setProperty(XmlSerializerContext.XML_namespaces, "{per:'http://www.apache.org/person/'}");
This code change will produce the same output as before, but will perform slightly better since it doesn't have to crawl the POJO tree before serializing the result.
2.2 - URI properties
Bean properties of type java.net.URI
or java.net.URL
have special meaning to the RDF serializer.
They are interpreted as resource identifiers.
In the following code, we're adding 2 new properties.
The first property is annotated with @BeanProperty to identify that this property is the
resource identifier for this bean.
The second unannotated property is interpreted as a reference to another resource.
public class Person {
// Bean properties
@Rdf(beanUri=true)
public URI uri;
public URI addressBookUri;
...
// Normal constructor
public Person(int id, String name, String uri, String addressBookUri) throws URISyntaxException {
this.id = id;
this.name = name;
this.uri = new URI(uri);
this.addressBookUri = new URI(addressBookUri);
}
}
We alter our code to pass in values for these new properties.
// Create our bean.
Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook");
Now when we run the sample code, we get the following:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/">
<rdf:Description rdf:about="http://sample/addressBook/person/1">
<per:addressBookUri rdf:resource="http://sample/addressBook"/>
<per:id>1</per:id>
<per:name>John Smith</per:name>
</rdf:Description>
</rdf:RDF>
The {@link org.apache.juneau.annotation.URI} annotation can also be used on classes and properties
to identify them as URLs when they're not instances of java.net.URI
or java.net.URL
(not needed if @Rdf(beanUri=true)
is already specified).
The following properties would have produced the same output as before. Note that the @URI annotation is only needed
on the second property.
public class Person {
// Bean properties
@Rdf(beanUri=true) public String uri;
@URI public String addressBookUri;
Also take note of the {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_relativeUriBase} and {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_absolutePathUriBase}
settings that can be specified on the serializer to resolve relative and context-root-relative URIs to fully-qualfied URIs.
This can be useful if you want to keep the URI authority and context root information out of the bean logic layer.
The following code produces the same output as before, but the URIs on the beans are relative.
// Create a new serializer with readable output.
RdfSerializer s = new RdfSerializer.XmlAbbrev()
.setProperty(RdfProperties.RDF_rdfxml_tab, 3);
.setProperty(SerializerContext.SERIALIZER_relativeUriBase, "http://myhost/sample");
.setProperty(SerializerContext.SERIALIZER_absolutePathUriBase, "http://myhost");
// Create our bean.
Person p = new Person(1, "John Smith", "person/1", "/");
// Serialize the bean to RDF/XML.
String rdfXml = s.serialize(p);
2.3 - @Bean and @BeanProperty annotations
The {@link org.apache.juneau.annotation.Bean} and {@link org.apache.juneau.annotation.BeanProperty} annotations
are used to customize the behavior of beans across the entire framework.
In addition to using them to identify the resource URI for the bean shown above, they have various other uses:
- Hiding bean properties.
- Specifying the ordering of bean properties.
- Overriding the names of bean properties.
- Associating transforms at both the class and property level (to convert non-serializable POJOs to serializable forms).
For example, we now add a birthDate
property, and associate a swap with it to transform
it to an ISO8601 date-time string in GMT time.
By default, Calendars
are treated as beans by the framework, which is usually not how you want them serialized.
Using swaps, we can convert them to standardized string forms.
public class Person {
// Bean properties
@BeanProperty(swap=CalendarSwap.ISO8601DTZ.class) public Calendar birthDate;
...
// Normal constructor
public Person(int id, String name, String uri, String addressBookUri, String birthDate) throws Exception {
...
this.birthDate = new GregorianCalendar();
this.birthDate.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(birthDate));
}
}
And we alter our code to pass in the birthdate.
// Create our bean.
Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946");
Now when we rerun the sample code, we'll get the following:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/">
<rdf:Description rdf:about="http://sample/addressBook/person/1">
<per:addressBookUri rdf:resource="http://sample/addressBook"/>
<per:id>1</per:id>
<per:name>John Smith</per:name>
<per:birthDate>1946-08-12T00:00:00Z</per:birthDate>
</rdf:Description>
</rdf:RDF>
2.4 - Collections
Collections and arrays are converted to RDF sequences.
In our example, let's add a list-of-beans property to our sample class:
public class Person {
// Bean properties
public LinkedList<Address> addresses = new LinkedList<Address>();
...
}
The Address
class has the following properties defined:
@Rdf(prefix="addr")
public class Address {
// Bean properties
@Rdf(beanUri=true) public URI uri;
public URI personUri;
public int id;
@Rdf(prefix="mail")
public String street, city, state;
@Rdf(prefix="mail")
public int zip;
public boolean isCurrent;
}
Next, add some quick-and-dirty code to add an address to our person bean:
// Create a new serializer (revert back to namespace autodetection).
RdfSerializer s = new RdfSerializer.XmlAbbrev().setProperty(RdfProperties.RDF_rdfxml_tab, 3);
// Create our bean.
Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946");
Address a = new Address();
a.uri = new URI("http://sample/addressBook/address/1");
a.personUri = new URI("http://sample/addressBook/person/1");
a.id = 1;
a.street = "100 Main Street";
a.city = "Anywhereville";
a.state = "NY";
a.zip = 12345;
a.isCurrent = true;
p.addresses.add(a);
Now when we run the sample code, we get the following:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/"
xmlns:mail="http://www.apache.org/mail/"
xmlns:addr="http://www.apache.org/address/">
<rdf:Description rdf:about="http://sample/addressBook/person/1">
<per:addressBookUri rdf:resource="http://sample/addressBook"/>
<per:id>1</per:id>
<per:name>John Smith</per:name>
<per:addresses>
<rdf:Seq>
<rdf:li>
<rdf:Description rdf:about="http://sample/addressBook/address/1">
<addr:personUri rdf:resource="http://sample/addressBook/person/1"/>
<addr:id>1</addr:id>
<mail:street>100 Main Street</mail:street>
<mail:city>Anywhereville</mail:city>
<mail:state>NY</mail:state>
<mail:zip>12345</mail:zip>
<addr:isCurrent>true</addr:isCurrent>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</per:addresses>
</rdf:Description>
</rdf:RDF>
2.5 - Root property
For all RDF languages, the POJO objects get broken down into simple triplets.
Unfortunately, for tree-structured data like the POJOs shown above, this causes the root node of the tree to become lost.
There is no easy way to identify that person/1
is the root node in our tree once in triplet form, and in
some cases it's impossible.
By default, the {@link org.apache.juneau.jena.RdfParser} class handles this by scanning
all the nodes and identifying the nodes without incoming references.
However, this is inefficient, especially for large models.
And in cases where the root node is referenced by another node in the model by URL, it's not possible to locate the root at all.
To resolve this issue, the property {@link org.apache.juneau.jena.RdfSerializerContext#RDF_addRootProperty} was introduced.
When enabled, this adds a special root
attribute to the root node to make it easy to locate by the parser.
To enable, set the RDF_addRootProperty property to true on the serializer:
// Create a new serializer.
RdfSerializer s = new RdfSerializer.XmlAbbrev()
.setProperty(RdfProperties.RDF_rdfxml_tab, 3),
.setProperty(RdfSerializerContext.RDF_addRootProperty, true);
Now when we rerun the sample code, we'll see the added root
attribute on the root resource.
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/"
xmlns:mail="http://www.apache.org/mail/"
xmlns:addr="http://www.apache.org/address/">
<rdf:Description rdf:about="http://sample/addressBook/person/1">
<j:root>true</j:root>
<per:addressBookUri rdf:resource="http://sample/addressBook"/>
<per:id>1</per:id>
<per:name>John Smith</per:name>
<per:addresses>
<rdf:Seq>
<rdf:li>
<rdf:Description rdf:about="http://sample/addressBook/address/1">
<addr:personUri rdf:resource="http://sample/addressBook/person/1"/>
<addr:id>1</addr:id>
<mail:street>100 Main Street</mail:street>
<mail:city>Anywhereville</mail:city>
<mail:state>NY</mail:state>
<mail:zip>12345</mail:zip>
<addr:isCurrent>true</addr:isCurrent>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</per:addresses>
</rdf:Description>
</rdf:RDF>
2.6 - Typed literals
XML-Schema datatypes can be added to non-String
literals through the {@link org.apache.juneau.jena.RdfSerializerContext#RDF_addLiteralTypes}
setting.
To enable, set the RDF_addLiteralTypes property to true on the serializer:
// Create a new serializer (revert back to namespace autodetection).
RdfSerializer s = new RdfSerializer.XmlAbbrev()
.setProperty(RdfProperties.RDF_rdfxml_tab, 3),
.setProperty(RdfSerializerContext.RDF_addLiteralTypes, true);
Now when we rerun the sample code, we'll see the added root
attribute on the root resource.
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j="http://www.apache.org/juneau/"
xmlns:jp="http://www.apache.org/juneaubp/"
xmlns:per="http://www.apache.org/person/"
xmlns:mail="http://www.apache.org/mail/"
xmlns:addr="http://www.apache.org/address/">
<rdf:Description rdf:about="http://sample/addressBook/person/1">
<per:addressBookUri rdf:resource="http://sample/addressBook"/>
<per:id rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</per:id>
<per:name>John Smith</per:name>
<per:addresses>
<rdf:Seq>
<rdf:li>
<rdf:Description rdf:about="http://sample/addressBook/address/1">
<addr:personUri rdf:resource="http://sample/addressBook/person/1"/>
<addr:id rdf:datatype="http://www.w3.org/2001/XMLSchema#int">1</addr:id>
<mail:street>100 Main Street</mail:street>
<mail:city>Anywhereville</mail:city>
<mail:state>NY</mail:state>
<mail:zip rdf:datatype="http://www.w3.org/2001/XMLSchema#int">12345</mail:zip>
<addr:isCurrent rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">true</addr:isCurrent>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</per:addresses>
</rdf:Description>
</rdf:RDF>
2.7 - Non-tree models and recursion detection
The RDF serializer is designed to be used against tree structures.
It expects that there not be loops in the POJO model (e.g. children with references to parents, etc...).
If you try to serialize models with loops, you will usually cause a StackOverflowError
to
be thrown (if {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_maxDepth} is not reached first).
If you still want to use the XML serializer on such models, Juneau provides the
{@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_detectRecursions} setting.
It tells the serializer to look for instances of an object in the current branch of the tree and
skip serialization when a duplicate is encountered.
Recursion detection introduces a performance penalty of around 20%.
For this reason the setting is disabled by default.
2.8 - Configurable properties
See the following classes for all configurable properties that can be used on this serializer:
- {@link org.apache.juneau.BeanContext} - Bean context properties.
- {@link org.apache.juneau.jena.RdfSerializerContext} - Serializer context properties.
2.9 - Other notes
- Like all other Juneau serializers, the RDF serializer is thread safe and maintains an internal cache of bean classes encountered.
For performance reasons, it's recommended that serializers be reused whenever possible instead of always creating new instances.
The {@link org.apache.juneau.jena.RdfParser} class is the top-level class for all Jena-based parsers.
Language-specific parsers are defined as inner subclasses of the RdfParser
class:
- {@link org.apache.juneau.jena.RdfParser.Xml}
- {@link org.apache.juneau.jena.RdfParser.NTriple}
- {@link org.apache.juneau.jena.RdfParser.Turtle}
- {@link org.apache.juneau.jena.RdfParser.N3}
The RdfParser.Xml
parser handles both regular and abbreviated RDF/XML.
Static reusable instances of parsers are also provided with default settings:
- {@link org.apache.juneau.jena.RdfParser#DEFAULT_XML}
- {@link org.apache.juneau.jena.RdfParser#DEFAULT_TURTLE}
- {@link org.apache.juneau.jena.RdfParser#DEFAULT_NTRIPLE}
- {@link org.apache.juneau.jena.RdfParser#DEFAULT_N3}
For an example, we will build upon the previous example and parse the generated RDF/XML back into the original bean.
// Create a new serializer with readable output.
RdfSerializer s = new RdfSerializer.XmlAbbrev()
.setProperty(RdfProperties.RDF_rdfxml_tab, 3)
.setProperty(RdfSerializerContext.RDF_addRootProperty, true);
// Create our bean.
Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946");
Address a = new Address();
a.uri = new URI("http://sample/addressBook/address/1");
a.personUri = new URI("http://sample/addressBook/person/1");
a.id = 1;
a.street = "100 Main Street";
a.city = "Anywhereville";
a.state = "NY";
a.zip = 12345;
a.isCurrent = true;
p.addresses.add(a);
// Serialize the bean to RDF/XML.
String rdfXml = s.serialize(p);
// Parse it back into a bean using the reusable XML parser.
p = RdfParser.DEFAULT_XML.parse(rdfXml, Person.class);
// Render it as JSON.
String json = JsonSerializer.DEFAULT_LAX_READABLE.serialize(p);
System.err.println(json);
We print it out to JSON to show that all the data has been preserved:
{
uri: 'http://sample/addressBook/person/1',
addressBookUri: 'http://sample/addressBook',
id: 1,
name: 'John Smith',
birthDate: '1946-08-12T00:00:00Z',
addresses: [
{
uri: 'http://sample/addressBook/address/1',
personUri: 'http://sample/addressBook/person/1',
id: 1,
street: '100 Main Street',
city: 'Anywhereville',
state: 'NY',
zip: 12345,
isCurrent: true
}
]
}
3.1 - Parsing into generic POJO models
The RDF parser is not limited to parsing back into the original bean classes.
If the bean classes are not available on the parsing side, the parser can also be used to
parse into a generic model consisting of Maps
, Collections
, and primitive
objects.
You can parse into any Map
type (e.g. HashMap
, TreeMap
), but
using {@link org.apache.juneau.ObjectMap} is recommended since it has many convenience methods
for converting values to various types.
The same is true when parsing collections. You can use any Collection (e.g. HashSet
, LinkedList
)
or array (e.g. Object[]
, String[]
, String[][]
), but using
{@link org.apache.juneau.ObjectList} is recommended.
When the map or list type is not specified, or is the abstract Map
, Collection
, or List
types,
the parser will use ObjectMap
and ObjectList
by default.
In the following example, we parse into an ObjectMap
and use the convenience methods for performing data conversion on values in the map.
// Parse RDF into a generic POJO model.
ObjectMap m = RdfParser.DEFAULT_XML.parse(rdfXml, ObjectMap.class);
// Get some simple values.
String name = m.getString("name");
int id = m.getInt("id");
// Get a value convertable from a String.
URI uri = m.get(URI.class, "uri");
// Get a value using a swap.
CalendarSwap swap = new CalendarSwap.ISO8601DTZ();
Calendar birthDate = m.get(swap, "birthDate");
// Get the addresses.
ObjectList addresses = m.getObjectList("addresses");
// Get the first address and convert it to a bean.
Address address = addresses.get(Address.class, 0);
However, there are caveats when parsing into generic models due to the nature of RDF.
Watch out for the following:
- The ordering of entries are going to be inconsistent.
- Bean URIs are always going to be denoted with the key "uri".
Therefore, you cannot have a bean with a URI property and a separate property named "uri".
The latter will overwrite the former.
This isn't a problem when parsing into beans instead of generic POJO models.
- All values are strings.
This normally isn't a problem when using ObjectMap
and ObjectList
since
various methods are provided for converting to the correct type anyway.
- The results may not be what is expected if there are lots of URL reference loops in the RDF model.
As nodes are processed from the root node down through the child nodes, the parser keeps
track of previously processed parent URIs and handles them accordingly.
If it finds that the URI has previously been processed, it handles it as a normal URI string and doesn't
process further.
However, depending on how complex the reference loops are, the parsed data may end up having the
same data in it, but structured differently from the original POJO.
We can see some of these when we render the ObjectMap
back to JSON.
System.err.println(JsonSerializer.DEFAULT_LAX_READABLE.serialize(m));
This is what's produced:
{
uri: 'http://sample/addressBook/person/1',
addresses: [
{
uri: 'http://sample/addressBook/address/1',
isCurrent: 'true',
zip: '12345',
state: 'NY',
city: 'Anywhereville',
street: '100 Main Street',
id: '1',
personUri: 'http://sample/addressBook/person/1'
}
],
birthDate: '1946-08-12T00:00:00Z',
addressBookUri: 'http://sample/addressBook',
name: 'John Smith',
id: '1',
root: 'true'
}
As a general rule, parsing into beans is often more efficient than parsing into generic models.
And working with beans is often less error prone than working with generic models.
3.2 - Configurable properties
See the following classes for all configurable properties that can be used on this parser:
- {@link org.apache.juneau.BeanContext} - Bean context properties.
- {@link org.apache.juneau.jena.RdfParserContext} - Parser context properties.
3.3 - Other notes
- Like all other Juneau parsers, the RDF parser is thread safe and maintains an internal cache of bean classes encountered.
For performance reasons, it's recommended that parser be reused whenever possible instead of always creating new instances.
Juneau provides fully-integrated support for RDF serialization/parsing in the REST server and client APIs.
The next two sections describe these in detail.
4.1 - REST server support
There are four general ways of defining REST interfaces with support for RDF media types.
Two using the built-in Juneau Server API, and two using the JAX-RS integration component.
- Create a servlet that subclasses from {@link org.apache.juneau.rest.jena.RestServletJenaDefault}.
This includes serialization and parsing for all Jena supported types, including all supported flavors of RDF.
- Create a servlet that subclasses from {@link org.apache.juneau.rest.RestServlet} and specify the
RDF serializers and parsers using the {@link org.apache.juneau.rest.annotation.RestResource#serializers()} and
{@link org.apache.juneau.rest.annotation.RestResource#parsers()} on the entire servlet class, or
the {@link org.apache.juneau.rest.annotation.RestMethod#serializers()} and {@link org.apache.juneau.rest.annotation.RestMethod#parsers()}
annotations on individual methods within the class.
- Register {@link org.apache.juneau.rest.jaxrs.rdf.DefaultJenaProvider} with JAX-RS to provide support RDF support for all JAX-RS resource.
This includes serialization and parsing for all Juneau supported types (JSON, XML, HTML...), including all supported flavors of RDF.
- Create and register a subclass of {@link org.apache.juneau.rest.jaxrs.BaseProvider} and specify the serializers and parsers to use on JAX-RS resources.
In general, the Juneau REST server API is much more configurable and easier to use than JAX-RS, but beware that the author may be slightly biased in this statement.
4.1.1 - Using RestServletJenaDefault
The quickest way to implement a REST resource with RDF support is to create a subclass of {@link org.apache.juneau.rest.jena.RestServletJenaDefault}.
This class provides support for all the RDF flavors in addition to JSON, XML, HTML, and URL-Encoding.
The reason why RDF support was not added to {@link org.apache.juneau.rest.RestServletDefault} directly was to keep the Jena prerequisites
out of the org.apache.juneau.rest
package.
The AddressBookResource
example shown in the first chapter uses the RestServletJenaDefault
class.
The start of the class definition is shown below:
// Proof-of-concept resource that shows off the capabilities of working with POJO resources.
// Consists of an in-memory address book repository.
@RestResource(
messages="nls/AddressBookResource",
properties={
@Property(name=RdfProperties.RDF_rdfxml_tab, value="3"),
@Property(name=RdfSerializerContext.RDF_addRootProperty, value="true"),
@Property(name=SerializerContext.SERIALIZER_quoteChar, value="'"),
@Property(name=HtmlSerializerContext.HTML_uriAnchorText, value=TO_STRING),
@Property(name=HtmlDocSerializerContext.HTMLDOC_title, value="$L{title}"),
@Property(name=HtmlDocSerializerContext.HTMLDOC_description, value="$L{description}"),
@Property(name=HtmlDocSerializerContext.HTMLDOC_links, value="{options:'?method=OPTIONS',doc:'doc'}")
},
encoders=GzipEncoder.class
)
public class AddressBookResource extends RestServletJenaDefault {
Notice how serializer and parser properties can be specified using the @RestResource.properties()
annotation.
The RDF_rdfxml_tab and RDF_addRootProperty are properties on the RDF serializers.
The SERIALIZER_quoteChar property is common to all serializers.
The remaining properties are specific to the HTML serializer.
The $L{...}
variable represent localized strings pulled from the resource bundle identified by the messages
annotation.
These variables are replaced at runtime based on the HTTP request locale.
Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables.
See {@link org.apache.juneau.rest.RestServlet#getVarResolver()} for more information.
This document won't go into all the details of the Juneau RestServlet
class.
Refer to the {@link org.apache.juneau.rest} documentation for more information on the REST servlet class in general.
The rest of the code in the resource class consists of REST methods that simply accept and return POJOs.
The framework takes care of all content negotiation, serialization/parsing, and error handling.
Below are 3 of those methods to give you a general idea of the concept:
// GET person request handler
@RestMethod(name="GET", path="/people/{id}/*", rc={200,404})
public Person getPerson(RestRequest req, @Path int id) throws Exception {
properties.put(HtmlDocSerializerContext.HTMLDOC_title, req.getPathInfo());
return findPerson(id);
}
// POST person handler
@RestMethod(name="POST", path="/people", guards=AdminGuard.class, rc={307,404})
public void createPerson(RestResponse res, @Body CreatePerson cp) throws Exception {
Person p = addressBook.createPerson(cp);
res.sendRedirect(p.uri);
}
// DELETE person handler
@RestMethod(name="DELETE", path="/people/{id}", guards=AdminGuard.class, rc={200,404})
public String deletePerson(RestResponse res, @Path int id) throws Exception {
Person p = findPerson(id);
addressBook.remove(p);
return "DELETE successful";
}
The resource class can be registered with the web application like any other servlet, or can be
defined as a child of another resource through the {@link org.apache.juneau.rest.annotation.RestResource#children()} annotation.
4.1.2 - Using RestServlet with annotations
For fine-tuned control of media types, the {@link org.apache.juneau.rest.RestServlet} class
can be subclassed directly.
The serializers/parsers can be specified through annotations at the class and/or method levels.
An equivalent AddressBookResource
class could be defined to only support RDF/XML using
the following definition:
@RestResource(
serializers={RdfSerializer.XmlAbbrev.class},
parsers={RdfParser.Xml.class},
properties={
@Property(name=RdfProperties.RDF_rdfxml_tab, value="3"),
@Property(name=RdfSerializerContext.RDF_addRootProperty, value="true"),
@Property(name=SerializerContext.SERIALIZER_quoteChar, value="'")
}
)
public class AddressBookResource extends RestServlet {
Likewise, serializers and parsers can be specified/augmented/overridden at the method level like so:
// GET person request handler
@RestMethod(name="GET", path="/people/{id}/*", rc={200,404},
serializers={RdfSerializer.XmlAbbrev.class},
parsers={RdfParser.Xml.class},
properties={
@Property(name=RdfProperties.RDF_rdfxml_tab, value="3"),
@Property(name=SerializerContext.SERIALIZER_quoteChar, value="'")
}
)
public Person getPerson(RestRequest req, @Path int id) throws Exception {
properties.put(HtmlDocSerializerContext.HTMLDOC_title, req.getPathInfo());
return findPerson(id);
}
The {@link org.apache.juneau.rest.annotation.RestMethod#serializersInherit()} and
{@link org.apache.juneau.rest.annotation.RestMethod#parsersInherit()} control how various artifacts
are inherited from the parent class.
Refer to {@link org.apache.juneau.rest} for additional information on using these annotations.
4.1.3 - Using JAX-RS DefaultJenaProvider
RDF media type support in JAX-RS can be achieved by using the {@link org.apache.juneau.rest.jaxrs.rdf.DefaultJenaProvider} class.
It implements the JAX-RS MessageBodyReader
and MessageBodyWriter
interfaces for all Juneau supported media types.
The DefaultJenaProvider
class definition is shown below:
@Provider
@Produces(
"application/json,text/json,"+ // JsonSerializer
"application/json+simple,text/json+simple,"+ // JsonSerializer.Simple
"application/json+schema,text/json+schema,"+ // JsonSchemaSerializer
"text/xml,"+ // XmlDocSerializer
"text/xml+simple,"+ // XmlDocSerializer.Simple
"text/xml+schema,"+ // XmlSchemaDocSerializer
"text/html,"+ // HtmlDocSerializer
"application/x-www-form-urlencoded,"+ // UrlEncodingSerializer
"text/xml+soap,"+ // SoapXmlSerializer
"text/xml+rdf,"+ // RdfSerializer.Xml
"text/xml+rdf+abbrev,"+ // RdfSerializer.XmlAbbrev
"text/n-triple,"+ // RdfSerializer.NTriple
"text/turtle,"+ // RdfSerializer.Turtle
"text/n3,"+ // RdfSerializer.N3
"application/x-java-serialized-object" // JavaSerializedObjectSerializer
)
@Consumes(
"application/json,text/json,"+ // JsonParser
"text/xml,"+ // XmlParser
"text/html,"+ // HtmlParser
"application/x-www-form-urlencoded,"+ // UrlEncodingParser
"text/xml+rdf,"+ // RdfParser.Xml
"text/n-triple,"+ // RdfParser.NTriple
"text/turtle,"+ // RdfParser.Turtle
"text/n3,"+ // RdfParser.N3
"application/x-java-serialized-object" // JavaSerializedObjectParser
)
@JuneauProvider(
serializers={
JsonSerializer.class,
JsonSerializer.Simple.class,
JsonSchemaSerializer.class,
XmlDocSerializer.class,
XmlDocSerializer.Simple.class,
XmlSchemaDocSerializer.class,
HtmlDocSerializer.class,
UrlEncodingSerializer.class,
SoapXmlSerializer.class,
RdfSerializer.Xml.class,
RdfSerializer.XmlAbbrev.class,
RdfSerializer.NTriple.class,
RdfSerializer.Turtle.class,
RdfSerializer.N3.class,
JavaSerializedObjectSerializer.class
},
parsers={
JsonParser.class,
XmlParser.class,
HtmlParser.class,
UrlEncodingParser.class,
RdfParser.Xml.class,
RdfParser.NTriple.class,
RdfParser.Turtle.class,
RdfParser.N3.class,
JavaSerializedObjectParser.class,
}
)
public final class DefaultJenaProvider extends BaseProvider {}
That's the entire class. It consists of only annotations to hook up media types to Juneau serializers and parsers.
The @Provider, @Produces, and @Consumes annotations are standard JAX-RS annotations, and the @JuneauProvider annotation is from Juneau.
To enable the provider, you need to make the JAX-RS environment aware of it.
In Wink, this is accomplished by adding an entry to a config file.
<web-app version="2.3">
<servlet>
<servlet-name>WinkService</servlet-name>
<servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>
<init-param>
<param-name>applicationConfigLocation</param-name>
<param-value>/WEB-INF/wink.cfg</param-value>
</init-param>
</servlet>
Simply include a reference to the provider in the configuration file.
org.apache.juneau.rest.jaxrs.DefaultJenaProvider
Properties can be specified on providers through the {@link org.apache.juneau.rest.jaxrs.JuneauProvider#properties()} annotation.
Properties can also be specified at the method level by using the {@link org.apache.juneau.rest.annotation.RestMethod#properties} annotation, like so:
@GET
@Produces("*/*")
@RestMethod( /* Override some properties */
properties={
@Property(name=RdfProperties.RDF_rdfxml_tab, value="3"),
@Property(name=SerializerContext.SERIALIZER_quoteChar, value="'")
}
)
public Message getMessage() {
return message;
}
Limitations
In general, the Juneau REST API is considerably more flexible than the JAX-RS API, since you can specify and override
serializers, parsers, properties, transforms, converters, guards, etc... at both the class and method levels.
Therefore, the JAX-RS API has the following limitations that the Juneau Server API does not:
- The ability to specify different media type providers at the class and method levels.
For example, you may want to use RdfSerializer.Xml
with one set of properties on
one class, and another instance with different properties on another class.
There is currently no way to define this at the class level.
You can override properties at the method level, but this can be cumbersome since it would have to be
done for all methods in the resource.
- The Juneau Server API allows you to manipulate properties programatically through the {@link org.apache.juneau.rest.RestResponse#setProperty(String,Object)}
method, and through the {@link org.apache.juneau.rest.annotation.Properties} annotation.
There is no equivalent in JAX-RS.
4.1.4 - Using JAX-RS BaseProvider with annotations
To provide support for only RDF media types, you can define your own provider class, like so:
@Provider
@Produces(
"text/xml+rdf,"+ // RdfSerializer.Xml
"text/xml+rdf+abbrev,"+ // RdfSerializer.XmlAbbrev
"text/n-triple,"+ // RdfSerializer.NTriple
"text/turtle,"+ // RdfSerializer.Turtle
"text/n3"+ // RdfSerializer.N3
)
@Consumes(
"text/xml+rdf,"+ // RdfParser.Xml
"text/n-triple,"+ // RdfParser.NTriple
"text/turtle,"+ // RdfParser.Turtle
"text/n3" // RdfParser.N3
)
@JuneauProvider(
serializers={
RdfSerializer.Xml.class,
RdfSerializer.XmlAbbrev.class,
RdfSerializer.NTriple.class,
RdfSerializer.Turtle.class,
RdfSerializer.N3.class,
},
parsers={
RdfParser.Xml.class,
RdfParser.NTriple.class,
RdfParser.Turtle.class,
RdfParser.N3.class,
},
properties={
@Property(name=RdfProperties.RDF_rdfxml_tab, value="3"),
@Property(name=SerializerContext.SERIALIZER_quoteChar, value="'")
}
)
public final class MyRdfProvider extends BaseProvider {}
Then register it with Wink the same way as DefaultJenaProvider
.
4.2 - REST client support
The {@link org.apache.juneau.rest.client.RestClient} class provides an easy-to-use REST client interface with
pluggable media type handling using any of the Juneau serializers and parsers.
Defining a client to support RDF media types on HTTP requests and responses can be done in one line of code:
// Create a client to handle RDF/XML requests and responses.
RestClient client = new RestClient(RdfSerializer.XmlAbbrev.class, RdfParser.Xml.class);
The client handles all content negotiation based on the registered serializers and parsers.
The following code is pulled from the main method of the ClientTest
class in the sample web application, and
is run against the AddressBookResource
class running within the sample app.
It shows how the client can be used to interact with the REST API while completely hiding the negotiated content type and working with nothing more than beans.
String root = "http://localhost:9080/sample/addressBook";
// Get the current contents of the address book
AddressBook ab = client.doGet(root).getResponse(AddressBook.class);
System.out.println("Number of entries = " + ab.size());
// Delete the existing entries
for (Person p : ab) {
String r = client.doDelete(p.uri).getResponse(String.class);
System.out.println("Deleted person " + p.name + ", response = " + r);
}
// Make sure they're gone
ab = client.doGet(root).getResponse(AddressBook.class);
System.out.println("Number of entries = " + ab.size());
// Add 1st person again
CreatePerson cp = new CreatePerson(
"Barack Obama",
toCalendar("Aug 4, 1961"),
new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, true),
new CreateAddress("5046 S Greenwood Ave", "Chicago", "IL", 60615, false)
);
Person p = client.doPost(root + "/people", cp).getResponse(Person.class);
System.out.println("Created person " + p.name + ", uri = " + p.uri);
// Add 2nd person again, but add addresses separately
cp = new CreatePerson(
"George Walker Bush",
toCalendar("Jul 6, 1946")
);
p = client.doPost(root + "/people", cp).getResponse(Person.class);
System.out.println("Created person " + p.name + ", uri = " + p.uri);
// Add addresses to 2nd person
CreateAddress ca = new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true);
Address a = client.doPost(p.uri + "/addresses", ca).getResponse(Address.class);
System.out.println("Created address " + a.uri);
ca = new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false);
a = client.doPost(p.uri + "/addresses", ca).getResponse(Address.class);
System.out.println("Created address " + a.uri);
// Find 1st person, and change name
Person[] pp = client.doGet(root + "?q={name:\"'Barack+Obama'\"}").getResponse(Person[].class);
String r = client.doPut(pp[0].uri + "/name", "Barack Hussein Obama").getResponse(String.class);
System.out.println("Changed name, response = " + r);
p = client.doGet(pp[0].uri).getResponse(Person.class);
System.out.println("New name = " + p.name);
The code above produces the following output.
Number of entries = 2
Deleted person Barack Obama, response = DELETE successful
Deleted person George Walker Bush, response = DELETE successful
Number of entries = 0
Created person Barack Obama, uri = http://localhost:9080/sample/addressBook/people/3
Created person George Walker Bush, uri = http://localhost:9080/sample/addressBook/people/4
Created address http://localhost:9080/sample/addressBook/addresses/7
Created address http://localhost:9080/sample/addressBook/addresses/8
Changed name, response = PUT successful
New name = Barack Hussein Obama