Juneau supports converting arbitrary POJOs to and from URL-encoded strings using ultra-efficient serializers and parsers.
The serializer converts POJOs directly to URL-encoded strings without the need for intermediate DOM objects using a highly-efficient state machine.
Likewise, the parser creates POJOs directly from URL-encoded strings without the need for intermediate DOM objects.
Juneau uses UON (URL-Encoded Object Notation) for representing POJOs.
The UON specification can be found here.
Juneau can serialize and parse instances of any of the following POJO types:
- Java primitives and primitive objects (e.g.
String
, Integer
, Boolean
, Float
).
- Java Collections Framework objects (e.g.
HashSet
, TreeMap
) containing anything on this list.
- Multi-dimensional arrays of any type on this list.
- Java Beans with properties of any type on this list.
- Classes with standard transformations to and from
Strings
(e.g. classes containing toString()
, fromString()
, valueOf()
, constructor(String)
).
- Non-serializable classes and properties with associated
PojoSwaps
that convert them to serializable forms.
Refer to POJO Categories for a complete definition of supported POJOs.
Prerequisites
The Juneau URL-encoding serialization and parsing support does not require any external prerequisites.
It only requires Java 1.6 or above.
1.1 - URL-encoding support overview - example
The example shown here is from the Address Book resource located in the org.apache.juneau.sample.war
application.
The POJO model consists of a List
of Person
beans, with each Person
containing
zero or more Address
beans.
When you point a browser at /sample/addressBook/people/1
, the POJO is rendered as HTML:
By appending ?Accept=application/x-www-form-urlencoded&plainText=true
to the URL, you can view the data as a URL-encoded string:
Juneau supports two kinds of serialization:
- Construction of full URL query parameter strings (e.g.
&key=value
pairs) from beans and maps.
- Construction of URL query parameter value strings (e.g. just the
value
portion of &key=value
pairs) from any POJO.
Top-level beans and maps can serialized as key/value pairs as shown below:
http://localhost/sample?foo=bar&baz=bing
Lower-level beans and maps are also serialized as key/value pairs, but are surrounded with a "$o(...)" construct to denote an object mapping,
and uses a comma as the parameter delimiter instead of "&".
http://localhost/sample?a1=$o(foo=bar,baz=bing)
The UON specification defines two separate modes:
- Strict mode - Serialized model is fully equivalent to JSON and can be losslessly converted back and forth into a JSON model without additional information.
- Lax mode - A shortened form that excludes data type information. Ideal if the data types of values are fixed and already known by the parser.
Java type | JSON equivalent | Strict syntax | Lax syntax |
Maps/beans |
OBJECT |
a1=$o(b1=x1,b2=x2) a1=$o(b1=$o(c1=x1,c2=x2)) |
a1=(b1=x1,b2=x2) a1=(b1=(c1=x1,c2=x2)) |
Collections/arrays |
ARRAY |
a1=$a(x1,x2) a1=$a($a(x1,x2),$a(x3,x4)) a1=$a($o(b1=x1,b2=x2),$o(c1=x1,c2=x2)) |
a1=(x1,x2) a1=((x1,x2),(x3,x4)) a1=((b1=x1,b2=x2),(c1=x1,c2=x2)) |
Booleans |
BOOLEAN |
a1=$b(true)&a2=$b(false) |
a1=true&a2=false |
int/float/double/... |
NUMBER |
a1=$n(123)&a2=$n(1.23e1) |
a1=123&a2=1.23e1 |
null |
NULL |
a1=%00 |
a1=%00 |
String |
STRING |
a1=foobar |
a1=foobar |
Refer to the UON specification for a complete set of syntax rules.
PojoSwaps
can be used to convert non-serializable POJOs into serializable forms, such as converting
Calendar
object to ISO8601 strings, or byte[]
arrays to Base-64 encoded strings.
These transforms can be associated at various levels:
- On serializer and parser instances to handle all objects of the class type globally.
- On classes through the
@Bean
annotation.
- On bean properties through the
@BeanProperty
annotations.
http://localhost/sample?a1=Sun~,+03+Mar+1901+09:05:06+GMT
For more information about transforms, refer to {@link org.apache.juneau.transform}.
{@link org.apache.juneau.urlencoding.UrlEncodingSerializer} and {@link org.apache.juneau.urlencoding.UonSerializer} classes are used to convert POJOs to URL-encoded strings.
The UonSerializer
class converts parameter values to UON notation.
The UrlEncodingSerializer
class converts a POJO to key/value URL-Encoded pairs using UonSerializer
to serialize the values.
If you're trying to construct complete URL-Encoded entities, use UrlEncodingSerializer
.
If you're constructing your own key/value pairs, use UonSerializer
.
The serializers include several configurable settings.
Static reusable instances of serializers are provided with commonly-used settings:
- {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#DEFAULT} - All default settings, strict mode.
- {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#DEFAULT_SIMPLE} - All default settings, lax mode.
- {@link org.apache.juneau.urlencoding.UrlEncodingSerializer#DEFAULT_READABLE} - Use whitespace and indentation for readability.
- {@link org.apache.juneau.urlencoding.UonSerializer#DEFAULT} - All default settings, strict mode.
- {@link org.apache.juneau.urlencoding.UonSerializer#DEFAULT_SIMPLE} - All default settings, lax mode.
- {@link org.apache.juneau.urlencoding.UonSerializer#DEFAULT_READABLE} - Use whitespace and indentation for readability.
- {@link org.apache.juneau.urlencoding.UonSerializer#DEFAULT_ENCODING} - Same as DEFAULT, but use URL-Encoding on special characters.
- {@link org.apache.juneau.urlencoding.UonSerializer#DEFAULT_SIMPLE_ENCODING} - Same as DEFAULT_SIMPLE, but use URL-Encoding on special characters.
The general guidelines on which serializer to use is:
- Use strict mode serializers if the data types of the value are not known on the parsing side, and this
information needs to be preserved during transmission.
- Use lax mode serializers if the data types of the value are known on the parsing side.
For example, if you're serializing/parsing beans, lax mode is usually sufficient since the data types
can be inferred from the bean properties.
- Use encoding serializers when you're using the results to construct a URI yourself, and therefore
need invalid URI characters to be encoded.
- Use unencoding serializers when you're creating parameter values and passing them off to some other
utility class that will itself encode invalid URI characters.
- Use the readable serializer for debugging purposes.
Notes about examples
The examples shown in this document will use default strict settings.
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 upon it.
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 a URL-encoded value:
// Use serializer with readable output, simple mode.
UonSerializer s = UonSerializer.DEFAULT;
// Create our bean.
Person p = new Person(1, "John Smith");
// Serialize the bean to URL-encoded parameter value.
String urlencoded = s.serialize(p);
The code above produces the following output:
$o(id=$n(1),name=John+Smith)
The {@link org.apache.juneau.urlencoding.UrlEncodingSerializer} class converts
maps and beans into top-level query parameter strings.
// Use serializer with readable output, simple mode.
UrlEncodingSerializer s = UrlEncodingSerializer.DEFAULT;
// Serialize the bean to URL-encoded query string.
String urlencoded = s.serialize(p);
The code above produces the following output:
id=$n(1)&name=John+Smith
The general method guidelines are as follows:
- Use
UonSerializer
to create individual query parameter values.
- Use
UrlEncodingSerializer
to create complete URL-encoded query strings.
By default, the UrlEncodingSerializer
class will URL-Encode special characters, and the UonSerializer
will NOT URL-encode special characters.
2.1 - @Bean and @BeanProperty annotations
The {@link org.apache.juneau.annotation.Bean @Bean} and {@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotations
are used to customize the behavior of beans across the entire framework.
They have various 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 transform with it to transform
it to an ISO8601 date-time string in GMT time.
We'll also add a couple of URI
properties.
By default, Calendars
are treated as beans by the framework, which is usually not how you want them serialized.
Using transforms, we can convert them to standardized string forms.
public class Person {
// Bean properties
public int id;
public String name;
public URI uri;
public URI addressBookUri;
@BeanProperty(swap=CalendarSwap.ISO8601DTZ.class) public Calendar birthDate;
// Bean constructor (needed by parser)
public Person() {}
// Normal constructor
public Person(int id, String name, String uri, String addressBookUri, String birthDate) throws Exception {
this.id = id;
this.name = name;
this.uri = new URI(uri);
this.addressBookUri = new URI(addressBookUri);
this.birthDate = new GregorianCalendar();
this.birthDate.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(birthDate));
}
}
Next, 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:
$o(id=$n(1),name=John+Smith,uri=http://sample/addressBook/person/1,addressBookUri=http://sample/addressBook,birthDate=1946-08-12T00:00:00Z)
Using UrlEncodingSerializer
instead would create the following:
id=$n(1)&name=John+Smith&uri=http://sample/addressBook/person/1&addressBookUri=http://sample/addressBook&birthDate=1946-08-12T00:00:00Z
Another useful feature is the {@link org.apache.juneau.annotation.Bean#propertyNamer()} annotation that allows you to plug in your own
logic for determining bean property names.
The {@link org.apache.juneau.PropertyNamerDashedLC} is an example of an alternate property namer.
It converts bean property names to lowercase-dashed format.
@Bean(propertyNamer=PropertyNamerDashedLC.class)
public class Person {
...
$o(id=$n(1),name=John+Smith,uri=http://sample/addressBook/person/1,address-book-uri=http://sample/addressBook,birth-date=1946-08-12T00:00:00Z)
2.2 - Collections
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:
public class Address {
// Bean properties
public URI uri;
public URI personUri;
public int id;
public String street, city, state;
public int zip;
public boolean isCurrent;
}
Next, add some quick-and-dirty code to add an address to our person bean:
// Use serializer with readable output, simple mode.
UonSerializer s = UonSerializer.DEFAULT_READABLE;
// 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 (in readable format):
$o(
id=$n(1),
name=John+Smith,
uri=http://sample/addressBook/person/1,
addressBookUri=http://sample/addressBook,
birthDate=1946-08-12T00:00:00Z,
addresses=$a(
$o(
uri=http://sample/addressBook/address/1,
personUri=http://sample/addressBook/person/1,
id=$n(1),
street=100+Main+Street,
city=Anywhereville,
state=NY,
zip=$n(12345),
isCurrent=$b(true)
)
)
)
If we were to use lax mode instead, we would get the following:
(
id=1,
name=John+Smith,
uri=http://sample/addressBook/person/1,
addressBookUri=http://sample/addressBook,
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
)
)
)
Note how the data type information is removed, so it's not possible to distinguish between numbers/booleans/strings, and between objects/arrays.
However, this is fine if we're parsing back into the same beans, since we can inver the data types from the bean property metadata.
If we were to use UrlEncodingSerializer
instead, we would get the following:
id=$n(1)&
name=John+Smith&
uri=http://sample/addressBook/person/1&
addressBookUri=http://sample/addressBook&
birthDate=1946-08-12T00:00:00Z&
addresses=$a(
$o(
uri=http://sample/addressBook/address/1,
personUri=http://sample/addressBook/person/1,
id=$n(1),
street=100+Main+Street,
city=Anywhereville,
state=NY,
zip=$n(12345),
isCurrent=$b(true)
)
)
Note how the top level Person
bean is serialized using the standard '&' delimiter, whereas the lower-level Address
bean is serialized using the ',' character to prevent the addresses
field from being incompletely parsed.
2.3 - Non-tree models and recursion detection
The URL-encoding serializer is designed to be used against POJO 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 URL-encoding 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.
For example, let's make a POJO model out of the following classes:
public class A {
public B b;
}
public class B {
public C c;
}
public class C {
public A a;
}
Now we create a model with a loop and serialize the results.
// Clone an existing serializer and set property for detecting recursions.
UrlEncodingSerializer s = UrlEncodingSerializer.DEFAULT_READABLE.clone().setProperty(SerializerContext.SERIALIZER_detectRecursions, true);
// Create a recursive loop.
A a = new A();
a.b = new B();
a.b.c = new C();
a.b.c.a = a;
// Serialize.
String json = s.serialize(a);
What we end up with is the following, which does not serialize the contents of the c
field:
$o(
b=$o(
c=$o()
)
)
Without recursion detection enabled, this would cause a stack-overflow error.
Recursion detection introduces a performance penalty of around 20%.
For this reason the setting is disabled by default.
2.4 - 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.urlencoding.UonSerializerContext} - UON serializer context properties.
- {@link org.apache.juneau.urlencoding.UrlEncodingSerializerContext} - URL-Encoding serializer context properties.
2.5 - Other notes
- Like all other Juneau serializers, the URL-encoding serializers are thread safe and maintain 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.
{@link org.apache.juneau.urlencoding.UrlEncodingParser} and {@link org.apache.juneau.urlencoding.UonParser} classes are used to convert URL-encoded strings back into POJOs.
The UonParser
class converts UON-encoded parameter values to POJOs.
The UrlEncodingParser
class converts entire URL-Encoded strings to POJOs using UonSerializer
to serialize indivisual values.
If you're trying to parse an entire URL-Encoded string, use UrlEncodingParser
.
If you're trying to parse an individual value (such as that returned by RestServlet.getQueryParameter(name)
), use UonParser
.
The following static reusable instances of UrlEncodingParser
are provided for convenience:
- {@link org.apache.juneau.urlencoding.UrlEncodingParser#DEFAULT} - Default parser for entire URL-encoded strings, decode
%xx
sequences.
- {@link org.apache.juneau.urlencoding.UonParser#DEFAULT} - Default parser for URL-encoded parameter values, don't decode
%xx
sequences.
- {@link org.apache.juneau.urlencoding.UonParser#DEFAULT_DECODING} - Default parser for URL-encoded parameter values, decode
%xx
sequences.
The general guildlines on which parser to use is:
- Use the
DEFAULT
parser for parameter values that have already had %xx
sequences decoded,
such as when using HttpServletRequest.getQueryParameter(name)
.
- Use the
DEFAULT_ENCODED
parser if the input has not already had %xx
sequences decoded.
Let's build upon the previous example and parse the generated URL-encoded string back into the original bean.
We start with the URL-encoded string that was generated.
// Use serializer with readable output.
UonSerializer s = UonSerializer.DEFAULT_READABLE;
// 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.
String urlencoded = s.serialize(p);
This code produced the following:
$o(
id=$n(1),
name=John+Smith,
uri=http://sample/addressBook/person/1,
addressBookUri=http://sample/addressBook,
birthDate=1946-08-12T00:00:00Z,
addresses=$a(
$o(
uri=http://sample/addressBook/address/1,
personUri=http://sample/addressBook/person/1,
id=$n(1),
street=100+Main+Street,
city=Anywhereville,
state=NY,
zip=$n(12345),
isCurrent=$b(true)
)
)
)
The code to convert this back into a bean is:
// Parse it back into a bean using the reusable JSON parser.
Person p = UonParser.DEFAULT.parse(urlencoded, Person.class);
// Render it back as JSON.
json = JsonSerializer.DEFAULT_SIMPLE_READABLE.serialize(p);
We print it back out to JSON to show that all the data has been preserved:
{
id: 1,
name: 'John Smith',
uri: 'http://sample/addressBook/person/1',
addressBookUri: 'http://sample/addressBook',
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 URL-encoding 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.
Starting back with our original URL-encoded string:
$o(
id=$n(1),
name=John+Smith,
uri=http://sample/addressBook/person/1,
addressBookUri=http://sample/addressBook,
birthDate=1946-08-12T00:00:00Z,
addresses=$a(
$o(
uri=http://sample/addressBook/address/1,
personUri=http://sample/addressBook/person/1,
id=$n(1),
street=100+Main+Street,
city=Anywhereville,
state=NY,
zip=$n(12345),
isCurrent=$b(true)
)
)
)
We can parse this into a generic ObjectMap
:
// Parse URL-encoded string into a generic POJO model.
ObjectMap m = UonParser.DEFAULT.parse(urlencoded, ObjectMap.class);
// Convert it back to JSON.
String json = JsonSerializer.DEFAULT_SIMPLE_READABLE.serialize(m);
What we end up with is the exact same output.
Even the numbers and booleans are preserved because they are parsed into Number
and Boolean
objects
when parsing into generic models.
{
id: 1,
name: 'John Smith',
uri: 'http://sample/addressBook/person/1',
addressBookUri: 'http://sample/addressBook',
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
}
]
}
Once parsed into a generic model, various convenience methods are provided on the ObjectMap
and ObjectList
classes to retrieve values:
// Parse URL-encoded string into a generic POJO model.
ObjectMap m = UonParser.DEFAULT.parse(urlencoded, 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 transform.
CalendarSwap transform = new CalendarSwap.ISO8601DTZ();
Calendar birthDate = m.get(transform, "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);
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.urlencoding.UonParserContext} - UON parser context properties.
- {@link org.apache.juneau.urlencoding.UrlEncodingParserContext} - URL-Encoding parser context properties.
3.3 - Other notes
- Like all other Juneau parsers, the URL-encoding parsers are thread safe and maintain 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 URL-encoding 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 JSON.
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.server.RestServletDefault}.
This includes URL-encoding serialization/parsing support by default, in addition to several other media types.
- Create a servlet that subclasses from {@link org.apache.juneau.server.RestServlet} and specify the
URL-encoding serializer and/or parser using the {@link org.apache.juneau.server.annotation.RestResource#serializers()} and
{@link org.apache.juneau.server.annotation.RestResource#parsers()} on the entire servlet class, or
the {@link org.apache.juneau.server.annotation.RestMethod#serializers()} and {@link org.apache.juneau.server.annotation.RestMethod#parsers()}
annotations on individual methods within the class.
- Register {@link org.apache.juneau.server.jaxrs.DefaultProvider} with JAX-RS.
This includes URL-encoding serialization/parsing support by default, in addition to several other media types.
- Create and register a subclass of {@link org.apache.juneau.server.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 RestServletDefault
The quickest way to implement a REST resource with URL-encoding support is to create a subclass of {@link org.apache.juneau.server.RestServletDefault}.
This class provides support for JSON, XML, HTML, URL-Encoding, and others.
The AddressBookResource
example shown in the first chapter uses the RestServletJenaDefault
class
which is a subclass of RestServletDefault
with additional support for RDF languages.
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=UonSerializerContext.UON_simpleMode, value="true"),
@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.
In this case, we're overriding the UON_simpleMode property to produce lax UON notation.
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.server.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.server} 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.server.annotation.RestResource#children()} annotation.
4.1.2 - Using RestServlet with annotations
For fine-tuned control of media types, the {@link org.apache.juneau.server.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 URL-encoding using
the following definition:
@RestResource(
serializers={UrlEncodingSerializer.class},
parsers={UrlEncodingParser.class},
properties={
@Property(name=UonSerializerContext.UON_simpleMode, value="true")
}
)
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={UrlEncodingSerializer.class},
parsers={UrlEncodingParser.class},
properties={
@Property(name=UonSerializerContext.UON_simpleMode, value="true")
}
)
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.server.annotation.RestMethod#serializersInherit()} and
{@link org.apache.juneau.server.annotation.RestMethod#parsersInherit()} control how various artifacts
are inherited from the parent class.
Refer to {@link org.apache.juneau.server} for additional information on using these annotations.
4.1.3 - Using JAX-RS DefaultProvider
URL-encoding media type support in JAX-RS can be achieved by using the {@link org.apache.juneau.server.jaxrs.DefaultProvider} class.
It implements the JAX-RS MessageBodyReader
and MessageBodyWriter
interfaces for all Juneau supported media types.
The DefaultProvider
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
"text/uon,"+ // UonSerializer
"application/x-www-form-urlencoded,"+ // UrlEncodingSerializer
"text/xml+soap,"+ // SoapXmlSerializer
"application/x-java-serialized-object" // JavaSerializedObjectSerializer
)
@Consumes(
"application/json,text/json,"+ // JsonParser
"text/xml,"+ // XmlParser
"text/html,"+ // HtmlParser
"text/uon,"+ // UonParser
"application/x-www-form-urlencoded,"+ // UrlEncodingParser
"application/x-java-serialized-object" // JavaSerializedObjectParser
)
@JuneauProvider(
serializers={
JsonSerializer.class,
JsonSerializer.Simple.class,
JsonSchemaSerializer.class,
XmlDocSerializer.class,
XmlDocSerializer.Simple.class,
XmlSchemaDocSerializer.class,
HtmlDocSerializer.class,
UonSerializer.class,
UrlEncodingSerializer.class,
SoapXmlSerializer.class,
JavaSerializedObjectSerializer.class
},
parsers={
JsonParser.class,
XmlParser.class,
HtmlParser.class,
UonParser.class,
UrlEncodingParser.class,
JavaSerializedObjectParser.class,
}
)
public final class DefaultProvider 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.server.jaxrs.DefaultProvider
Properties can be specified on providers through the {@link org.apache.juneau.server.jaxrs.JuneauProvider#properties()} annotation.
Properties can also be specified at the method level by using the {@link org.apache.juneau.server.annotation.RestMethod#properties} annotation, like so:
@GET
@Produces("*/*")
@RestMethod( /* Override some properties */
properties={
@Property(name=UonSerializerContext.UON_simpleMode, value="true")
}
)
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 JsonSerializer
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.server.RestResponse#setProperty(String,Object)}
method, and through the {@link org.apache.juneau.server.annotation.Properties} annotation.
There is no equivalent in JAX-RS.
4.1.4 - Using JAX-RS BaseProvider with annotations
To provide support for only JSON media types, you can define your own provider class, like so:
@Provider
@Produces(
"application/x-www-form-urlencoded" // UrlEncodingSerializer
)
@Consumes(
"application/x-www-form-urlencoded" // UrlEncodingParser
)
@JuneauProvider(
serializers={
UrlEncodingSerializer.class
},
parsers={
UrlEncodingParser.class,
}
properties={
@Property(name=UonSerializerContext.UON_simpleMode, value="true")
}
)
public final class MyUrlEncodingProvider extends BaseProvider {}
Then register it with Wink the same way as DefaultProvider
.
4.2 - REST client support
The {@link org.apache.juneau.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 the URL-encoding media type on HTTP requests and responses can be done in one line of code:
// Create a client to handle URL-encoded requests and responses.
RestClient client = new RestClient(UrlEncodingSerializer.class, UrlEncodingParser.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);
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