Apache Juneau Overview

Table of Contents
  1. Introduction

    1. Features

    2. Components

  2. juneau-marshall

    1. Serializers

    2. Parsers

    3. SerializerGroups and ParserGroups

    4. ObjectMap and ObjectList

    5. Configurable Properties

    6. Contexts, Builders, Sessions, and PropertyStores

    7. Transforms

      1. PojoSwaps

      2. Per-media-type PojoSwaps

      3. One-way PojoSwaps

      4. @Swap Annotation

      5. Templated Swaps

      6. Swap Methods

      7. Surrogate Classes

      8. @Bean Annotation

      9. @BeanProperty Annotation

      10. @BeanConstructor Annotation

      11. @BeanIgnore Annotation

      12. @NameProperty Annotation

      13. @ParentProperty Annotation

      14. POJO Builders

      15. URIs

      16. BeanFilters

      17. Interface filters

      18. Stop Classes

      19. Bypass Serialization using Readers and InputStreams

    8. Bean Name and Dictionaries

      1. Bean Subtypes

    9. Virtual Beans

    10. Reading Continuous Streams

    11. Comparison with Jackson

    12. POJO Categories

    13. Best Practices

    14. Additional Information

      1. JSON

      2. XML

      3. HTML

      4. UON

      5. URL-Encoding

  3. juneau-marshall-rdf

  4. juneau-dto

    1. HTML5

    2. Atom

    3. Swagger

  5. juneau-svl

    1. Simple Variable Language

    2. SVL Variables

    3. VarResolvers and VarResolverSessions

    4. Other Notes

  6. juneau-config

    1. Overview

      1. Syntax Rules

    2. Entry Types

      1. Primitive Types

      2. POJOs

      3. Arrays

      4. Collections

      5. Binary Data

    3. Variables

      1. Logic Variables

    4. Encoded Entries

    5. Section Maps

    6. Section Beans

    7. Section Interfaces

    8. Setting Values

      1. File System Changes

      2. Custom Entry Serialization

      3. Setting Values in Bulk

    9. Listeners

    10. Serializing

    11. Config Stores

      1. ConfigMemoryStore

      2. ConfigFileStore

      3. Custom ConfigStores

      4. ConfigStore Listeners

    12. Read-only Configs

    13. Closing Configs

  7. juneau-rest-server

    1. Hello World Example

    2. Class Hierarchy

    3. Instantiation

      1. RestServlet

      2. BasicRestServlet

      3. Children

      4. Router Pages

      5. Resource Resolvers

      6. Lifecycle Hooks

    4. @RestResource

      1. Annotation Inheritance

    5. RestContext

    6. @RestMethod

      1. Java Method Parameters

      2. RestRequest

      3. RestResponse

      4. RequestBody

      5. RequestHeaders

      6. RequestQuery

      7. RequestFormData

      8. @RestMethod.path()

      9. RequestPathMatch

      10. Method Return Types

      11. ReaderResource

      12. StreamResource

      13. Redirect

      14. @RestMethod.matchers()

    7. @Body

      1. Handling Form Posts

      2. Handling Multi-Part Form Posts

    8. @FormData

    9. @Query

    10. @Header

    11. Serializers

    12. Parsers

    13. Properties

    14. Transforms

    15. Guards

    16. Converters

    17. Messages

    18. Encoders

    19. SVL Variables

    20. Configuration Files

    21. Static files

    22. Client Versioning

    23. OPTIONS pages

      1. RestInfoProvider

      2. BasicRestInfoProvider

    24. @HtmlDoc

      1. Widgets

      2. Predefined Widgets

      3. UI Customization

      4. Stylesheets

    25. Default Headers

    26. Logging and Error Handling

    27. HTTP Status Codes

    28. Overloading HTTP Methods

    29. Built-in Parameters

    30. Custom Serializers and Parsers

    31. Using with OSGi

    32. Remoteable Proxies

      1. Client Side

      2. Server Side

      3. @Remoteable Annotation

    33. Using with Spring and Injection frameworks

    34. Using HTTP/2 features

    35. Predefined Label Beans

    36. Other Notes

  8. juneau-rest-server-jaxrs

    1. Juneau JAX-RS Provider

  9. juneau-rest-client

    1. Interface Proxies Against 3rd-party REST Interfaces

    2. SSL Support

      1. SSLOpts Bean

    3. Authentication

      1. BASIC Authentication

      2. FORM-based Authentication

      3. OIDC Authentication

    4. Using Response Patterns

    5. Piping Response Output

    6. Debugging

    7. Logging

    8. Interceptors

    9. Remoteable Proxies

    10. Other Useful Methods

  10. juneau-microservice-server

    1. Microservice Introduction

    2. Getting Started

      1. Installing in Eclipse

      2. Running in Eclipse

      3. Building and Running from Command-Line

    3. Manifest File

      1. Manifest API

    4. Config

      1. Config API

    5. Resource Classes

    6. Predefined Resource Classes

    7. RestMicroservice

      1. Extending RestMicroservice

  11. juneau-examples-core

  12. juneau-examples-rest

    1. RootResources

    2. HelloWorldResource

    3. MethodExampleResource

    4. UrlEncodedFormResource

    5. RequestEchoResource

    6. AddressBookResource

      1. Classes

      2. Demo

      3. Traversable

      4. Queryable

      5. Introspectable

      6. ClientTest

      7. Browser Tips

    7. SampleRemoteableServlet

    8. TempDirResource

    9. AtomFeedResource

    10. DockerRegistryResource

    11. TumblrParserResource

    12. PhotosResource

    13. JsonSchemaResource

    14. SqlQueryResource

    15. ConfigResource

    16. LogsResource

  13. Security Best-Practices

  14. Release Notes

1 - Introduction

Juneau is a single cohesive framework consisting of the following parts:

Questions via email to dev@juneau.apache.org are always welcome.

Juneau is packed with features that may not be obvious at first. Users are encouraged to ask for code reviews by providing links to specific source files such as through GitHub. Not only can we help you with feedback, but it helps us understand usage patterns to further improve the product.

History

Juneau started off as a popular internal IBM toolkit called Juno. Originally used for serializing POJOs to and from JSON, it later expanded in scope to include a variety of content types, and then later REST servlet, client, and microservice APIs. It's use grew to more than 50 projects and was one of the most popular community source projects within IBM.

In June of 2016, the code was donated to the Apache Foundation under the project Apache Juneau where it has continued to evolve to where it is today.

1.1 - Features

1.2 - Components

We've strived to keep prerequisites to an absolute minimum in order to make adoption as easy as possible.

The library consists of the following artifacts found in the Maven group "org.apache.juneau":

CategoryMaven ArtifactsDescriptionPrerequisites
juneau-core juneau-marshall Serializers and parsers for:
  • JSON
  • XML
  • HTML
  • UON
  • URL-Encoding
  • MessagePack
  • SOAP/XML
  • CSV
  • BSON (coming soon)
  • YAML (coming soon)
  • Protobuf (coming soon)
  • Java 6
juneau-marshall-rdf Serializers and parsers for:
  • RDF/XML
  • RDF/XML-Abbrev
  • N-Triple
  • Turtle
  • N3
  • Java 6
  • Apache Jena 2.7.1
juneau-dto Data Transfer Objects for:
  • HTML5
  • Atom
  • Cognos
  • JSON-Schema
  • Swagger 2.0
  • Java 6
juneau-svl Simple Variable Language API
  • Java 6
juneau-config Configuration file API
  • Java 6
juneau-rest juneau-rest-server REST Servlet API
  • Java 6
  • Servlet 3.1
juneau-rest-server-jaxrs Optional JAX-RS support
  • Java 6
  • JAX-RS 2.0
juneau-rest-client REST Client API
  • Java 6
  • Apache HttpClient 4.5.3
juneau-microservice juneau-microservice-server REST Microservice Server API
  • Java 8
  • Eclipse Jetty 9.4.3
juneau-examples juneau-examples-core Core code examples
juneau-examples-rest REST code examples
juneau-all juneau-all Combination of the following:
  • juneau-marshall
  • juneau-dto
  • juneau-svl
  • juneau-config
  • juneau-rest-server
  • juneau-rest-client
  • Java 6
  • Servlet 3.1
  • Apache HttpClient 4.5.3

Each component are also packaged as stand-alone OSGi modules.

2 - juneau-marshall

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-marshall</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-marshall-7.0.0.jar

OSGi Module

org.apache.juneau.marshall_7.0.0.jar

The juneau-marshall artifact contains the API for defining serializers and parsers, and marshalling support for JSON, XML, HTML, URL-Encoding, UON and others.

It also defines many convenience utility classes used throughout the framework.

2.1 - Serializers

One of the goals of Juneau was to make serialization as simple as possible. In a single line of code, you should be able to serialize and parse most POJOs. Despite this simplicity, Juneau provides lots of extensibility and configuration properties for tailoring how POJOs are serialized and parsed.

The built-in serializers in Juneau are fast, efficient, and highly configurable. They work by serializing POJOs directly to streams instead of using intermediate Document Object Model objects.

In most cases, you can serialize objects in one line of code by using one of the default serializers:

// A simple bean public class Person { public String name = "John Smith"; public int age = 21; } // Serialize to JSON, XML, or HTML Person p = new Person(); // Produces: // "{name:'John Smith',age:21}" String json = JsonSerializer.DEFAULT.serialize(p); // Produces: // <object> // <name>John Smith</name> // <age>21</age> // </object> String xml = XmlSerializer.DEFAULT.serialize(p); // Produces: // <table> // <tr><th>key</th><th>value</th></tr> // <tr><td>name</td><td>John Smith</td></tr> // <tr><td>age</td><td>21</td></tr> // </table> String html = HtmlSerializer.DEFAULT.serialize(p); // Produces: // "(name='John Smith',age=21)" String uon = UonSerializer.DEFAULT.serialize(p); // Produces: // "name='John+Smith'&age=21" String urlencoding = UrlEncodingSerializer.DEFAULT.serialize(p); // Produces: // 82 A4 6E 61 6D 65 AA 4A 6F 68 6E 20 53 6D 69 74 68 A3 61 67 65 15 byte[] b = MsgPackSerializer.DEFAULT.serialize(p);

In addition to the default serializers, customized serializers can be created using various built-in options:

// Use one of the default serializers to serialize a POJO String json = JsonSerializer.DEFAULT.serialize(someObject); // Create a custom serializer for lax syntax using single quote characters JsonSerializer serializer = JsonSerializer.create().simple().sq().build(); // Clone an existing serializer and modify it to use single-quotes JsonSerializer serializer = JsonSerializer.DEFAULT.builder().sq().build(); // Serialize a POJO to JSON String json = serializer.serialize(someObject);

Default serialization support is provided for Java primitives, Maps, Collections, beans, and arrays.
Extensible support for other data types such as Calendars, Dates, Iterators is available through the use of POJO swaps (described later).

2.2 - Parsers

Parsers work by parsing input directly into POJOs instead of having to create intermediate Document Object Models. This allows them to parse input with minimal object creation.

Like the serializers, you can often parse objects in one line of code by using one of the default parsers:

// Use one of the predefined parsers. Parser parser = JsonParser.DEFAULT; // Parse a JSON object as a bean. String json = "{name:'John Smith',age:21}"; Person p = parser.parse(json, Person.class); // Or parse it into a generic Map. Map m1 = parser.parse(json, Map.class); // Parse a JSON string. json = "'foobar'"; String s2 = parser.parse(json, String.class); // Parse a JSON number as a Long or Float. json = "123"; Long l3 = parser.parse(json, Long.class); Float f3 = parser.parse(json, Float.class); // Parse a JSON object as a HashMap<String,Person>. json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}"; Map<String,Person> m4 = parser.parse(json, HashMap.class, String.class, Person.class) // Parse a JSON object as a HashMap<String,LinkedList<Person>>. json = "{a:[{name:'John Smith',age:21},{name:'Joe Smith',age:42}]}"; Map<String,List<Person>> m5 = parser.parse(json, HashMap.class, String.class, LinkedList.class, Person.class) // Parse a JSON array of integers as a Collection of Integers or int[] array. json = "[1,2,3]"; List<Integer> l6 = parser.parse(json, LinkedList.class, Integer.class); int[] i7 = parser.parse(json, int[].class);

The parsers can also be used to populating existing bean and collection objects:

// Use one of the predefined parsers. Parser parser = JsonParser.DEFAULT; // Populate the properties on an existing bean from a JSON object. String json = "{name:'John Smith',age:21}"; Person p = new Person(); parser.parseIntoBean(json, p); // Populate an existing list from a JSON array of numbers. json = "[1,2,3]"; List<Integer> l2 = new LinkedList<Integer>(); parser.parseIntoCollection(json, l2, Integer.class); // Populate an existing map from a JSON object containing beans. json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}"; Map<String,Person> m3 = new TreeMap<String,Person>(); parser.parseIntoMap(json, m3, String.class, Person.class);

2.3 - SerializerGroups and ParserGroups

Above the serializers and parsers are the {@link org.apache.juneau.serializer.SerializerGroup} and {@link org.apache.juneau.parser.ParserGroup} classes. These classes allow serializers and parsers to be retrieved by W3C-compliant HTTP Accept and Content-Type values...

// Construct a new serializer group with configuration parameters that get applied to all serializers. SerializerGroup sg = SerializerGroup.create() .append(JsonSerializer.class, UrlEncodingSerializer.class); .ws // or .useWhitespace(true) .pojoSwaps(CalendarSwap.ISO8601DT.class) .build(); // Find the appropriate serializer by Accept type and serialize our POJO to the specified writer. sg.getSerializer("text/invalid, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0") .serialize(myPersonObject, myWriter); // Construct a new parser group with configuration parameters that get applied to all parsers. ParserGroup pg = ParserGroup.create() .append(JsonSerializer.class, UrlEncodingSerializer.class); .pojoSwaps(CalendarSwap.ISO8601DT.class) .build(); Person p = pg.getParser("text/json").parse(myReader, Person.class);

The REST servlet API builds upon the SerializerGroup and ParserGroup classes to provide annotated REST servlets that automatically negotiate the HTTP media types and allow the developer to work with requests and responses as POJOs.

2.4 - ObjectMap and ObjectList

The {@link org.apache.juneau.ObjectMap} and {@link org.apache.juneau.ObjectList} classes are generic Java representations of JSON objects and arrays. These classes can be used to create "unstructured" models for serialization (as opposed to "structured" models consisting of beans). If you want to quickly generate JSON/XML/HTML from generic maps/collections, or parse JSON/XML/HTML into generic maps/collections, these classes work well.

These classes extend directly from the following JCF classes:

The ObjectMap and ObjectList classes are very similar to the JSONObject and JSONArray classes found in other libraries. However, the names were chosen because the concepts of Maps and Lists are already familiar to Java programmers, and these classes can be used with any of the serializers or parsers.

These object can be serialized in one of two ways:

  1. Using the provided {@link org.apache.juneau.ObjectMap#serializeTo(java.io.Writer)} or {@link org.apache.juneau.ObjectList#serializeTo(java.io.Writer)} methods.
  2. Passing them to one of the {@link org.apache.juneau.serializer.Serializer} serialize methods.
  3. Simply calling the {@link org.apache.juneau.ObjectMap#toString()} or {@link org.apache.juneau.ObjectList#toString()} methods which will serialize it as Simplified JSON.

Any valid JSON can be parsed into an unstructured model consisting of generic {@link org.apache.juneau.ObjectMap} and {@link org.apache.juneau.ObjectList} objects.
(In theory, any valid XML can also be parsed into an unstructured model, although this has not been officially 'tested')

// Parse an arbitrary JSON document into an unstructered data model // consisting of ObjectMaps, ObjectLists, and java primitive objects. Parser parser = JsonParser.DEFAULT; String json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}"; ObjectMap m = parser.parse(json, ObjectMap.class); // Use ObjectMap API to extract data from the unstructured model. int johnSmithAge = m.getObjectMap("a").getInt("age"); // Convert it back into JSON. json = JsonSerializer.DEFAULT.serialize(m); // Or convert it to XML. String xml = XmlSerializer.DEFAULT.serialize(m); // Or just use toString(). json = m.toString();

The ObjectMap and ObjectList classes have many convenience features:

// Convert the map to a bean. MyBean m = objectMap.cast(MyBean.class); // Find entries by multiple keys. MyBean m = objectMap.find(MyBean.class, "key1", "key2"); // Fluent-style appenders. objectMap.append("key1", "val1").append("key2", "val2"); // REST-like functions for manipulating nodes in the data structure using URL-like notation. objectMap.getAt("foo/bar/myBean", MyBean.class); objectMap.putAt("foo/bar/myBean", MyBean.class); objectMap.postAt("foo/bar/myListOfBeans", MyBean.class); objectMap.deleteAt("foo/bar/myBean"); // Copy with inclusion or exclusion. ObjectMap m2 = objectMap.include("key1", "key2", "key3"); ObjectMap m3 = objectMap.exclude("key1", "key2", "key3"); // Serialize using another serializer. String xml = objectMap.serializeTo(XmlSerializer.DEFAULT); // Nested maps. objectMap.setInner(objectMapInner);

2.5 - Configurable Properties

Serializers and parsers have a wide variety of configurable properties.
They all extend from the {@link org.apache.juneau.BeanContextBuilder} class that allows you to easily construct new instances from scratch or build upon existing instances.
For example, the following code shows how to configure a JSON serializer:

WriterSerializer s = JsonSerializer .create() // Create a JsonSerializerBuilder .simple() // Simple mode .ws() // Use whitespace .sq() // Use single quotes .build(); // Create a JsonSerializer

Configurable settings can also be set declaratively.
The following produces the same serializer.

WriterSerializer s = JsonSerializer .create() .set(JSON_simpleMode, true) .set(SERIALIZER_useWhitespace, true) .set(SERIALIZER_quoteChar, "'") .build();

However, each of the serializers and parsers already contain reusable instances with common configurations.
For example, JSON has the following predefined reusable serializers and parsers:

These can be used directly, as follows:

// Serialize a POJO to LAX JSON. String json = JsonSerializer.DEFAULT_LAX.serialize(myPojo);

For performance reasons, serializers and parsers are immutable. However, they can be 'copied' and modified using the builder() method.

// Clone and customize an existing serializer. WriterSerializer s = JsonSerializer.DEFAULT_LAX .builder() // Create a new builder with copied settings. .quoteChar('"') // Use a different quote character. .build();

Additional Information

The following is a list of all configurable properties across all serializers and parsers.

2.6 - Contexts, Builders, Sessions, and PropertyStores

All the serializers, parsers, and REST server/client classes use the following design pattern:

For example, the class hierarchy for JsonSerializer is:

Each context object in the hierarchy define properties that can be stored in a PropertyStore such as SERIALIZER_useWhitespace or JSON_simpleMode.

The class hierarchy for JsonSerializerBuilder is:

The class hierarchy for JsonSerializerSession is:

The general idea behind a {@link org.apache.juneau.PropertyStore} is to serve as a reusable configuration of an artifact (such as a serializer) such that the artifact can be cached and reused if the property stores are 'equal'.

For example, two serializers of the same type created with the same configuration will always end up being the same serializer:

// Two serializers created with identical configurations will always be the same copy. WriterSerializer s1 = JsonSerializer.create().pojoSwaps(MySwap.class).simple().build(); WriterSerializer s2 = JsonSerializer.create().set(JSON_simpleMode, true).pojoSwaps(MySwap.class).build(); assert(s1 == s2);

This has the effect of significantly improving performance, especially if you're creating many serializers and parsers.

The {@link org.apache.juneau.PropertyStoreBuilder} class is used to build up and instantiate immutable PropertyStore objects.

In the example above, the property store being built looks like the following:

PropertyStore ps = PropertyStore .create() .set("BeanContext.pojoSwaps.lc", MySwap.class) .set("JsonSerializer.simpleMode.b", true) .build();

Property stores are immutable, comparable, and their hashcodes are calculated exactly one time. That makes them particularly suited for use as hashmap keys, and thus for caching reusable serializers and parsers.

Refer to the {@link org.apache.juneau.PropertyStore} javadoc for a detailed explaination on how property stores work.

2.7 - Transforms

By default, the Juneau framework can serialize and parse a wide variety of POJOs out-of-the-box.
However, two special classes are provided tailor how certain Java objects are handled by the framework.
These classes are:

Transforms are added to serializers and parsers (and REST clients) using the following configuration properties:

Annotations are also provided for specifying transforms directly on classes and methods (all described in later sections):

2.7.1 - PojoSwaps

{@link org.apache.juneau.transform.PojoSwap PojoSwaps} are a critical component of Juneau. They allow the serializers and parsers to handle Java objects that wouldn't normally be serializable.

Swaps are, simply put, 'object swappers' that swap in serializable objects for non-serializable ones during serialization, and vis-versa during parsing.

Some examples of non-serializable POJOs are File, Reader, Iterable, etc... These are classes that aren't beans and cannot be represented as simple maps, collections, or primitives.

In the following example, we introduce a PojoSwap that will swap in ISO8601 strings for Date objects:

// Sample swap for converting Dates to ISO8601 strings. public class MyDateSwap extends PojoSwap<Date,String> { // ISO8601 formatter. private DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // Converts a Date object to an ISO8601 string. @Override /* PojoSwap */ public String swap(BeanSession session, Date o) { return format.format(o); } // Converts an ISO8601 string to a Date object. @Override /* PojoSwap */ public Date unswap(BeanSession session, String o, ClassMeta hint) throws Exception { return format.parse(o); } }

The swap can then be associated with serializers and parsers like so:

// Sample bean with a Date field. public class MyBean { public Date date = new Date(112, 2, 3, 4, 5, 6); } // Create a new JSON serializer, associate our date swap with it, and serialize a sample bean. WriterSerializer s = JsonSerializer.create().simple().pojoSwaps(MyDateSwap.class).build(); String json = s.serialize(new MyBean()); // == "{date:'2012-03-03T04:05:06-0500'}" // Create a JSON parser, associate our date swap with it, and reconstruct our bean (including the date). ReaderParser p = JsonParser.create().pojoSwaps(MyDateSwap.class).build(); MyBean bean = p.parse(json, MyBean.class); int day = bean.date.getDay(); // == 3

The {@link org.apache.juneau.BeanMap#get(Object)} and {@link org.apache.juneau.BeanMap#put(String,Object)} methods will automatically convert to swapped values as the following example shows:

// Create a new bean context and add our swap. BeanContext bc = BeanContext.create().pojoSwaps(MyDateSwap.class).build(); // Create a new bean. MyBean myBean = new MyBean(); // Wrap it in a bean map. BeanMap<Bean> beanMap = bc.forBean(myBean); // Use the get() method to get the date field as an ISO8601 string. String date = (String)beanMap.get("date"); // == "2012-03-03T04:05:06-0500" // Use the put() method to set the date field to an ISO8601 string. beanMap.put("date", "2013-01-01T12:30:00-0500"); // Set it to a new value. // Verify that the date changed on the original bean. int year = myBean.date.getYear(); // == 113

Another example of a PojoSwap is one that converts byte[] arrays to BASE64-encoded strings:

public class ByteArrayBase64Swap extends StringSwap<byte[]> { @Override /* StringSwap */ public String swap(byte[] b) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream b64os = MimeUtility.encode(baos, "base64"); b64os.write(b); b64os.close(); return new String(baos.toByteArray()); } @Override /* StringSwap */ public byte[] unswap(String s, ClassMeta<?> hint) throws Exception { byte[] b = s.getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(b); InputStream b64is = MimeUtility.decode(bais, "base64"); byte[] tmp = new byte[b.length]; int n = b64is.read(tmp); byte[] res = new byte[n]; System.arraycopy(tmp, 0, res, 0, n); return res; } }

The following example shows the BASE64 swap in use:

// Create a JSON serializer and register the BASE64 encoding swap with it. WriterSerializer s = JsonSerializer.create().simple().pojoSwaps(ByteArrayBase64Swap.class).build(); ReaderParser p = JsonParser.create().pojoSwaps(ByteArrayBase64Swap.class).build(); byte[] bytes = {1,2,3}; String json = s.serialize(bytes); // Produces "'AQID'" bytes = p.parse(json, byte[].class); // Reproduces {1,2,3} byte[][] bytes2d = {{1,2,3},{4,5,6},null}; json = s.serialize(bytes2d); // Produces "['AQID','BAUG',null]" bytes2d = p.parse(json, byte[][].class); // Reproduces {{1,2,3},{4,5,6},null}

Several PojoSwaps are already provided for common Java objects:

In particular, the {@link org.apache.juneau.transforms.CalendarSwap} and {@link org.apache.juneau.transforms.DateSwap} transforms provide a large number of customized swaps to ISO, RFC, or localized strings.

Swaps have access to the session locale and timezone through the {@link org.apache.juneau.BeanSession#getLocale()} and {@link org.apache.juneau.BeanSession#getTimeZone()} methods.
This allows you to specify localized swap values when needed.

If using the REST server API, the locale and timezone are set based on the Accept-Language and Time-Zone headers on the request.

  • The 'swapped' class type must be a serializable type.
    See the definition for Category 4 objects in POJO Categories.

2.7.2 - Per-media-type PojoSwaps

Swaps can also be defined per-media-type.

The {@link org.apache.juneau.transform.PojoSwap#forMediaTypes()} method can be overridden to provide a set of media types that the swap is invoked on.
It's also possible to define multiple swaps against the same POJO as long as they're differentiated by media type.
When multiple swaps are defined, the best-match media type is used.

In the following example, we define 3 swaps against the same POJO. One for JSON, one for XML, and one for all other types.

public class PojoSwapTest { public static class MyPojo {} public static class MyJsonSwap extends PojoSwap<MyPojo,String> { @Override /* PojoSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/json"); } @Override /* PojoSwap */ public String swap(BeanSession session, MyPojo o) throws Exception { return "It's JSON!"; } } public static class MyXmlSwap extends PojoSwap<MyPojo,String> { @Override /* PojoSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/xml"); } @Override /* PojoSwap */ public String swap(BeanSession session, MyPojo o) throws Exception { return "It's XML!"; } } public static class MyOtherSwap extends PojoSwap<MyPojo,String> { @Override /* PojoSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/*"); } @Override /* PojoSwap */ public String swap(BeanSession session, MyPojo o) throws Exception { return "It's something else!"; } } @Test public void doTest() throws Exception { SerializerGroup g = SerializerGroup.create() .append(JsonSerializer.class, XmlSerializer.class, HtmlSerializer.class) .sq() .pojoSwaps(MyJsonSwap.class, MyXmlSwap.class, MyOtherSwap.class) .build(); MyPojo myPojo = new MyPojo(); String json = g.getWriterSerializer("text/json").serialize(myPojo); assertEquals("'It\\'s JSON!'", json); String xml = g.getWriterSerializer("text/xml").serialize(myPojo); assertEquals("<string>It's XML!</string>", xml); String html = g.getWriterSerializer("text/html").serialize(myPojo); assertEquals("<string>It's something else!</string>", html); } }

2.7.3 - One-way PojoSwaps

In the previous sections, we defined two-way swaps, meaning swaps where the original objects could be reconstructing during parsing.
However, there are certain kinds of POJOs that we may want to support for serializing, but that are not possible to reconstruct during parsing.
For these, we can use one-way object swaps.

A one-way POJO swap is simply an object transform that only implements the {@code swap()} method.
The {@code unswap()} method is simply left unimplemented.

An example of a one-way swaps would be one that allows {@code Iterators} to be serialized as JSON arrays.
It can make sense to be able to render {@code Iterators} as arrays, but in general it's not possible to reconstruct an {@code Iterator} during parsing.

public class IteratorSwap extends PojoSwap<Iterator,List> { @Override /* PojoSwap */ public List swap(Iterator o) { List l = new LinkedList(); while (o.hasNext()) l.add(o.next()); return l; } }

Here is an example of our one-way swap being used.
Note that trying to parse the original object will cause a {@link org.apache.juneau.parser.ParseException} to be thrown.

// Create a JSON serializer that can serialize Iterators. WriterSerializer s = JsonSerializer.create().simple().pojoSwaps(IteratorSwap.class).build(); // Construct an iterator we want to serialize. Iterator i = new ObjectList(1,2,3).iterator(); // Serialize our Iterator String json = s.serialize(i); // Produces "[1,2,3]" // Try to parse it. ReaderParser p = JsonParser.create().pojoSwaps(IteratorSwap.class).build(); i = p.parse(s, Iterator.class); // Throws ParseException!!!

2.7.4 - @Swap Annotation

{@link org.apache.juneau.annotation.Swap @Swap} can be used to associate a swap class using an annotation.
This is often cleaner than using the builder pojoSwaps() method since you can keep your swap class near your POJO class.

@Swap(MyPojoSwap.class) public class MyPojo { ... } // Sample swap for converting MyPojo classes to a simple string. public class MyPojoSwap extends PojoSwap<MyPojo,String> { @Override /* PojoSwap */ public String swap(BeanSession session, MyPojo o) { return o.toSomeSerializableForm(); } }

Multiple swaps can be associated with a POJO by using the {@link org.apache.juneau.annotation.Swaps @Swaps} annotation:

@Swaps( { @Swap(MyJsonSwap.class), @Swap(MyXmlSwap.class), @Swap(MyOtherSwap.class) } ) public class MyPojo {}

Readers get serialized directly to the output of a serializer. Therefore it's possible to implement a swap that provides fully-customized output.

public class MyJsonSwap extends PojoSwap<MyPojo,Reader> { public MediaType[] forMediaTypes() { return MediaType.forStrings("*/json"); } public Reader swap(BeanSession session, MyPojo o) throws Exception { return new StringReader("{message:'Custom JSON!'}"); } }

The @Swap annotation can also be used on getters and setters as well to apply a swap to individual property values

@BeanProperty(swap=MyPojoSwap.class) public MyPojo getMyPojo();

2.7.5 - Templated Swaps

The {@link org.apache.juneau.annotation.Swap#template() @Swap.template()} annotation allows you to associate arbitrary contextual strings with swaps.
The primary purpose is for providing template names, such as for Apache FreeMarker, therefore the name 'template'.
However, the usage of the string is open-ended.

For example, you could pair a template string like so:

@Swap(impl=FreeMarkerSwap.class, template="MyPojo.div.ftl") public class MyPojo {}

The implementation of the FreeMarker swap would look something like this:

// Our templated swap class. public class FreeMarkerSwap extends PojoSwap<Object,Reader> { public MediaType[] forMediaTypes() { // Make sure this only applies to the HTML serializer. return MediaType.forStrings("*/html"); } public Reader swap(BeanSession session, Object o, String template) throws Exception { // Call some method that uses FreeMarker to convert 'o' to raw HTML using // the 'MyPojo.div.ftl' template. return getFreeMarkerReader(template, o); } }

2.7.6 - Swap Methods

Various methods can be defined on a class directly to affect how it gets serialized.
This can often be simpler than using PojoSwaps.

Objects serialized as Strings can be parsed back into their original objects by implementing one of the following methods on the class:

  • public static T fromString(String) method.
    Any of the following method names also work:
    • valueOf(String)
    • parse(String)
    • parseString(String)
    • forName(String)
    • forString(String)
  • public T(String) constructor.

Note that these methods cover conversion from several built-in Java types, meaning the parsers can automatically construct these objects from strings:

  • fromString(String) - {@link java.util.UUID}
  • valueOf(String) - {@link java.lang.Boolean}, {@link java.lang.Byte}, {@link java.lang.Double}, {@link java.lang.Float}, {@link java.lang.Integer}, {@link java.lang.Long}, {@link java.lang.Short}, {@link java.sql.Date}, {@link java.sql.Time}, {@link java.sql.Timestamp}
  • parse(String) - {@link java.text.DateFormat}, {@link java.text.MessageFormat}, {@link java.text.NumberFormat}, {@link java.util.Date}, {@link java.util.logging.Level}
  • parseString(String) - {@link javax.xml.bind.DatatypeConverter}
  • forName(String) - {@link java.lang.Class}

If you want to force a bean-like class to be serialized as a string, you can use the {@link org.apache.juneau.annotation.BeanIgnore @BeanIgnore} annotation on the class to force it to be serialized to a string using the toString() method.

Serializing to other intermediate objects can be accomplished by defining a swap method directly on the class:

  • public X swap(BeanSession) method, where X is any serializable object.

The BeanSession parameter allows you access to various information about the current serialization session.
For example, you could provide customized results based on the media type being produced ({@link org.apache.juneau.BeanSession#getMediaType()}).

The following example shows how an HTML5 form template object can be created that gets serialized as a populated HTML5 {@link org.apache.juneau.dto.html5.Form} bean.

import static org.apache.juneau.dto.html5.HtmlBuilder.*; /** * A simple HTML form template whose serialized form is an HTML5 Form object. */ public class FormTemplate { private String action; private int value1; private boolean value2; // Some constructor that initializes our fields. public FormTemplate(String action, int value1, boolean value2) { this.action = action; this.value1 = value1; this.value2 = value2; } // Special swap method that converts this template to a serializable bean public Form swap(BeanSession session) { return form(action, input("text").name("v1").value(value1), input("text").name("v2").value(value2) ); } }

Swapped objects can be converted back into their original form by the parsers by specifying one of the following methods:

  • public static T unswap(BeanSession, X) method where X is the swap class type.
  • public T(X) constructor where X is the swap class type.

The following shows how our form template class can be modified to allow the parsers to reconstruct our original object:

import static org.apache.juneau.dto.html5.HtmlBuilder.*; /** * A simple HTML form template whose serialized form is an HTML5 Form object. * This time with parsing support. */ @Bean(beanDictionary=HtmlBeanDictionary.class) public class FormTemplate { private String action; private int value1; private boolean value2; // Our 'unswap' constructor public FormTemplate(Form f) { this.action = f.getAttr("action"); this.value1 = f.getChild(Input.class, 0) .getAttr(int.class, "value"); this.value2 = f.getChild(Input.class, 1) .getAttr(boolean.class, "value"); } public FormTemplate(String action, int value1, boolean value2) { this.action = action; this.value1 = value1; this.value2 = value2; } public Form swap(BeanSession session) { return form(action, input("text").name("v1").value(value1), input("text").name("v2").value(value2) ); } }

2.7.7 - Surrogate Classes

Surrogate classes are very similar in concept to PojoSwaps except they're simpler to define.

For example, let's say we want to be able to serialize the following class, but it's not serializable for some reason (for example, there are no properties exposed):

// Not serializable because it's not a bean because it has no public properties. public class MyNonSerializableClass { protected String foo; }

This could be solved with the following PojoSwap.

// A serializable bean with 1 property. public class MySerializableSurrogate { public String foo; } // A PojoSwap that swaps out our non-serializable object with our serializable object. public class MySwap extends PojoSwap<MyNonSerializableClass,MySerializableSurrogate> { @Override /* PojoSwap */ public MySerializableSurrogate swap(MyNonSerializableClass o) { // Create some serializable class and manually copy the data into it. MySerializableSurrogate s = new MySerializableSurrogate(); s.foo = o.foo; return s; } }

However, the same can be accomplished by using a surrogate class that simply contains a constructor with the non-serializable class as an argument:

public class MySerializableSurrogate { public String foo; // Constructor takes in our non-serializable object! public MySerializableSurrogate(MyNonSerializableClass c) { this.foo = c.foo; } }

The surrogate class is registered in the same way as a PojoSwap:

// Create a JSON serializer that can serialize Iterators. WriterSerializer s = JsonSerializer.create().pojoSwaps(MySerializableSurrogate.class).build();

When the serializer encounters the non-serializable class, it will serialize an instance of the surrogate instead.

See Also:
  • {@link org.apache.juneau.transform.Surrogate}

2.7.8 - @Bean Annotation

The {@link org.apache.juneau.annotation.Bean @Bean} annotation is used to tailor how beans are interpreted by the framework.

Bean property inclusion and ordering on a bean class can be done using the {@link org.apache.juneau.annotation.Bean#properties() @Bean.properties()} annotation.

// Address class with only street/city/state properties (in that order). // All other properties are ignored. @Bean(properties="street,city,state") public class Address { ... }

Bean properties can be excluded using the {@link org.apache.juneau.annotation.Bean#excludeProperties() @Bean.excludeProperties()} annotation.

// Address class with only street/city/state properties (in that order). // All other properties are ignored. @Bean(excludeProperties="city,state"}) public class Address { ... }

Bean properties can be sorted alphabetically using {@link org.apache.juneau.annotation.Bean#sort() @Bean.sort()}

// Address class with only street/city/state properties (in that order). // All other properties are ignored. @Bean(sort=true) public class MyBean { ... }

The {@link org.apache.juneau.annotation.Bean#propertyNamer() @Bean.propertyNamer()} annotation is used to provide customized naming of properties.

Property namers are used to transform bean property names from standard form to some other form.
For example, the {@link org.apache.juneau.PropertyNamerDLC} will convert property names to dashed-lowercase, and these will be used as attribute names in JSON and element names in XML.

// Define a class with dashed-lowercase property names. @Bean(propertyNamer=PropertyNamerDashedLC.class) public class MyBean { ... }

The {@link org.apache.juneau.annotation.Bean#interfaceClass @Bean.interfaceClass()} annotation is used to limit properties on beans to specific interface classes.
When specified, only the list of properties defined on the interface class will be used during serialization.
Additional properties on subclasses will be ignored.

// Parent class @Bean(interfaceClass=A.class) public abstract class A { public String f0 = "f0"; } // Child class public class A1 extends A { public String f1 = "f1"; } JsonSerializer s = JsonSerializer.DEFAULT_LAX; A1 a1 = new A1(); String r = s.serialize(a1); assertEquals("{f0:'f0'}", r); // Note f1 is not serialized.

Note that this annotation can be used on the parent class so that it filters to all child classes.
Or can be set individually on the child classes.

The {@link org.apache.juneau.annotation.Bean#stopClass @Bean.stopClass()} annotation is another way to limit which properties are serialized (except from the opposite direction).
It's identical in purpose to the stop class specified by {@link java.beans.Introspector#getBeanInfo(Class, Class)}.
Any properties in the stop class or in its base classes will be ignored during analysis.

For example, in the following class hierarchy, instances of C3 will include property p3, but not p1 or p2.

public class C1 { public int getP1(); } public class C2 extends C1 { public int getP2(); } @Bean(stopClass=C2.class) public class C3 extends C2 { public int getP3(); }

The {@link org.apache.juneau.annotation.Bean#propertyFilter() @Bean.propertyFilter()} annotation and {@link org.apache.juneau.transform.PropertyFilter} class can be used to perform interception and inline handling of bean getter and setter calls.

// Register filter on bean class. @Bean(propertyFilter=AddressPropertyFilter.class) public class Address { public String getTaxInfo() {...} public void setTaxInfo(String s) {...} } // Property filter that strips out sensitive information in our bean. public class AddressPropertyFilter extends PropertyFilter { @Override /* PropertyFilter */ public Object readProperty(Object bean, String name, Object value) { if ("taxInfo".equals(name)) return "redacted"; return value; } @Override /* PropertyFilter */ public Object writeProperty(Object bean, String name, Object value) { AddressBook a = (Address)bean; if ("taxInfo".equals(name) && "redacted".equals(value)) return TaxInfoUtils.lookUpByName(a.getStreet(), a.getCity(), a.getState()); return value; } }

2.7.9 - @BeanProperty Annotation

The {@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotation is used to tailor how individual bean properties are interpreted by the framework.

The {@link org.apache.juneau.annotation.BeanProperty#name() @BeanProperty.name()} annotation is used to override the name of the bean property.

public class MyBean { @BeanProperty(name="Bar") public String getFoo() {...} }

If the {@link org.apache.juneau.BeanContext#BEAN_beanFieldVisibility} setting on the bean context excludes this field (e.g. the visibility is set to the default of PUBLIC, but the field is PROTECTED), this annotation can be used to force the field to be identified as a property.

public class MyBean { @BeanProperty protected String getFoo() {...} }

The bean property named "*" is the designated "dynamic property" which allows for "extra" bean properties not otherwise defined.
This is similar in concept to the Jackson @JsonGetterAll and @JsonSetterAll annotations, but generalized for all supported marshall languages.
The primary purpose is for backwards compatibility in parsing newer streams with addition information into older beans.

The following shows various ways of using dynamic bean properties.

// Option #1 - A simple public Map field. // The field name can be anything. public class BeanWithDynaField { @BeanProperty(name="*") public Map<String,Object> extraStuff = new LinkedHashMap<String,Object>(); } // Option #2 - Getters and setters. // Method names can be anything. // Getter must return a Map with String keys. // Setter must take in two arguments, a String and Object. public class BeanWithDynaMethods { @BeanProperty(name="*") public Map<String,Object> getMyExtraStuff() { ... } @BeanProperty(name="*") public void setAnExtraField(String name, Object value) { ... } } // Option #3 - Getter only. // Properties will be added through the getter. public class BeanWithDynaGetterOnly { @BeanProperty(name="*") public Map<String,Object> getMyExtraStuff() { ... } }

Similar rules apply for value types and swaps.
The property values optionally can be any serializable type or use swaps.

// A serializable type other than Object. public class BeanWithDynaFieldWithListValues { @BeanProperty(name="*") public Map<String,List<String>> getMyExtraStuff() { ... } } // A swapped value. public class BeanWithDynaFieldWithSwappedValues { @BeanProperty(name="*", swap=CalendarSwap.ISO8601DTZ.class) public Map<String,Calendar> getMyExtraStuff() { ... } }

  • Note that if you're not interested in these additional properties, you can also use the {@link org.apache.juneau.BeanContext#BEAN_ignoreUnknownBeanProperties} setting to ignore values that don't fit into existing properties.

The {@link org.apache.juneau.annotation.BeanProperty#value() @BeanProperty.value()} annotation is a synonym for {@link org.apache.juneau.annotation.BeanProperty#name() @BeanProperty.name()}.
Use it in cases where you're only specifying a name so that you can shorten your annotation.

The following annotations are equivalent:

@BeanProperty(name="foo") @BeanProperty("foo")

The {@link org.apache.juneau.annotation.BeanProperty#type() @BeanProperty.type()} annotation is used to identify a specialized class type for a generalized property.
Normally the type is inferred through reflection of the field type or getter return type.
However, you'll want to specify this value if you're parsing beans where the bean property class is an interface or abstract class to identify the bean type to instantiate.
Otherwise, you may cause an {@link java.lang.InstantiationException} when trying to set these fields.

This property must denote a concrete class with a no-arg constructor.

public class MyBean { // Identify concrete type as a HashMap. @BeanProperty(type=HashMap.class) public Map p1;

The {@link org.apache.juneau.annotation.BeanProperty#params() @BeanProperty.params()} annotation is for bean properties of type map or collection.
It's used to identify the class types of the contents of the bean property object when the general parameter types are interfaces or abstract classes.

public class MyBean { // This is a HashMap<String,Integer>. @BeanProperty(type=HashMap.class, params={String.class,Integer.class}) public Map p1; }

The {@link org.apache.juneau.annotation.BeanProperty#properties() @BeanProperty.properties()} annotation is used to limit which child properties are rendered by the serializers.
It can be used on any of the following bean property types:

  • Beans - Only render the specified properties of the bean.
  • Maps - Only render the specified entries in the map.
  • Bean/Map arrays - Same, but applied to each element in the array.
  • Bean/Map collections - Same, but applied to each element in the collection.

public class MyClass { // Only render 'f1' when serializing this bean property. @BeanProperty(properties={"f1"}) public MyChildClass x1 = new MyChildClass(); } public class MyChildClass { public int f1 = 1; public int f2 = 2; } // Renders "{x1:{f1:1}}" String json = JsonSerializer.DEFAULT.serialize(new MyClass());

The {@link org.apache.juneau.annotation.BeanProperty#format() @BeanProperty.format()} annotation specifies a String format for converting a bean property value to a formatted string.

// Serialize a float as a string with 2 decimal places. @BeanProperty(format="$%.2f") public float price;

2.7.10 - @BeanConstructor Annotation

The {@link org.apache.juneau.annotation.BeanConstructor @BeanConstructor} annotation is used to map constructor arguments to property names on bean with read-only properties.
Since method parameter names are lost during compilation, this annotation essentially redefines them so that they are available at runtime.

The definition of a read-only bean is a bean with properties with only getters, like shown below:

// Our read-only bean. public class Person { private final String name; private final int age; @BeanConstructor(properties="name,age"}) public Person(String name, int age) { this.name = name; this.age = age; } // Read only properties. // Getters, but no setters. public String getName() { return name; } public int getAge() { return age; } }

// Parsing into a read-only bean. String json = "{name:'John Smith',age:45}"; Person p = JsonParser.DEFAULT.parse(json); String name = p.getName(); // "John Smith" int age = p.getAge(); // 45

Beans can also be defined with a combination of read-only and read-write properties.

2.7.11 - @BeanIgnore Annotation

The {@link org.apache.juneau.annotation.BeanIgnore @BeanIgnore} annotation is used to ignore classes, fields, and methods from being interpreted as beans or bean components.

When applied to classes, objects will be converted to strings even though they look like beans.

// Not really a bean! Use toString() instead! @BeanIgnore public class MyBean {...}

When applied to fields and getters/setters, they will be ignored as bean properties.

public class MyBean { // Not a bean property! @BeanIgnore public String foo; // Not a bean property! @BeanIgnore public String getBar() {...} }

2.7.12 - @NameProperty Annotation

The {@link org.apache.juneau.annotation.NameProperty @NameProperty} annotation is used to identify a setter as a method for setting the name of a POJO as it's known by its parent object.

A commonly-used case is when you're parsing a JSON map containing beans where one of the bean properties is the key used in the map.

// JSON { id1: {name: 'John Smith', sex:'M'}, id2: {name: 'Jane Doe', sex:'F'} }

public class Person { @NameProperty public String id; // Value gets assigned from object key public String name; public char sex; }

2.7.13 - @ParentProperty Annotation

The {@link org.apache.juneau.annotation.ParentProperty @ParentProperty} annotation is used to identify a setter as a method for adding a parent reference to a child object.

A commonly-used case is when you're parsing beans and a child bean has a reference to a parent bean.

public class AddressBook { public List<Person> people; } public class Person { @ParentProperty public AddressBook addressBook; // A reference to the containing address book. public String name; public char sex; }

Parsers will automatically set this field for you in the child beans.

2.7.14 - POJO Builders

Juneau parsers can use builders to instantiate POJOs.
This is useful in cases where you want to create beans with read-only properties.
Note that while it's possible to do this using the {@link org.apache.juneau.annotation.BeanConstructor @BeanConstructor} annotation, using builders can often be cleaner.

A typical builder usage is shown below:

MyBean b = MyBean.create().foo("foo").bar(123).build();

The code for such a builder is shown below:

public class MyBean { // Read-only properties. public final String foo; public final int bar; // Private constructor. private MyBean(MyBeanBuilder b) { this.foo = b.foo; this.bar = b.bar; } // Static method that creates a builder. public static MyBeanBuilder create() { return new MyBeanBuilder(); } // Builder class. public static class MyBeanBuilder { private String foo; private int bar; // Method that creates the bean. public MyBean build() { return new MyBean(this); } // Bean property setters. @BeanProperty public MyBeanBuilder foo(String foo) { this.foo = foo; return this; } @BeanProperty public MyBeanBuilder bar(int bar) { this.bar = bar; return this; } } }

The POJO class can be any type including beans.
Builders MUST be beans with one or more writable properties.
The bean properties themselves do not need to be readable (i.e. getters are optional).

Builders require two parts:

  1. A way to detect and instantiate a builder using reflection.
  2. A way to instantiate a POJO from a builder.

The first can be accomplished through any of the following:

  • A static create() method on the POJO class that returns a builder instance.

    public static MyBuilder create() {...}

  • A public constructor on the POJO class that takes in a single parameter that implements the {@link org.apache.juneau.transform.Builder} interface.
    The builder class must have a public no-arg constructor.

    public MyPojo(MyBuilder b) {...}

  • A {@link org.apache.juneau.annotation.Builder @Builder} annotation on the POJO class.
    The builder class must have a public no-arg constructor.

    @Builder(MyBuilder.class) public class MyPojo {...}

The second can be accomplished through any of the following:

  • The existence of a build() method on the builder class.

    public MyPojo build() {...}

  • The existence of a public constructor on the POJO class that takes in the builder instance.

    public MyPojo(MyBuilder b) {...}

See Also:
  • {@link org.apache.juneau.annotation.Builder}
  • {@link org.apache.juneau.transform.Builder}

2.7.15 - URIs

Juneau serializers have sophisticated support for transforming relative URIs to absolute form.

The following example shows a bean containing URIs of various forms and how they end up serialized.

// Our bean with properties containing various kinds of URIs. public class TestURIs { public URI f1a = URI.create("http://www.apache.org/f1a"), f1b = URI.create("/f1b"), f1c = URI.create("/f1c/x/y"), f1d = URI.create("f1d"), f1e = URI.create("f1e/x/y"), f1f = URI.create(""), f2a = URI.create("context:/f2a/x"), f2b = URI.create("context:/f2b"), f2c = URI.create("context:/"), f2d = URI.create("context:/.."), f3a = URI.create("servlet:/f3a/x"), f3b = URI.create("servlet:/f3b"), f3c = URI.create("servlet:/"), f3d = URI.create("servlet:/.."), f4a = URI.create("request:/f4a/x"), f4b = URI.create("request:/f4b"), f4c = URI.create("request:/"), f4d = URI.create("request:/.."), f5 = null; } // Create a serializer. WriterSerializer s = JsonSerializer create() .simple() .uriContext("{authority:'http://foo.com:123',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/myPath'}") .uriResolution(ABSOLUTE) .uriRelativity(RESOURCE) .build(); // Produces: // { // f1a:'http://www.apache.org/f1a', // f1b:'http://foo.com:123/f1b', // f1c:'http://foo.com:123/f1c/x/y', // f1d:'http://foo.com:123/myContext/myServlet/f1d', // f1e:'http://foo.com:123/myContext/myServlet/f1e/x/y', // f1f:'http://foo.com:123/myContext/myServlet', // f2a:'http://foo.com:123/myContext/f2a/x', // f2b:'http://foo.com:123/myContext/f2b', // f2c:'http://foo.com:123/myContext', // f2d:'http://foo.com:123' // f3a:'http://foo.com:123/myContext/myServlet/f3a/x', // f3b:'http://foo.com:123/myContext/myServlet/f3b', // f3c:'http://foo.com:123/myContext/myServlet', // f3d:'http://foo.com:123/myContext', // f4a:'http://foo.com:123/myContext/myServlet/myPath/f4a/x', // f4b:'http://foo.com:123/myContext/myServlet/myPath/f4b', // f4c:'http://foo.com:123/myContext/myServlet/myPath', // f4d:'http://foo.com:123/myContext/myServlet', // } String json = s.serialize(new TestURIs());

URI resolution is controlled by the following settings:

  • {@link org.apache.juneau.serializer.Serializer#SERIALIZER_uriContext}
    Setting that defines the URI contextual information used to resolve relative URIs.
  • {@link org.apache.juneau.serializer.Serializer#SERIALIZER_uriRelativity}
    Setting that defines how relative URIs should be interpreted.
    Possible values:
    • {@link org.apache.juneau.UriRelativity#RESOURCE}
      Relative URIs should be considered relative to the servlet URI.
      (e.g. "http://host:port/context-root/servlet-path").
    • {@link org.apache.juneau.UriRelativity#PATH_INFO}
      Relative URIs should be considered relative to the request URI.
      (e.g. "http://host:port/context-root/servlet-path/path-info").
  • {@link org.apache.juneau.serializer.Serializer#SERIALIZER_uriResolution}
    Setting that defines the final format of serialized URIs.
    Possible values:
    • {@link org.apache.juneau.UriResolution#ABSOLUTE}
      Resolve to an absolute URL.
      (e.g. "http://host:port/context-root/servlet-path/path-info").
    • {@link org.apache.juneau.UriResolution#ROOT_RELATIVE}
      Resolve to a root-relative URL.
      (e.g. "/context-root/servlet-path/path-info").
    • {@link org.apache.juneau.UriResolution#NONE}
      Don't do any URL resolution.

Juneau automatically interprets any {@link java.net.URL} and {@link java.net.URI} objects as URIs and will resolve them accordingly.
The {@link org.apache.juneau.annotation.URI @URI} annotation can be used to extend that to other bean properties and class types so that they also get interpreted as URIs.
For example:

// Applied to a class whose toString() method returns a URI. @URI public class MyURI { @Override /* Object */ public String toString() { return "http://localhost:9080/foo/bar"; } } // Applied to bean properties public class MyBean { @URI public String beanUri; @URI public String getParentUri() { ... } }

2.7.16 - BeanFilter Class

The {@link org.apache.juneau.transform.BeanFilter} class is the programmatic equivalent to the {@link org.apache.juneau.annotation.Bean @Bean} annotation.

In practice, it's usually simpler to use the {@link org.apache.juneau.annotation.Bean @Bean} and {@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotations on your bean classes.
However, bean filters make it possible to accomplish the same when you can't add annotations to existing code.

Bean filters are defined through {@link org.apache.juneau.transform.BeanFilterBuilder BeanFilterBuilders}.

In the previous examples, we defined this bean annotation:

@Bean(properties="street,city,state") public class Address { ... }

The programmatic equivalent would be:

public class AddressFilter extends BeanFilterBuilder<Address> { // Must provide a no-arg constructor! public AddressFilter() { includeProperties("street,city,state"); // The properties we want exposed. } }

Bean filters are added to serializers and parsers using the following:

  • {@link org.apache.juneau.BeanContext#BEAN_beanFilters}
  • {@link org.apache.juneau.BeanContextBuilder#beanFilters(Object...)}

For example:

// Create a new JSON serializer and associate a bean filter with it. WriterSerializer s = JsonSerializer .create() .beanFilters(AddressFilter.class) .build();

Note that if you use the annotation, you do NOT need to set anything on the serializers/parsers.
The annotations will be detected and bean filters will automatically be created for them.

The {@link org.apache.juneau.BeanContextBuilder#beanFilters(Object...)} method also allows you to pass in interfaces.
Any class that's not a subclass of {@link org.apache.juneau.transform.BeanFilterBuilder} get interpreted as bean interface classes.
These cause bean implementations of those interfaces to only expose the properties defined on the interface.

// An interface with the 3 properties we want serialized. public interface AddressInterface { public String getStreet(); public String getCity(); public String getState(); } // Our bean implementation. public class Address implements AddressInterface { public String getStreet() {...}; public String getCity() {...}; public String getState() {...}; public String getCountry() {...}; } // Create a new JSON serializer that only exposes street,city,state on Address bean. // The 'country' field will be ignored. WriterSerializer s = JsonSerializer .create() .beanFilters(AddressInterface.class) .build();

2.7.17 - Interface Filters

Occasionally, you may want to limit bean properties to only those defined on a parent class or interface.
This is accomplished through interface filters.

Interface filters are defined through the following:

  • {@link org.apache.juneau.BeanContext#BEAN_beanFilters}
  • {@link org.apache.juneau.annotation.Bean#interfaceClass() @Bean.interfaceClass()}
  • {@link org.apache.juneau.transform.BeanFilterBuilder#interfaceClass(Class)}

For example, let's define the following interface and implementation:

// Interface public class MyInterface { public String getFoo(); } // Implementation public class MyInterfaceImpl implements MyInterface { public String getFoo() {...} public String getBar() {...} }

Suppose we only want to render the properties defined on our interface, not the implementation.
To do so, we can define the following bean filter:

// Define transform that limits properties to only those defined on MyClass public class MyInterfaceFilter extends BeanFilter<MyInterface> { public MyInterfaceFilter() { interfaceClass(MyInterface.class); } }

When serialized, the serialized bean will only include properties defined on the interface.

WriterSerializer s = JsonSerializer .create() .simple() .beanFilters(MyInterfaceFilter.class) .build(); MyInterface o = new MyInterfaceImpl(); // Prints "{foo:'foo'}" // bar is ignored because it's not on the interface String json = s.serialize(o);

The {@link org.apache.juneau.BeanContextBuilder#beanFilters(Object...)} method will automatically interpret any non-BeanFilter classes passed in as meaning interface classes.
So in the previous example, the BeanFilter class could have been avoided altogether by just passing in MyInterface.class to the serializer, like so:

WriterSerializer s = JsonSerializer .create() .beanFilters(MyInterface.class) Shortcut! .build();

The annotation equivalent is {@link org.apache.juneau.annotation.Bean#interfaceClass() Bean#interfaceClass()}.

@Bean(interfaceClass=MyInterface.class) public class MyInterfaceImpl implements MyInterface { public String getFoo() {...} public String getBar() {...} }

The annotation can be used in a couple of ways.

Using the annotation on an interface will be inherited by all children.

@Bean(interfaceClass=MyInterface.class) public class MyInterface { public String getFoo(); }

The annotation can be used on parent classes as well.
Child beans will only serialize properties defined on the parent class.

@Bean(interfaceClass=MyAbstractClass.class) public abstract class MyAbstractClass { public String getFoo() {...}; }

2.7.18 - Stop Classes

Whereas interface filters limit properties defined on child classes, stop filters do the opposite and limit properties defined on parent classes.

Stop classes are defined through the following:

  • {@link org.apache.juneau.annotation.Bean#stopClass() @Bean.stopClass()}
  • {@link org.apache.juneau.transform.BeanFilterBuilder#stopClass(Class)}

Stop classes are identical in purpose to the stop class specified by {@link java.beans.Introspector#getBeanInfo(Class, Class)}.
Any properties in the stop class or in its base classes will be ignored during serialization.

For example, in the following class hierarchy, instances of C3 will include property p3, but not p1 or p2.

public class C1 { public int getP1(); } public class C2 extends C1 { public int getP2(); } @Bean(stopClass=C2.class) public class C3 extends C2 { public int getP3(); } // Serializes property 'p3', but NOT 'p1' or 'p2'. String json = JsonSerializer.DEFAULT.serialize(new C3());

2.7.19 - Bypass Serialization using Readers and InputStreams

Juneau serializers treat instances of Readers and InputStreams special by simply serializing their contents directly to the output stream or writer.
This allows you to embed fully customized serializer output.

public class MyBean { // A bean property that produces raw JSON. public Reader f1 = new StringReader("{'foo':'bar'}"); } // Produces "{f1:{'foo':'bar'}}" String json = JsonSerializer.DEFAULT_LAX.toString(new MyBean());

Note that if you're serializing Readers and InputStreams, it's up to you to make sure you're producing valid output (in this case JSON).

A more typical scenario where this is useful is by using swaps to convert POJOs to Readers whose contents are determined via the {@link org.apache.juneau.BeanSession#getMediaType()} method.
In the following example, we're customizing the JSON output for a particular bean type, but leaving all other renditions as-is:

@Swap(MyBeanSwapSometimes.class) public class MyBean {...} // A swap that produces specialized output for JSON, but default serialization for // all other media types. public class MyBeanSwapSometimes extends PojoSwap<MyBean,Object> { public Object swap(BeanSession session, MyPojo o) throws Exception { MediaType mt = session.getMediaType(); if (mt.hasSubType("json")) return new StringReader("{myPojo:'foobar'}"); // Custom JSON output return o; // Otherwise serialize it as a normal bean } }

  • Due to the nature of the RDF serializers, Readers and InputStreams are serialized as literals, not as RDF text. This is due to the fact that the RDF serializers use a DOM for serialization, so we don't have access to the underlying stream.

2.8 - Bean Names and Dictionaries

While parsing into beans, Juneau attempts to determine the class types of bean properties through reflection on the bean property getter or setter.
Often this is insufficient if the property type is an interface or abstract class that cannot be instantiated.
This is where bean names and dictionaries come into play.

Bean names and dictionaries are used for identifying class types when they cannot be inferred through reflection.

Bean classes are given names through the {@link org.apache.juneau.annotation.Bean#typeName() @Bean.typeName()} annotation.
These names are then added to the serialized output as virtual "_type" properties (or element names in XML).

On the parsing side, these type names are resolved to classes through the use of bean dictionaries.

For example, if a bean property is of type Object, then the serializer will add "_type" attributes so that the class can be determined during parsing.

@Bean(typeName="foo") public class Foo { // A bean property where the object types cannot be inferred since it's an Object[]. @BeanProperty(beanDictionary={Bar.class,Baz.class}) public Object[] x = new Object[]{new Bar(), new Baz()}; } @Bean(typeName="bar") public class Bar {} @Bean(typeName="baz") public class Baz {}

When serialized as JSON, "_type" attributes would be added when needed to infer the type during parsing:

{ x: [ {_type:'bar'}, {_type:'baz'} ] }

Type names can be represented slightly differently in different languages.
For example, the dictionary name is used as element names when serialized to XML.
This allows the typeName annotation to be used as a shortcut for defining element names for beans.

When serialized as XML, the bean is rendered as:

<foo> <x> <bar/> <baz/> </x> </foo>

Bean dictionaries are registered through the following:

The bean dictionary setting can consist of any of the following types:

// Create a parser and tell it which classes to try to resolve. ReaderParser p = JsonParser .create() .beanDictionary(Foo.class, Bar.class) .build(); // Same, but use property. ReaderParser p = JsonParser .create() .addTo(BEAN_beanDictionary, Foo.class) .addTo(BEAN_beanDictionary, Bar.class) .build(); // Use the predefined HTML5 bean dictionary which is a BeanDictionaryList. ReaderParser p = HtmlParser .create() .beanDictionary(HtmlBeanDictionary.class) .build();

The "_type" property name can be overridden through the following:

When using the annotation, you'll typically want to define it on an interface class so that it can be inherited by all subclasses.

@Bean(typePropertyName="mytype", beanDictionary={MyClass1.class,MyClass2.class}) public interface MyInterface {...} @Bean(typeName="C1") public class MyClass1 implements MyInterface {...} @Bean(typeName="C2") public class MyClass2 implements MyInterface {...} MyInterface[] x = new MyInterface[]{ new MyClass1(), new MyClass2() }; // Produces "[{mytype:'C1',...},{mytype:'C2',...}]" String json = JsonSerializer.DEFAULT_LAX.serialize(x);

2.8.1 - Bean Subtypes

In addition to the bean type name support described above, simplified support is provided for bean subtypes.

Bean subtypes are similar in concept to bean type names, except for the following differences:

  • You specify the list of possible subclasses through an annotation on a parent bean class.
  • You do not need to register the subtype classes on the bean dictionary of the parser.

In the following example, the abstract class has two subclasses:

// Abstract superclass @Bean( beanDictionary={A1.class, A2.class} ) public abstract class A { public String f0 = "f0"; } // Subclass 1 @Bean(typeName="A1") public class A1 extends A { public String f1; } // Subclass 2 @Bean(typeName="A2") public class A2 extends A { public String f2; }

When serialized, the subtype is serialized as a virtual "_type" property:

JsonSerializer s = JsonSerializer.DEFAULT_LAX; A1 a1 = new A1(); a1.f1 = "f1"; String r = s.serialize(a1); assertEquals("{_type:'A1',f1:'f1',f0:'f0'}", r);

The following shows what happens when parsing back into the original object.

JsonParser p = JsonParser.DEFAULT; A a = p.parse(r, A.class); assertTrue(a instanceof A1);

2.9 - Virtual Beans

The {@link org.apache.juneau.BeanContext#BEAN_useInterfaceProxies} setting (enabled by default) allows the Juneau parsers to parse content into virtual beans (bean interfaces without implementation classes).

For example, the following code creates an instance of the specified unimplemented interface:

// Our unimplemented interface public interface Address { String getStreet(); void setStreet(String x); String getCity(); void setCity(String x); StateEnum getState(); void setState(StateEnum x); int getZip(); void setZip(int zip); } // Our code Address address = JsonParser.DEFAULT.parse( "{street:'123 Main St', city:'Anywhere', state:'PR', zip:12345}", Address.class ); int zip = address.getZip(); address.setState(StateEnum.NY);

Getter and setter values can be any parsable values, even other virtual beans.

Under-the-covers, a virtual bean is simply a proxy interface on top of an existing BeanMap instance. From a programmatic point-of-view, they're indistinguishable from real beans, and can be manipulated and serialized like any other bean.

Virtual beans can also be created programmatically using the BeanContext class:

Address address = BeanContext.DEFAULT.createSession().newBean(Address.class);

2.10 - Reading Continuous Streams

The following parsers can be configured to read continous streams of objects from the same input stream:

The {@link org.apache.juneau.json.JsonParser} and {@link org.apache.juneau.uon.UonParser} classes can read continous streams by using the {@link org.apache.juneau.parser.Parser#PARSER_unbuffered} setting.
This prevents the parsers from using an internal buffer that would read past the end of the currently parsed POJO.

Examples:

// If you're calling parse on the same input multiple times, use a session instead of the parser directly. ReaderParserSession p = JsonParser.create().unbuffered().build().createSession(); Object x; Reader r; r = new StringReader("{foo:'bar'}{baz:'qux'}"); x = p.parse(r, ObjectMap.class); // {foo:'bar'} x = p.parse(r, ObjectMap.class); // {baz:'qux'} r = reader("[123][456]"); x = p.parse(r, int[].class); // [123] x = p.parse(r, int[].class); // [456]

Note that this isn't perfect in all cases since you can't combine two JSON numbers into a single reader (e.g. "123" + "456" = "123456").

For obvious reasons, do not use the following properties when reading continuous streams:

The {@link org.apache.juneau.msgpack.MsgPackParser} class doesn't use any internal buffering to begin with, so it can be used with continuous streams without any special properties.

2.11 - Comparison with Jackson

Juneau was developed independently from Jackson, but shares many of the same features and capabilities. Whereas Jackson was created to work primarily with JSON, Juneau was created to work for multiple languages. Therefore, the terminology and annotations in Juneau are similar, but language-agnostic.

The following charts describe equivalent features between the two libraries:

Annotations
JacksonJuneau
@JsonGetter
@JsonSetter
{@link org.apache.juneau.annotation.BeanProperty @BeanProperty}
@JsonAnyGetter
@JsonAnySetter
{@link org.apache.juneau.annotation.BeanProperty#name() @BeanProperty(name="*")}
@JsonIgnore
@JsonIgnoreType
{@link org.apache.juneau.annotation.BeanIgnore @BeanIgnore}
@JsonIgnoreProperties({...}) {@link org.apache.juneau.annotation.Bean#excludeProperties @Bean(excludeProperties="...")}
@JsonAutoDetect(fieldVisibility=...) No equivalent annotation, but can be controlled via:
{@link org.apache.juneau.BeanContext#BEAN_beanFieldVisibility}
{@link org.apache.juneau.BeanContext#BEAN_beanMethodVisibility}
Future annotation support planned.
@JsonCreator
@JsonProperty
{@link org.apache.juneau.annotation.BeanConstructor @BeanConstructor}
@JacksonInject No equivalent.
Future support planned.
@JsonSerialize
@JsonDeserialize
Juneau uses swaps to convert non-serializable object to serializable forms:
{@link org.apache.juneau.annotation.Swap @Swap}
@JsonInclude No equivalent annotation, but can be controlled via various settings:
{@link org.apache.juneau.BeanContext}
{@link org.apache.juneau.serializer.Serializer}
Future annotation support planned.
@JsonPropertyOrder {@link org.apache.juneau.annotation.Bean#properties @Bean(properties="...")}
{@link org.apache.juneau.annotation.Bean#sort @Bean(sort=x)}
@JsonValue
@JsonRawValue
Can be replicated using swaps with Reader swapped values.

2.12 - POJO Categories

The following chart shows POJOs categorized into groups and whether they can be serialized or parsed:

GroupDescriptionExamplesCan
serialize?
Can
parse?
1 Java primitives and primitive objects
  • {@code String}
  • {@code Integer}
  • {@code Float}
  • {@code Boolean}
yes yes
2 Java Collections Framework objects and Java arrays      
2a With standard keys/values
Map keys are group [1, 4a, 6a] objects.
Map, Collection, and array values are group [1, 2, 3ac, 4a, 6a] objects.
  • HashSet<String,Integer>
  • TreeMap<Integer,Bean>
  • List<int[][]>
  • Bean[]
yes yes
2b With non-standard keys/values
Map keys are group [2, 3, 4b, 5, 6b, 7] objects.
Map, Collection, and array values are group [3b, 4b, 5, 6b, 7] objects.
  • HashSet<Bean,Integer>
  • TreeMap<Integer,Reader>
yes no
3 Java Beans      
3a With standard properties
These are beans that have one or more properties defined by public getter or public fields.
Properties can also be defined as final read-only fields and passed in as constructor args.
Property values are group [1, 2, 3ac, 4a, 6a] objects.
  yes yes
3b With non-standard properties or not true beans
These include true beans that have one or more properties defined by getter and setter methods or properties, but property types include group [3b, 4b, 5, 6b, 7] objects.
This also includes classes that look like beans but aren't true beans. For example, classes that have getters but not setters, or classes without no-arg constructors.
  yes no
3c Virtual beans
These are unimplemented bean interfaces with properties of type [1, 2, 3ac, 4a, 6a] objects.
Parsers will automatically create interface proxies on top of BeanMap instances.
  yes yes
3d Read-only beans without setters
The same as 3a, but without property setters or constructor args.
  yes no
4 Swapped objects
These are objects that are not directly serializable, but have {@link org.apache.juneau.transform.PojoSwap PojoSwaps} associated with them. The purpose of a POJO swap is to convert an object to another object that is easier to serialize and parse. For example, the {@link org.apache.juneau.transforms.DateSwap.ISO8601DT} class can be used to serialize {@link java.util.Date} objects to ISO8601 strings, and parse them back into {@link java.util.Date} objects.
     
4a 2-way swapped to group [1, 2a, 3ac] objects
For example, a swap that converts a {@code Date} to a {@code String}.
  • java.util.Date
  • java.util.GregorianCalendar
yes yes
4b 1-way swapped to group [1, 2, 3] objects
For example, a swap that converts an {@code Iterator} to a {@code List}. This would be one way, since you cannot reconstruct an {@code Iterator}.
  • java.util.Iterator
yes no
5 Readers and InputStreams
Contents are serialized directly to the output stream or writer.
Typically used for low-level language-specific replacement of POJOs using per-Media-Type POJO swaps.
  • {@code FileInputStream}
  • {@code StringReader}
yes no
6 Non-serializable objects with standard methods for converting to a serializable form
     
6a Classes with a method that converts it to a serializable form:
  • public X swap(BeanSession); where X is in groups [1, 2a, 3ac].
  • public String toString(); where the string is any meaningful data.
And a method that converts it back into the original object:
  • public static T fromString(String);
  • public static T valueOf(String);
  • public static T parse(String);
  • public static T parseString(String);
  • public static T forName(String);
  • public static T forString(String);
  • public T(X); where X is in groups [1, 2a, 3ac].
  • public static T unswap(BeanSession,X); where X is in groups [1, 2a, 3ac].
  • java.lang.Class
  • java.sql.Time
  • java.sql.Timestamp
  • java.text.MessageFormat
  • java.text.NumberFormat
  • java.util.Date
  • java.util.UUID
  • java.util.logging.Level
  • javax.xml.bind.DatatypeConverter
yes yes
6b Classes that only have a method to convert to a serializable form:
  • public X swap(BeanSession); where X is in groups [1, 2, 3].
  • public String toString(); where the string is any meaningful data.
  yes no
7 All other objects
Anything that doesn't fall into one of the groups above are simply converted to {@code Strings} using the {@code toString()} method.
  yes no

2.13 - Best Practices

  1. Reuse instances of serializers and parsers whenever possible.
    They are designed to be thread safe and maintain internal caches of bean metadata to increase performance.
  2. The {@link org.apache.juneau.serializer.Serializer#SERIALIZER_detectRecursions SERIALIZER_detectRecursions} option on the {@link org.apache.juneau.serializer.Serializer} class can cause a performance penalty of around 20%. Therefore, it's recommended that this option be used only when necessary.
  3. In general, JSON serialization and parsing is about 20% faster than XML. JSON is also more compact than XML. MessagePack is fastest and most compact of all.
  4. The RDF parsers are SLOW. RDF simply isn't efficient with node traversal, so creating tree structures out of RDF models is highly inefficient.
  5. The {@link org.apache.juneau.parser.Parser} methods that take in {@link org.apache.juneau.ClassMeta} parameters are slightly faster than methods that take in {@link java.lang.Class} or {@link java.lang.Object} parameters, since the latter methods involve hash lookups to resolve to {@link org.apache.juneau.ClassMeta} parameters.

2.14 - Additional Information

Extensive javadocs exist for individual language support. Refer to these docs for language-specific information.

2.13.1 - JSON

2.13.2 - XML

2.13.3 - HTML

Additional Information - org.apache.juneau.html
  1. HTML serialization support

  2. HTML parsing support

2.13.4 - UON

2.13.5 - URL-Encoding

3 - juneau-marshall-rdf

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-marshall-rdf</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-marshall-rdf-7.0.0.jar

OSGi Module

org.apache.juneau.marshall.rdf_7.0.0.jar

The juneau-marshall-rdf library provides additional serializers and parsers for RDF. These rely on the Apache Jena library to provide support for the following languages:

The serializers and parsers work identically to those in juneau-marshall, but are packaged separately so that you don't need to pull in the Jena dependency unless you need it.

// A simple bean public class Person { public String name = "John Smith"; public int age = 21; } // Serialize a bean to JSON, XML, or HTML Person p = new Person(); // Produces: // <rdf:RDF // xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" // xmlns:jp="http://www.apache.org/juneaubp/" // xmlns:j="http://www.apache.org/juneau/"> // <rdf:Description> // <jp:name>John Smith</jp:name> // <jp:age>21</jp:age> // </rdf:Description> // </rdf:RDF> String rdfXml = RdfSerializer.DEFAULT_XMLABBREV.serialize(p); // Produces: // @prefix jp: <http://www.apache.org/juneaubp/> . // @prefix j: <http://www.apache.org/juneau/> . // [] jp:age "21" ; // jp:name "John Smith" . String rdfN3 = RdfSerializer.DEFAULT_N3.serialize(p); // Produces: // _:A3bf53c85X3aX157cf407e2dX3aXX2dX7ffd <http://www.apache.org/juneaubp/name> "John Smith" . // _:A3bf53c85X3aX157cf407e2dX3aXX2dX7ffd <http://www.apache.org/juneaubp/age> "21" . String rdfNTriple = RdfSerializer.DEFAULT_NTRIPLE.serialize(p);

Additional Information - org.apache.juneau.jena
  1. RDF support overview

    1. Example

  2. RdfSerializer class

    1. Namespaces

    2. URI properties

    3. @Bean and @BeanProperty annotations

    4. Collections

    5. Root property

    6. Typed literals

    7. Non-tree models and recursion detection

    8. Configurable properties

    9. Other notes

  3. RdfParser class

    1. Parsing into generic POJO models

    2. Configurable properties

    3. Other notes

4 - juneau-dto

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-dto</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-dto-7.0.0.jar

OSGi Module

org.apache.juneau.dto_7.0.0.jar

The juneau-dto library contains several predefined POJOs for generating commonly-used document types. This section describes support for these POJOs.

4.1 - HTML5

The Juneau HTML5 DTOs are simply beans with fluent-style setters that allow you to quickly construct HTML fragments as Java objects. These object can then be serialized to HTML using one of the existing HTML serializers, or to other languages such as JSON using the JSON serializers.

The {@link org.apache.juneau.dto.html5.HtmlBuilder} class is a utility class with predefined static methods that allow you to easily construct DTO instances in a minimal amount of code.

The following examples show how to create HTML tables.

Java code HTML
import static org.apache.juneau.dto.html5.HtmlBuilder.*; Object mytable = table( tr( th("c1"), th("c2") ), tr( td("v1"), td("v2") ) ); String html = HtmlSerializer.DEFAULT.serialize(mytable); <table> <tr> <th>c1</th> <th>c2</th> </tr> <tr> <td>v1</td> <td>v2</td> </tr> </table>
import static org.apache.juneau.dto.html5.HtmlBuilder.*; Object mydiv = div().align("center").onmouseover("alert(\"boo!\");") .children( p("Juneau supports ", b(i("mixed")), " content!") ); String html = HtmlSerializer.DEFAULT.serialize(mydiv); <div align='center' onmouseover='alert("boo!");'> <p>Juneau supports <b><i>mixed</i></b> content!</p> </table>
import static org.apache.juneau.dto.html5.HtmlBuilder.*; Object myform = form().action("/submit").method("POST") .children( "Position (1-10000): ", input("number").name("pos").value(1), br(), "Limit (1-10000): ", input("number").name("limit").value(100), br(), button("submit", "Submit"), button("reset", "Reset") ); String html = HtmlSerializer.DEFAULT.serialize(myform); <form action='/submit' method='POST'> Position (1-10000): <input name='pos' type='number' value='1'/><br/> Limit (1-10000): <input name='pos' type='number' value='100'/><br/> <button type='submit'>Submit</button> <button type='reset'>Reset</button> </form>

Using the HTML5 DTOs, you should be able to construct any valid HTML5 from full document bodies to any possible fragments.

The {@link org.apache.juneau.html.HtmlParser} class can be used convert these HTML documents back into POJOs.

Other serializers and parsers (e.g. {@link org.apache.juneau.json.JsonSerializer}) can be used to represent these POJOs in languages other than HTML.

Additional Information - org.apache.juneau.dto.html5
  1. Overview

    1. Generating HTML5

    2. Parsing HTML5

    3. HTML5 Templates

4.2 - Atom

The Juneau ATOM feed DTOs are simply beans with fluent-style setters.
The following code shows a feed being created programmatically using the {@link org.apache.juneau.dto.atom.AtomBuilder} class.

import static org.apache.juneau.dto.atom.AtomBuilder.*; Feed feed = feed("tag:juneau.apache.org", "Juneau ATOM specification", "2016-01-02T03:04:05Z") .subtitle(text("html").text("Describes <em>stuff</em> about Juneau")) .links( link("alternate", "text/html", "http://juneau.apache.org").hreflang("en"), link("self", "application/atom+xml", "http://juneau.apache.org/feed.atom") ) .rights("Copyright (c) ...") .generator( generator("Juneau").uri("http://juneau.apache.org/").version("1.0") ) .entries( entry("tag:juneau.sample.com,2013:1.2345", "Juneau ATOM specification snapshot", "2016-01-02T03:04:05Z") .links( link"alternate", "text/html", "http://juneau.apache.org/juneau.atom"), link("enclosure", "audio/mpeg", "http://juneau.apache.org/audio/juneau_podcast.mp3").length(1337) ) .published("2016-01-02T03:04:05Z") .authors( person("Jane Smith").uri("http://juneau.apache.org/").email("janesmith@apache.org") ) .contributors( person("John Smith") ) .content( content("xhtml") .lang("en") .base("http://www.apache.org/") .text("<div><p><i>[Update: Juneau supports ATOM.]</i></p></div>") ) );

To serialize this to ATOM, use the {@link org.apache.juneau.xml.XmlSerializer} class:

Example with no namespaces

// Create a serializer with readable output, no namespaces yet. XmlSerializer s = XmlSerializer.create().sq().ws().build(); // Serialize to ATOM/XML String atomXml = s.serialize(feed);

Results

<feed> <id> tag:juneau.apache.org </id> <link href='http://juneau.apache.org/' rel='alternate' type='text/html' hreflang='en'/> <link href='http://juneau.apache.org/feed.atom' rel='self' type='application/atom+xml'/> <rights> Copyright (c) ... </rights> <title type='text'> Juneau ATOM specification </title> <updated>2016-01-02T03:04:05Z</updated> <generator uri='http://juneau.apache.org/' version='1.0'> Juneau </generator> <subtitle type='html'> Describes <em>stuff</em> about Juneau </subtitle> <entry> <author> <name>Jane Smith</name> <uri>http://juneau.apache.org/</uri> <email>janesmith@apache.org</email> </author> <contributor> <name>John Smith</name> </contributor> <id> tag:juneau.apache.org </id> <link href='http://juneau.apache.org/juneau.atom' rel='alternate' type='text/html'/> <link href='http://juneau.apache.org/audio/juneau_podcast.mp3' rel='enclosure' type='audio/mpeg' length='12345'/> <title> Juneau ATOM specification snapshot </title> <updated>2016-01-02T03:04:05Z</updated> <content base='http://www.apache.org/' lang='en' type='xhtml'> <div xmlns="http://www.w3.org/1999/xhtml" ><p><i>[Update: Juneau supports ATOM.]</i></p></div> </content> <published>2016-01-02T03:04:05Z</published> </entry> </feed>

The {@link org.apache.juneau.xml.XmlParser} class can be used convert these Atom documents back into POJOs.

Other serializers and parsers (e.g. {@link org.apache.juneau.json.JsonSerializer}) can be used to represent these POJOs in languages other than XML.

Additional Information - org.apache.juneau.dto.atom
  1. Overview

    1. Serializing ATOM feeds

      1. ATOM/JSON

      2. ATOM/RDF/XML

      3. ATOM/HTML

    2. Parsing ATOM feeds

4.3 - Swagger

The Juneau Swagger DTOs are simply beans with fluent-style setters that allow you to quickly construct Swagger documents as Java objects. These object can then be serialized to JSON using one of the existing JSON serializers, or to other languages such as XML or HTML using the other serializers.

The {@link org.apache.juneau.dto.swagger.SwaggerBuilder} class is a utility class with predefined static methods that allow you to easily construct DTO instances in a minimal amount of code.

The following is an example Swagger document from the Swagger website.

{ "swagger": "2.0", "info": { "title": "Swagger Petstore", "description": "This is a sample server Petstore server.", "version": "1.0.0", "termsOfService": "http://swagger.io/terms/", "contact": { "email": "apiteam@swagger.io" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } }, "host": "petstore.swagger.io", "basePath": "/v2", "tags": [ { "name": "pet", "description": "Everything about your Pets", "externalDocs": { "description": "Find out more", "url": "http://swagger.io" } } ], "schemes": [ "http" ], "paths": { "/pet": { "post": { "tags": [ "pet" ], "summary": "Add a new pet to the store", "description": "", "operationId": "addPet", "consumes": [ "application/json", "text/xml" ], "produces": [ "application/json", "text/xml" ], "parameters": [ { "in": "body", "name": "body", "description": "Pet object that needs to be added to the store", "required": true } ], "responses": { "405": { "description": "Invalid input" } } } } }, }

This document can be generated by the following Java code:

static import org.apache.juneau.dto.swagger.SwaggerBuilder.*; Swagger swagger = swagger() .swagger("2.0") .info( info("Swagger Petstore", "1.0.0") .description("This is a sample server Petstore server.") .termsOfService("http://swagger.io/terms/") .contact( contact().email("apiteam@swagger.io") ) .license( license("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html") ) ) .host("petstore.swagger.io") .basePath("/v2") .tags( tag("pet").description("Everything about your Pets") .externalDocs( externalDocumentation("http://swagger.io", "http://swagger.io") ) ) .schemes("http") .path("/pet", "post", operation() .tags("pet") .summary("Add a new pet to the store") .description("") .operationId("addPet") .consumes(MediaType.JSON, MediaType.XML) .produces(MediaType.JSON, MediaType.XML) .parameters( parameterInfo("body", "body") .description("Pet object that needs to be added to the store") .required(true) ) .response(405, responseInfo("Invalid input")) ); // Serialize using JSON serializer. String swaggerJson = JsonSerializer.DEFAULT_READABLE.serialize(swagger); // Or just use toString(). String swaggerJson = swagger.toString();

Methods that take in beans and collections of beans can also take in JSON representations of those objects.

// Pass in a JSON object representation of an Info object. swagger.info("{title:'Swagger Petstore',...}");

Properties can also be accessed via the {@link org.apache.juneau.dto.swagger.SwaggerElement#get(String,Class)} and {@link org.apache.juneau.dto.swagger.SwaggerElement#set(String,Object)} methods.
These methods can also be used to set and retrieve non-Swagger attributes such as "$ref" (which is not a part of the Swagger spec, but is part of the JSON Schema spec).

// Set a non-standard attribute. swagger.set("$ref", "http://foo.com"); // Retrieve a non-standard attribute. URI ref = swagger.get("$ref", URI.class);

Swagger docs can be parsed back into Swagger beans using the following code:

Swagger swagger = JsonParser.DEFAULT.parse(swaggerJson, Swagger.class);

5 - juneau-svl

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-svl</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-svl-7.0.0.jar

OSGi Module

org.apache.juneau.svl_7.0.0.jar

5.1 - Simple Variable Language

The juneau-svl module defines an API for a language called Simple Variable Language.
In a nutshell, Simple Variable Language (or SVL) is text that contains variables of the form "$varName{varKey}".
It is used extensively in the Config, REST and Microservice APIs.

Most variables can be recursively nested within the varKey (e.g. "$FOO{$BAR{xxx},$BAZ{xxx}}") and can return values that themselves contain more variables.

The {@link org.apache.juneau.svl.VarResolver} class is used to resolve variables.
The {@link org.apache.juneau.svl.VarResolver#DEFAULT} resolver is a reusable instance of this class configured with the following basic variables:

The following logic variables are also provided:

Example:

// Use the default variable resolver to resolve a string that contains $S (system property) variables String myProperty = VarResolver.DEFAULT.resolve("The Java home directory is $S{java.home}");

The following shows how variables can be arbitrarily nested...

// Look up a property in the following order: // 1) MYPROPERTY environment variable. // 2) 'my.property' system property if environment variable not found. // 3) 'not found' string if system property not found. String myproperty = VarResolver.DEFAULT.resolve("$E{MYPROPERTY,$S{my.property,not found}}");

5.2 - SVL Variables

Variables are defined through the {@link org.apache.juneau.svl.Var} API.
The API comes with several predefined variables and is easily extensible.

The following is an example of a variable that performs URL-Encoding on strings.

// First create our var. public class UrlEncodeVar extends SimpleVar { // Must have a no-arg constructor! public UrlEncodeVar() { super("UE"); } // The method we must implement @Override public String resolve(VarResolverSession session, String key) { return URLEncoder.encode(key, "UTF-8"); } } // Next create a var resolver that extends the existing DEFAULT resolver // that supports resolving system properties. VarResolver r = VarResolver.DEFAULT .builder() .vars(UrlEncodeVar.class) .build(); // Retrieve a system property and URL-encode it if necessary. String myProperty = r.resolve("$UE{$S{my.property}}");

The following shows the class hierarchy of the {@link org.apache.juneau.svl.Var} class:

The following is the list of default variables defined in all modules:

ModuleClassPattern
juneau-svl {@link org.apache.juneau.svl.vars.EnvVariablesVar} $E{key[,default]}
{@link org.apache.juneau.svl.vars.SystemPropertiesVar} $S{key[,default]}
{@link org.apache.juneau.svl.vars.ArgsVar} $A{key[,default]}
{@link org.apache.juneau.svl.vars.ManifestFileVar} $MF{key[,default]}
{@link org.apache.juneau.svl.vars.IfVar} $IF{arg,then[,else]}
{@link org.apache.juneau.svl.vars.SwitchVar} $SW{arg,pattern1:then1[,pattern2:then2...]}
{@link org.apache.juneau.svl.vars.CoalesceVar} $CO{arg1[,arg2...]}
{@link org.apache.juneau.svl.vars.PatternMatchVar} $PM{arg,pattern}
{@link org.apache.juneau.svl.vars.NotEmptyVar} $NE{arg}
{@link org.apache.juneau.svl.vars.UpperCaseVar} $UC{arg}
{@link org.apache.juneau.svl.vars.LowerCaseVar} $LC{arg}
juneau-config {@link org.apache.juneau.config.vars.ConfigVar} $C{key[,default]}
juneau-rest-server {@link org.apache.juneau.rest.vars.FileVar} $F{path[,default]}}
{@link org.apache.juneau.rest.vars.ServletInitParamVar} $I{name[,default]}
{@link org.apache.juneau.rest.vars.LocalizationVar} $L{key[,args...]}
{@link org.apache.juneau.rest.vars.RequestAttributeVar} $RA{key1[,key2...]}
{@link org.apache.juneau.rest.vars.RequestFormDataVar} $RF{key1[,key2...]}
{@link org.apache.juneau.rest.vars.RequestHeaderVar} $RH{key1[,key2...]}
{@link org.apache.juneau.rest.vars.RequestHeaderVar} $RI{key}
{@link org.apache.juneau.rest.vars.RequestPathVar} $RP{key1[,key2...]}
{@link org.apache.juneau.rest.vars.RequestQueryVar} $RQ{key1[,key2...]}
{@link org.apache.juneau.rest.vars.RequestVar} $R{key1[,key2...]}
{@link org.apache.juneau.rest.vars.SerializedRequestAttrVar} $SA{contentType,key[,default]}
{@link org.apache.juneau.rest.vars.UrlVar} $U{uri}>
{@link org.apache.juneau.rest.vars.UrlEncodeVar} $UE{uriPart}
{@link org.apache.juneau.rest.vars.WidgetVar} $W{name}

5.3 - VarResolvers and VarResolverSessions

The main class for performing variable resolution is {@link org.apache.juneau.svl.VarResolver}.
Two methods are provided for resolving variables:

Var resolvers can rely on the existence of other objects.
For example, {@link org.apache.juneau.config.vars.ConfigVar} relies on the existence of a {@link org.apache.juneau.config.Config}.
This is accomplished through the following:

The following two classes are identical in behavior except for which objects they can access:

Context and session objects are set through the following methods:

Both kinds of objects are accessible through the following method:

Var resolvers can be cloned and extended by using the {@link org.apache.juneau.svl.VarResolver#builder()} method.
Cloning a resolver will copy it's {@link org.apache.juneau.svl.Var} class names and context objects.

Example:

// Create a resolver that copies the default resolver and adds $C and $A vars. VarResolver myVarResolver = VarResolver.DEFAULT .builder() .vars(ConfigVar.class, ArgsVar.class) .build();

5.4 - Other Notes

6 - juneau-config

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-config</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-config-7.0.0.jar

OSGi Module

org.apache.juneau.config_7.0.0.jar

6.1 - Overview

The juneau-config library contains a powerful API for creating and using INI-style config files.

Example configuration file:

# A set of entries [Section1] # An integer key1 = 1 # A boolean key2 = true # An array key3 = 1,2,3 # A POJO key4 = http://bar

Config files are access through the {@link org.apache.juneau.config.Config} class which are created through the {@link org.apache.juneau.config.ConfigBuilder} class.
Builder creator methods are provided on the Config class:

// Create a Config object Config c = Config.create().name("MyConfig.cfg").build(); // Shortcut Config c = Config.create("MyConfig.cfg").build();

Once instantiated, reading values from the config are simple:

// Read values from section #1 int key1 = c.getInt("Section1/key1"); boolean key2 = c.getBoolean("Section1/key2"); int[] key3 = c.getObject("Section1/key3", int[].class); URL key4 = c.getObject("Section1/key4", URL.class);

The config language may look simple, but it is a very powerful feature with many capabilities including:

6.1.1 - Syntax Rules

  • Each config file contains zero or more sections containing zero or more entries:

    [Section1] key1 = 1 [Section2] key1 = 2

  • Unicode escapes can be used in values.

    key1 = \u0070\u0075\u0062\u006c\u0069\u0063

  • Comment lines start with the '#' character and can be placed on lines before sections and entries:

    # A comment about this section [Section1] # A comment about this entry key1 = 1

  • Comments can also be placed on the same line as entries:

    key1 = 1 # A comment about this entry

  • Values containing '#' must be escaped to prevent identification as a comment character:

    valueContainingPound = Value containing \u0023 character


    Likewise, '\' should be escaped to prevent confusion with unicode escapes.
  • Values containing newlines can span multiple lines.
    Subsequent lines start with a tab character.

    multiLineValue = line 1, line 2, line 3


    When retrieved, the above translates to "line1,\nline2,\nline3".
  • Leading and trailing whitespace on values are ignored.
  • Whitespace is not ignored within multi-line values (except for the leading tab character on each line).
  • Blank lines can be used anywhere in the file.

    # A comment line # Another comment line [Section1] ...

  • Values located before any sections are considered part of the no-name section, meaning they are accessed simply by key and not section/key.

    # Top of file # Use config.getString("key1") to retrieve. key1 = val1 # The first section [Section1] # Use config.getString("Section1/key2") to retrieve. key2 = val2

  • Section and key names must be at least one character long and not consist of any of the following characters:

    / \ [ ] = #

  • Whitespace in section and key names is technically allowed but discouraged.

6.2 - Entry Types

Configuration files can contain entries for anything from primitive types up to complex hierarchies of POJOs consisting of maps, collections, and/or beans.

6.2.1 - Primitive Types

The most common case for configuration values are primitives.

# A string key1 = foo # A boolean key2 = true # An integer key3 = 123 # A long key4 = 10000000000 # Doubles key5 = 6.67e−11 key6 = Infinity

The following methods are provided for accessing primitive values:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getString(String) getString(String)}
    • {@link org.apache.juneau.config.Config#getString(String,String) getString(String,String)}
    • {@link org.apache.juneau.config.Config#getBoolean(String) getBoolean(String)}
    • {@link org.apache.juneau.config.Config#getBoolean(String,boolean) getBoolean(String,boolean)}
    • {@link org.apache.juneau.config.Config#getInt(String) getInt(String)}
    • {@link org.apache.juneau.config.Config#getInt(String,int) getInt(String,int)}
    • {@link org.apache.juneau.config.Config#getLong(String) getLong(String)}
    • {@link org.apache.juneau.config.Config#getLong(String,long) getLong(String,long)}
    • {@link org.apache.juneau.config.Config#getFloat(String) getFloat(String)}
    • {@link org.apache.juneau.config.Config#getFloat(String,float) getFloat(String,long)}
    • {@link org.apache.juneau.config.Config#getDouble(String) getDouble(String)}
    • {@link org.apache.juneau.config.Config#getDouble(String,double) getDouble(String,long)}

On integers and longs, "K", "M", and "G" can be used to identify kilo, mega, and giga.

key1 = 100K # Same as 1024000 key2 = 100M # Same as 104857600

Numbers can also use hexadecimal and octal notation:

hex1 = 0x12FE hex2 = 0X12FE octal1 = 01234

Strings with newlines get broken into separate lines:

key1 = This is a particularly long sentence that we want to split onto separate lines.

Typically, multi-line values are started on the next line for clarity like so:

key1 = This is a particularly long sentence that we want to split onto separate lines.

6.2.2 - POJOs

The following methods are provided for accessing POJO values:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getObject(String,Class) getObject(String,Class)}
    • {@link org.apache.juneau.config.Config#getObjectWithDefault(String,Object,Class) getObjectWithDefault(String,T,Class)}

In theory, any parsable POJO type can be represented as a config value.
However in practice, we're typically talking about the following:

  • Objects convertible from Strings.
  • Beans.

An example of an object convertible from a String was already shown in an example above.
In that case, it was a URL which has a public constructor that takes in a String:

# A POJO key4 = http://bar

// Read values from section #1 URL key4 = c.getObject("Section1/key4", URL.class);

Beans are represented as {@link org.apache.juneau.json.JsonSerializer#DEFAULT_LAX Lax JSON} by default:

// Contact information [ContactInfo] address = { street: '123 Main Street', city: 'Anywhere', state: 'NY', zip: 12345 }

// Example bean public class Address { public String street, city; public StateEnum state; public int zip; } // Example usage Config c = Config.create("MyConfig.cfg").build(); Address myAddress = c.getObject("ContactInfo/address", Address.class);

The format for beans depends on the serializer and parser registered on the Config which is defined in the builder via the following methods:

  • {@link org.apache.juneau.config.ConfigBuilder}
    • {@link org.apache.juneau.config.ConfigBuilder#serializer(Class) serializer(Class)}
    • {@link org.apache.juneau.config.ConfigBuilder#serializer(WriterSerializer) serializer(WriterSerializer)}
    • {@link org.apache.juneau.config.ConfigBuilder#parser(Class) parser(Class)}
    • {@link org.apache.juneau.config.ConfigBuilder#parser(ReaderParser) parser(ReaderParser)}

The default parser can also be overridden on the following getters:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getObject(String,Parser,Class) getObject(String,Parser,Class)}
    • {@link org.apache.juneau.config.Config#getObjectWithDefault(String,Parser,Object,Class) getObjectWithDefault(String,T,Parser,Class)}

6.2.3 - Arrays

The following methods are provided for accessing arrays:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getStringArray(String) getStringArray(String)}
    • {@link org.apache.juneau.config.Config#getStringArray(String,String[]) getStringArray(String,String)}
    • {@link org.apache.juneau.config.Config#getObject(String,Class) getObject(String,Class)}
    • {@link org.apache.juneau.config.Config#getObject(String,Parser,Class) getObject(String,Parser,Class)}

The getStringArray() methods allow you to retrieve comma-delimited lists of values:

key1 = foo, bar, baz

String[] key1 = c.getStringArray("key1");

String arrays can also be represented in JSON when using the getObject() methods:

key1 = ['foo','bar','baz']

String[] key1 = c.getObject("key1", String.class);

Primitive arrays can also be retrieved using the getObject() methods:

key1 = [1,2,3]

int[] key1 = c.getObject("key1", int[].class);

Arrays of POJOs can also be retrieved using the getObject() methods:

addresses = [ { street: '123 Main Street', city: 'Anywhere', state: 'NY', zip: 12345 }, { street: '456 Main Street', city: 'Anywhere', state: 'NY', zip: 12345 } ]

Address[] addresses = c.getObject("addresses", Address[].class);

6.2.4 - Collections

The following methods are provided for accessing maps and collections:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getObject(String,Type,Type...) getObject(String,Type,Type...)}
    • {@link org.apache.juneau.config.Config#getObject(String,Parser,Type,Type...) getObject(String,Parser,Type,Type...)}
    • {@link org.apache.juneau.config.Config#getObjectWithDefault(String,Object,Type,Type...) getObjectWithDefault(String,T,Type,Type...)}
    • {@link org.apache.juneau.config.Config#getObjectWithDefault(String,Parser,Object,Type,Type...) getObjectWithDefault(String,T,Parser,Type,Type...)}

The Type,Type... arguments allow you to specify the component types for maps and collections.
List class arguments can be followed by zero or one arguments representing the entry types.
Map class arguments can be followed by zero or two arguments representing the key and value types.
The arguments can be chained to produce any data structure consisting of maps, collections, or POJOs.


Examples are shown below:

  • getObject("...", List.class)
    Produces: List<?>
  • getObject("...", LinkedList.class)
    Produces: LinkedList<?>
  • getObject("...", HashSet.class, Integer.class)
    Produces: HashSet<Integer>
  • getObject("...", Map.class)
    Produces: Map<?,?>
  • getObject("...", HashMap.class)
    Produces: HashMap<?,?>
  • getObject("...", LinkedHashMap.class, String.class, MyBean.class)
    Produces: LinkedHashMap<String,MyBean>
  • getObject("...", HashMap.class, Integer.class, ArrayList.class, MyBean[].class)
    Produces: LinkedHashMap<Integer,ArrayList<MyBean[]>>
Example:

addresses = [ { street: '123 Main Street', city: 'Anywhere', state: 'NY', zip: 12345 }, { street: '456 Main Street', city: 'Anywhere', state: 'NY', zip: 12345 } ]

List<Address> addresses = c.getObject("addresses", ArrayList.class, Address.class);

Oftentimes, it might be useful to parse into the {@link org.apache.juneau.ObjectList} and {@link org.apache.juneau.ObjectMap} classes that provide the various convenience methods for working with JSON-like data structures:

ObjectMap m = c.getObject("key1", ObjectMap.class); ObjectList l = c.getObject("key2", ObjectList.class);

6.2.5 - Binary Data

The following methods are provided for accessing binary data:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#getBytes(String) getBytes(String)}
    • {@link org.apache.juneau.config.Config#getBytes(String,byte[]) getBytes(String,byte[])}

Binary data can be represented in 3 formats:

  • BASE-64 (default)
    Example: "Zm9vYmFycw=="
  • Hexadecimal
    Example: "666F6F62617273"
  • Spaced hexadecimal
    Example: "66 6F 6F 62 61 72 73"

The binary data format is controlled via the following setting:

  • {@link org.apache.juneau.config.Config#CONFIG_binaryFormat}

For example:

key = Zm9vYmFycw==

byte[] bytes = c.getBytes("key");

Binary lines can be split up into separate lines for readability:

key = Zm9vYm Fycw==

Binary data line wrapping can be controlled via the following setting:

  • {@link org.apache.juneau.config.Config#CONFIG_binaryLineLength}

6.3 - Variables

Config files can contain variables that get resolved dynamically using the previously-described {@link org.apache.juneau.svl.VarResolver} API.

Example:

#-------------------------- # My section #-------------------------- [MySection] # A system property locale = $S{java.locale, en_US} # An environment variable path = $E{PATH, unknown} # Another value in this config file anotherLocale = $C{MySection/locale} # Look for system property, or env var if that doesn't exist, or a default value if that doesn't exist. nested = $S{mySystemProperty,$E{MY_ENV_VAR,$C{MySection/anotherLocale}}} # A POJO with embedded variables aBean = {foo:'$S{foo}',baz:$C{MySection/anInt}}

Config c = Config.create().build(); Locale locale = cf.getObject("MySection/locale", Locale.class); String path = cf.getString("MySection/path"); int sameAsAnInt = cf.getInt("MySection/sameAsAnInt"); ABean bean = cf.getObject("MySection/aBean", ABean.class);

By default, Configs use the {@link org.apache.juneau.svl.VarResolver#DEFAULT} variable resolver which provides support for the following variables and constructs:

The variable resolver is controlled via the following setting:

Additionally, the following method can be used to retrieve a Config with a different variable resolver:

6.3.1 - Logic Variables

The default variable resolver also provides the following logic variables for performing simple logical operations:

  • {@link org.apache.juneau.svl.vars.IfVar} - $IF{arg,then[,else]}
  • {@link org.apache.juneau.svl.vars.SwitchVar} - $SW{arg,pattern1:then1[,pattern2:then2...]}
  • {@link org.apache.juneau.svl.vars.CoalesceVar} - $CO{arg1[,arg2...]}
  • {@link org.apache.juneau.svl.vars.PatternMatchVar} - $PM{arg,pattern}
  • {@link org.apache.juneau.svl.vars.NotEmptyVar} - $NE{arg}
  • {@link org.apache.juneau.svl.vars.UpperCaseVar} - $UC{arg}
  • {@link org.apache.juneau.svl.vars.LowerCaseVar} - $LC{arg}

The $IF variable can be used for simple if/else logic:

# Value set to 'foo' if myBooleanProperty is true key1 = $IF{ $S{myBooleanProperty}, foo } # Value set to 'foo' if myBooleanProperty is true, 'bar' if false. key2 = $IF{ $S{myBooleanProperty}, foo, bar } # Value set to key1 value if myBooleanProperty is true, key2 value if false. key3 = $IF{ $S{myBooleanProperty}, $C{key1}, $C{key2} }

The $SW variable can be used for switch blocks based on pattern matching:

# Shell command depends on the OS shellCommand = $SW{ $LC{$S{os.name}}, *win*: bat, linux: bash, *: sh }

The $CO variable can be used for coalescing of values (finding the first non-null/empty match):

# Debug flag can be enabled by system property or environment variable. debug = $CO{ $S{debug}, $E{DEBUG}, false }

The $PM variable can be used for calculating boolean values:

# Debug flag can be enabled by system property or environment variable. isWindows = $PM{ $LC{$S{os.name}}, *win* }

6.4 - Encoded Entries

Encoded entries allow for sensitive information such as passwords to be obfuscated.

Encoded entries are denoted with a '*' character at the end of the key name.

For example, the following password is marked for encoding:

[MyHost] url = http://localhost:9080/foo user = me password* = {AwwJVhwUQFZEMg==}

The default encoder is {@link org.apache.juneau.config.encode.ConfigXorEncoder} which is a simple XOR+Base64 encoder.

Custom encoders can be used to provide your own encoding support by implementing the {@link org.apache.juneau.config.encode.ConfigEncoder} interface.

Encoders are controlled via the following setting:

Encoded values can be set to plain-text values.
The curly brackets around the value identify whether the value has been encoded or not.

Unencoded values are encoded when the file is saved using the {@link org.apache.juneau.config.Config#commit()} method.
They can also be encoded immediately by calling {@link org.apache.juneau.config.Config#encodeEntries()}.

6.5 - Section Maps

Config sections can be retrieved in-bulk using {@link org.apache.juneau.config.Config#getSectionAsMap(String)}.

Example:

// Example config file [MyAddress] street = 123 Main Street city = Anywhere state = NY zip = 12345

// Example usage Config c = Config.create("MyConfig.cfg").build(); ObjectMap m = c.getSectionAsMap("MyAddress"); String street = m.getString("street"); String city = m.getString("city"); String state = m.getString("state"); int zip = m.getInt("zip");

Maps created this way are snapshot copies of the section at the time of the method call.

6.6 - Section Beans

Config files can also be used to directly populate beans using {@link org.apache.juneau.config.Config#getSectionAsBean(String,Class,boolean)}.

Example:

// Example config file [MyAddress] street = 123 Main Street city = Anywhere state = NY zip = 12345

// Example bean public class Address { public String street, city; public StateEnum state; public int zip; } // Example usage Config c = Config.create("MyConfig.cfg").build(); Address myAddress = c.getSectionAsBean("MyAddress", Address.class);

Like maps, beans created this way are snapshot copies of the section at the time of the method call.

6.7 - Section Interfaces

Config sections can also be accessed via interface proxies using {@link org.apache.juneau.config.Config#getSectionAsInterface(String,Class)}.

While section maps and beans retrieve copies of the configuration data at the time of the method call, section interfaces can also be use to set values in the underlying configuration.

Example:

// Example config file [MySection] string = foo int = 123 enum = ONE bean = {foo:'bar',baz:123} int3dArray = [[[123,null],null],null] bean1d3dListMap = {key:[[[[{foo:'bar',baz:123}]]]]}

// Example interface. // Setters are optional. public interface MyConfigInterface { String getString(); void setString(String x); int getInt(); void setInt(int x); MyEnum getEnum(); void setEnum(MyEnum x); MyBean getBean(); void setBean(MyBean x); int[][][] getInt3dArray(); void setInt3dArray(int[][][] x); Map<String,List<MyBean[][][]>> getBean1d3dListMap(); void setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); } // Example usage. Config c = Config.create("MyConfig.cfg").build(); MyConfigInterface ci = c.getSectionAsInterface("MySection", MyConfigInterface.class); // Read a value. int myInt = ci.getInt(); // Write a value. ci.setBean(new MyBean()); // Commit your changes to the store. c.commit();

6.8 - Setting Values

The following methods allow you to add, remove, and modify entries and sections in a config file:

// Construct the sample config file programmatically Config c = Config.create("MyConfig.cfg").build() .set("key1", 1) .set("key2", true) .set("key3", new int[]{1,2,3}) .set("key4", new URI("http://foo")) .set("Section1/key1", 2) .set("Section1/key2", false) .set("Section1/key3", new int[]{4,5,6}) .set("Section1/key4", new URI("http://bar")) .commit();

The method {@link org.apache.juneau.config.Config#set(String,Object,Serializer,ConfigMod[],String,List)} can be used for adding comments and pre-lines to entries, and specifying encoded values.

// Set an encoded value with some comments. c.set("key1", 1, null, ConfigMod.ENCODED, "Same-line comment", Arrays.asList( "# Comment 1", "", "# Comment 2" ) );

# Comment 1 # Comment 2 key1 = 1 # Same-line comment

The last 4 arguments in {@link org.apache.juneau.config.Config#set(String,Object,Serializer,ConfigMod[],String,List)} are optional in that if you pass null, the current value will not be overwritten.
To unset the same-line comment, you should pass in a blank string.
To remove pre-lines, you should pass in an empty list.

Sections can be added with optional pre-lines using the setSection methods:

// Set an encoded value with some comments. c.setSection("NewSection", Arrays.asList( "# Section comment 1", "", "# Section comment 2" ) );

# Section comment 1 # Section comment 2 [NewSection]

Changes made to the configuration are transactional in nature.
They are kept in memory until you call the {@link org.apache.juneau.config.Config#commit()} method.
Until then, you still see the modified values when you call any of the getters, but the modified values exist only in memory.

Changes can be rolled back using the {@link org.apache.juneau.config.Config#rollback()} method.

6.8.1 - File System Changes

In general, external file modifications will be detected immediately in the Config object when a watcher thread is enabled (explained later).
Otherwise, they are detected when a commit is performed.

The Config object maintains an in-memory record of all changes that have been applied to it through getters and setters.
When the underlying file changes, the new contents are loaded and the in-memory changes are then applied to the new configuration.
This provides the benefits of real-time updates of configurations while not losing any changes made in the JVM.

If the commit() method is called on the Config objects after the file system contents have been modified, we will then reload the configuration from the file system, apply the changes, and then try to save to the file system again (up to 10 times).

If the same entry is both internally and externally modified, the external modification will be overwritten (although both change events will be seen by listeners).

6.8.2 - Custom Entry Serialization

Setter methods that take in a Serializer can be used to provide custom serialization of entries instead of using the predefined serializer.

// Set an XML value instead of JSON. c.set("key1", myAddress, XmlSerializer.DEFAULT_SQ_READABLE);

key1 = <address> <street>123 Main Street</street> <city>Anywhere</city> <state>NY</state> <zip>12345</zip> </address>

The value can then be retrieved using the equivalent parser:

Address myAddress = c.getObject("key1", XmlParser.DEFAULT, Address.class);

6.8.3 - Setting Values in Bulk

The following methods can be used to bulk-load configuration values:

  • {@link org.apache.juneau.config.Config}
    • {@link org.apache.juneau.config.Config#setSection(String,List,Map) setSection(String,List,Map)}
    • {@link org.apache.juneau.config.Config#load(Map) load(Map)}
    • {@link org.apache.juneau.config.Config#load(Reader,boolean) load(Reader,boolean)}
    • {@link org.apache.juneau.config.Config#load(String,boolean) load(String,boolean)}

Changes can then be committed using the {@link org.apache.juneau.config.Config#commit()} method.

6.9 - Listeners

Configuration change events can be listened for using the following methods:

The {@link org.apache.juneau.config.event.ConfigEventListener} interface consists of the following method:

The {@link org.apache.juneau.config.event.ConfigEvent} class provides access to all the information about the updated entry:

The listener method is triggered:

In both cases, the listener is triggered after the changes have been committed.

final Config c = Config.create("MyConfig.cfg").build(); // Add a listener for changes to MySection/key1 c.addListener( new ConfigEventListener() { @Override public void onConfigChange(List<ConfigEvent> events) { for (ConfigEvent event : events) { if (event.getType() == SET_ENTRY) { String section = event.getSection(); String key = event.getKey(); if (section.equals("MySection") && key.equals("key1")) { // Get the new value from the event. String newVal = event.getValue(); // Or get the new value from the config (since the change has already been committed). newVal = c.getString("MySection/key1"); } } } } } )

6.10 - Serializing

The following methods are used for serializing Config objects back into INI files:

Both methods are thread safe.

The Config class implements the {@link org.apache.juneau.Writable} which means it can be returned as-is by REST methods to be serialized as INI text.

6.11 - Config Stores

Configuration files are stored in entities called Stores.

The methods that need to be implemented on a store are:

Read is self-explanatory:

public String read(String name) { // Return the contents of the specified configuration. }

Write is slightly trickier:

public String write(String name, String oldContents, String newContents) { // If the old contents match the current stored contents, the new contents will get stored, // and the method returns null indicating success. // If the old contents DO NOT match the current stored contents (i.e. it was modified in some way), // the new contents are NOT stored, and the method returns the current stored contents. // If the old contents are null, then just always write the new contents. }

The update method is called whenever the stored file gets modified externally:

public String update(String name, String newContents) { // Do something with the updated contents. }

Two configuration stores are provided by default:

The store is defined on the Config object via the following setting:

Example:

// Create a config with in-memory storage. Config c = Config.create("MyConfig.cfg").store(ConfigMemoryStore.DEFAULT).build();

The default store used is {@link org.apache.juneau.config.store.ConfigFileStore#DEFAULT} which defines the execution directory as the file system directory to store and retrieve files.

6.11.1 - ConfigMemoryStore

The {@link org.apache.juneau.config.store.ConfigMemoryStore} class is simply an in-memory storage location for configuration files.
There is no hard persistence and is used primarily for testing purposes.

However, the implementation provides a good idea on how stores work (especially the write method):

public class ConfigMemoryStore extends ConfigStore { // Some methods ommitted. private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>(); @Override /* ConfigStore */ public synchronized String read(String name) { return emptyIfNull(cache.get(name)); } @Override /* ConfigStore */ public synchronized String write(String name, String expectedContents, String newContents) { // This is a no-op. if (isEquals(expectedContents, newContents)) return null; String currentContents = read(name); if (expectedContents != null && ! isEquals(currentContents, expectedContents)) return currentContents; update(name, newContents); // Success! return null; } @Override /* ConfigStore */ public synchronized ConfigMemoryStore update(String name, String newContents) { cache.put(name, newContents); super.update(name, newContents); // Trigger any listeners. return this; } }

6.11.2 - ConfigFileStore

The {@link org.apache.juneau.config.store.ConfigFileStore} is the typical store used for configuration files. It provides the following configurable settings:

  • {@link org.apache.juneau.config.store.ConfigFileStore}
    • {@link org.apache.juneau.config.store.ConfigFileStore#FILESTORE_charset FILESTORE_charset}
    • {@link org.apache.juneau.config.store.ConfigFileStore#FILESTORE_directory FILESTORE_directory}
    • {@link org.apache.juneau.config.store.ConfigFileStore#FILESTORE_updateOnWrite FILESTORE_updateOnWrite}
    • {@link org.apache.juneau.config.store.ConfigFileStore#FILESTORE_useWatcher FILESTORE_useWatcher}
    • {@link org.apache.juneau.config.store.ConfigFileStore#FILESTORE_watcherSensitivity FILESTORE_watcherSensitivity}
Example:

// Create a config store with a watcher thread and high sensitivity. ConfigFileStore fs = ConfigFileStore.create().directory("configs").useWatcher().watcherSensitivity(HIGH).build(); // Create a config using the store defined above. Config c = Config.create("MyConfig.cfg").store(fs).build();

6.11.3 - Custom ConfigStores

The ConfigStore API has been written to allow easy development of custom configuration storage classes.

The example below shows a starting point for an implementation based on polling a relational database.
Completing it is left as an exercise:

Example Store Class:

public class ConfigSqlStore extends ConfigStore { // Configurable properties static final String CONFIGSQLSTORE_jdbcUrl = "ConfigSqlStore.jdbcUrl.s", CONFIGSQLSTORE_tableName = "ConfigSqlStore.tableName.s", CONFIGSQLSTORE_nameColumn = "ConfigSqlStore.nameColumn.s", CONFIGSQLSTORE_valueColumn = "ConfigSqlStore.valueColumn.s", CONFIGSQLSTORE_pollInterval = "ConfigSqlStore.pollInterval.i"; // Instance fields private final String jdbcUrl; private final String tableName, nameColumn, valueColumn; private final Timer watcher; private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>(); // Constructor called from builder. protected ConfigSqlStore(PropertyStore ps) { super(ps); this.jdbcUrl = getStringProperty(CONFIGSQLSTORE_jdbcUrl, "jdbc:derby:mydb"); this.tableName = getStringProperty(CONFIGSQLSTORE_tableName); this.nameColumn = getStringProperty(CONFIGSQLSTORE_nameColumn); this.valueColumn = getStringProperty(CONFIGSQLSTORE_valueColumn); int pollInterval = getStringProperty(CONFIGSQLSTORE_pollInterval, 600); TimerTask timerTask = new TimerTask() { @Override public void run() { ConfigSqlStore.this.poll(); } }; this.watcher = new Timer("MyTimer"); watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 10000); } private synchronized void poll() { // Loop through all our entries and find the latest values. for (Map.Entry<String,String> e : cache.entrySet()) { String name = e.getKey(); String cacheContents = e.getValue(); String newContents = getDatabaseValue(name); // Change detected! if (! cacheContents.equals(newContents)) update(name, newContents); } } // Reads the value from the database. protected String getDatabaseValue(String name) { // Implement me! } @Override /* ConfigStore */ public synchronized String read(String name) { String contents = cache.get(name); if (contents == null) { contents = getDatabaseValue(name); update(name, contents); } return contents; } @Override /* ConfigStore */ public synchronized String write(String name, String expectedContents, String newContents) { // This is a no-op. if (isEquals(expectedContents, newContents)) return null; String currentContents = read(name); if (expectedContents != null && ! isEquals(currentContents, expectedContents)) return currentContents; update(name, newContents); // Success! return null; } @Override /* ConfigStore */ public synchronized ConfigSqlStore update(String name, String newContents) { cache.put(name, newContents); super.update(name, newContents); // Trigger any listeners. return this; } @Override /* Closeable */ public synchronized void close() { if (watcher != null) watcher.cancel(); } }

The purpose of the builder class is to simply set values in the {@link org.apache.juneau.PropertyStore} that's passed to the ConfigStore:

Example Builder Class:

public class ConfigSqlStoreBuilder extends ConfigStoreBuilder { public ConfigSqlStoreBuilder() { super(); } public ConfigSqlStoreBuilder(PropertyStore ps) { super(ps); } public ConfigSqlStoreBuilder jdbcUrl(String value) { super.set(CONFIGSQLSTORE_jdbcUrl, value); return this; } public ConfigSqlStoreBuilder tableName(String value) { super.set(CONFIGSQLSTORE_tableName, value); return this; } public ConfigSqlStoreBuilder nameColumn(String value) { super.set(CONFIGSQLSTORE_nameColumn, value); return this; } public ConfigSqlStoreBuilder valueColumn(String value) { super.set(CONFIGSQLSTORE_valueColumn, value); return this; } public ConfigSqlStoreBuilder pollInterval(int value) { super.set(CONFIGSQLSTORE_pollInterval, value); return this; } @Override /* ContextBuilder */ public ConfigFileStore build() { return new ConfigFileStore(getPropertyStore()); } }

6.11.4 - ConfigStore Listeners

The ConfigStore class has the following listener methods:

  • {@link org.apache.juneau.config.store.ConfigStore}
    • {@link org.apache.juneau.config.store.ConfigStore#register(String,ConfigStoreListener) register(String,ConfigStoreListener)} - Register a listener on the specified config name.
    • {@link org.apache.juneau.config.store.ConfigStore#unregister(String,ConfigStoreListener) unregister(String,ConfigStoreListener)} - Unregister a listener on the specified config name.

Note that this is a different listener than {@link org.apache.juneau.config.event.ConfigEventListener}.
In this case, we're just listening for changed files:

  • {@link org.apache.juneau.config.store.ConfigStoreListener}
    • {@link org.apache.juneau.config.store.ConfigStoreListener#onChange(String)} - Called when file changes. New contents are passed in.

This listener is used by the Config class to listen for changes on the file system so that it can be updated in real-time.

6.12 - Read-only Configs

The following settings can be used to create read-only Config objects:

Example:

// Create a read-only config Config c = Config.create("MyConfig.cfg").readOnly().build();

This causes all methods that make modifications to throw {@link java.lang.UnsupportedOperationException}.

6.13 - Closing Configs

In general, it's good practice to close Config if you're only creating them temporarily so that their listeners get unregistered from the underlying storage APIs.

Example:

// Create a transient config. Config c = Config.create("MyConfig.cfg").build(); // Do stuff with it. // Then close the config to unregister the listeners. c.close();

7 - juneau-rest-server

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-rest-server</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-rest-server-7.0.0.jar

OSGi Module

org.apache.juneau.rest.server_7.0.0.jar

The juneau-rest-server library allows you to quickly wrap POJOs and expose them as full-fledged REST resources served up in a servlet container using a bare-minimum amount of code.
The primary goal for Juneau was to make it as easy as possible to implement easy-to-read and self-documenting REST resources using very little code.

One of the biggest advantages of the Juneau REST framework over similar architectures is that it hides the serialization layer from the developer.
The developer can work entirely with POJOs and let the Juneau framework handle all the serialization and parsing work.
The developer need never know what the Accept or Content-Type or Accept-Encoding (etc...) header values are because those details are all handled by the framework.

The API builds upon the existing JEE Servlet API.
The root class, {@link org.apache.juneau.rest.RestServlet} is nothing but a specialized {@link javax.servlet.http.HttpServlet}, and the {@link org.apache.juneau.rest.RestRequest} and {@link org.apache.juneau.rest.RestResponse} classes are nothing more than specialized {@link javax.servlet.http.HttpServletRequest} and {@link javax.servlet.http.HttpServletResponse} objects.
This allows maximum flexibility for the developer since you can let Juneau handle operations such as serialization, or you can revert to the existing servlet APIs to do low-level processing of requests yourself.
It also means you need nothing more than a Servlet container such as Jetty to use the REST framework.

Features

Many of the examples in this document are pulled directly from juneau-examples-rest.

7.1 - Hello World Example

A REST resource is simply a Java class annotated with {@link org.apache.juneau.rest.annotation.RestResource}.
The most common case is a class that extends {@link org.apache.juneau.rest.RestServlet}, which itself is simply an extension of {@link javax.servlet.http.HttpServlet} which allows it to be deployed as a servlet.

In this example, we define a resource called HelloWorldResource.
This example is located in the juneau-examples-rest project.
It's assumed the reader is familiar with defining servlets in web applications.

Like any servlet, we could define our resource in the web.xml file of the web application like so...

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.3"> <servlet> <servlet-name>HelloWorldResource</servlet-name> <servlet-class>com.foo.sample.HelloWorldResource</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldResource</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

Our servlet code is shown below:

// Sample REST resource that prints out a simple "Hello world!" message. @RestResource( path="/helloWorld", title="Hello World", description="An example of the simplest-possible resource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" }, aside={ "<div style='max-width:400px' class='text'>", " <p>This page shows a resource that simply response with a 'Hello world!' message</p>", " <p>The POJO serialized is a simple String.</p>", "</div>" } ) ) public class HelloWorldResource extends BasicRestServlet { @RestMethod(name=GET, path="/*", summary="Responds with \"Hello world!\"") public String sayHello() { return "Hello world!"; } }

This is what it looks like in a browser:

It doesn't much simpler than that.
In this case, we're simply returning a string that will be converted to any of the supported languages (e.g. JSON, XML, HTML, ...).
However, we could have returned any POJO consisting of beans, maps, collections, etc...

The {@link org.apache.juneau.rest.BasicRestServlet} class that we're using here is a subclass of {@link org.apache.juneau.rest.RestServlet} that provides default support for a variety of content types.
Implementers can choose to use this class, or create their own subclass of {@link org.apache.juneau.rest.RestServlet} with their own specialized serializers and parsers.

7.2 - Class Hierarchy

The class hierarchy for the REST servlet class is shown below:

The servlets with RDF support require Jena on the classpath.
All other serializers and parsers do not have any external library dependencies.
For this reason, we have separate servlets for supporting RDF so that you don't need Jena if you don't need to support RDF.

Everything is configured through the following classes which you will see a lot:

7.3 - Instantiation

REST resources are deployed in one of two ways:

When deployed in a J2EE container, you MUST extend from one of the servlet classes.

When deployed as a child of another resource, you MAY extend from one of the servlet classes but it's not necessary.
The only requirement is that the class be annotated with @RestResource and have one of the following constructors:

And even that restriction is relaxed if you implement your own REST resource resolver (described later).

7.3.1 - RestServlet

The {@link org.apache.juneau.rest.RestServlet} class is the entry point for your REST resources.
It extends directly from HttpServlet and is deployed like any other servlet.

When the servlet init() method is called, it triggers the code to find and process the @RestResource annotations on that class and all child classes.
These get constructed into a {@link org.apache.juneau.rest.RestContext} object that holds all the configuration information about your resource in a read-only object.

Most developers are not going to be using the RestServlet class itself, and instead will extend from one of the preconfigured default servlets such as {@link org.apache.juneau.rest.BasicRestServlet}.
The RestServlet class by itself is not configured with any serializers and parsers, and therefore not very useful on it's own.
However, the class does provide a couple of convenience methods to be aware of:

  • {@link org.apache.juneau.rest.RestServlet} extends HttpServlet
    • {@link org.apache.juneau.rest.RestServlet#log(Level,String,Object...) log(Level,String,Object...)}
    • {@link org.apache.juneau.rest.RestServlet#log(Level,Throwable,String,Object...) log(Level,Throwable,String,Object...)}
    • {@link org.apache.juneau.rest.RestServlet#getContext() getContext()}

Since this is a servlet, you also have the ability to intercept calls to the init and service methods in your subclass.

7.3.2 - BasicRestServlet

The {@link org.apache.juneau.rest.BasicRestServlet} class is a subclass of {@link org.apache.juneau.rest.RestServlet} preconfigured with the following:

  • A default set of serializers and parsers (pretty much all of them except for the RDF ones).
  • Some basic HTML boilerplate for the HTML representation of your POJOs.
  • Support for auto-generated Swagger documentation through OPTIONS page requests.
  • Configuration of default CSS stylesheets.

The entirety of the class is shown below.
You should notice that very little code is being used and everything is configurable through annotations:

@RestResource( serializers={ HtmlDocSerializer.class, HtmlStrippedDocSerializer.class, HtmlSchemaDocSerializer.class, JsonSerializer.class, JsonSerializer.Simple.class, JsonSchemaSerializer.class, XmlDocSerializer.class, XmlSchemaDocSerializer.class, UonSerializer.class, UrlEncodingSerializer.class, MsgPackSerializer.class, SoapXmlSerializer.class, PlainTextSerializer.class }, parsers={ JsonParser.class, XmlParser.class, HtmlParser.class, UonParser.class, UrlEncodingParser.class, MsgPackParser.class, PlainTextParser.class }, properties={ // URI-resolution is disabled by default. Need to enable it. @Property(name=SERIALIZER_uriResolution, value="ROOT_RELATIVE") }, allowedMethodParams="OPTIONS", htmldoc=@HtmlDoc( header={ "<h1>$R{resourceTitle}</h1>", "<h2>$R{methodSummary,resourceDescription}</h2>", "<a href='http://juneau.apache.org'><img src='$U{servlet:/htdocs/juneau.png}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/></a>" }, navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" }, stylesheet="servlet:/styles/light.css", head={ "<link rel='icon' href='$U{servlet:/htdocs/juneau.png}'/>" } ), // Optional external configuration file. config="$S{juneau.configFile}", // These are static files that are served up by the servlet under the specified sub-paths. // For example, "/servletPath/htdocs/javadoc.css" resolves to the file "[servlet-package]/htdocs/javadoc.css" staticFiles={"htdocs:htdocs","styles:styles"} ) public abstract class BasicRestServlet extends RestServlet { // Show resource options. @RestMethod(name=OPTIONS, path="/*", htmldoc=@HtmlDoc( navlinks={ "back: servlet:/", "json: servlet:/?method=OPTIONS&Accept=text/json&plainText=true" }, aside="NONE" ), summary="Swagger documentation", description="Auto-generated swagger documentation for this resource" ) public Swagger getOptions(RestRequest req) { return req.getSwagger(); } }

Your top-level resource will simply extend from this class, as shown in the Hello World example from a couple sections back.

There's a lot going on in this class.
But not to worry, the details will be described later.

7.3.3 - Children

Child Resources are REST servlets or objects that are linked to parent resources through the {@link org.apache.juneau.rest.annotation.RestResource#children() @RestResource.children()} annotation.

Example:

/** Parent Resource */ @RestResource( path="/parent", children={FooResource.class} ) public MyResource extends BasicRestServlet {...}

/** Child Resource */ @RestResource( path="/foo" // Path relative to parent resource. ) public FooResource {...} // Note that we don't need to extend from RestServlet.

The path of the child resource gets appended to the path of the parent resource.
So in the example above, the child resource is accessed through the URL /parent/foo.

A HUGE advantage of using child resources is that they do not need to be declared in the JEE web.xml file.
Initialization of and access to the child resources occurs through the parent resource.
Children can be nested arbitrary deep to create complex REST interfaces with a single top-level REST servlet.

See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_children}

7.3.4 - Router Pages

The {@link org.apache.juneau.rest.BasicRestServletGroup} class provides a default "router" page for child resources when a parent resource is nothing more than a grouping of child resources.

The RootResources class in the Samples project is an example of a router page:

/** * Sample REST resource showing how to implement a "router" resource page. */ @RestResource( path="/", title="Root resources", description="Example of a router resource page.", children={ HelloWorldResource.class, MethodExampleResource.class, RequestEchoResource.class, TempDirResource.class, AddressBookResource.class, SampleRemoteableServlet.class, PhotosResource.class, AtomFeedResource.class, JsonSchemaResource.class, SqlQueryResource.class, TumblrParserResource.class, CodeFormatterResource.class, UrlEncodedFormResource.class, SourceResource.class, ConfigResource.class, LogsResource.class, DockerRegistryResource.class, ShutdownResource.class } ) public class RootResources extends RestServletGroup { // NO CODE!!! }

When you bring up this resource in a browser, you see the following that provides a list of navigable links to your child resources:

The {@link org.apache.juneau.rest.BasicRestServletGroup} class is nothing more than a subclass of {@link org.apache.juneau.rest.BasicRestServlet} with a getChildren() method mapped to the servlet root path.
The method returns a POJO with is just a linked-list of beans with name/description properties.

// The entire contents of the RestServletGroup class. public class RestServletGroup extends BasicRestServlet { @RestMethod(name=GET, path="/", description="Child resources") public ChildResourceDescriptions getChildren(RestRequest req) { return new ChildResourceDescriptions(this, req); } }

7.3.5 - Resource Resolvers

By default, you can add the {@link org.apache.juneau.rest.annotation.RestResource @RestResource} to any class as long as it has one of the following constructors:

  • public T()
  • public T(RestContextBuilder)

The latter constructor can be used to get access to the {@link org.apache.juneau.rest.RestContextBuilder} object to make any configurations to the resource before it's initialized.

Resource object resolution is controlled through the following API:

  • {@link org.apache.juneau.rest.RestResourceResolver}
    • {@link org.apache.juneau.rest.BasicRestResourceResolver}

This API can be extended to provide your own custom resource resolution.
Later topics discuss how to use this API to instantiate resources using Spring.

See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_resourceResolver}

7.3.6 - Lifecycle Hooks

Lifecycle hooks allow you to hook into lifecycle events of the servlet/resource creation and REST calls.

For example, if you want to add an initialization method to your resource:

@RestResource(...) public class MyResource { // Our database. private Map<Integer,Object> myDatabase; @RestHook(INIT) public void initMyDatabase(RestContextBuilder builder) throws Exception { myDatabase = new LinkedHashMap<>(); } }

Or if you want to intercept REST calls:

@RestResource(...) public class MyResource { // Add a request attribute to all incoming requests. @RestHook(PRE_CALL) public void onPreCall(RestRequest req) { req.setAttribute("foo", "bar"); } }

The hook events can be broken down into two categories:

  • Resource lifecycle events:
    • {@link org.apache.juneau.rest.annotation.HookEvent#INIT INIT} - Right before initialization.
    • {@link org.apache.juneau.rest.annotation.HookEvent#POST_INIT POST_INIT} - Right after initialization.
    • {@link org.apache.juneau.rest.annotation.HookEvent#POST_INIT_CHILD_FIRST POST_INIT_CHILD_FIRST} - Right after initialization, but run child methods first.
    • {@link org.apache.juneau.rest.annotation.HookEvent#DESTROY DESTROY} - Right before servlet destroy.
  • REST call lifecycle events:
    • {@link org.apache.juneau.rest.annotation.HookEvent#START_CALL START_CALL} - At the beginning of a REST call.
    • {@link org.apache.juneau.rest.annotation.HookEvent#PRE_CALL PRE_CALL} - Right before the @RestMethod method is invoked.
    • {@link org.apache.juneau.rest.annotation.HookEvent#POST_CALL POST_CALL} - Right after the @RestMethod method is invoked.
    • {@link org.apache.juneau.rest.annotation.HookEvent#END_CALL END_CALL} - At the end of the REST call after the response has been flushed.
See Also:
  • {@link org.apache.juneau.rest.annotation.RestHook}

7.4 - @RestResource

The {@link org.apache.juneau.rest.annotation.RestResource @RestResource} annotation is the primary way of defining and configuring REST resource classes.
The functionality of the class itself is covered in detail in the topics below.

7.4.1 - Annotation Inheritance

The {@link org.apache.juneau.rest.annotation.RestResource @RestResource} annotation can also be used on parents and interfaces of resource classes.
When multiple annotations are defined at different levels, the annotation values are combined.

This is a particularly useful feature because it allows you to define your own configured parent resource classes that can be extended by all your child resources so that they all share common settings.

Annotation Inheritence Rules
{@link org.apache.juneau.rest.annotation.RestResource#guards() guards()} Guards on child are combined with those on parent class.
Guards are executed child-to-parent in the order they appear in the annotation.
Guards on methods are executed before those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#converters() converters()} Converters on child are combined with those on parent class.
Converters are executed child-to-parent in the order they appear in the annotation.
Converters on methods are executed before those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#beanFilters() beanFilters()} Bean filters on child are combined with those on parent class.
{@link org.apache.juneau.rest.annotation.RestResource#pojoSwaps() pojoSwaps()} POJO swaps on child are combined with those on parent class.
{@link org.apache.juneau.rest.annotation.RestResource#properties() properties()} Properties on child are combined with those on parent class.
Properties are applied parent-to-child in the order they appear in the annotation.
Properties on methods take precedence over those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#serializers() serializers()} Serializers on child are combined with those on parent class.
Serializers on methods take precedence over those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#parsers() parsers()} Parsers on child are combined with those on parent class.
Parsers on methods take precedence over those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#responseHandlers() responseHandlers()} Response handlers on child are combined with those on parent class.
{@link org.apache.juneau.rest.annotation.RestResource#encoders() encoders()} Encoders on child are combined with those on parent class.
{@link org.apache.juneau.rest.annotation.RestResource#defaultRequestHeaders() defaultRequestHeaders()} Headers on child are combined with those on parent class.
Headers are applied parent-to-child in the order they appear in the annotation.
Headers on methods take precedence over those on classes.
{@link org.apache.juneau.rest.annotation.RestResource#defaultResponseHeaders() defaultResponseHeaders()} Headers on child are combined with those on parent class.
Headers are applied parent-to-child in the order they appear in the annotation.
{@link org.apache.juneau.rest.annotation.RestResource#children() children()} Children on child are combined with those on parent class.
Children are list parent-to-child in the order they appear in the annotation.
{@link org.apache.juneau.rest.annotation.RestResource#path() path()} Path is searched for in child-to-parent order.
{@link org.apache.juneau.rest.annotation.RestResource#title() title()} Label is searched for in child-to-parent order.
{@link org.apache.juneau.rest.annotation.RestResource#description() description()} Description is searched for in child-to-parent order.
{@link org.apache.juneau.rest.annotation.RestResource#config() config()} Config file is searched for in child-to-parent order.
{@link org.apache.juneau.rest.annotation.RestResource#staticFiles() staticFiles()} Static files on child are combined with those on parent class.
Static files are are executed child-to-parent in the order they appear in the annotation.

7.5 - RestContext

The {@link org.apache.juneau.rest.RestContext} object is the workhorse class for all of the configuration of a single REST resource class.
It's by-far the most important class in the REST API.

Every class annotated with @RestResource ends up with an instance of this object.
The object itself is read-only and unchangeable.
It is populated through the following:

The annotation should be self-explanatory at this point.
The builder allows you to perform all of the same configuration as the annotation programmatically.

The {@link org.apache.juneau.rest.RestContextBuilder} class extends {@link org.apache.juneau.BeanContextBuilder} allowing you to programmatically set any properties defined on that builder class.
It also implements {@link javax.servlet.ServletConfig}

To access this object, simply pass it in as a constructor argument or in an INIT hook:

// Option #1 - Pass in through constructor. public MyResource(RestContextBuilder builder) { builder .pojoSwaps(CalendarSwap.RFC2822DTZ.class) .set(PARSER_debug, true); } // Option #2 - Use an INIT hook. @RestHook(INIT) public void init(RestContextBuilder builder) throws Exception { builder .pojoSwaps(CalendarSwap.RFC2822DTZ.class) .set(PARSER_debug, true); }

Warning: This class is huge.
Through it, you can configure bean/serializer/parser settings, define config files, children, resource finders, info providers, etc...

7.6 - @RestMethod

REST Java methods are identified on REST servlets using the {@link org.apache.juneau.rest.annotation.RestMethod @RestMethod} annotation.
The annotation allows the framework to identify the available REST methods through reflection.

Example:

@RestMethod(name=GET, path="/") public String sayHello() { return "Hello world!"; }

There are no restrictions on the name of the Java method.

7.6.1 - Java Method Parameters

Java methods can contain any of the following parameters in any order:

  • Parameters based on class types:
    • Request/response objects:
      • {@link org.apache.juneau.rest.RestRequest} - The request object.
      • {@link javax.servlet.http.HttpServletRequest} - The superclass of RestRequest.
      • {@link org.apache.juneau.rest.RestResponse} - The response object.
      • {@link javax.servlet.http.HttpServletResponse} - The superclass of RestResponse.
    • Parsed request header values:
      • {@link org.apache.juneau.http.Accept}
      • {@link org.apache.juneau.http.AcceptCharset}
      • {@link org.apache.juneau.http.AcceptEncoding}
      • {@link org.apache.juneau.http.AcceptLanguage}
      • {@link org.apache.juneau.http.Authorization}
      • {@link org.apache.juneau.http.CacheControl}
      • {@link org.apache.juneau.http.Connection}
      • {@link org.apache.juneau.http.ContentLength}
      • {@link org.apache.juneau.http.ContentType}
      • {@link org.apache.juneau.http.Date}
      • {@link org.apache.juneau.http.Expect}
      • {@link org.apache.juneau.http.From}
      • {@link org.apache.juneau.http.Host}
      • {@link org.apache.juneau.http.IfMatch}
      • {@link org.apache.juneau.http.IfModifiedSince}
      • {@link org.apache.juneau.http.IfNoneMatch}
      • {@link org.apache.juneau.http.IfRange}
      • {@link org.apache.juneau.http.IfUnmodifiedSince}
      • {@link org.apache.juneau.http.MaxForwards}
      • {@link org.apache.juneau.http.Pragma}
      • {@link org.apache.juneau.http.ProxyAuthorization}
      • {@link org.apache.juneau.http.Range}
      • {@link org.apache.juneau.http.Referer}
      • {@link org.apache.juneau.http.TE}
      • {@link org.apache.juneau.http.UserAgent}
      • {@link org.apache.juneau.http.Upgrade}
      • {@link org.apache.juneau.http.Via}
      • {@link org.apache.juneau.http.Warning}
      • {@link java.util.TimeZone}
    • Direct streams on request/response:
      • {@link java.io.InputStream}
      • {@link javax.servlet.ServletInputStream}
      • {@link java.io.Reader}
      • {@link java.io.OutputStream}
      • {@link javax.servlet.ServletOutputStream}
      • {@link java.io.Writer}
    • Localization:
      • {@link java.util.ResourceBundle} - Client-localized resource bundle.
      • {@link org.apache.juneau.utils.MessageBundle} - A resource bundle with additional features.
      • {@link java.util.Locale} - Client locale.
    • Request APIs:
      • {@link org.apache.juneau.rest.RequestHeaders} - API for accessing request headers.
      • {@link org.apache.juneau.rest.RequestQuery} - API for accessing request query parameters.
      • {@link org.apache.juneau.rest.RequestFormData} - API for accessing request form data.
      • {@link org.apache.juneau.rest.RequestPathMatch} - API for accessing path variables.
      • {@link org.apache.juneau.rest.RequestBody} - API for accessing request body.
    • Other:
      • {@link org.apache.juneau.http.HttpMethod} - The method name matched (when using @RestMethod(name="*"))
      • {@link org.apache.juneau.rest.RestLogger} - Logger with additional features.
      • {@link org.apache.juneau.rest.RestContext} - The resource read-only context.
      • {@link org.apache.juneau.parser.Parser} - The parser matching the request content type.
      • {@link org.apache.juneau.dto.swagger.Swagger} - The auto-generated Swagger doc.
      • {@link org.apache.juneau.config.Config} - The external config for the resource.
      • {@link org.apache.juneau.rest.RequestProperties} - API for modifying request-time configuration properties.
  • Annotated parameters:
    • {@link org.apache.juneau.rest.annotation.Path} - Variables in matched URL path patterns.
    • {@link org.apache.juneau.rest.annotation.FormData} - Multipart form post parameter values.
    • {@link org.apache.juneau.rest.annotation.HasFormData} - Denotes whether the form data parameter exists.
    • {@link org.apache.juneau.rest.annotation.Query} - Query parameters. Using this prevents the HTTP body from being processed as a URL-Encoded form post.
    • {@link org.apache.juneau.rest.annotation.HasQuery} - Denotes whether the query parameter exists.
    • {@link org.apache.juneau.rest.annotation.Header} - A header value.
    • {@link org.apache.juneau.rest.annotation.Method} - The HTTP method name.
    • {@link org.apache.juneau.rest.annotation.PathRemainder} - The remainder value after path pattern match.
    • {@link org.apache.juneau.rest.annotation.Body} - The HTTP content parsed as a POJO.
Example:

@RestMethod(name=GET, path="/example1/{a1}/{a2}/{a3}/*") public String doGetExample1( RestRequest req, RestResponse res, @Method String method, @Path String a1, @Path int a2, @Path UUID a3, @Query("p1") int p1, @Query("p2") String p2, @Query("p3") UUID p3, @HasQuery("p3") boolean hasP3, @PathRemainder String remainder, @Header("Accept-Language") String lang, @Header("Accept") String accept, @Header("DNT") int doNotTrack, RequestProperties properties, ResourceBundle nls ) { // Do something with all of those }

Notes:
  • All annotations have programmatic equivalents on the {@link org.apache.juneau.rest.RestRequest} class.
See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_paramResolvers} - For configuring custom parameter types.

7.6.2 - RestRequest

The {@link org.apache.juneau.rest.RestRequest} object is an extension of the HttpServletRequest class with various built-in convenience methods for use in building REST interfaces.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RestRequest req) {...}

There are many useful methods on this object, but the main ones are shown below:

  • {@link org.apache.juneau.rest.RestRequest} extends HttpServletRequest
    • {@link org.apache.juneau.rest.RestRequest#getHeaders() getHeaders()}
    • {@link org.apache.juneau.rest.RestRequest#getQuery() getQuery()}
    • {@link org.apache.juneau.rest.RestRequest#getFormData() getFormData()}
    • {@link org.apache.juneau.rest.RestRequest#getBody() getBody()}
    • {@link org.apache.juneau.rest.RestRequest#getPathMatch() getPathMatch()}
    • {@link org.apache.juneau.rest.RestRequest#getLogger() getLogger()}
    • {@link org.apache.juneau.rest.RestRequest#getInfoProvider() getInfoProvider()}
    • {@link org.apache.juneau.rest.RestRequest#getSwagger() getSwagger()}
    • {@link org.apache.juneau.rest.RestRequest#getConfig() getConfig()}
    • {@link org.apache.juneau.rest.RestRequest#getVarResolverSession() getVarResolverSession()}
    • {@link org.apache.juneau.rest.RestRequest#getMessageBundle() getMessageBundle()}
    • {@link org.apache.juneau.rest.RestRequest#getProperties() getProperties()}
    • {@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(String,boolean,MediaType) getClasspathReaderResource(String,boolean,MediaType)}

7.6.3 - RestResponse

The {@link org.apache.juneau.rest.RestResponse} object is an extension of the HttpServletResponse class with various built-in convenience methods for use in building REST interfaces.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RestResponse req) {...}

The important methods on this class are shown below:

  • {@link org.apache.juneau.rest.RestResponse} extends HttpServletResponse
    • {@link org.apache.juneau.rest.RestResponse#setOutput(Object) setOutput(Object)}
    • {@link org.apache.juneau.rest.RestResponse#getProperties() getProperties()}
    • {@link org.apache.juneau.rest.RestResponse#getHtmlDocBuilder() getHtmlDocBuilder()}
    • {@link org.apache.juneau.rest.RestResponse#getDirectWriter(String) getDirectWriter(String)}

7.6.4 - RequestBody

The {@link org.apache.juneau.rest.RequestBody} object is the API for accessing the body of an HTTP request.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RequestBody body) {...}

Example:

@RestMethod(...) public void doPost(RequestBody body) { // Convert body to a linked list of Person objects. List<Person> l = body.asType(LinkedList.class, Person.class); ... }

The important methods on this class are:

  • {@link org.apache.juneau.rest.RequestBody}
    • {@link org.apache.juneau.rest.RequestBody#getReader() getReader()}
    • {@link org.apache.juneau.rest.RequestBody#getInputStream() getInputStream()}
    • {@link org.apache.juneau.rest.RequestBody#asType(Class) asType(Class)}
    • {@link org.apache.juneau.rest.RequestBody#asType(Type,Type...) asType(Type,Type...)}
    • {@link org.apache.juneau.rest.RequestBody#asString() asString()}
    • {@link org.apache.juneau.rest.RequestBody#asHex() asHex()}
    • {@link org.apache.juneau.rest.RequestBody#asSpacedHex() asSpacedHex()}

7.6.5 - RequestHeaders

The {@link org.apache.juneau.rest.RequestHeaders} object is the API for accessing the headers of an HTTP request.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RequestHeaders headers) {...}

Example:

@RestMethod(...) public Object myMethod(RequestHeaders headers) { // Add a default value. headers.addDefault("ETag", DEFAULT_UUID); // Get a header value as a POJO. UUID etag = headers.get("ETag", UUID.class); // Get a standard header. CacheControl = headers.getCacheControl(); }

The important methods on this class are:

  • {@link org.apache.juneau.rest.RequestHeaders} extends TreeMap<String,String[]>
    • {@link org.apache.juneau.rest.RequestHeaders#get(String,Class) get(String,Class)}
    • {@link org.apache.juneau.rest.RequestHeaders#get(String,Type,Type...) get(String,Type,Type)}
    • {@link org.apache.juneau.rest.RequestHeaders#getString(String,String) getString(String,String)}
    • {@link org.apache.juneau.rest.RequestHeaders#getInt(String,int) getInt(String,int)}
    • {@link org.apache.juneau.rest.RequestHeaders#getBoolean(String,boolean) getBoolean(String,boolean)}
    • {@link org.apache.juneau.rest.RequestHeaders#addDefault(String,Object) addDefault(String,Object)}

7.6.6 - RequestQuery

The {@link org.apache.juneau.rest.RequestQuery} object is the API for accessing the GET query parameters of an HTTP request.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RequestQuery query) {...}

Example:

@RestMethod(...) public Object myMethod(RequestQuery query) { // Get query parameters converted to various types. int p1 = query.get("p1", 0, int.class); String p2 = query.get("p2", String.class); UUID p3 = query.get("p3", UUID.class); }

An important distinction between the behavior of this object and HttpServletRequest.getParameter(String) is that the former will NOT load the body of the request on FORM POSTS and will only look at parameters found in the query string.
This can be useful in cases where you're mixing GET parameters and FORM POSTS and you don't want to inadvertantly read the body of the request to get a query parameter.

The important methods on this class are:

  • {@link org.apache.juneau.rest.RequestQuery} extends LinkedHashMap<String,String[]>
    • {@link org.apache.juneau.rest.RequestQuery#get(String,Class) get(String,Class)}
    • {@link org.apache.juneau.rest.RequestQuery#get(String,Type,Type...) get(String,Type,Type)}
    • {@link org.apache.juneau.rest.RequestQuery#getString(String,String) getString(String,String)}
    • {@link org.apache.juneau.rest.RequestQuery#getInt(String,int) getInt(String,int)}
    • {@link org.apache.juneau.rest.RequestQuery#getBoolean(String,boolean) getBoolean(String,boolean)}
    • {@link org.apache.juneau.rest.RequestQuery#addDefault(String,Object) addDefault(String,Object)}
    • {@link org.apache.juneau.rest.RequestQuery#getSearchArgs() getSearchArgs()}

7.6.7 - RequestFormData

The {@link org.apache.juneau.rest.RequestFormData} object is the API for accessing the HTTP request body as form data.
It can be accessed by passing it as a parameter on your REST Java method:

@RestMethod(...) public Object myMethod(RequestFormData query) {...}

Example:

@RestMethod(...) public Object myMethod(RequestFormData formData) { // Get query parameters converted to various types. int p1 = formData.get("p1", 0, int.class); String p2 = formData.get("p2", String.class); UUID p3 = formData.get("p3", UUID.class); }

Note that this object does NOT take GET parameters into account and only returns values found in the body of the request.

The important methods on this class are:

  • {@link org.apache.juneau.rest.RequestFormData} extends LinkedHashMap<String,String[]>
    • {@link org.apache.juneau.rest.RequestFormData#get(String,Class) get(String,Class)}
    • {@link org.apache.juneau.rest.RequestFormData#get(String,Type,Type...) get(String,Type,Type)}
    • {@link org.apache.juneau.rest.RequestFormData#getString(String,String) getString(String,String)}
    • {@link org.apache.juneau.rest.RequestFormData#getInt(String,int) getInt(String,int)}
    • {@link org.apache.juneau.rest.RequestFormData#getBoolean(String,boolean) getBoolean(String,boolean)}
    • {@link org.apache.juneau.rest.RequestFormData#addDefault(String,Object) addDefault(String,Object)}

7.6.8 - @RestMethod.path()

The {@link org.apache.juneau.rest.annotation.RestMethod#path() @RestMethod.path()} annotation allows you to define URL path patterns to match against.
These patterns can contain variables of the form "{xxx}" that can be passed in directly to the Java methods as extra parameters.

In the following example, 3 separate GET request handlers are defined with different path patterns.
Note how the variables are passed in as additional arguments on the method, and how those arguments are automatically converted to the specified class type...

// Default method @RestMethod(name=GET, path="/*") public void doGetDefault() { ... } // Method with path pattern @RestMethod(name=GET, path="/xxx") public void doGetNoArgs(...) { ... } // Method with path pattern with arguments @RestMethod(name=GET, path="/xxx/{foo}/{bar}/{baz}/{bing}") public void doGetWithArgs(@Path String foo, @Path int bar, @Path MyEnum baz, @Path UUID bing) { ... }

By default, path patterns are matched using a best-match heuristic.
When overlaps occur, URLs are matched from most-specific to most-general order:

// Try first @RestMethod(name=GET, path="/foo/bar") public void method1() { ... } // Try second @RestMethod(name=GET, path="/foo/{bar}") public void method2(...) { ... } // Try third @RestMethod(name=GET, path="/foo/*") public void method3(...) { ... } // Try last @RestMethod(name=GET, path="/*") public void method4(...) { ... }

The match heuristic behavior can be overridden by the {@link org.apache.juneau.rest.annotation.RestMethod#priority() @RestMethod.priority()} annotation property.
However, in practice this is almost never needed.

Paths that end with "/*" will do a prefix match on the incoming URL.
Any remainder after the match can be accessed through {@link org.apache.juneau.rest.RequestPathMatch#getRemainder()} or parameters with the {@link org.apache.juneau.rest.annotation.PathRemainder @PathRemainder} annotation.
On the other hand, paths that don't end with "/*" (e.g. "/" or "/foo") will require an exact URL match, and if any remainder exists, a 404 (not found) error will be thrown.

The following example shows the distinction.

@RestMethod(name=GET, path="/*") public void doGet(@PathRemainder String remainder) { // URL path pattern can have remainder accessible through req.getRemainder(). } @RestMethod(name=PUT, path="/") public void doPut() { // URL path pattern must match exactly and will cause a 404 error if a remainder exists. }

Annotations are provided for easy access to URL parameters with automatic conversion to any parsable type.
For example, the following example can process the URL "/urlWithParams?foo=foo&bar=[1,2,3]&baz=067e6162-3b6f-4ae2-a171-2470b63dff00"...

// Example GET request with access to query parameters @RestMethod(name=GET, path="/urlWithParams") public String doGetWithParams(@Query("foo") String foo, @Query("bar") int bar, @Query("baz") UUID baz) throws Exception { return "GET /urlWithParams?foo="+foo+"&bar="+bar+"&baz="+baz; }

See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_path}
  • {@link org.apache.juneau.rest.annotation.Path}
  • {@link org.apache.juneau.rest.annotation.PathRemainder}
  • {@link org.apache.juneau.rest.RequestPathMatch}

7.6.9 - RequestPathMatch

The {@link org.apache.juneau.rest.RequestPathMatch} object is the API for accessing the matched variables and remainder on the URL path.

@RestMethod(...) public Object myMethod(RequestPathMatch path) {...}

Example:

@RestMethod(..., path="/{foo}/{bar}/{baz}/*") public void doGet(RequestPathMatch path) { // Example URL: /123/qux/true/quux int foo = pm.getInt("foo"); // =123 String bar = pm.getString("bar"); // =qux boolean baz = pm.getBoolean("baz"); // =true String remainder = pm.getRemainder(); // =quux }

The important methods on this class are:

  • {@link org.apache.juneau.rest.RequestPathMatch} extends TreeMap<String,String>
    • {@link org.apache.juneau.rest.RequestPathMatch#get(String,Class) get(String,Class)}
    • {@link org.apache.juneau.rest.RequestPathMatch#get(String,Type,Type...) get(String,Type,Type)}
    • {@link org.apache.juneau.rest.RequestPathMatch#getString(String) getString(String)}
    • {@link org.apache.juneau.rest.RequestPathMatch#getInt(String) getInt(String)}
    • {@link org.apache.juneau.rest.RequestPathMatch#getBoolean(String) getBoolean(String)}
    • {@link org.apache.juneau.rest.RequestPathMatch#getRemainder() getRemainder()}

7.6.10 - Method Return Types

The return type can be any serializable POJO as defined in POJO Categories.
It can also be void if the method is not sending any output (e.g. a request redirect) or is setting the output using the {@link org.apache.juneau.rest.RestResponse#setOutput(Object)} method.

Example:

@RestMethod(name=GET) public String doGet() { return "Hello World!"; }

Out-of-the-box, besides POJOs, the following return types are handled as special cases:

  • {@link java.io.InputStream}
    The contents are simply piped to the output stream returned by {@link org.apache.juneau.rest.RestResponse#getNegotiatedOutputStream()}.
    Note that you should call {@link org.apache.juneau.rest.RestResponse#setContentType(String)} to set the Content-Type header if you use this object type.
  • {@link java.io.Reader}
    The contents are simply piped to the output stream returned by {@link org.apache.juneau.rest.RestResponse#getNegotiatedWriter()}.
    Note that you should call {@link org.apache.juneau.rest.RestResponse#setContentType(String)} to set the Content-Type header if you use this object type.
  • {@link org.apache.juneau.rest.Redirect}
    Represents an HTTP redirect response.
  • {@link org.apache.juneau.Streamable}
    Interface that identifies that an object can be serialized directly to an output stream.
  • {@link org.apache.juneau.Writable}
    Interface that identifies that an object can be serialized directly to a writer.
  • {@link org.apache.juneau.utils.ZipFileList}
    Special interface for sending zip files as responses.

This is controlled through the following extensible API:

  • {@link org.apache.juneau.rest.ResponseHandler}
    • {@link org.apache.juneau.rest.response.DefaultHandler}
    • {@link org.apache.juneau.rest.response.InputStreamHandler}
    • {@link org.apache.juneau.rest.response.ReaderHandler}
    • {@link org.apache.juneau.rest.response.RedirectHandler}
    • {@link org.apache.juneau.rest.response.StreamableHandler}
    • {@link org.apache.juneau.rest.response.WritableHandler}
    • {@link org.apache.juneau.rest.response.ZipFileListResponseHandler}

REST Java methods can generate output in any of the following ways:

  • By returning a serializable POJO, or any of the following:
    {@link java.io.Reader}, {@link java.io.InputStream}, {@link org.apache.juneau.Streamable}, {@link org.apache.juneau.Writable}
  • By calling {@link org.apache.juneau.rest.RestResponse#setOutput(Object)} with any of the types above.
  • By accessing the {@link java.io.Writer} directly by calling {@link org.apache.juneau.rest.RestResponse#getNegotiatedWriter()} and writing the output yourself.
Example:

// Equivalent method 1 @RestMethod(name=GET, path="/example1/{personId}") public Person doGet1(@Path UUID personId) { Person p = getPersonById(personId); return p; } // Equivalent method 2 @RestMethod(name=GET, path="/example2/{personId}") public void doGet2(RestResponse res, @Path UUID personId) { Person p = getPersonById(personId); res.setOutput(p); } // (Sorta) Equivalent method 3 // (Ignores any converters or method-level properties) @RestMethod(name=GET, path="/example3/{personId}") public void doGet3(RestRequest req, RestResponse res, @Path UUID personId) { Person p = getPersonById(personId); String accept = req.getHeader("Accept", "text/json"); WriterSerializer s = res.getSerializerGroup().getWriterSerializer(accept); res.setContentType(s.getResponseContentType()); s.serialize(p, res.getNegotiatedWriter()); }

See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_responseHandlers} - For configuring custom response handlers.

7.6.11 - ReaderResource

The {@link org.apache.juneau.rest.ReaderResource} class is a convenience object for defining thread-safe reusable character-based responses.
In essence, it's a container for character data with optional response headers and support for resolving SVL variables.

The {@link org.apache.juneau.rest.ReaderResource} class implements the {@link org.apache.juneau.Writable} interface which is handled by the {@link org.apache.juneau.rest.response.WritableHandler} class.
This allows these objects to be returned as responses by REST methods.

Example:

@RestMethod(...) public Object sayHello(RestRequest req) { // Return a reader resource loaded from a file with support for request-time SVL variables. return ReaderResource.create() .contents(new File("helloWorld.txt")) .varResolver(req.getVarResolver()) .header("Cache-Control", "no-cache") .mediaType(TEXT_PLAIN) .build(); }

The important methods on this class are:

  • {@link org.apache.juneau.rest.ReaderResourceBuilder}
    • {@link org.apache.juneau.rest.ReaderResourceBuilder#contents(Object...) contents(Object...)}
    • {@link org.apache.juneau.rest.ReaderResourceBuilder#header(String,Object) header(String,Object)}
    • {@link org.apache.juneau.rest.ReaderResourceBuilder#mediaType(MediaType) mediaType(MediaType)}
    • {@link org.apache.juneau.rest.ReaderResourceBuilder#varResolver(VarResolverSession) varResolver(VarResolverSession)}

7.6.12 - StreamResource

The {@link org.apache.juneau.rest.StreamResource} class is the binary equivalent to the {@link org.apache.juneau.rest.ReaderResource} object.
In essence, it's a container for binary data with optional response headers.

The {@link org.apache.juneau.rest.StreamResource} class implements the {@link org.apache.juneau.Streamable} interface which is handled by the {@link org.apache.juneau.rest.response.StreamableHandler} class.
This allows these objects to be returned as responses by REST methods.

Example:

@RestMethod(...) public Object showPicture(RestRequest req) { // Return a stream resource loaded from a file. return StreamResource.create() .contents(new File("mypicture.png")) .header("Cache-Control", "no-cache") .mediaType(IMAGE_PNG) .build(); }

The important methods on this class are:

  • {@link org.apache.juneau.rest.StreamResourceBuilder}
    • {@link org.apache.juneau.rest.StreamResourceBuilder#contents(Object...) contents(Object...)}
    • {@link org.apache.juneau.rest.StreamResourceBuilder#header(String,Object) header(String,Object)}
    • {@link org.apache.juneau.rest.StreamResourceBuilder#mediaType(MediaType) mediaType(MediaType)}

7.6.13 - Redirect

The {@link org.apache.juneau.rest.Redirect} object is a convenience shortcut for performing HTTP 302 redirects.

Redirect objects are handled by the {@link org.apache.juneau.rest.response.RedirectHandler} class.
This allows these objects to be returned as responses by REST methods.

The following example shows the difference between handling redirects via RestResponse and the simplified approach of using this class.

Example:

// Redirect to "/contextPath/servletPath/foobar" // Using RestRequest/RestResponse @RestMethod(...) public void redirectExample1(RestRequest req, RestResponse res) throws IOException { res.sendRedirect(req.getServletURI() + "/foobar"); } // Using Redirect @RestMethod(...) public Redirect redirectExample2() { return new Redirect("foobar"); }

The constructor can use a MessageFormat-style pattern with multiple arguments.

Example:

@RestMethod(...) public Redirect redirectExample3() { return new Redirect("foo/{0}/bar/{1}", id1, id2); }

The arguments are automatically URL-encoded.

Redirecting to the servlet root can be accomplished by simply using the no-arg constructor.

Example:

// Simply redirect to the servlet root. // Equivalent to res.sendRedirect(req.getServletURI()). @RestMethod(...) public Redirect redirectExample4() { return new Redirect(); }

7.6.14 - @RestMethod.matchers()

{@link org.apache.juneau.rest.RestMatcher RestMatchers} are used to allow multiple Java methods to be tied to the same HTTP method and path, but differentiated by some request attribute such as a specific header value.

Example:

// GET method that gets invoked for administrators @RestMethod(name=GET, path="/*", matchers=IsAdminMatcher.class) public Object doGetForAdmin() { ... } // GET method that gets invoked for everyone else @RestMethod(name=GET, path="/*") public Object doGetForEveryoneElse() { ... }

The interface for matchers is simple:

public class IsAdminMatcher extends RestMatcher { @Override /* RestMatcher */ public boolean matches(RestRequest req) { return req.isUserInRole("ADMINS_GROUP"); } }

Notes:
  • If no methods are found with a matching matcher, a 412 Precondition Failed status is returned.
  • If multiple matchers are specified on the same method, ONLY ONE matcher needs to match for the method to be invoked.
  • Note that you CANNOT define identical paths on different methods UNLESS you use matchers.
    That includes paths that are only different in variable names (e.g. "/foo/{bar}" and "/foo/{baz}").
    If you try to do so, a ServletException will be thrown on startup.
  • Methods with matchers take precedence over methods without.
    Otherwise, methods are attempted in the order they appear in the class.
See Also:
  • {@link org.apache.juneau.rest.annotation.RestMethod#matchers}
  • {@link org.apache.juneau.rest.matchers.MultipartFormDataMatcher}
  • {@link org.apache.juneau.rest.matchers.UrlEncodedFormMatcher}

7.7 - @Body

The {@link org.apache.juneau.rest.annotation.Body @Body} annotation provides easy access to the HTTP body content as any parsable POJO type
In the example below, we're POSTing beans.

// Example POST of a bean @RestMethod(name=POST, path="/") public void doPost(@Body Person person) throws Exception { // Do something with person. }

The HTTP body of a request can be retrieved as a parsed POJO using either the {@link org.apache.juneau.rest.RestRequest#getBody()} method, or a parameter annotated with {@link org.apache.juneau.rest.annotation.Body @Body}.

// Equivalent method 1 @RestMethod(name=POST, path="/example1") public void doPost1(@Body Person p) { // Do something with p. } // Equivalent method 2 @RestMethod(name=POST, path="/example2") public void doPost2(RestRequest req) { Person p = req.getBody).asType(Person.class); // Do something with p. }

The Juneau framework will automatically determine the appropriate Parser to use based on the Content-Type HTTP header.
So the body content could be JSON or XML or any other supported parsing types.

See Also:

7.7.1 - Handling Form Posts

The best way to handle a form post is by using an input bean.
The samples include a UrlEncodedFormResource class that takes in URL-Encoded form post of the form "aString=foo&aNumber=123&aDate=2001-07-04T15:30:45Z".
The code is shown here:

@RestResource( path="/urlEncodedForm" ) public class UrlEncodedFormResource extends BasicRestServlet { /** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@Body FormInputBean input) throws Exception { // Just mirror back the request return input; } public static class FormInputBean { public String aString; public int aNumber; @BeanProperty(pojoSwaps=CalendarSwap.ISO8601DT.class) public Calendar aDate; } }

Another possibility is to access the form parameters individually:

/** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@FormData("aString") String aString, @FormData("aNumber") int aNumber, @FormData("aDate") Calendar aDate) throws Exception { ... }

The advantage to the form input bean is that it can handle any of the parsable types (e.g. JSON, XML...) in addition to URL-Encoding.
The latter approach only supports URL-Encoding.

  • If you're using form input beans, DO NOT use the @FormData attribute or {@link org.apache.juneau.rest.RestRequest#getParameter(String)} method since this will cause the underlying JEE servlet to parse the HTTP body as a form post.
    Your input bean will end up being null since there won't be any content left after the servlet has parsed the body of the request.
    This applies to WHENEVER you use @Body or {@link org.apache.juneau.rest.RestRequest#getBody()}

7.7.2 - Handling Multi-Part Form Posts

The Juneau framework does not natively support multipart form posts.
However, it can be done in conjunction with the Apache Commons File Upload library.

The samples include a TempDirResource class that uses the File Upload library to allow files to be uploaded as multipart form posts.

Example:

@RestResource( path="/tempDir" ) public class TempDirResource extends DirectoryResource { /** * [POST /upload] - Upload a file as a multipart form post. * Shows how to use the Apache Commons ServletFileUpload class for handling multi-part form posts. */ @RestMethod(name=POST, path="/upload", matchers=TempDirResource.MultipartFormDataMatcher.class) public Redirect uploadFile(RestRequest req) throws Exception { ServletFileUpload upload = new ServletFileUpload(); FileItemIterator iter = upload.getItemIterator(req); while (iter.hasNext()) { FileItemStream item = iter.next(); if (item.getFieldName().equals("contents")) { File f = new File(getRootDir(), item.getName()); IOPipe.create(item.openStream(), new FileOutputStream(f)).closeOut().run(); } } return new Redirect(); // Redirect to the servlet root. } /** Causes a 404 if POST isn't multipart/form-data */ public static class MultipartFormDataMatcher extends RestMatcher { @Override /* RestMatcher */ public boolean matches(RestRequest req) { String contentType = req.getContentType(); return contentType != null && contentType.startsWith("multipart/form-data"); } }

7.8 - @FormData

The {@link org.apache.juneau.rest.annotation.FormData @FormData} annotation is used to retrieve request form post entries.

Example:

@RestMethod(name=POST) public void doPost(RestRequest req, RestResponse res, @FormData("p1") int p1, @FormData("p2") String p2, @FormData("p3") UUID p3) {...}

This is functionally equivalent to the following code:

@RestMethod(name=POST) public void doPost(RestRequest req, RestResponse res) { RequestFormData fd = req.getFormData(); int p1 = fd.get("p1", 0, int.class); String p2 = fd.get("p2", String.class); UUID p3 = fd.get("p3", UUID.class); }

The registered {@link org.apache.juneau.rest.RestContext#REST_partParser} is used to convert strings to POJOs and controls what POJO types are supported.
As a general rule, any POJO convertible from a String is supported.

See Also:

7.9 - @Query

The {@link org.apache.juneau.rest.annotation.Query @Query} annotation is used to retrieve request URL query parameters.
It's identical to {@link org.apache.juneau.rest.annotation.FormData @FormData}, but only retrieves the parameter from the URL string, not URL-encoded form posts.

Unlike {@link org.apache.juneau.rest.annotation.FormData @FormData}, using this annotation does not result in the servlet reading the contents of URL-encoded form posts.
Therefore, this annotation can be used in conjunction with the {@link org.apache.juneau.rest.annotation.Body @Body} annotation or {@link org.apache.juneau.rest.RestRequest#getBody()} method for application/x-www-form-urlencoded POST calls.

Example:

@RestMethod(name=GET) public void doGet(RestRequest req, RestResponse res, @Query("p1") int p1, @Query("p2") String p2, @Query("p3") UUID p3) {...}

This is functionally equivalent to the following code:

@RestMethod(name=GET) public void doGet(RestRequest req, RestResponse res) { RequestQuery q = req.getQuery(); int p1 = q.get("p1", 0, int.class); String p2 = q.get("p2", String.class); UUID p3 = q.get("p3", UUID.class); }

The registered {@link org.apache.juneau.rest.RestContext#REST_partParser} is used to convert strings to POJOs and controls what POJO types are supported.
As a general rule, any POJO convertible from a String is supported.

See Also:

7.10 - @Header

The {@link org.apache.juneau.rest.annotation.Header @Header} annotation is used to retrieve request headers.

Example:

@RestMethod(name=GET) public void doGet(RestRequest req, RestResponse res, @Header("ETag") UUID etag) {...}

This is functionally equivalent to the following code:

@RestMethod(name=GET) public void doPostPerson(RestRequest req, RestResponse res) { RequestHeaders h = req.getHeaders(); UUID etag = h.get("ETag", UUID.class); }

The registered {@link org.apache.juneau.rest.RestContext#REST_partParser} is used to convert strings to POJOs and controls what POJO types are supported.
As a general rule, any POJO convertible from a String is supported.

See Also:

7.11 - Serializers

REST resources use the {@link org.apache.juneau.serializer.Serializer} API for defining serializers for serializing response POJOs.

The servlet will pick which serializer to use by matching the request Accept header with the media types defined through the {@link org.apache.juneau.serializer.Serializer#getMediaTypes()} method.

Serializers can be associated with REST servlets in the following ways:

The following are all equivalent ways of defining serializers used by a resource:

// Option #1 - Defined via annotation. @RestResource(serializers={JsonSerializer.class, XmlSerializer.class}) public class MyResource { // Option #2 - Defined via builder passed in through resource constructor. public MyResource(RestContextBuilder builder) throws Exception { // Using method on builder. builder.serializers(JsonSerializer.class, XmlSerializer.class); // Same, but use pre-instantiated parsers. builder.serializers(JsonSerializer.DEFAULT, XmlSerializer.DEFAULT); // Same, but using property. builder.set(REST_serializers, JsonSerializer.class, XmlSerializer.class); } // Option #3 - Defined via builder passed in through init method. @RestHook(INIT) public void init(RestContextBuilder builder) throws Exception { builder.serializers(JsonSerializer.class, XmlSerializer.class); } // Override at the method level. @RestMethod(serializers={HtmlSerializer.class}) public MyPojo myMethod() { // Return a POJO to be serialized. return new MyPojo(); } }

See Also:

7.12 - Parsers

REST resources use the {@link org.apache.juneau.parser.Parser} API for defining parsers for parsing request body content and converting them into POJOs.

The servlet will pick which parser to use by matching the request Content-Type header with the media types defined through the {@link org.apache.juneau.parser.Parser#getMediaTypes()} method.

Parsers can be associated with REST servlets in the following ways:

The following are all equivalent ways of defining parsers used by a resource:

// Option #1 - Defined via annotation. @RestResource(parsers={JsonParser.class, XmlParser.class}) public class MyResource { // Option #2 - Defined via builder passed in through resource constructor. public MyResource(RestContextBuilder builder) throws Exception { // Using method on builder. builder.parsers(JsonParser.class, XmlParser.class); // Same, but use pre-instantiated parsers. builder.parsers(JsonParser.DEFAULT, XmlParser.DEFAULT); // Same, but using property. builder.set(REST_parsers, JsonParser.class, XmlParser.class); } // Option #3 - Defined via builder passed in through init method. @RestHook(INIT) public void init(RestContextBuilder builder) throws Exception { builder.parsers(JsonParser.class, XmlParser.class); } // Override at the method level. @RestMethod(parsers={HtmlParser.class}) public Object myMethod(@Body MyPojo myPojo) { // Do something with your parsed POJO. } }

See Also:

7.13 - Properties

As shown in previous sections, Juneau serializers and parsers are highly-configurable through properties. (See Configurable Properties)

These properties can be defined for serializers and parsers registered on a REST resource via the following:

Example:

import static org.apache.juneau.BeanContext.*; import static org.apache.juneau.serializer.Serializer.*; import static org.apache.juneau.json.JsonSerializer.*; // Servlet with properties applied @RestResource( properties={ // Bean properties should be sorted alphabetically. @Property(name=BEAN_sortProperties, value="true"), // Nulls should not be serialized @Property(name=SERIALIZER_trimNulls, value="true"), // Solidus characters should be escaped in JSON @Property(name=JSON_escapeSolidus, value="true") } ) public MyRestServlet extends BasicRestServlet {...}

The programmatic equivalent to this is:

// Servlet with properties applied @RestResource(...) public MyRestServlet extends BasicRestServlet { public MyRestServlet(RestContextBuilder builder) { builder .sortProperties(); // Note: RestContextBuilder extends from BeanContextBuilder .set(SERIALIZER_trimNulls, true); .set(JSON_escapeSolidus, true); } }

Properties can also be overridden at the Java method level:

// GET method with method-level properties @RestMethod( name=GET, path="/*", properties={ // Bean properties should be sorted alphabetically. @Property(name=BEAN_sortProperties, value="true"), // Nulls should not be serialized @Property(name=SERIALIZER_trimNulls, value="true"), // Solidus characters should be escaped in JSON @Property(name=JSON_escapeSolidus, value="true") } public Object doGet() { ... }

Using the {@link org.apache.juneau.rest.RequestProperties} object:

// Access it from RestRequest. public Object doGet(RestRequest req) { RequestProperties properties = req.getProperties(); properties.put(JSON_escapeSolidus, true); } // Or just pass in a RequestProperties object. public Object doGet(RequestProperties properties) { properties.put(JSON_escapeSolidus, true); }

Properties set via {@link org.apache.juneau.rest.RequestProperties} are session-override properties that are passed in through {@link org.apache.juneau.serializer.SerializerSessionArgs} and {@link org.apache.juneau.parser.ParserSessionArgs} and can only be used on configuration settings marked as Session-overridable: true.

Properties are open-ended and can be used for other purposes.
They're made available through the following methods:

See Also:

7.14 - Transforms

The Juneau serializers and parsers can be configured on how to handle POJOs through the use of Transforms. (See Transforms)

Transforms are associated serializers and parsers registered on a REST resource via the following:

// Servlet with transforms applied @RestResource( pojoSwaps={ // Calendars should be serialized/parsed as ISO8601 date-time strings CalendarSwap.DEFAULT_ISO8601DT.class, // Byte arrays should be serialized/parsed as BASE64-encoded strings ByteArrayBase64Swap.class }, beanFilters={ // Subclasses of MyInterface will be treated as MyInterface objects. // Bean properties not defined on that interface will be ignored. } ) public MyRestServlet extends BasicRestServlet {...}

The programmatic equivalent to this is:

// Servlet with properties applied @RestResource(...) public MyRestServlet extends BasicRestServlet { public MyRestServlet(RestContextBuilder builder) { builder .pojoSwaps( CalendarSwap.DEFAULT_ISO8601DT.class, ByteArrayBase64Swap.class ) .beanFilters(MyInterface.class); } }

7.15 - Guards

Guards are classes that control access to REST classes and methods.

Guards are associated with resource classes and methods via the following:

// Define a guard that only lets Billy make a request public BillyGuard extends RestGuard { @Override /* RestGuard */ public boolean isRequestAllowed(RestRequest req) { return req.getUserPrincipal().getName().equals("Billy"); } } // Servlet with class-level guard applied @RestResource(guards=BillyGuard.class) public MyRestServlet extends RestServletDefult { // Delete method that only Billy is allowed to call. @RestMethod(name="DELETE") public doDelete(RestRequest req, RestResponse res) throws Exception {...} }

A common use for guards is to only allow admin access to certain Java methods...

// DELETE method @RestMethod(name=DELETE, guards={AdminGuard.class}) public void doDelete(RestRequest req, RestResponse res) throws Exception {...}

public class AdminGuard extends RestGuard { @Override /* RestGuard */ public boolean isRequestAllowed(RestRequest req) { return req.getUserPrincipal().isUserInRole("ADMIN"); } }

A guard failure results in an HTTP 401 Unauthorized response.
However, this can be configured by overriding the {@link org.apache.juneau.rest.RestGuard#guard(RestRequest,RestResponse)} and processing the response yourself.

public class AdminGuard extends RestGuard { @Override /* RestGuard */ public boolean guard(RestRequest req, RestResponse res) throws RestException { if (! isOkay(req)) throw new RestException(SC_FORBIDDEN, "Access denied!!!"); return true; } }

When guards are associated at the class-level, it's equivalent to associating guards on all Java methods on the servlet.
If multiple guards are present, ALL guards must pass.

See Also:

7.16 - Converters

Converters can be thought of as "post-processors" for POJOs before they get passed to the serializers.

Converters are associated with resource classes and methods via the following:

Example:

// Associate the Traversable converter to all Java REST methods in this servlet @RestResource(converters=Traversable.class) public MyRestServlet extends RestServlet { ... }

They can also be defined at the method level:

// GET person request handler. // Traversable conversion enabled to allow nodes in returned POJO tree to be addressed. // Queryable conversion enabled to allow returned POJO to be searched/viewed/sorted. @RestMethod( name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class} ) public Person getPerson(@Path int id) { return findPerson(id); }

The following converter is used to provide support for addressing child nodes in a POJO tree with URL path remainders.
In this code, the 3rd parameter is the object that was returned by the Java method.
The converter uses the {@link org.apache.juneau.utils.PojoRest} wrapper class to address nodes in the tree.

/** * Converter for enablement of PojoRest support on response objects returned by a @RestMethod method. * When enabled, objects in a POJO tree returned by the REST method can be addressed through additional URL path information. */ public class Traversable implements RestConverter { @Override /* RestConverter */ public Object convert(RestRequest req, Object o) throws RestException { if (o == null) return null; String pathRemainder = req.getPathMatch().getRemainder(); if (pathRemainder != null) { try { // Use the PojoRest class to traverse our POJO model. PojoRest p = new PojoRest(o); o = p.get(pathRemainder); } catch (PojoRestException e) { throw new RestException(e.getStatus(), e); } catch (Exception e) { throw new RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } return o; } }

Juneau defines the following converters out-of-the-box:

See Also:

7.17 - Messages

The {@link org.apache.juneau.rest.annotation.RestResource#messages @RestResource.messages()} annotation is used to associate a resource bundle with a servlet class.

// Servlet with associated resource bundle @RestResource(messages="nls/MyMessages") public MyRestServlet extends RestServlet { // Returns the localized greeting from the "greeting" key in MyMessages.properties @RestMethod(name=GET, path="/") public String printLocalizedGreeting(RestRequest req) { return req.getMessage("greeting"); }

The resource bundle can also be passed into the method by simply specifying a parameter of type {@link java.util.ResourceBundle} or {@link org.apache.juneau.utils.MessageBundle}:

@RestMethod(name=GET) public String printLocalizedGreeting(MessageBundle messages) { return messages.getString("greeting"); }

If a resource bundle is shared by multiple servlets, the label and description can be prefixed by the class name:

#-------------------------------------------------------------------------------- # Contents of MyMessages.properties #-------------------------------------------------------------------------------- greeting = Hello!

#-------------------------------------------------------------------------------- # Contents of shared MyMessages.properties #-------------------------------------------------------------------------------- MyRestServlet.greeting = Hello!

See Also:

7.18 - Encoders

The {@link org.apache.juneau.rest.annotation.RestResource#encoders @RestResource.encoders()} annotation can be used to associate character encoders with a servlet class.
Encoders can be used to enable various kinds of compression (e.g. "gzip") on requests and responses based on the request Accept-Encoding and Content-Encoding headers.

Example:

// Servlet with automated support for GZIP compression @RestResource(encoders={GzipEncoder.class}) public MyRestServlet extends RestServlet { ... }

Juneau defines the following encoders out-of-the-box:

See Also:

7.19 - SVL Variables

In the previous examples, there were several cases where embedded variables were contained within annotation values:

@RestResource( title="$L{my.label}" )

Variables take the form $X{contents} where X can consist of zero or more ASCII characters and contents can be virtually anything.
This is called Simple Variable Language, or SVL, and is defined here: juneau-svl.

Features

Variables are configured on resources via the following API:

Example:

// Defined a variable that simply wrapps all strings inside [] brackets. // e.g. "$BRACKET{foobar}" -> "[foobar]" public class BracketVar extends SimpleVar { public BracketVar() { super("BRACKET"); } @Override /* Var */ public String resolve(VarResolverSession session, String arg) { return '[' + arg + ']'; } } // Register it with our resource. @RestResource(...) public class MyResource { public MyResource(RestContextBuilder builder) { builder.vars(BracketVar.class); } }

The methods involved with variables are:

There are two distinct groups of variables:

The following is the default list of supported variables.

Default REST SVL Variables:
ModuleClassPatternInitialization
time
Request
time
juneau-svl {@link org.apache.juneau.svl.vars.EnvVariablesVar} $E{key[,default]} yes yes
{@link org.apache.juneau.svl.vars.SystemPropertiesVar} $S{key[,default]} yes yes
{@link org.apache.juneau.svl.vars.ArgsVar} $A{key[,default]} yes yes
{@link org.apache.juneau.svl.vars.ManifestFileVar} $MF{key[,default]} yes yes
{@link org.apache.juneau.svl.vars.IfVar} $IF{arg,then[,else]} yes yes
{@link org.apache.juneau.svl.vars.SwitchVar} $SW{arg,pattern1:then1[,pattern2:then2...]} yes yes
{@link org.apache.juneau.svl.vars.CoalesceVar} $CO{arg1[,arg2...]} yes yes
{@link org.apache.juneau.svl.vars.PatternMatchVar} $PM{arg,pattern} yes yes
{@link org.apache.juneau.svl.vars.NotEmptyVar} $NE{arg} yes yes
{@link org.apache.juneau.svl.vars.UpperCaseVar} $UC{arg} yes yes
{@link org.apache.juneau.svl.vars.LowerCaseVar} $LC{arg} yes yes
juneau-config {@link org.apache.juneau.config.vars.ConfigVar} $C{key[,default]} yes yes
juneau-rest-server {@link org.apache.juneau.rest.vars.FileVar} $F{path[,default]}} no yes
{@link org.apache.juneau.rest.vars.ServletInitParamVar} $I{name[,default]} yes yes
{@link org.apache.juneau.rest.vars.LocalizationVar} $L{key[,args...]} no yes
{@link org.apache.juneau.rest.vars.RequestAttributeVar} $RA{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.RequestFormDataVar} $RF{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.RequestHeaderVar} $RH{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.RequestHeaderVar} $RI{key} no yes
{@link org.apache.juneau.rest.vars.RequestPathVar} $RP{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.RequestQueryVar} $RQ{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.RequestVar} $R{key1[,key2...]} no yes
{@link org.apache.juneau.rest.vars.SerializedRequestAttrVar} $SA{contentType,key[,default]} no yes
{@link org.apache.juneau.rest.vars.UrlVar} $U{uri}> no yes
{@link org.apache.juneau.rest.vars.UrlEncodeVar} $UE{uriPart} yes yes
{@link org.apache.juneau.rest.vars.WidgetVar} $W{name} no yes

7.20 - Configuration Files

The Server API provides methods for associating configuration files with REST servlets so that configuration properties can be defined in external files.

In recap, the Configuration API provides support for INI-style configuration files with embedded string variables:

Example:

#-------------------------- # Examples #-------------------------- [MyProperties] path = $E{PATH} javaHome = $S{java.home} customMessage = Java home is $C{MyProperties/javaHome} and the environment path is $C{MyProperties/path}.

These properties are then accessible through the {@link org.apache.juneau.config.Config} class.

Config c = Config.create("myconfig.cfg").build(); String path = c.getString("MyProperties/path"); File javaHome = c.getObject("MyProperties/javaHome", File.class); String customMessage = c.getString("MyProperties/customMessage");

Configuration files are associated with REST resources through the following:

Example:

@RestResource( // Config file is located at ./config_dir/myconfig.cfg config="config_dir/myconfig.cfg", ... ) public class MyResource {...}

The annotation itself can contain string variables.
For example, the Microservice API {@link org.apache.juneau.rest.BasicRestServlet} class defines the location of the config file as a system property "juneau.configFile":

@RestResource( // Config file location is defined as a system property config="$S{juneau.configFile}", ... ) public class MyResource {...}

Once a config file has been associated with a REST resource, it can be accessed through the {@link org.apache.juneau.rest.RestContextBuilder#getConfig()} method.

A common usage is to use this method to initialize fields in your servlet.

@RestResource( // Config file is located at ./config_dir/myconfig.cfg config="config_dir/myconfig.cfg", ... ) public class MyResource { private final String path; private final File javaHome; public MyResource(RestContextBuilder builder) { Config c = builder.getConfig(); path = c.getString("MyProperties/path"); javaHome = c.getObject(File.class, "MyProperties/javaHome"); }

Another common usage is to refer to config properties through $C variables in your annotations:

@RestResource( // Get stylesheet from myconfig.cfg, but default to devops.css if it's not specified htmldoc=@HtmlDoc( stylesheet="$C{MyServlet/stylesheet,servlet:/styles/devops.css}", ) ... ) public class MyResource {...}

It's even possible to reference request-level variables in your config file if you use {@link org.apache.juneau.rest.RestRequest#getConfig()} to access the config file:

#------------------------------------- # Contents of config_dir/myconfig.cfg #------------------------------------- [HelloWorldResource] message = Hello $RQ{person}!

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( config="config_dir/myconfig.cfg", ... ) public class HelloWorldResource extends BasicRestServlet { /** * GET request handler. * Specify the GET parameter "?person=X" for a specialized message! */ @RestMethod(name=GET, path="/") public String sayHello(RestRequest req) { return req.getConfig().getString("HelloWorldResource/message"); } }

You can even add resource bundles into the mix:

#------------------------------------- # Contents of config_dir/myconfig.cfg #------------------------------------- [HelloWorldResource] message = $L{localizedMessage,$RQ{person}}

#------------------------------------------- # Contents of HelloWorldResource.properties #------------------------------------------- localizedMessage = Hello {0}!

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( messages="HelloWorldResources", config="config_dir/myconfig.cfg", ... ) public class HelloWorldResource extends BasicRestServlet { /** * GET request handler. * Specify the GET parameter "?person=X" for a specialized message! */ @RestMethod(name=GET, path="/") public String sayHello(RestRequest req) { return req.getConfig().getString("HelloWorldResource/message"); } }

7.21 - Static files

The {@link org.apache.juneau.rest.annotation.RestResource#staticFiles @RestResource.staticFiles()} annotation is used to define paths and locations of statically-served files such as images or HTML documents.

The value is a JSON map of paths to packages/directories located on either the classpath or working directory.

Example:

package com.foo.mypackage; @RestResource( path="/myresource", staticFiles={"htdocs:docs"} ) public class MyResource extends BasicRestServlet {...}

Static files are found by calling {@link java.lang.Class#getResource(String)} up the class hierarchy. If not found, then an attempt is made to find the class in the Java working directory.

In the example above, given a GET request to /myresource/htdocs/foobar.html, the servlet will attempt to find the foobar.html file in the following ordered locations:

  1. com.foo.mypackage.docs package.
  2. [working-dir]/docs directory.
Notes:
See Also:

7.22 - Client Versioning

Client version headers are used to support backwards compatibility for breaking REST interface changes.
Using them, you're able to return different responses based on which client is making a request.

The APIs involved with defining client version headers are:

Example:

// Option #1 - Defined via annotation resolving to a config file setting with default value. @RestResource(clientVersionHeader="Client-Version") public class MyResource { // Call this method if Client-Version is at least 2.0. // Note that this also matches 2.0.1. @RestMethod(name=GET, path="/foobar", clientVersion="2.0") public Object method1() { ... } // Call this method if Client-Version is at least 1.1, but less than 2.0. @RestMethod(name=GET, path="/foobar", clientVersion="[1.1,2.0)") public Object method2() { ... } // Call this method if Client-Version is less than 1.1. @RestMethod(name=GET, path="/foobar", clientVersion="[0,1.1)") public Object method3() { ... }

See Also:

7.23 - OPTIONS pages

One of the most useful features of Juneau is the ability to generate Swagger-based OPTIONS pages for self-documenting designs (i.e. REST interfaces that document themselves).

OPTIONS page for HelloWorld sample resource

The {@link org.apache.juneau.rest.BasicRestServlet} class implements the page by creating a method mapped to the OPTIONS HTTP method that simply returns a Swagger bean:

@RestResource(...) public class BasicRestServlet extends RestServlet { @RestMethod(name=OPTIONS, path="/*", summary="Resource options", htmldoc=@HtmlDoc( navlinks={ "back: servlet:/,", "json: servlet:/?method=OPTIONS&Accept=text/json&plainText=true" } ) ) public Swagger getOptions(RestRequest req) { return req.getSwagger(); } }

This page is constructed using the Info Provider API described next.

7.23.1 - RestInfoProvider

The {@link org.apache.juneau.rest.RestInfoProvider} class is used to find the title and description for your resource and also generate the Swagger documentation.
It can be overridden to provide your own custom Swagger documentation.

The methods on this interface are:

  • {@link org.apache.juneau.rest.RestInfoProvider}
    • {@link org.apache.juneau.rest.RestInfoProvider#getSwagger(RestRequest) getSwagger(RestRequest)}
    • {@link org.apache.juneau.rest.RestInfoProvider#getSiteName(RestRequest) getSiteName(RestRequest)}
    • {@link org.apache.juneau.rest.RestInfoProvider#getTitle(RestRequest) getTitle(RestRequest)}
    • {@link org.apache.juneau.rest.RestInfoProvider#getDescription(RestRequest) getDescription(RestRequest)}
    • {@link org.apache.juneau.rest.RestInfoProvider#getMethodSummary(Method,RestRequest) getMethodSummary(Method,RestRequest)}
    • {@link org.apache.juneau.rest.RestInfoProvider#getMethodDescription(Method,RestRequest) getMethodDescription(Method,RestRequest)}

The info provider in turn supplies the information returned by the following methods:

  • {@link org.apache.juneau.rest.RestRequest}
    • {@link org.apache.juneau.rest.RestRequest#getSwagger() getSwagger()}
    • {@link org.apache.juneau.rest.RestRequest#getSiteName() getSiteName()}
    • {@link org.apache.juneau.rest.RestRequest#getResourceTitle() getResourceTitle()}
    • {@link org.apache.juneau.rest.RestRequest#getResourceDescription() getResourceDescription()}
    • {@link org.apache.juneau.rest.RestRequest#getMethodSummary() getMethodSummary()}
    • {@link org.apache.juneau.rest.RestRequest#getMethodDescription() getMethodDescription()}

Info providers are registered through the following property:

  • {@link org.apache.juneau.rest.RestContext#REST_infoProvider}

While you can implement this interface from scratch, you may want to instead consider extending from the BasicRestInfoProvider class described next.

7.23.2 - BasicRestInfoProvider

The {@link org.apache.juneau.rest.BasicRestInfoProvider} class is the default implementation of the {@link org.apache.juneau.rest.RestInfoProvider} interface.

It finds and collects information gathered from the following locations:

  • Localized JSON Swagger files in the classpath.
  • Reflection.
  • annotations.
  • Info provided in properties files.

The class itself is designed to be extended if you wish to rely mostly on the default behavior, but tweak certain aspects.

In addition to the methods defined on the RestInfoProvider interface, it also includes the following methods:

  • {@link org.apache.juneau.rest.BasicRestInfoProvider} implements {@link org.apache.juneau.rest.RestInfoProvider}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getVersion(RestRequest) getVersion(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getContact(RestRequest) getContact(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getConsumes(RestRequest) getConsumes(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getProduces(RestRequest) getProduces(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getLicense(RestRequest) getLicense(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getTags(RestRequest) getTags(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getTermsOfService(RestRequest) getTermsOfService(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getExternalDocs(RestRequest) getExternalDocs(RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodSummary(Method,RestRequest) getMethodSummary(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodDescription(Method,RestRequest) getMethodDescription(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodOperationId(Method,RestRequest) getMethodOperationId(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodConsumes(Method,RestRequest) getMethodConsumes(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodProduces(Method,RestRequest) getMethodProduces(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodParameters(Method,RestRequest) getMethodParameters(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodResponses(Method,RestRequest) getMethodResponses(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getMethodTags(Method,RestRequest) getMethodTags(Method,RestRequest)}
    • {@link org.apache.juneau.rest.BasicRestInfoProvider#getSwaggerFromFile(RestRequest) getSwaggerFromFile(RestRequest)}

The default provider provides several options for defining Swagger documentation on your resource:

  • Provide nothing.
    You'll still get an auto-generated Swagger doc with information gather solely through reflection, including methods, parameters, consumes/produces, etc...
  • Specify localized JSON Swagger files on your classpath.
    Example:

    MyResource_ja_JP.json

  • Use {@link org.apache.juneau.rest.annotation.ResourceSwagger @ResourceSwagger} and {@link org.apache.juneau.rest.annotation.MethodSwagger @MethodSwagger} annotations on your resource classes and methods.
    Example:

    @RestMethod( swagger=@MethodSwagger(tags="foo,bar,baz") ) public Object myMethod() {...}

  • Use properties files identified by the {@link org.apache.juneau.rest.annotation.RestResource#messages @RestResource.messages()} annotation.
    Example:

    MyResource.myMethod.tags = foo,bar,baz

  • Use any combination of the above.
  • Extend the BasicRestInfoProvider and provide customized behavior.
    Example:

    // Our customized info provider. // Extend from the default implementation and selectively override values. public class MyRestInfoProvider extends BasicRestInfoProvider { // Must provide this constructor! public MyRestInfoProvider(RestContext context) { super(context); } @Override /* RestInfoProvider */ public Swagger getSwagger(RestRequest req) throws RestException { Swagger s = super.getSwagger(req); // Made inline modifications to generated swagger. return s; } } // Registered via annotation @RestResource(infoProvider=MyRestInfoProvider.class) public class MyResource {...}

7.24 - @HtmlDoc

The {@link org.apache.juneau.rest.annotation.HtmlDoc @HtmlDoc} annotation is used to customize the HTML view of your serialized POJOs.
It's used in the following locations:

The annotation itself is just a convenience for setting configuration properties set on the {@link org.apache.juneau.html.HtmlDocSerializer} class.
For example, the following two pieces of code are equivalent:

// Title defined via property. @RestResource( properties={ @Property(name=HTMLDOC_title, value="My Resource Page") } ) // Title defined via @HtmlDoc annotation. @RestResource( htmldoc=@HtmlDoc( title="My Resource Page" ) )

The purpose of these annotation is to populate the HTML document view which by default consists of the following structure:

<html> <head> <style type='text/css'> CSS styles and links to stylesheets </style> </head> <body> <header> Page header </header> <nav> Navigation links </nav> <aside> Side-bar text </aside> <article> Contents of serialized object </article> <footer> Footer message </footer> </body> </html>

The outline above is controlled by the {@link org.apache.juneau.html.HtmlDocTemplate} interface which can be overridden via the {@link org.apache.juneau.rest.annotation.HtmlDoc#template @HtmlDoc.template()} annotation.

The HelloWorldResource class was an example of the @HtmlDoc annotation in use:

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( path="/helloWorld", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" }, aside={ "<div style='max-width:400px' class='text'>", " <p>This page shows a resource that simply response with a 'Hello world!' message</p>", " <p>The POJO serialized is a simple String.</p>", "</div>" } ) ) public class HelloWorldResource extends BasicRestServlet {...}

SVL variables can be used in any of these annotations:

@RestResource( path="/helloWorld", // Register a config file. config="MyConfig.cfg", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS", // Add a nav link to view the source code for this class. "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java" }, aside={ // Localize our messages. "<div style='max-width:400px' class='text'>", " <p>$L{localizedMessage1}</p>", " <p>$L{localizedMessage2}</p>", "</div>" } ) ) public class HelloWorldResource extends BasicRestServlet {...}

See Also:

7.24.1 - Widgets

The {@link org.apache.juneau.rest.widget.Widget} class allows you to add arbitrary HTML, CSS, and Javascript to HTML pages.
They are registered in the following locations:

  • {@link org.apache.juneau.rest.annotation.HtmlDoc#widgets() @HtmlDoc.widgets}
  • {@link org.apache.juneau.rest.RestContextBuilder#widgets(Class...)}
  • {@link org.apache.juneau.rest.RestContextBuilder#widgets(Widget...)}
  • {@link org.apache.juneau.rest.RestContext#REST_widgets}
Example:

@RestMethod( widgets={ MyWidget.class } htmldoc=@HtmlDoc( navlinks={ "$W{MyWidget}" }, aside={ "Check out this widget: $W{MyWidget}" } ) )

The Widget class is composed of the following methods:

  • {@link org.apache.juneau.rest.widget.Widget}
    • {@link org.apache.juneau.rest.widget.Widget#getHtml(RestRequest) getHtml(RestRequest)}
    • {@link org.apache.juneau.rest.widget.Widget#getStyle(RestRequest) getStyle(RestRequest)}
    • {@link org.apache.juneau.rest.widget.Widget#getScript(RestRequest) getScript(RestRequest)}

The HTML content returned by the {@link org.apache.juneau.rest.widget.Widget#getHtml(RestRequest) getHtml(RestRequest)} method is added wherever the "$W{...}" variable is used.

The CSS returned by {@link org.apache.juneau.rest.widget.Widget#getScript(RestRequest) getScript(RestRequest)} is added to the style section in the page header.

The Javascript returned by {@link org.apache.juneau.rest.widget.Widget#getScript(RestRequest) getScript(RestRequest)} is added to the script section in the page header.

The following examples shows how to associate a widget with a REST method and then have it rendered in the links and aside section of the page.
It shows an example of a widget that renders an image located in the htdocs static files directory in your classpath (see {@link org.apache.juneau.rest.annotation.RestResource#staticFiles() @RestResource.staticFiles()}):

public class MyWidget extends Widget { @Override /* Widget */ public String getHtml(RestRequest req) throws Exception { UriResolver r = req.getUriResolver(); // API used for resolving URIs. return "<img class='myimage' onclick='myalert(this)' src='"+r.resolve("servlet:/htdocs/myimage.png")+"'>"; } @Override /* Widget */ public String getScript(RestRequest req) throws Exception { return "" + "\n function myalert(imageElement) {" + "\n alert('cool!');" + "\n }"; } @Override /* Widget */ public String getStyle(RestRequest req) throws Exception { return "" + "\n .myimage {" + "\n border: 10px solid red;" + "\n }"; } }

The Widget class also defines the following two convenience methods for loading Javascript and CSS files from the classpath or file system.

  • {@link org.apache.juneau.rest.widget.Widget}
    • {@link org.apache.juneau.rest.widget.Widget#getClasspathResourceAsString(String) getClasspathResourceAsString(String)}
    • {@link org.apache.juneau.rest.widget.Widget#getClasspathResourceAsString(String,Locale) getClasspathResourceAsString(String,Locale)}
Example:

public class MyWidget extends Widget { ... @Override /* Widget */ public String getScript(RestRequest req) throws Exception { return getClasspathResourceAsString("MyWidget.js"); } @Override /* Widget */ public String getStyle(RestRequest req) throws Exception { return getClasspathResourceAsString("MyWidget.css"); } }

See Also:
  • {@link org.apache.juneau.rest.RestContext#REST_widgets}

7.24.2 - Predefined Widgets

TODO

7.24.3 - UI Customization

The HTML views of POJOs can somewhat be considered a rudimentary User Interface.
In reality, a better term for them would be a Developer Interface as they're meant to be used primarily by developers and not end users.
Despite that distinction, it is possible to 'brand' the HTML page to whatever you desire.

The sample root page below includes some default branding for Juneau and Apache:

In particular, you may want to replace these icons:

The Juneau REST framework does not provide specific branding support (i.e. there is no concept of a brand icon).
Instead, it just uses the existing open-ended API for defining branding.

The Juneau icon shown is a result of the header annotation on the {@link org.apache.juneau.rest.BasicRestServlet} class:

@RestResource( ... htmldoc=@HtmlDoc( header={ "<h1>$R{resourceTitle}</h1>", "<h2>$R{methodSummary,resourceDescription}</h2>", "<a href='http://juneau.apache.org'>" +"<img src='$U{servlet:/htdocs/juneau.png}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/>" +"</a>" }, head={ // Browser tab icon. "<link rel='icon' href='$U{servlet:/htdocs/juneau.png}'/>" } ), staticFiles={"htdocs:htdocs"} ) public abstract class BasicRestServlet extends RestServlet {...}

The "juneau.png" image file is located in org.apache.juneau.rest.htdocs package and is served up via the staticFiles annotation (i.e. anything in the org.apache.juneau.rest.htdocs package is served up under the path /servlet-path/htdocs).
Then we just reference using a URI resolution variable "$U{servlet:/htdocs/juneau.png}".

To change this image, you can extend the BasicRestServlet class and simply override the annotations pointing to your own icon.

@RestResource( ... htmldoc=@HtmlDoc( header={ "<h1>$R{resourceTitle}</h1>", "<h2>$R{methodSummary,resourceDescription}</h2>", "<a href='http://my.project.org'>" +"<img src='$U{servlet:/my-htdocs/my-project.png}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/>" +"</a>" }, head={ // Browser tab icon. "<link rel='icon' href='$U{servlet:/my-htdocs/my-project.png}'/>" } ), staticFiles={"my-htdocs:my-htdocs"} ) public class MyResourceBaseClass extends BasicRestServlet {...}

The footer icon shown is generated by a predefined widget:

@RestResource( htmldoc=@HtmlDoc( widgets={ PoweredByApache.class }, footer="$W{PoweredByApache}" ), ... ) public class RootResources extends RestServletJenaGroup {...}

The widget definition is shown below:

public class PoweredByApache extends Widget { /** * Returns an Apache image tag hyperlinked to "http://apache.org" */ @Override /* Widget */ public String getHtml(RestRequest req) throws Exception { UriResolver r = req.getUriResolver(); return "<a href='http://apache.org'><img style='float:right;padding-right:20px;height:32px' src='"+r.resolve("servlet:/htdocs/asf.png")+"'>"; } }

To provide your own footer icon, simply define it in your own footer section:

@RestResource( htmldoc=@HtmlDoc( footer="<img style='float:right;padding-right:20px;height:32px' src='$U{servlet:/my-htdocs/my-project.png}'>" ), staticFiles={"my-htdocs:my-htdocs"} ... ) public class MyResourceBaseClass extends BasicRestServlet {...}

Note how the "User Interface" is open-ended to pretty much lets you do whatever you want.

7.24.4 - Stylesheets

The sample root page renders in the default "devops" look-and-feel:

The sample root page provides a dropdown widget to try out the other default look-and-feels:

For example, the "light" look-and-feel:

And the "dark" look-and-feel:

The stylesheet URL is controlled by the {@link org.apache.juneau.rest.annotation.HtmlDoc#stylesheet() @HtmlDoc.stylesheet()} annotation.
The {@link org.apache.juneau.rest.BasicRestServlet} class defines the stylesheet served up as a static file:

@RestResource( htmldoc=@HtmlDoc( stylesheet="$C{REST/stylesheet,servlet:/styles/devops.css}", ), staticFiles={"styles:styles"} ) public abstract class BasicRestServlet extends RestServlet {...}

The "$C{REST/stylesheet,servlet:/styles/devops.css}" variable says to use the URI defined in your servlet's config file, if there is one, and to default to serving up the file org/apache/juneau/rest/styles/devops.css.

To provide your own stylesheet, simply override the stylesheet attribute and point to a different file:

@RestResource( htmldoc=@HtmlDoc( stylesheet="servlet:/my-styles/my-style.css}", ), staticFiles={"my-styles:my-styles"} ) public class MyResourceBaseClass extends BasicRestServlet {...}

You can try out different stylesheets by passing in a stylesheet attribute in the request URL.
The example above show this in use.

In case you're curious about how the menu item works, it's defined via a widget:

@RestResource( htmldoc=@HtmlDoc( widgets={ PoweredByApache.class, ContentTypeMenuItem.class, StyleMenuItem.class }, navlinks={ "options: ?method=OPTIONS", "$W{ContentTypeMenuItem}", "$W{StyleMenuItem}", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java" }, ) public class RootResources extends RestServletJenaGroup {...}

The StyleMenuItem is a widget that extends from {@link org.apache.juneau.rest.widget.MenuItemWidget}, a specialized widget for creating pop-up menus.
In the case of StyleMenuItem, it's simply returning a list of links wrapped in a div tag:

import static org.apache.juneau.dto.html5.HtmlBuilder.*; public class StyleMenuItem extends MenuItemWidget { private static final String[] BUILT_IN_STYLES = {"devops", "light", "original", "dark"}; @Override /* Widget */ public String getLabel(RestRequest req) { return "styles"; } @Override /* MenuItemWidget */ public Div getContent(RestRequest req) throws Exception { Div div = div(); for (String s : BUILT_IN_STYLES) { java.net.URI uri = req.getUri(true, new AMap<String,String>().append("stylesheet", "styles/"+s+".css")); div.children(a(uri, s), br()); } return div; } }

7.25 - Default Headers

The following annotations are provided for specifying default header values for requests and responses:

Example:

// Servlet with default headers @RestResource( // Assume "text/json" Accept value when Accept not specified defaultRequestHeaders={"Accept: text/json"}, // Add a version header attribute to all responses defaultResponseHeaders={"X-Version: 1.0"} ) public MyRestServlet extends RestServlet { ... }

Default headers can also be specified programmatically by overriding the following methods:

7.26 - Logging and Error Handling

The {@link org.apache.juneau.rest.RestContext#REST_logger} property allows you to configure logging for your resource.
The interface is shown below:

The {@link org.apache.juneau.rest.RestLogger#logObjects(Level,String,Object[]) logObjects()} method is particularly useful because it allows you to pass in POJOs as arguments that serialized using {@link org.apache.juneau.json.JsonSerializer#DEFAULT_LAX_READABLE}, but only if the message is actually logged.

Example:

logger.logObjects(DEBUG, "Pojo contents:\n{0}", myPojo);

By default, the Juneau framework uses the built-in Java Logging API for logging.
But you can define your own implementation to use any framework you wish.

The {@link org.apache.juneau.rest.RestLogger} instance is accessible via the following:

In addition, the logger can be accessed by passing it as a parameter to your REST java method:

@RestMethod() public Object doSomething(RestLogger logger) {...}

If your resource extends from {@link org.apache.juneau.rest.RestServlet}, you can also use and override the following methods:

7.27 - HTTP Status Codes

By default, a 200 (OK) status is automatically set as the HTTP status when a Java method executes successfully.

Other status codes can be generated by throwing a {@link org.apache.juneau.rest.RestException} with a specific HTTP status code, or calling {@link javax.servlet.http.HttpServletResponse#setStatus(int)}.

Non-OK (200) status codes are automatically triggered by the following conditions:

Code Description When triggered
401 Unauthorized A {@link org.apache.juneau.rest.RestGuard guard} prevented the method from being executed
404 Not Found No matching path patterns were found on any method
405 Method Not Implemented A path pattern matched, but no Java methods were found for the HTTP method
406 Not Acceptable A path pattern matched, but no Java methods were found with a matching serializer for the Accept on the request
412 Precondition Failed A path pattern matched, but no Java methods were found that were not rejected by {@link org.apache.juneau.rest.RestMatcher matchers}
415 Unsupported Media Type A path pattern matched, but no Java methods were found with a matching parser for the Content-Type on the request
500 Internal Server Error The Java method threw an exception other than {@link org.apache.juneau.rest.RestException}

7.28 - Overloading HTTP Methods

Through the use of the built-in "method" GET parameter, you can implement requests beyond the basic REST http method types.

For example, the URL "/sample/foo?method=BAR" will cause the following method to be invoked...

@RestMethod(name="BAR") public void doBar(RestRequest req, RestResponse res) { // Handle BAR requests }

To support overloaded methods, the {@link org.apache.juneau.rest.annotation.RestResource#allowedMethodParams() @RestResource.allowedMethodParams()} setting must be enabled on your servlet.

@RestResource( // Allow &method parameter on BAR requests allowedMethodParams="BAR" )

7.29 - Built-in Parameters

The following URL parameters have special meaning and can be passed in through the URL of the request:

GET Parameter Description
&plainText=true Response will always be Content-Type: text/plain and the returned text will be human-readable ({@link org.apache.juneau.serializer.Serializer#SERIALIZER_useWhitespace SERIALIZER_useWhitespace} enabled).
Useful for debugging.
&debug=true Request body content will be dumped to log file.
&noTrace=true If an error occurs, don't log the stack trace to the log file.
Useful for automated JUnit testcases testing error states to prevent the log file from filling up with useless stack traces.
&method=X Overload the HTTP method as a GET parameter (e.g "POST").
Must be enabled via {@link org.apache.juneau.rest.annotation.RestResource#allowedMethodParams() @RestResource.allowedMethodParams()} setting.
&Header-Name=headerValue Specify a header value as a GET parameter.
Must be enabled via {@link org.apache.juneau.rest.annotation.RestResource#allowHeaderParams() @RestResource.allowHeaderParams()} setting.
&body=X Pass in the HTTP body content on PUT and POST methods as a UON-encoded GET parameter.
Must be enabled via {@link org.apache.juneau.rest.annotation.RestResource#allowBodyParam() @RestResource.allowBodyParam()} setting.
&x-response-headers=X Pass-through headers to the response.
Must be a UON-encoded map of key-value pairs.

7.30 - Custom Serializers and Parsers

A very easy-to-use API is provided for defining your own serializers and parsers at both the servlet and method levels.

The following examples show a custom serializer and parser defined at the method level. It's the PhotosResource class pulled from the Samples project. It shows an example of defining a serializer and parser to handle images.

/** * Sample resource that allows images to be uploaded and retrieved. */ @RestResource( path="/photos", messages="nls/PhotosResource", title="Photo REST service", description="Use a tool like Poster to upload and retrieve jpeg and png images.", htmldoc=@HtmlDoc( navlinks={ "options: ?method=OPTIONS" } ) ) public class PhotosResource extends BasicRestServlet { // Our cache of photos private Map<Integer,Photo> photos = new TreeMap<Integer,Photo>(); /** Bean class for storing photos */ public static class Photo { private int id; BufferedImage image; Photo(int id, BufferedImage image) { this.id = id; this.image = image; } public URI getURI() throws URISyntaxException { return new URI("photos/"+id); } public int getID() { return id; } } /** GET request handler for list of all photos */ @RestMethod(name=GET, path="/") public Collection<Photo> getAllPhotos(RestRequest req, RestResponse res) throws Exception { res.setPageTitle("Photo REST service"); res.setPageText("Use a tool like Poster to upload and retrieve jpeg and png images."); return photos.values(); } /** GET request handler for single photo */ @RestMethod(name=GET, path="/{id}", serializers=ImageSerializer.class) public BufferedImage getPhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.get(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return p.image; } /** PUT request handler */ @RestMethod(name=PUT, path="/{id}", parsers=ImageParser.class) public String addPhoto(RestRequest req, @Path int id, @Body BufferedImage image) throws Exception { photos.put(id, new Photo(id, image)); return "OK"; } /** POST request handler */ @RestMethod(name=POST, path="/", parsers=ImageParser.class) public Photo setPhoto(RestRequest req, @Body BufferedImage image) throws Exception { int id = photos.size(); Photo p = new Photo(id, image); photos.put(id, p); return p; } /** DELETE request handler */ @RestMethod(name=DELETE, path="/{id}") public String deletePhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.remove(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return "OK"; } /** OPTIONS request handler */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); } /** Serializer for converting images to byte streams */ @Produces("image/png,image/jpeg") public static class ImageSerializer extends OutputStreamSerializer { @Override /* Serializer */ public void serialize(Object o, OutputStream out, SerializerSession session) throws IOException, SerializeException { RenderedImage image = (RenderedImage)o; String mediaType = ctx.getMediaType(); ImageIO.write(image, mediaType.substring(mediaType.indexOf('/')+1), out); } } /** Parser for converting byte streams to images */ @Consumes("image/png,image/jpeg") public static class ImageParser extends InputStreamParser { @Override /* Parser */ public <T> T parse(InputStream in, ClassMeta<T> type, ParserSession session) throws ParseException, IOException { BufferedImage image = ImageIO.read(in); return (T)image; } } }

7.31 - Using with OSGi

Since REST servlets are basically just HttpServlets, incorporating them into an OSGi environment is pretty straightforward.

The following code shows how to register your REST servlets in an OSGi Activator:

package org.apache.juneau.examples.rest; import org.osgi.framework.*; import org.osgi.service.http.*; import org.osgi.util.tracker.*; import org.apache.juneau.rest.samples.*; /** * Activator class used when running samples as a bundle in an OSGi environment. */ public class Activator implements BundleActivator, ServiceTrackerCustomizer { private ServiceTracker httpServiceTracker; private BundleContext context; @Override /* BundleActivator */ public void start(BundleContext context) throws Exception { this.context = context; httpServiceTracker = new ServiceTracker(context, HttpService.class.getName(), this); httpServiceTracker.open(); } @Override /* BundleActivator */ public void stop(BundleContext context) throws Exception { httpServiceTracker.close(); } @Override /* ServiceTrackerCustomizer */ public Object addingService(ServiceReference reference) { Object service = context.getService(reference); if (service instanceof HttpService) { HttpService s = (HttpService)service; try { s.registerServlet("/sample", new MyRestServlet(), null, null); } catch (Exception e) { throw new RuntimeException(e); } } return service; } @Override /* ServiceTrackerCustomizer */ public void modifiedService(ServiceReference reference, Object service) { } @Override /* ServiceTrackerCustomizer */ public void removedService(ServiceReference reference, Object service) { } }

7.32 - Remoteable Proxies

The Remoteable Service API allows for client side code to use interface proxies for calling methods on POJOs on the server side.

Proxy interfaces are retrieved using the {@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class)} method. The remoteable servlet is a specialized subclass of {@link org.apache.juneau.rest.RestServlet} that provides a full-blown REST interface for calling remoteable services (e.g. POJOs) remotely.

The following simplified example shows how a method on a POJO on a server can be called through an interface on a client...

public interface IAddressBook { Person createPerson(CreatePerson cp) throws Exception; Person findPerson(int id); Address findAddress(int id); Person findPersonWithAddress(int id); }

The client side code for invoking this method is shown below...

// Create a RestClient using JSON for serialization, and point to the server-side remoteable servlet. RestClient client = RestClient.create() .rootUrl("https://localhost:9080/juneau/sample/remoteable") .build(); // Create a proxy interface. IAddressBook ab = client.getRemoteableProxy(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) );

The requirements for a method to be callable through the remoteable service are:

Juneau provides the capability of calling methods on POJOs on a server through client-side proxy interfaces. It offers a number of advantages over other similar remote proxy interfaces, such as being much simpler to define and use, and allowing much more flexibility in the types of objects serialized.

The remote proxy interface API allows you to invoke server-side POJO methods on the client side using REST as the communications protocol:

// Create a client with basic JSON support. RestClient client = RestClient.create().rootUrl("http://localhost/remoteable").build(); // Get an interface proxy. IAddressBook ab = client.getRemoteableProxy(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new Person( "John Smith", "Aug 1, 1999", new Address("My street", "My city", "My state", 12345, true) ) );

Under the covers, this method call gets converted to a REST POST.

HTTP POST http://localhost/remoteable/org.apache.juneau.examples.rest.IAddressBook/createPerson Accept: application/json Content-Type: application/json [ { "name":"John Smith", "birthDate":"Aug 1, 1999", "addresses":[ { "street":"My street", "city":"My city", "state":"My state", "zip":12345, "isCurrent":true } ] } ]

Note that the body of the request is an array. This array contains the serialized arguments of the method. The object returned by the method is then serialized as the body of the response.

To define a remoteable interface, simply add the {@link org.apache.juneau.remoteable.Remoteable @Remoteable} annotation to your interface class.

@Remoteable public interface IAddressBook {...}

This annotation tells the framework that all methods defined on this interface can be executed remotely. It can be applied to super-interfaces, super-classes, etc..., and exposes the methods at whatever level it is defined.

The {@link org.apache.juneau.remoteable.RemoteMethod @RemoteMethod} annotation can also be used on individual methods to tailor which methods are exposed or their paths.

There are two ways to expose remoteable proxies on the server side:

  1. Extending from RemoteableServlet.
  2. Using a @RestMethod(name=PROXY) annotation on a Java method.

The RemoteableServlet class is a simple specialized servlet with an abstract getServiceMap() method to define the server-side POJOs:

@RestResource( path="/remote" ) public class SampleRemoteableServlet extends RemoteableServlet { // Our server-side POJO. AddressBook addressBook = new AddressBook(); @Override /* RemoteableServlet */ protected Map<Class<?>,Object> getServiceMap() throws Exception { Map<Class<?>,Object> m = new LinkedHashMap<Class<?>,Object>(); // In this simplified example, we expose the same POJO service under two different interfaces. // One is IAddressBook which only exposes methods defined on that interface, and // the other is AddressBook itself which exposes all methods defined on the class itself (dangerous!). m.put(IAddressBook.class, addressBook); m.put(AddressBook.class, addressBook); return m; } }

The @RestMethod(name=PROXY) approach is easier if you only have a single interface you want to expose. You simply define a Java method whose return type is an interface, and return the implementation of that interface:

// Our exposed proxy object. @RestMethod(name=PROXY, path="/addressbookproxy/*") public IAddressBook getProxy() { return addressBook; }

In either case, the proxy communications layer is pure REST. Therefore, in cases where the interface classes are not available on the client side, the same method calls can be made through pure REST calls. This can also aid significantly in debugging, since calls to the remoteable service can be made directly from a browser with no coding involved.

The parameters and return types of the Java methods can be any of the supported serializable and parsable types. This ends up being WAY more flexible than other proxy interfaces since Juneau can handle so may POJO types out-of-the-box. Most of the time you don't even need to modify your existing Java implementation code.

The RemoteableServlet class itself shows how sophisticated REST interfaces can be built on the Juneau RestServlet API using very little code. The class consists of only 53 lines of code, yet is a sophisticated discoverable and self-documenting REST interface. And since the remote proxy API is built on top of REST, it can be debugged using just a browser.

The requirements for a method to be callable through a remoteable service are:

7.32.1 - Client Side

Remoteable interface proxies are retrieved through the existing {@link org.apache.juneau.rest.client.RestClient} class.

It may seem that the client-side code would need to be complex. In reality, it builds upon existing serializing, parsing, and REST capabilities in Juneau resulting in very little additional code. The entire code for the RestClient.getRemoteableProxy(Class) method is shown below:

public <T> T getRemoteableProxy(final Class<T> interfaceClass) { return (T)Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class[] { interfaceClass }, new InvocationHandler() { @Override /* InvocationHandler */ public Object invoke(Object proxy, Method method, Object[] args) { try { String uri = remoteableServletUri + '/' + interfaceClass.getName() + '/' + ClassUtils.getMethodSignature(method); return doPost(uri, args).getResponse(method.getReturnType()); } catch (Exception e) { throw new RuntimeException(e); } } }); }

Since we build upon the existing RestClient API, we inherit all of it's features. For example, convenience methods for setting POJO filters and properties to customize the behavior of the serializers and parsers, and the ability to provide your own customized Apache HttpClient for handling various scenarios involving authentication and Internet proxies.

7.32.2 - Server Side

The server side is only slightly more complex, but boasts useful debugging and discovery capabilities.

The {@link org.apache.juneau.rest.remoteable.RemoteableServlet} class is an implementation of {@link org.apache.juneau.rest.RestServlet} that provides a REST interface for invoking calls on POJOs. The RemoteableServlet class is abstract and must implement a single method for providing the set of POJOs to expose as remote interfaces.

The samples bundle includes a sample implementation of a remoteable service that can be used to interact with the address book POJO also included in the bundle. The method that must be implemented is {@link org.apache.juneau.rest.remoteable.RemoteableServlet#getServiceMap()} that simply returns a mapping of Java interfaces (or classes) to POJO instances.

@RestResource( path="/remoteable" ) public class SampleRemoteableServlet extends RemoteableServlet { // The POJO being manipulated (i.e. the remoteable service) AddressBook addressBook = new AddressBook(); @Override /* RemoteableServlet */ protected Map<Class<?>,Object> getServiceMap() throws Exception { Map<Class<?>,Object> m = new LinkedHashMap<Class<?>,Object>(); // In this simplified example, we expose the same POJO service under two different interfaces. // One is IAddressBook which only exposes methods defined on that interface, and // the other is AddressBook itself which exposes all public methods defined on the class itself. m.put(IAddressBook.class, addressBook); m.put(AddressBook.class, addressBook); return m; } }

Since this class is a servlet, and can be deployed as such. In the sample code, it's listed as a child resource to org.apache.juneau.rest.samples.RootResources which makes it available under the URL /juneau/sample/remoteable.

If you point your browser to that URL, you get a list of available interfaces:

Clicking the hyperlinks on each shows you the list of methods that can be invoked on that service. Note that the IAddressBook link shows that you can only invoke methods defined on that interface, whereas the AddressBook link shows ALL public methods defined on that class. Since AddressBook extends from LinkedList, you may notice familiar collections framework methods listed.

Let's see how we can interact with this interface through nothing more than REST calls to get a better idea on how this works. We'll use the same method call as in the introduction. First, we need to create the serialized form of the arguments:

Object[] args = new Object[] { new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) }; String asJson = JsonSerializer.DEFAULT_LAX_READABLE.toString(args); System.err.println(asJson);

That produces the following JSON output:

[ { name: 'Test Person', birthDate: 'Aug 1, 1999', addresses: [ { street: 'Test street', city: 'Test city', state: 'Test state', zip: 12345, isCurrent: true } ] } ]

Note that in this example we're using JSON. However, various other content types can also be used such as XML, URL-Encoding, UON, or HTML. In practice however, JSON will preferred since it is often the most efficient.

Next, we can use a tool such as Poster to make the REST call. Methods are invoked by POSTing the serialized object array to the URI of the interface method. In this case, we want to POST our JSON to /juneau/sample/remoteable/org.apache.juneau.examples.addressbook.IAddressBook/createPerson(org.apache.juneau.examples.addressbook.CreatePerson). Make sure that we specify the Content-Type of the body as text/json. We also want the results to be returned as JSON, so we set the Accept header to text/json as well.

When we execute the POST, we should see the following successful response whose body contains the returned Person bean serialized to JSON:

From there, we could use the following code snippet to reconstruct the response object from JSON:

String response = "output from above"; Person p = JsonParser.DEFAULT.parse(response, Person.class);

If we alter our servlet to allow overloaded GET requests, we can invoke methods using nothing more than a browser...

@RestResource( path="/remoteable", // Allow us to use method=POST from a browser. allowedMethodParams="*" ) public class SampleRemoteableServlet extends RemoteableServlet {

For example, here we call the findPerson(int) method to retrieve a person and get the returned POJO (in this case as HTML since that's what's in the Accept header when calling from a browser):

When specifying the POST body as a &content parameter, the method arguments should be in UON notation. See {@link org.apache.juneau.uon.UonSerializer} for more information about this encoding. Usually you can also pass in JSON if you specify &Content-Type=text/json in the URL parameters but passing in unencoded JSON in a URL may not work in all browsers. Therefore, UON is preferred.

7.32.3 - @Remoteable Annotation

What if you want fine-tuned control over which methods are exposed in an interface instead of just all public methods? For this, the {@link org.apache.juneau.remoteable.Remoteable @Remoteable} annotation is provided. It can be applied to individual interface methods to only expose those methods through the remoteable servlet.

For example, to expose only the first 2 methods in our IAddressBook interface...

public interface IAddressBook { @Remoteable Person createPerson(CreatePerson cp) throws Exception; @Remoteable Person findPerson(int id); Address findAddress(int id); Person findPersonWithAddress(int id); }

On the server side, the option to restrict access to only annotated methods is defined through a property:

@RestResource( path="/remoteable", properties={ // Only expose methods annotated with @Remoteable. @Property(name=REMOTEABLE_includeOnlyRemotableMethods, value="true") } ) public class SampleRemoteableServlet extends RemoteableServlet {

The @Remoteable annotation can also be applied to the interface class to expose all public methods defined on that interface.

@Remoteable public interface IAddressBook { Person createPerson(CreatePerson cp) throws Exception; Person findPerson(int id); Address findAddress(int id); Person findPersonWithAddress(int id); }

7.33 - Using with Spring and Injection frameworks

The Juneau REST server API is compatible with dependency injection frameworks such as Spring.

The important class is the {@link org.apache.juneau.rest.RestResourceResolver} class which is used to resolve child servlet/resource implementation classes inside parent contexts. In other words, it's used for resolving {@link org.apache.juneau.rest.annotation.RestResource#children() @RestResource.children()} instances.

The general approach starts with defining a resolver that uses the Spring application context for resolution:

public class SpringRestResourceResolver extends RestResourceResolverSimple { private final ApplicationContext appContext; public SpringRestResourceResolver(ApplicationContext appContext) { this.appContext = appContext; } @Override /* RestResourceResolverSimple */ public Object resolve(Class<?> resourceType, RestContextBuilder builder) throws Exception { Object resource = appContext.getBean(type); // If Spring can't resolve it, use default resolution (just look for no-arg constructor). if (resource == null) { resource = super.resolve(resourceType, builder); } return resource; } }

Next, define the Spring configuration to return our resolver:

@Configuration public abstract class MySpringConfiguration { @Autowired private static ApplicationContext appContext; public static ApplicationContext getAppContext(){ return appContext; } public static void setAppContext(ApplicationContext appContext){ MySpringConfiguration.appContext = appContext; } @Bean public RestResourceResolver restResourceResolver(ApplicationContext appContext) { return new SpringRestResourceResolver(appContext); } }

Finally, define your Root resource with a constructor that takes in our rest resource resolver and sets it on the config object during initialization.

@RestResource( children={ ... } ) public class Root extends RestServletGroup { private final RestResourceResolver resolver; @Inject public Root(RestResourceResolver resolver) { this.resolver = resolver; } @RestHook(INIT) public void initSpring(RestContextBuilder builder) throws Exception { builder.setResourceResolver(resolver); } }

After that, just define constructors on your child resources to take in Spring beans:

@RestResource( path="/child" ) public class MyChildResource extends BasicRestServlet { private final Bean1 bean1; private final Bean2 bean2; private final Bean3 bean3; @Inject public MyChildResource(Bean1 bean1, Bean2 bean2, Bean3 bean3) { this.bean1 = bean1; this.bean2 = bean2; this.bean3 = bean3; }

7.34 - Using HTTP/2 features

Juneau is built as a veneer on top of the Servlet API, allowing you to use low-level Servlet APIs whenever needed.
This allows you to take advantage of the newest HTTP/2 features implemented in the new Servlet 4.0 specification.

Coming soon (sorry)

7.35 - Predefined Label Beans

The {@link org.apache.juneau.rest.labels} package contains some reusable beans that are useful for creating linked items in HTML views.

The {@link org.apache.juneau.rest.labels.ResourceDescription} class is a bean with name/description properties for labeling and linking to child resources.
The following examples is pulled from the REST examples:

public class PredefinedLabelsResource extends BasicRestServlet { @RestMethod(name=GET, path="/") public ResourceDescription[] getChildMethods() { return new ResourceDescription[] { new ResourceDescription("beanDescription", "BeanDescription"), new ResourceDescription("htmlLinks", "HtmlLink") }; } }

It get rendered as a table of name/description columns with links to child methods:

The internals of the class show it simply has two bean properties with a link annotation defined on the name property:

public class ResourceDescription { @Html(link="servlet:/{name}") public Object getName() {...} public Object getDescription() {...} }

The {@link org.apache.juneau.rest.labels.BeanDescription} class provides a simple view of a bean and it's properties.

@RestMethod(name=GET, path="/beanDescription") public BeanDescription getBeanDescription() { return new BeanDescription(Person.class); }

This example renders the following:

The {@link org.apache.juneau.html.annotation.HtmlLink @HtmlLink} annotation can also be useful for rendering custom hyperlinks:

@RestMethod(name=GET, path="/htmlLinks") public ALink[] htmlLinks() { return new ALink[] { new ALink("apache", "http://apache.org"), new ALink("juneau", "http://juneau.apache.org") }; } @HtmlLink(nameProperty="n", hrefProperty="l") public static class ALink { public String n, l; public ALink(String n, String l) { this.n = n; this.l = l; } }

This example renders the following consisting of a list of hyperlinks:

See Also:

7.36 - Other Notes

8 - juneau-rest-server-jaxrs

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-rest-server-jaxrs</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-rest-server-jaxrs-7.0.0.jar

OSGi Module

org.apache.juneau.rest.server_7.0.0.jar

The juneau-rest-server-jaxrs library provides an implementation of a MessageBodyReader and MessageBodyWriter to allow any of the Juneau serializers and parsers to be used in a JAX/RS environment.

8.1 - Juneau JAX-RS Provider

The Juneau framework contains the juneau-rest-server-jaxrs bundle for performing simple integration of Juneau serializers and parsers in JAX-RS compliant environments.

It should be noted that although some of the functionality of the Juneau Server API is provided through the JAX-RS integration components, it is not nearly as flexible as using the {@link org.apache.juneau.rest.RestServlet} class directly.

What you can do with the Juneau JAX-RS provider classes:

What you can't do with the Juneau JAX-RS provider classes:

The Juneau JAX-RS provider API consists of the following classes:

For the most part, when using these components, you'll either use the existing BasicProvider, or define your own by subclassing BaseProvider.

9 - juneau-rest-client

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-rest-client</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-rest-client-7.0.0.jar

OSGi Module

org.apache.juneau.rest.client_7.0.0.jar

The REST client API provides the ability to access remote REST interfaces and transparently convert the input and output to and from POJOs using any of the provided serializers and parsers.

Built upon the Apache HttpClient libraries, it extends that API and provides specialized APIs for working with REST interfaces while maintaining all the functionality available in the HttpClient API.

// Create a reusable JSON client. try (RestClient client = RestClient.create().build()) { // The address of the root resource. String url = "http://localhost:9080/sample/addressBook"; // Do a REST GET against a remote REST interface and convert // the response to an unstructured ObjectMap object. ObjectMap m1 = client.doGet(url).getResponse(ObjectMap.class); // Same as above, except parse the JSON as a bean. AddressBook a2 = client.doGet(url).getResponse(AddressBook.class); } try (RestClient client = RestClient.create().serializer(XmlSerializer.class).parser(XmlSerializer.class).build()) { // Add a person to the address book. // Use XML as the transport medium. Person p = new Person("Joe Smith", 21); int returnCode = client.doPost(url + "/entries", p).run(); }

Juneau provides an HTTP client API that makes it extremely simple to connect to remote REST interfaces and seemlessly send and receive serialized POJOs in requests and responses.

Features

The client API is designed to work as a thin layer on top of the proven Apache HttpClient API. By leveraging the HttpClient library, details such as SSL certificate negotiation, proxies, encoding, etc... are all handled in Apache code.

The Juneau client API prereq's Apache HttpClient 4.5+. At a minimum, the following jars are required:

Example:

// Examples below use the Juneau Address Book resource example // Create a reusable client with JSON support try (RestClient client = RestClient.create().build()) { // GET request, ignoring output try { int rc = client.doGet("http://localhost:9080/sample/addressBook").run(); // Succeeded! } catch (RestCallException e) { // Failed! System.err.println( String.format("status=%s, message=%s", e.getResponseStatus(), e.getResponseMessage()) ); } // Remaining examples ignore thrown exceptions. // GET request, secure, ignoring output client.doGet("https://localhost:9443/sample/addressBook").run(); // GET request, getting output as a String. No POJO parsing is performed. // Note that when calling one of the getX() methods, you don't need to call connect() or disconnect(), since // it's automatically called for you. String output = client.doGet("http://localhost:9080/sample/addressBook") .getResponseAsString(); // GET request, getting output as a Reader Reader r = client.doGet("http://localhost:9080/sample/addressBook") .getReader(); // GET request, getting output as an untyped map // Input must be an object (e.g. "{...}") ObjectMap m = client.doGet("http://localhost:9080/sample/addressBook/0") .getResponse(ObjectMap.class); // GET request, getting output as an untyped list // Input must be an array (e.g. "[...]") ObjectList l = client.doGet("http://localhost:9080/sample/addressBook") .getResponse(ObjectList.class); // GET request, getting output as a parsed bean // Input must be an object (e.g. "{...}") // Note that you don't have to do any casting! Person p = client.doGet("http://localhost:9080/sample/addressBook/0") .getResponse(Person.class); // GET request, getting output as a parsed bean // Input must be an array of objects (e.g. "[{...},{...}]") Person[] pa = client.doGet("http://localhost:9080/sample/addressBook") .getResponse(Person[].class); // Same as above, except as a List<Person> List<Person> pl = client.doGet("http://localhost:9080/sample/addressBook") .getResponse(List.class, Person.class); // GET request, getting output as a parsed string // Input must be a string (e.g. "<string>foo</string>" or "'foo'") String name = client.doGet("http://localhost:9080/sample/addressBook/0/name") .getResponse(String.class); // GET request, getting output as a parsed number // Input must be a number (e.g. "<number>123</number>" or "123") int age = client.doGet("http://localhost:9080/sample/addressBook/0/age") .getResponse(Integer.class); // GET request, getting output as a parsed boolean // Input must be a boolean (e.g. "<boolean>true</boolean>" or "true") boolean isCurrent = client.doGet("http://localhost:9080/sample/addressBook/0/addresses/0/isCurrent") .getResponse(Boolean.class); } // GET request, getting a filtered object try (RestClient client = RestClient.create().pojoSwaps(CalendarSwap.ISO8601.class).build()) { Calendar birthDate = client.doGet("http://localhost:9080/sample/addressBook/0/birthDate") .getResponse(GregorianCalendar.class); // PUT request on regular field String newName = "John Smith"; int rc = client.doPut("http://localhost:9080/addressBook/0/name", newName).run(); // PUT request on filtered field Calendar newBirthDate = new GregorianCalendar(1, 2, 3, 4, 5, 6); rc = client.doPut("http://localhost:9080/sample/addressBook/0/birthDate", newBirthDate).run(); // POST of a new entry to a list Address newAddress = new Address("101 Main St", "Anywhere", "NY", 12121, false); rc = client.doPost("http://localhost:9080/addressBook/0/addresses", newAddress).run(); }

Notes:

9.1 - Interface Proxies Against 3rd-party REST Interfaces

The juneau-rest-client library can also be used to define interface proxies against 3rd-party REST interfaces.
This is an extremely powerful feature that allows you to quickly define easy-to-use interfaces against virtually any REST interface.

Similar in concept to remoteable services defined above, but in this case we simply define our interface with special annotations that tell us how to convert input and output to HTTP headers, query parameters, form post parameters, or request/response bodies.

// Our interface. @Remoteable public interface MyProxyInterface { @RemoteMethod(httpMethod=POST, path="/method") String callMyMethod(@Header("E-Tag") UUID etag, @Query("debug") boolean debug, @Body MyPojo pojo); } // Use a RestClient with default JSON support. try (RestClient client = RestClient.create().build()) { MyProxyInterface p = client.getRemoteableProxy(MyProxyInterface.class, "http://hostname/some/rest/interface"); String response = p.callMyMethod(UUID.randomUUID(), true, new MyPojo()); }

The call above translates to the following REST call:

POST http://hostname/some/rest/interface/method?debug=true HTTP/1.1 Accept: application/json Content-Type: application/json E-Tag: 475588d4-0b27-4f56-9296-cc683251d314 { "foo": "bar", "baz": 123, "qux": true}

The Java method arguments can be annotated with any of the following:

The return type of the Java method can be any of the following:

9.2 - SSL Support

The simplest way to enable SSL support in the client is to use the {@link org.apache.juneau.rest.client.RestClientBuilder#enableSSL(SSLOpts)} method and one of the predefined {@link org.apache.juneau.rest.client.SSLOpts} instances:

Example:

// Create a client that ignores self-signed or otherwise invalid certificates. RestClientBuilder builder = RestClient.create() .enableSSL(SSLOpts.LAX); // ...or... RestClientBuilder builder = RestClient.create() .enableLaxSSL();

This is functionally equivalent to the following:

RestClientBuilder builder = RestClient.create(); HostnameVerifier hv = new NoopHostnameVerifier(); TrustManager tm = new SimpleX509TrustManager(true); for (String p : new String[]{"SSL","TLS","SSL_TLS"}) { SSLContext ctx = SSLContext.getInstance(p); ctx.init(null, new TrustManager[] { tm }, null); SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(ctx, hv); builder.setSSLSocketFactory(sf); Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>.create() .register("https", sf).build(); builder.setConnectionManager(new PoolingHttpClientConnectionManager(r)); }

More complex SSL support can be enabled through the various {@link org.apache.http.impl.client.HttpClientBuilder} methods defined on the class.

9.2.1 - SSLOpts Bean

The {@link org.apache.juneau.rest.client.SSLOpts} class itself is a bean that can be created by the parsers. For example, SSL options can be specified in a config file and retrieved as a bean using the {@link org.apache.juneau.config.Config} class.

Contents of MyConfig.cfg

#================================================================================ # My Connection Settings #================================================================================ [Connection] url = https://myremotehost:9443 ssl = {certValidate:'LAX',hostVerify:'LAX'}

Code that reads an SSLOpts bean from the config file

// Read config file and set SSL options based on what's in that file. Config c = Config.create("MyConfig.cfg").build(); SSLOpts ssl = c.getObject(SSLOpts.class, "Connection/ssl"); RestClient rc = RestClient.create().enableSSL(ssl).build();

9.3 - Authentication

9.3.1 - BASIC Authentication

The {@link org.apache.juneau.rest.client.RestClientBuilder#basicAuth(String,int,String,String)} method can be used to quickly enable BASIC authentication support.

Example:

// Create a client that performs BASIC authentication using the specified user/pw. RestClient restClient = RestClient.create() .basicAuth(HOST, PORT, USER, PW) .build();

This is functionally equivalent to the following:

RestClientBuilder builder = RestClient.create(); AuthScope scope = new AuthScope(HOST, PORT); Credentials up = new UsernamePasswordCredentials(USER, PW); CredentialsProvider p = new BasicCredentialsProvider(); p.setCredentials(scope, up); builder.setDefaultCredentialsProvider(p);

9.3.2 - FORM-based Authentication

The {@link org.apache.juneau.rest.client.RestClientBuilder} class does not itself provide FORM-based authentication since there is no standard way of providing such support.
Typically, to perform FORM-based or other types of authentication, you'll want to create your own subclass of {@link org.apache.juneau.rest.client.RestClientBuilder} and override the {@link org.apache.juneau.rest.client.RestClientBuilder#createHttpClient()} method to provide an authenticated client.

The following example shows how the JazzRestClient class provides FORM-based authentication support.

/** * Constructor. */ public JazzRestClientBuilder(URI jazzUri, String user, String pw) throws IOException { ... } /** * Override the createHttpClient() method to return an authenticated client. */ @Override /* RestClientBuilder */ protected CloseableHttpClient createHttpClient() throws Exception { CloseableHttpClient client = super.createHttpClient(); formBasedAuthenticate(client); visitAuthenticatedURL(client); return client; } /* * Performs form-based authentication against the Jazz server. */ private void formBasedAuthenticate(HttpClient client) throws IOException { URI uri2 = jazzUri.resolve("j_security_check"); HttpPost request = new HttpPost(uri2); request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); // Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters. request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); NameValuePairs params = new NameValuePairs() .append(new BasicNameValuePair("j_username"", user)) .append(new BasicNameValuePair("j_password", pw)); request.setEntity(new UrlEncodedFormEntity(params)); HttpResponse response = client.execute(request); try { int rc = response.getStatusLine().getStatusCode(); Header authMsg = response.getFirstHeader("X-com-ibm-team-repository-web-auth-msg"); if (authMsg != null) throw new IOException(authMsg.getValue()); // The form auth request should always respond with a 200 ok or 302 redirect code if (rc == SC_MOVED_TEMPORARILY) { if (response.getFirstHeader("Location").getValue().matches("^.*/auth/authfailed.*$")) throw new IOException("Invalid credentials."); } else if (rc != SC_OK) { throw new IOException("Unexpected HTTP status: " + rc); } } finally { EntityUtils.consume(response.getEntity()); } } /* * This is needed for Tomcat because it responds with SC_BAD_REQUEST when the j_security_check URL is visited before an * authenticated URL has been visited. This same URL must also be visited after authenticating with j_security_check * otherwise tomcat will not consider the session authenticated */ private int visitAuthenticatedURL(HttpClient httpClient) throws IOException { HttpGet authenticatedURL = new HttpGet(jazzUri.resolve("authenticated/identity")); HttpResponse response = httpClient.execute(authenticatedURL); try { return response.getStatusLine().getStatusCode(); } finally { EntityUtils.consume(response.getEntity()); } }

9.3.3 - OIDC Authentication

The following example shows how the JazzRestClient class provides OIDC authentication support.

/** * Constructor. */ public JazzRestClientBuilder(URI jazzUri, String user, String pw) throws IOException { ... } /** * Override the createHttpClient() method to return an authenticated client. */ @Override /* RestClientBuilder */ protected CloseableHttpClient createHttpClient() throws Exception { CloseableHttpClient client = super.createHttpClient(); oidcAuthenticate(client); return client; } private void oidcAuthenticate(HttpClient client) throws IOException { HttpGet request = new HttpGet(jazzUri); request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); // Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters. request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); HttpResponse response = client.execute(request); try { int code = response.getStatusLine().getStatusCode(); // Already authenticated if (code == SC_OK) return; if (code != SC_UNAUTHORIZED) throw new RestCallException("Unexpected response during OIDC authentication: " + response.getStatusLine()); // x-jsa-authorization-redirect String redirectUri = getHeader(response, "X-JSA-AUTHORIZATION-REDIRECT"); if (redirectUri == null) throw new RestCallException("Expected a redirect URI during OIDC authentication: " + response.getStatusLine()); // Handle Bearer Challenge HttpGet method = new HttpGet(redirectUri + "&prompt=none"); addDefaultOidcHeaders(method); response = client.execute(method); code = response.getStatusLine().getStatusCode(); if (code != SC_OK) throw new RestCallException("Unexpected response during OIDC authentication phase 2: " + response.getStatusLine()); String loginRequired = getHeader(response, "X-JSA-LOGIN-REQUIRED"); if (! "true".equals(loginRequired)) throw new RestCallException("X-JSA-LOGIN-REQUIRED header not found on response during OIDC authentication phase 2: " + response.getStatusLine()); method = new HttpGet(redirectUri + "&prompt=none"); addDefaultOidcHeaders(method); response = client.execute(method); code = response.getStatusLine().getStatusCode(); if (code != SC_OK) throw new RestCallException("Unexpected response during OIDC authentication phase 3: " + response.getStatusLine()); // Handle JAS Challenge method = new HttpGet(redirectUri); addDefaultOidcHeaders(method); response = client.execute(method); code = response.getStatusLine().getStatusCode(); if (code != SC_OK) throw new RestCallException("Unexpected response during OIDC authentication phase 4: " + response.getStatusLine()); cookie = getHeader(response, "Set-Cookie"); Header[] defaultHeaders = new Header[] { new BasicHeader("User-Agent", "Jazz Native Client"), new BasicHeader("X-com-ibm-team-configuration-versions", "com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0"), new BasicHeader("Accept", "text/json"), new BasicHeader("Authorization", "Basic " + StringUtils.base64EncodeToString(user + ":" + pw)), new BasicHeader("Cookie", cookie) }; setDefaultHeaders(Arrays.asList(defaultHeaders)); } finally { EntityUtils.consume(response.getEntity()); } } private void addDefaultOidcHeaders(HttpRequestBase method) { method.addHeader("User-Agent", "Jazz Native Client"); method.addHeader("X-com-ibm-team-configuration-versions", "com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0"); method.addHeader("Accept", "text/json"); if (cookie != null) { method.addHeader("Authorization", "Basic " + StringUtils.base64EncodeToString(user + ":" + pw)); method.addHeader("Cookie", cookie); } }

9.4 - Using Response Patterns

One issue with REST (and HTTP in general) is that the HTTP response code must be set as a header before the body of the request is sent.
This can be problematic when REST calls invoke long-running processes, pipes the results through the connection, and then fails after an HTTP 200 has already been sent.

One common solution is to serialize some text at the end to indicate whether the long-running process succeeded (e.g. "FAILED" or "SUCCEEDED").

The {@link org.apache.juneau.rest.client.RestClient} class has convenience methods for scanning the response without interfering with the other methods used for retrieving output.

The following example shows how the {@link org.apache.juneau.rest.client.RestCall#successPattern(String)} method can be used to look for a SUCCESS message in the output:

Example:

// Throw a RestCallException if SUCCESS is not found in the output. restClient.doPost(URL) .successPattern("SUCCESS") .run();

The {@link org.apache.juneau.rest.client.RestCall#failurePattern(String)} method does the opposite. It throws an exception if a failure message is detected.

Example:

// Throw a RestCallException if FAILURE or ERROR is found in the output. restClient.doPost(URL) .failurePattern("FAILURE|ERROR") .run();

These convenience methods are specialized methods that use the {@link org.apache.juneau.rest.client.RestCall#responsePattern(ResponsePattern)} method which uses regular expression matching against the response body.
This method can be used to search for arbitrary patterns in the response body.

The following example shows how to use a response pattern finder to find and capture patterns for "x=number" and "y=string" from a response body.

Example:

final List<Number> xList = new ArrayList<Number>(); final List<String> yList = new ArrayList<String>(); String responseText = restClient.doGet(URL) .responsePattern( new ResponsePattern("x=(\\d+)") { @Override public void onMatch(RestCall restCall, Matcher m) throws RestCallException { xList.add(Integer.parseInt(m.group(1))); } @Override public void onNoMatch(RestCall restCall) throws RestCallException { throw new RestCallException("No X's found!"); } } ) .responsePattern( new ResponsePattern("y=(\\S+)") { @Override public void onMatch(RestCall restCall, Matcher m) throws RestCallException { yList.add(m.group(1)); } @Override public void onNoMatch(RestCall restCall) throws RestCallException { throw new RestCallException("No Y's found!"); } } ) .getResponseAsString();

Using response patterns does not affect the functionality of any of the other methods used to retrieve the response such as {@link org.apache.juneau.rest.client.RestCall#getResponseAsString()} or {@link org.apache.juneau.rest.client.RestCall#getResponse(Class)}.
HOWEVER, if you want to retrieve the entire text of the response from inside the match methods, use {@link org.apache.juneau.rest.client.RestCall#getCapturedResponse()} since this method will not absorb the response for those other methods.

9.5 - Piping Response Output

The {@link org.apache.juneau.rest.client.RestCall} class provides various convenience pipeTo() methods to pipe output to output streams and writers.

If you want to pipe output without any intermediate buffering, you can use the {@link org.apache.juneau.rest.client.RestCall#byLines()} method. This will cause the output to be piped and flushed after every line. This can be useful if you want to display the results in real-time from a long running process producing output on a REST call.

Example:

// Pipe output from REST call to System.out in real-time. restClient.doPost(URL).byLines().pipeTo(new PrintWriter(System.out)).run();

9.6 - Debugging

Use the {@link org.apache.juneau.rest.client.RestClientBuilder#debug()} method to enable logging for HTTP requests made from the client.

Under-the-covers, this is simply a shortcut for adding the {@link org.apache.juneau.rest.client.RestCallLogger#DEFAULT} interceptor to the client.
This causes the following output to be generated by the Java org.apache.juneau.rest.client logger at WARNING level:

=== HTTP Call (outgoing) ======================================================= === REQUEST === POST http://localhost:10000/testUrl HTTP/1.1 ---request headers--- Debug: true No-Trace: true Accept: application/json ---request entity--- Content-Type: application/json ---request content--- {"foo":"bar","baz":123} === RESPONSE === HTTP/1.1 200 OK ---response headers--- Content-Type: application/json;charset=utf-8 Content-Length: 21 Server: Jetty(8.1.0.v20120127) ---response content--- {"message":"OK then"} === END ========================================================================

This setting also causes a Debug: true header value to trigger logging of the request on the server side as well.

=== HTTP Request (incoming) ==================================================== HTTP POST /testUrl ---Headers--- Host: localhost:10000 Transfer-Encoding: chunked Accept: application/json Content-Type: application/json User-Agent: Apache-HttpClient/4.5 (Java/1.6.0_65) Connection: keep-alive Debug: true Accept-Encoding: gzip,deflate ---Default Servlet Headers--- ---Body--- {"foo":"bar","baz":123} === END ========================================================================

9.7 - Logging

Use the {@link org.apache.juneau.rest.client.RestClientBuilder#logTo(Level,Logger)} and {@link org.apache.juneau.rest.client.RestCall#logTo(Level,Logger)} methods to log HTTP calls.
These methods will cause the HTTP request and response headers and body to be logged to the specified logger.

Example:

// Log the HTTP request/response to the specified logger. int rc = restClient.doGet(URL).logTo(INFO, getLogger()).run();

The method call is ignored if the logger level is below the specified level.

Customized logging can be handled by sub-classing the {@link org.apache.juneau.rest.client.RestCallLogger} class and using the {@link org.apache.juneau.rest.client.RestCall#interceptor(RestCallInterceptor)} method.

9.8 - Interceptors

The {@link org.apache.juneau.rest.client.RestClientBuilder#interceptors(RestCallInterceptor...)} and {@link org.apache.juneau.rest.client.RestCall#interceptor(RestCallInterceptor)} methods can be used to intercept responses during specific connection lifecycle events.

The {@link org.apache.juneau.rest.client.RestCallLogger} class is an example of an interceptor that uses the various lifecycle methods to log HTTP requests.

/** * Specialized interceptor for logging calls to a log file. */ public class RestCallLogger extends RestCallInterceptor { private Level level; private Logger log; /** * Constructor. * * @param level The log level to log messages at. * @param log The logger to log to. */ protected RestCallLogger(Level level, Logger log) { this.level = level; this.log = log; } @Override /* RestCallInterceptor */ public void onInit(RestCall restCall) { if (log.isLoggable(level)) restCall.captureResponse(); } @Override /* RestCallInterceptor */ public void onConnect(RestCall restCall, int statusCode, HttpRequest req, HttpResponse res) { // Do nothing. } @Override /* RestCallInterceptor */ public void onRetry(RestCall restCall, int statusCode, HttpRequest req, HttpResponse res) { if (log.isLoggable(level)) log.log(level, MessageFormat.format("Call to {0} returned {1}. Will retry.", req.getRequestLine().getUri(), statusCode)); } @Override /* RestCallInterceptor */ public void onClose(RestCall restCall) throws RestCallException { try { if (log.isLoggable(level)) { String output = restCall.getCapturedResponse(); StringBuilder sb = new StringBuilder(); HttpUriRequest req = restCall.getRequest(); HttpResponse res = restCall.getResponse(); if (req != null) { sb.append("\n=== HTTP Call (outgoing) ========================================================="); sb.append("\n=== REQUEST ===\n").append(req); sb.append("\n---request headers---"); for (Header h : req.getAllHeaders()) sb.append("\n").append(h); if (req instanceof HttpEntityEnclosingRequestBase) { sb.append("\n---request entity---"); HttpEntityEnclosingRequestBase req2 = (HttpEntityEnclosingRequestBase)req; HttpEntity e = req2.getEntity(); if (e == null) sb.append("\nEntity is null"); else { if (e.getContentType() != null) sb.append("\n").append(e.getContentType()); if (e.getContentEncoding() != null) sb.append("\n").append(e.getContentEncoding()); if (e.isRepeatable()) { try { sb.append("\n---request content---\n").append(EntityUtils.toString(e)); } catch (Exception ex) { throw new RuntimeException(ex); } } } } } if (res != null) { sb.append("\n=== RESPONSE ===\n").append(res.getStatusLine()); sb.append("\n---response headers---"); for (Header h : res.getAllHeaders()) sb.append("\n").append(h); sb.append("\n---response content---\n").append(output); sb.append("\n=== END ========================================================================"); } log.log(level, sb.toString()); } } catch (IOException e) { log.log(Level.SEVERE, e.getLocalizedMessage(), e); } } }

9.9 - Remoteable Proxies

Juneau provides the capability of calling methods on POJOs on a server through client-side proxy interfaces.
It offers a number of advantages over other similar remote proxy interfaces, such as being much simpler to use and allowing much more flexibility.

Proxy interfaces are retrieved using the {@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class)}
method. The remoteable servlet is a specialized subclass of {@link org.apache.juneau.rest.RestServlet} that provides a full-blown REST interface for calling interfaces remotely.

In this example, we have the following interface defined that we want to call from the client side against a POJO on the server side (i.e. a Remoteable Service)...

public interface IAddressBook { Person createPerson(CreatePerson cp) throws Exception; }

The client side code for invoking this method is shown below...

// Create a RestClient using JSON for serialization, and point to the server-side remoteable servlet. RestClient client = RestClient.create() .rootUrl("https://localhost:9080/juneau/sample/remoteable") .build(); // Create a proxy interface. IAddressBook ab = client.getRemoteableProxy(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) );

The requirements for a method to be callable through a remoteable service are:

One significant feature is that the remoteable services servlet is a full-blown REST interface.
Therefore, in cases where the interface classes are not available on the client side, the same method calls can be made through pure REST calls.
This can also aid significantly in debugging since calls to the remoteable service can be called directly from a browser with no code involved.

9.10 - Other Useful Methods

The {@link org.apache.juneau.rest.client.RestClientBuilder#rootUrl(Object)} method can be used to specify a root URL on all requests so that you don't have to use absolute paths on individual calls.

// Create a rest client with a root URL RestClient rc = RestClient.create().rootUrl("http://localhost:9080/foobar").build(); String r = rc.doGet("/baz").getResponseAsString(); // Gets "http://localhost:9080/foobar/baz"

The {@link org.apache.juneau.rest.client.RestClientBuilder#set(String,Object)} method can be used to set serializer and parser properties.
For example, if you're parsing a response into POJOs and you want to ignore fields that aren't on the POJOs, you can use the {@link org.apache.juneau.BeanContext#BEAN_ignoreUnknownBeanProperties} property.

// Create a rest client that ignores unknown fields in the response RestClient rc = RestClient.create() .set(BEAN_ignoreUnknownBeanProperties, true) // or .ignoreUnknownBeanProperties(true) .build(); MyPojo myPojo = rc.doGet(URL).getResponse(MyPojo.class);

The {@link org.apache.juneau.rest.client.RestCall#retryable(int,long,RetryOn)} method can be used to automatically retry requests on failures.
This can be particularly useful if you're attempting to connect to a REST resource that may be in the process of still initializing.

// Create a rest call that retries every 10 seconds for up to 30 minutes as long as a connection fails // or a 400+ is received. restClient.doGet(URL) .retryable(180, 10000, RetryOn.DEFAULT) .run();

10 - juneau-microservice-server

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-microservice-server</artifactId> <version>7.0.0</version> </dependency>

Java Library

juneau-microservice-server-7.0.0.jar

OSGi Module

org.apache.juneau.microservice.server_7.0.0.jar

Microservice Starter Project

my-microservice.zip

Juneau Microservice is an API for creating stand-alone executable jars that can be used to start lightweight configurable REST interfaces with all the power of the Juneau REST server and client APIs.

10.1 - Microservice Introduction

The Microservice API consists of a combination of the Juneau Core, Server, and Client APIs and an embedded Eclipse Jetty Servlet Container.
It includes all libraries needed to execute in a Java 1.6+ environment.

Features include:

The juneau-microservice-server library consists of the following classes:

10.2 - Getting Started

The my-microservice.zip file is a zipped eclipse project that includes everything you need to create a REST microservice in an Eclipse workspace.

10.2.1 - Installing in Eclipse

Follow these instructions to create a new template project in Eclipse.

  1. Download the latest microservice-project zip file (e.g. my-microservice.zip).
  2. In your Eclipse workspace, go to File -> Import -> General -> Existing Projects into Workspace and select the zip file and click Finish.

  3. In your workspace, you should now see the following project:

The important elements in this project are:

  • RootResources.java - The top-level REST resource.
    This class routes HTTP requests to child resources:

    @RestResource( path="/", title="My Microservice", description="Top-level resources page", htmldoc=@HtmlDoc( widgets={ ContentTypeMenuItem.class, StyleMenuItem.class }, navlinks={ "options: servlet:/?method=OPTIONS" } ), children={ HelloWorldResource.class, ConfigResource.class, LogsResource.class } ) public class RootResources extends RestServletJenaGroup { // No code }

  • my-microservice.cfg - The external configuration file.
    Contains various useful settings.
    Can be used for your own resource configurations.

    #======================================================================================================================= # Basic configuration file for REST microservices # Subprojects can use this as a starting point. #======================================================================================================================= # What to do when the config file is saved. # Possible values: # NOTHING - Don't do anything. (default) # RESTART_SERVER - Restart the Jetty server. # RESTART_SERVICE - Shutdown and exit with code '3'. saveConfigAction = RESTART_SERVER #======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Path of the jetty.xml file used to configure the Jetty server. config = jetty.xml # Resolve Juneau variables in the jetty.xml file. resolveVars = true # Port to use for the jetty server. # You can specify multiple ports. The first available will be used. '0' indicates to try a random port. # The resulting available port gets set as the system property "availablePort" which can be referenced in the # jetty.xml file as "$S{availablePort}" (assuming resolveVars is enabled). port = 10000,0,0,0 #======================================================================================================================= # REST settings #======================================================================================================================= [REST] # Stylesheet to use for HTML views. # The default options are: # - servlet:/styles/juneau.css # - servlet:/styles/devops.css # Other stylesheets can be referenced relative to the servlet package or working directory. stylesheet = servlet:/styles/devops.css #======================================================================================================================= # Console settings #======================================================================================================================= [Console] enabled = true # List of available console commands. # These are classes that implements ConsoleCommand that allow you to submit commands to the microservice via # the console. # When listed here, the implementations must provide a no-arg constructor. # They can also be provided dynamically by overriding the Microservice.createConsoleCommands() method. commands = org.apache.juneau.microservice.console.ExitCommand, org.apache.juneau.microservice.console.RestartCommand, org.apache.juneau.microservice.console.HelpCommand #======================================================================================================================= # Logger settings #----------------------------------------------------------------------------------------------------------------------- # See FileHandler Java class for details. #======================================================================================================================= [Logging] ... #======================================================================================================================= # System properties #----------------------------------------------------------------------------------------------------------------------- # These are arbitrary system properties that are set during startup. #======================================================================================================================= [SystemProperties] # Configure Jetty for StdErrLog Logging # org.eclipse.jetty.util.log.class = org.eclipse.jetty.util.log.StrErrLog # Configure Jetty to log using java-util logging org.eclipse.jetty.util.log.class = org.apache.juneau.microservice.JettyLogger # Jetty logging level # Possible values: ALL, DEBUG, INFO, WARN, OFF org.eclipse.jetty.LEVEL = WARN derby.stream.error.file = $C{Logging/logDir}/derby-errors.log

  • jetty.xml - The Jetty configuration file.
    A bare-bones config file that can be extended to use any Jetty features.

    <Configure id="ExampleServer" class="org.eclipse.jetty.server.Server"> <Set name="connectors"> <Array type="org.eclipse.jetty.server.Connector"> <Item> <New class="org.eclipse.jetty.server.ServerConnector"> <Arg> <Ref refid="ExampleServer"/> </Arg> <Set name="port">$S{availablePort,8080}</Set> </New> </Item> </Array> </Set> <New id="context" class="org.eclipse.jetty.servlet.ServletContextHandler"> <Set name="contextPath">/</Set> <Call name="addServlet"> <Arg>org.apache.juneau.microservice.sample.RootResources</Arg> <Arg>/*</Arg> </Call> <Set name="sessionHandler"> <New class="org.eclipse.jetty.server.session.SessionHandler"/> </Set> </New> <Set name="handler"> <New class="org.eclipse.jetty.server.handler.HandlerCollection"> <Set name="handlers"> <Array type="org.eclipse.jetty.server.Handler"> <Item> <Ref refid="context"/> </Item> <Item> <New class="org.eclipse.jetty.server.handler.DefaultHandler"/> </Item> </Array> </Set> </New> </Set> <Set name="requestLog"> <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog"> <Set name="filename"><Property name="jetty.logs" default="$C{Logging/logDir,logs}"/>/jetty-requests.log</Set> <Set name="filenameDateFormat">yyyy_MM_dd</Set> <Set name="LogTimeZone">GMT</Set> <Set name="retainDays">90</Set> <Set name="append">false</Set> <Set name="LogLatency">true</Set> </New> </Set> <Get name="ThreadPool"> <Set name="minThreads" type="int">10</Set> <Set name="maxThreads" type="int">100</Set> <Set name="idleTimeout" type="int">60000</Set> <Set name="detailedDump">true</Set> </Get> </Configure>

At this point, you're ready to start the microservice from your workspace.

10.2.2 - Running in Eclipse

The my-microservice.launch file is already provided to allow you to quickly start your new microservice.

Go to Run -> Run Configurations -> Java Application -> my-microservice and click Run.
In your console view, you should see the following output:

Running class 'RestMicroservice' using config file 'my-microservice.cfg'. Server started on port 10000 List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help >

Now open your browser and point to http://localhost:10000. You should see the following:

You have started a REST interface on port 10000.
You can enter the command exit to shut it down.

10.2.3 - Building and Running from Command-Line

The pom.xml file is a basic Maven build script for creating your microservice as an executable uber-jar.

The easiest way to build your microservice is to run the following from the project root.

mvn clean install

Your target directory should now contain the following files:

  • my-microservice-1.0.jar
  • my-microservice.cfg
  • jetty.xml

To start from a command line, run the following command from inside your target directory:

java -jar my-microservice-1.0.jar

You should see the following console output:

Running class 'RestMicroservice' using config file 'my-microservice.cfg'. Server started on port 10000 List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help >

If you get this error message: java.net.BindException: Address already in use, then this microservice is already running elsewhere and so it cannot bind to port 10000.

10.3 - Manifest File

The generated META-INF/MANIFEST.MF file is used to describe the microservice.
If you open it, you'll see the following:

Main-Class: org.apache.juneau.microservice.RestMicroservice Main-ConfigFile: microservice.cfg

The Main-Class entry is the standard manifest entry describing the entry point for the executable jar.
In most cases, this value will always be org.apache.juneau.microservice.RestMicroservice.
However, it is possible to extend this class or implement your own microservice, in which case you'll need to modify this value to point to the new class.

The Main-ConfigFile entry points to the location of an external configuration file for our microservice.

In addition to these predefined manifest entries, you can add your own particular entries to the manifest file and access them through the Manifest API described next.

10.3.1 - Manifest API

The {@link org.apache.juneau.microservice.Microservice#getManifest()} method is a static method that can be used to retrieve the manifest file as a {@link org.apache.juneau.utils.ManifestFile}.

// Get Main-Class from manifest file. String mainClass = Microservice.getInstance().getManifest().getString("Main-Class", "unknown"); // Get Rest-Resources from manifest file. String[] restResources = Microservice.getInstance().getManifest().getStringArray("Rest-Resources");

The {@link org.apache.juneau.utils.ManifestFile} class extends {@link org.apache.juneau.ObjectMap}.
That makes it possible to retrieve entries as a wide variety of object types such as java primitives, arrays, collections, maps, or even POJOs serialized as JSON.

10.4 - Config

The microservice config file is an external INI-style configuration file that is used to configure your microservice.

If you open the my-microservice.cfg file, you'll see several predefined sections and settings.
The contents were shown in the previous sections.

Although the config file looks deceptively simple, the config file API is a very powerful feature with many capabilities, including:

Examples:

#-------------------------- # My section #-------------------------- [MySection] # An integer anInt = 1 # A boolean aBoolean = true # An int array anIntArray = 1,2,3 # A POJO that can be converted from a String aURL = http://foo # An encoded password aPassword* = {HRAaRQoT} # A POJO that can be converted from JSON aBean = {foo:'bar',baz:123} # A system property locale = $S{java.locale, en_US} # An environment variable path = $E{PATH, unknown} # A manifest file entry mainClass = $MF{Main-Class} # Another value in this config file sameAsAnInt = $C{MySection/anInt} # A command-line argument in the form "myarg=foo" myArg = $A{myarg} # The first command-line argument firstArg = $A{0} # Look for system property, or env var if that doesn't exist, or command-line arg if that doesn't exist. nested = $S{mySystemProperty,$E{MY_ENV_VAR,$A{0}}} # A POJO with embedded variables aBean2 = {foo:'$A{0}',baz:$C{MySection/anInt}}

// Java code for accessing config entries above. Config c = Microservice.getInstance().getConfig(); int anInt = c.getInt("MySection/anInt"); boolean aBoolean = c.getBoolean("MySection/aBoolean"); int[] anIntArray = c.getObject(int[].class, "MySection/anIntArray"); URL aURL = c.getObject(URL.class, "MySection/aURL"); String aPassword = c.getString("MySection/aPassword"); MyBean aBean = c.getObject(MyBean.class, "MySection/aBean"); Locale locale = c.getObject(Locale.class, "MySection/locale"); String path = c.getString("MySection/path"); String mainClass = c.getString("MySection/mainClass"); int sameAsAnInt = c.getInt("MySection/sameAsAnInt"); String myArg = c.getString("MySection/myArg"); String firstArg = c.getString("MySection/firstArg");

10.4.1 - Config File API

There are 3 primary ways of getting access to the config file.

  • {@link org.apache.juneau.microservice.Microservice#getConfig()}
    Any initialization-time variables and $A and $MF variables can be used.
  • {@link org.apache.juneau.rest.RestContext#getConfig()}
    Any initialization-time variables and $A and $MF variables can be used.
    Example usage:

    #------------------------------- # Properties for MyHelloResource #------------------------------- [MyHelloResource] greeting = Hello world!

    @RestResource(...) public class MyHelloResource extends BasicRestServlet { // Access config file when initializing fields. private String greeting = getConfig().getString("MyHelloResource/greeting"); // Or access config file in servlet init method. @Override /* Servlet */ public void init() { String greeting = getConfig().getString("MyHelloResource/greeting"); } }

    Additional user-defined variables can be defined at this level by adding a {@link org.apache.juneau.rest.annotation.HookEvent#INIT} hook method and using the {@link org.apache.juneau.rest.RestContextBuilder#vars(Class...)} method.

  • {@link org.apache.juneau.rest.RestRequest#getConfig()} - An instance method to access it from inside a REST method.
    Any initialization-time or request-time variables and $A and $MF variables can be used.
    Example usage:

    #----------------------------- # Contents of microservice.cfg #----------------------------- [MyHelloResource] greeting = Hello $RP{person}! localizedGreeting = $L{HelloMessage,$RP{person}}

    #--------------------------------- # Contents of MyHelloResource.java #--------------------------------- @RestResource( path="/hello", messages="nls/Messages", ... ) public class MyHelloResource extends BasicRestServlet { /** Standard hello message. */ @RestMethod(name=GET, path="/{person}") public String sayHello(RestRequest req) { return req.getConfig().getString("MyHelloResource/greeting"); } /** Hello message in users language. */ @RestMethod(name=GET, path="/localized/{person}") public String sayLocalizedHello(RestRequest req) { return req.getConfig().getString("MyHelloResource/localizedGreeting"); } }

    #--------------------------------------- # Contents of nls/Messages_en.properties #--------------------------------------- MyHelloResource.HelloMessage = Hello {0}!

    Additional user-defined variables can be defined at this level by overriding the {@link org.apache.juneau.rest.RestContextBuilder#vars(Class...)} method.

That sayLocalizedHello() example might need some explanation since there's a lot going on there.
Here's what happens when an HTTP call is made to GET /hello/localized/Bob:

  1. The HTTP call matches the /hello path on the MyHelloResource class.
  2. The HTTP call matches the /localized/{person} path on the sayLocalizedHello() method.
  3. The request attribute person gets assigned the value "Bob".
  4. The call to req.getConfig().getString("MyHelloResource/localizedGreeting") finds the value "$L{HelloMessage,$RP{person}}".
  5. The arguments in the $L{} variable get resolved, resulting in "$L{HelloMessage,Bob}".
  6. The $L{} variable gets resolved to the message "Hello {0}!" in the localized properties file of the servlet based on the Accept-Language header on the request.
  7. The arguments get replaced in the message resulting in "Hello Bob!".
  8. The resulting message "Hello Bob!" is returned as a POJO to be serialized to whatever content type was specified on the Accept header on the request.

This particular example is needlessly complex, but it gives an idea of how variables can be used recursively to produce sophisticated results

10.5 - Resource Classes

Now let's take a look at the resource classes themselves.
The top-level page...

...is generated by this class...

@RestResource( path="/", title="My Microservice", description="Top-level resources page", htmldoc=@HtmlDoc( widgets={ ContentTypeMenuItem.class, StyleMenuItem.class }, navlinks={ "options: servlet:/?method=OPTIONS" } ), children={ HelloWorldResource.class, ConfigResource.class, LogsResource.class } ) public class RootResources extends RestServletJenaGroup { // No code! }

If you click the helloWorld link in your application, you'll get a simple hello world message:

...which is generated by this class...

@RestResource( path="/helloWorld", title="Hello World example", description="Simplest possible REST resource" ) public class HelloWorldResource extends BasicRestServlet { @RestMethod(name=GET, path="/*") public String sayHello() { return "Hello world!"; } }

10.6 - Predefined Resource Classes

The following predefined resource classes are also provided for easy inclusion into your microservice:

10.7 - RestMicroservice

The {@link org.apache.juneau.microservice.RestMicroservice} class is the main application entry-point for REST microservices.

The class hierarchy is:

Refer to the Javadocs for these class for more information.

10.7.1 - Extending RestMicroservice

This example shows how the {@link org.apache.juneau.microservice.RestMicroservice} class can be extended to implement lifecycle listener methods or override existing methods.
We'll create a new class com.foo.SampleCustomRestMicroservice.

First, the manifest file needs to be modified to point to our new microservice:

Main-Class: com.foo.SampleCustomRestMicroservice

Then we define the following class:

/** * Sample subclass of a RestMicroservice that provides customized behavior. * This class must be specified in the Main-Class entry in the manifest file and optionally * a Main-ConfigFile entry. */ public class SampleCustomRestMicroservice extends RestMicroservice { /** * Must implement a main method and call start()! */ public static void main(String[] args) throws Exception { new SampleCustomRestMicroservice(args).start().join(); } /** * Must implement a constructor! * * @param args Command line arguments. * @throws Exception */ public SampleCustomRestMicroservice(String[] args) throws Exception { super(args); }

The microservice APIs provide several useful methods that can be used or extended.

See Also:
  • {@link org.apache.juneau.microservice.Microservice}
  • {@link org.apache.juneau.microservice.RestMicroservice}

11 - juneau-examples-core

Archive File

juneau-examples-core-7.0.0.zip

The juneau-examples-core project contains various code examples for using the core APIs.

The project project can be loaded into your workspace by importing the juneau-examples-core-7.0.0.zip file.

Instructions on how to install juneau-examples-core project

Download the juneau-examples-core-7.0.0.zip file from the downloads page (located in the binaries) and import it into your workspace as an existing project:

Select the archive file and import the project:

Once loaded, you should see the following project structure:

The Core library samples are currently a work-in-progress so there's not much here yet. This section will be updated as new code is added.

12 - juneau-examples-rest

Archive File

juneau-examples-rest-7.0.0.zip

The juneau-examples-rest project includes everything you need to start the Samples REST microservice in an Eclipse workspace.

This project is packaged as a Juneau Microservice project that allows REST resources to be started using embedded Jetty.

Instructions on how to install juneau-examples-rest project

Download the juneau-examples-rest-7.0.0.zip file from the downloads page (located in the binaries) and import it into your workspace as an existing project:

Select the archive file and import the project:

Once loaded, you should see the following project structure:

The microservice can be started from the juneau-examples-rest.launch file. It will start up the microservice on port 10000 which you can then view through a browser:

12.1 - RootResources

The RootResources class is the main page for the REST microservice. It serves as the jumping-off point for the other resources.

The class hierarchy for this class is:

Pointing a browser to the resource shows the following:

The RootResources class can also be defined as a servlet in a web.xml file:

<web-app version='2.3'> <servlet> <servlet-name>RootResources</servlet-name> <servlet-class>org.apache.juneau.rest.samples.RootResources</servlet-class> </servlet> <servlet-mapping> <servlet-name>RootResources</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

The RootResources class consists entirely of annotations:

RootResources.java

/** * Sample REST resource showing how to implement a "router" resource page. */ @RestResource( path="/", messages="nls/RootResources", htmldoc=@HtmlDoc( navlinks={ "options: ?method=OPTIONS" } ), children={ HelloWorldResource.class, MethodExampleResource.class, RequestEchoResource.class, TempDirResource.class, AddressBookResource.class, SampleRemoteableServlet.class, PhotosResource.class, AtomFeedResource.class, JsonSchemaResource.class, SqlQueryResource.class, TumblrParserResource.class, CodeFormatterResource.class, UrlEncodedFormResource.class, SourceResource.class, ConfigResource.class, LogsResource.class, DockerRegistryResource.class, ShutdownResource.class } ) public class RootResources extends RestServletJenaGroup { private static final long serialVersionUID = 1L; }

The resource bundle contains the localized strings for the resource:

RootResources.properties

#-------------------------------------------------------------------------------- # RootResources labels #-------------------------------------------------------------------------------- title = Root resources description = This is an example of a router resource that is used to access other resources.

The title and description keys identify the localized values return by the {@link org.apache.juneau.rest.RestRequest#getResourceTitle()} and {@link org.apache.juneau.rest.RestRequest#getResourceDescription()} methods.

The children annotation defines the child resources of this router resource. These are resources whose paths are relative to the parent resource.

Child resources must also be subclasses of {@link org.apache.juneau.rest.RestServlet}, and must specify a {@link org.apache.juneau.rest.annotation.RestResource#path() @RestResource.path()} annotation to identify the subpath of the child. For example, the HelloWorldResource class is annotated as follows:

HelloWorldResource.java

@RestResource(messages="nls/HelloWorldResource", path="/helloWorld") public class HelloWorldResource extends BasicRestServlet {

It should be noted that child resources do not need to be defined this way. They could also be defined as servlets in the same way as the root resource. The children annotation approach simply makes it easier to define them without having to touch the web.xml file again. Child resources can also be defined programmatically by using the {@link org.apache.juneau.rest.RestContextBuilder#children(Class[])} method.

Note that these router pages can be arbitrarily nested deep. You can define many levels of router pages for arbitrarily hierarchical REST interfaces.

12.2 - HelloWorldResource

The HelloWorldResource class is a simple resource that prints a "Hello world!" message.

HelloWorldResource.java

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( messages="nls/HelloWorldResource", path="/helloWorld", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class HelloWorldResource extends BasicRestServlet { private static final long serialVersionUID = 1L; /** GET request handler */ @RestMethod(name=GET, path="/*") public String sayHello() { return "Hello world!"; } }

HelloWorldResource.properties

#-------------------------------------------------------------------------------- # HelloWorldResource labels #-------------------------------------------------------------------------------- title = Hello World sample resource description = Simplest possible resource sayHello.summary = Responds with "Hello world!"

The class hierarchy for this class is:

Pointing a browser to the resource shows the following:

Using the special &Accept=text/json and &plainText=true parameters allows us to see this page rendered as JSON:

12.3 - MethodExampleResource

The MethodExampleResource class provides examples of the following:

The resource is provided to show how various HTTP entities (e.g. parameters, headers) can be accessed as either annotated Java parameters, or through methods on the RestRequest object.

MethodExampleResource.java

/** * Sample REST resource that shows how to define REST methods and OPTIONS pages */ @RestResource( path="/methodExample", messages="nls/MethodExampleResource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class MethodExampleResource extends BasicRestServlet { private static final long serialVersionUID = 1L; /** Example GET request that redirects to our example method */ @RestMethod(name=GET, path="/") public Redirect doExample() throws Exception { return new Redirect("example1/xxx/123/{0}/xRemainder?q1=123&q2=yyy", UUID.randomUUID()); } /** * Methodology #1 - GET request using annotated attributes. * This approach uses annotated parameters for retrieving input. */ @RestMethod(name=GET, path="/example1/{p1}/{p2}/{p3}/*") public String example1( @Method String method, // HTTP method. @Path String p1, // Path variables. @Path int p2, @Path UUID p3, @Query("q1") int q1, // Query parameters. @Query("q2") String q2, @Query("q3") UUID q3, @PathRemainder String remainder, // Path remainder after pattern match. @Header("Accept-Language") String lang, // Headers. @Header("Accept") String accept, @Header("DNT") int doNotTrack ) { // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); return output; } /** * Methodology #2 - GET request using methods on RestRequest and RestResponse. * This approach uses low-level request/response objects to perform the same as above. */ @RestMethod(name=GET, path="/example2/{p1}/{p2}/{p3}/*") public String example2( RestRequest req, // A direct subclass of HttpServletRequest. RestResponse res // A direct subclass of HttpServletResponse. ) { // HTTP method. String method = req.getMethod(); // Path variables. RequestPathMatch path = req.getPathMatch(); String p1 = path.get("p1", String.class); int p2 = path.get("p2", int.class); UUID p3 = path.get("p3", UUID.class); // Query parameters. RequestQuery query = req.getQuery(); int q1 = query.get("q1", 0, int.class); String q2 = query.get("q2", String.class); UUID q3 = query.get("q3", UUID.class); // Path remainder after pattern match. String remainder = req.getPathMatch().getRemainder(); // Headers. String lang = req.getHeader("Accept-Language"); String accept = req.getHeader("Accept"); int doNotTrack = req.getHeaders().get("DNT", int.class); // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); res.setOutput(output); // Or use getWriter(). } /** * Methodology #3 - GET request using special objects. * This approach uses intermediate-level APIs. * The framework recognizes the parameter types and knows how to resolve them. */ @RestMethod(name=GET, path="/example3/{p1}/{p2}/{p3}/*") public String example3( HttpMethod method, // HTTP method. RequestPathMatch path, // Path variables. RequestQuery query, // Query parameters. RequestHeaders headers, // Headers. AcceptLanguage lang, // Specific header classes. Accept accept ) { // Path variables. String p1 = path.get("p1", String.class); int p2 = path.get("p2", int.class); UUID p3 = path.get("p3", UUID.class); // Query parameters. int q1 = query.get("q1", 0, int.class); String q2 = query.get("q2", String.class); UUID q3 = query.get("q3", UUID.class); // Path remainder after pattern match. String remainder = path.getRemainder(); // Headers. int doNotTrack = headers.get("DNT", int.class); // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); res.setOutput(output); } }

The class consists of 4 methods:

There's a lot going on in this method. Notice how you're able to access URL attributes, parameters, headers, and content as parsed POJOs. All the input parsing is already done by the toolkit. You simply work with the resulting POJOs.

As you might notice, using annotations typically results in fewer lines of code and are therefore usually preferred over the API approach, but both are equally valid.

When you visit this page through the router page, you can see the following (after the automatic redirection occurs):

Notice how the conversion to POJOs is automatically done for us, even for non-standard POJOs such as UUID.

Self-documenting design through Swagger OPTIONS pages

One of the main features of Juneau is that it produces OPTIONS pages for self-documenting design (i.e. REST interfaces that document themselves).

Much of the information populated on the OPTIONS page is determined through reflection. This basic information can be augmented with information defined through:

OPTIONS pages are simply serialized {@link org.apache.juneau.dto.swagger.Swagger} DTO beans. Localized versions of these beans are retrieved using the {@link org.apache.juneau.rest.RestRequest#getSwagger()} method.

To define an OPTIONS request handler, the {@link org.apache.juneau.rest.BasicRestServlet} class defines the following Java method:

BasicRestServlet.java

/** OPTIONS request handler */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); }

The OPTIONS link that you see on the HTML version of the page is created through a property defined by the {@link org.apache.juneau.html.HtmlDocSerializer} class and specified on the resource class annotation:

@RestResource( htmldoc=@HtmlDoc( navlinks={ "options: servlet:/?method=OPTIONS" } ) )

This simply creates a link that's the same URL as the resource URL appended with "?method=OPTIONS", which is a shorthand way that the framework provides of defining overloaded GET requests. Links using relative or absolute URLs can be defined this way.

Metadata about the servlet class is combined with localized strings from a properties file associated through a @RestResource(messages="nls/MethodExampleResources") annotation. The properties file contains localized descriptions for the resource, resource methods, and method parameters.

MethodExampleResource.properties

#-------------------------------------------------------------------------------- # MethodExampleResource labels #-------------------------------------------------------------------------------- title = A simple REST method example resource doGetExample.summary = Sample GET method doGetExample1.summary = Sample GET using annotations doGetExample1.req.path.a1.description = Sample variable doGetExample1.req.path.a2.description = Sample variable doGetExample1.req.path.a3.description = Sample variable doGetExample1.req.query.p1.description = Sample parameter doGetExample1.req.query.p2.description = Sample parameter doGetExample1.req.query.p3.description = Sample parameter doGetExample1.req.header.Accept-Language.description = Sample header doGetExample1.req.header.DNT.description = Sample header doGetExample2.summary = Sample GET using Java APIs doGetExample2.req.path.a1.description = Sample variable doGetExample2.req.path.a2.description = Sample variable doGetExample2.req.path.a3.description = Sample variable doGetExample2.req.query.p1.description = Sample parameter doGetExample2.req.query.p2.description = Sample parameter doGetExample2.req.query.p3.description = Sample parameter doGetExample2.req.header.Accept-Language.description = Sample header doGetExample2.req.header.DNT.description = Sample header getOptions.summary = View these options

Clicking the options link on the page presents you with information about how to use this resource:

This page (like any other) can also be rendered in JSON or XML by using the &Accept URL parameter.

12.4 - UrlEncodedFormResource

The UrlEncodedFormResource class provides examples of the following:

The class is shown below:

UrlEncodedFormResource.java

/** * Sample REST resource for loading URL-Encoded form posts into POJOs. */ @RestResource( path="/urlEncodedForm", messages="nls/UrlEncodedFormResource" ) public class UrlEncodedFormResource extends BasicRestServlet { private static final long serialVersionUID = 1L; /** GET request handler */ @RestMethod(name=GET, path="/") public ReaderResource doGet(RestRequest req) throws IOException { return req.getClasspathReaderResource("UrlEncodedForm.html", true); } /** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@Body FormInputBean input) throws Exception { // Just mirror back the request return input; } public static class FormInputBean { public String aString; public int aNumber; @BeanProperty(pojoSwaps=CalendarSwap.ISO8601DT.class) public Calendar aDate; } }

The {@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(String,boolean)} method pulls in the following file located in the same package as the class:

UrlEncodedForm.html
TODO - Needs update

<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <style type='text/css'> @import '$R{servletURI}/style.css'; </style> <script type="text/javascript"> // Load results from IFrame into this document. function loadResults(buff) { var doc = buff.contentDocument || buff.contentWindow.document; var buffBody = doc.getElementById('data'); document.getElementById('results').innerHTML = buffBody.innerHTML; } </script> </head> <body> <h1>$R{resourceTitle}</h1> <h2>$R{resourceDescription}</h2> <div class='data'> <form id='form' action='$R{servletURI}' method='POST' target='buff'> <table> <tr> <th>$L{aString}</th> <td><input name="aString" type="text"></td> </tr> <tr> <th>$L{aNumber}</th> <td><input name="aNumber" type="number"></td> </tr> <tr> <th>$L{aDate}</th> <td><input name="aDate" type="datetime"> (ISO8601, e.g. "<code>2001-07-04T15:30:45Z</code>")</td> </tr> <tr> <td colspan='2' align='right'><button type="submit">$L{submit}</button></td> </tr> </table> </form> <br> <div id='results'> </div> </div> <iframe name='buff' style='display:none' onload="parent.loadResults(this)"></iframe> </body> </html>

The $L variables are string variable that pull in localized values from the resource bundle:

UrlEncodedFormResource.properties

#-------------------------------------------------------------------------------- # UrlEncodedFormResource labels #-------------------------------------------------------------------------------- title = URL-Encoded Form Post Example description = Shows how URL-Encoded form input can be loaded into POJOs. POJO is simply echoed back. aString = A String: aNumber = A Number: aDate = A Date: submit = submit

The $R variables are request string variables. In this case, $R{resourceTitle} and $R{resourceDescription} resolve to the values returned by {@link org.apache.juneau.rest.RestRequest#getResourceTitle()} and {@link org.apache.juneau.rest.RestRequest#getResourceDescription()}.

Pointing a browser to the resource shows the following:

Entering some values and clicking submit causes the form bean to be populated and returned back as a POJO response:

Another option is to construct the HTML form in Java using HTML5 beans. This is arguably a better approach since it's typically cleaner with less code, and the headers/links are already part of the page.

import static org.apache.juneau.dto.html5.HtmlBuilder.*; /** GET request handler */ @RestMethod(name=GET, path="/") public Div doGet(RestRequest req) { return div( script("text/javascript", "\n // Load results from IFrame into this document." +"\n function loadResults(buff) {" +"\n var doc = buff.contentDocument || buff.contentWindow.document;" +"\n var buffBody = doc.getElementById('data');" +"\n document.getElementById('results').innerHTML = buffBody.innerHTML;" +"\n }" ), form().id("form").action(req.getServletURI()).method(POST).target("buff").children( table( tr( th(req.getMessage("aString")), td(input().name("aString").type("text")) ), tr( th(req.getMessage("aNumber")), td(input().name("aNumber").type("number")) ), tr( th(req.getMessage("aDate")), td(input().name("aDate").type("datetime"), " (ISO8601, e.g. ", code("2001-07-04T15:30:45Z"), \" )") ), tr( td().colspan(2).style("text-align:right").children( button("submit", req.getMessage("submit")) ) ) ) ), br(), div().id("results"), iframe().name("buff").style("display:none").onload("parent.loadResults(this)") ); }

Additional Information

12.5 - RequestEchoResource

The RequestEchoResource class shows how existing complex POJOs can be serialized to a variety of content types. The example simply takes the incoming HttpServletRequest object and serializes it.

It provides examples of the following:

The class is shown below:

RequestEchoResource.java

/** * Sample REST resource for echoing HttpServletRequests back to the browser */ @RestResource( path="/echo", messages="nls/RequestEchoResource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ), properties={ @Property(name=SERIALIZER_maxDepth, value="10"), @Property(name=SERIALIZER_detectRecursions, value="true") }, beanFilters={ // Interpret these as their parent classes, not subclasses HttpServletRequest.class, HttpSession.class, ServletContext.class, }, pojoSwaps={ // Add a special POJO swap for Enumerations EnumerationSwap.class } ) public class RequestEchoResource extends BasicRestServlet { /** GET request handler */ @RestMethod(name=GET, path="/*", converters={Queryable.class,Traversable.class}) public HttpServletRequest doGet(RestRequest req, RestResponse res, ObjectMap properties) { // Set the HtmlDocSerializer title programmatically. res.setPageTitle(req.getPathInfo()); // Just echo the request back as the response. return req; } }

Again, there's a lot going on here that's new that requires some explanation. The HttpServletRequest object is not a tree-shaped POJO model. Instead, it contains lots of loops that can cause stack overflow errors if you were to try to serialize it as-is. Also, you want to look only at the properties defined on the HttpServletRequest class, not implementation-specific (i.e. WAS or Jetty) fields which can get messy.

The {@link org.apache.juneau.rest.annotation.RestResource#properties() @RestResource.properties()}, {@link org.apache.juneau.rest.annotation.RestResource#beanFilters() @RestResopurce.beanFilters()}, and {@link org.apache.juneau.rest.annotation.RestResource#pojoSwaps() @RestResopurce.pojoSwaps()} annotations are used to set behavior properties on the resource's underlying bean context, serializers, and parsers. You're using them here to modify the behavior of serialization for all content types. The annotations are functionally equivalent to using the {@link org.apache.juneau.rest.RestContextBuilder} class, as follows:

Hypothetical RequestEchoResource.createSerializers() method

/** Override the default rest serializers to add some transforms through an INIT hook*/ @RestHook(INIT) public void init(RestContextBuilder builder) throws Exception { // Add bean filters for the HttpServletRequest, HttpSession, and ServletContext objects // so that you don't show vendor-specific properties on subclasses. // Add Enumeration POJO swap to be able to render the contents of Enumeration properties. // The max depth and detect recursion options prevent any possible runaway serializations. // This shouldn't happen, but future JEE APIs may introduce deep hierarchies or loops. builder .beanFilters(HttpServletRequest.class, HttpSession.class, ServletContext.class) .pojoSwaps(EnumerationSwap.class) .setProperty(SERIALIZER_maxDepth, 10) .setProperty(SERIALIZER_detectRecursions, true) .pageLinks("{...}"); }

Note how the annotations generally require fewer lines of code.

Pointing a browser to the resource shows the following:

This gives you an idea of what kinds of POJO models can be serialized, since you are serializing a regular old HttpServletRequest object.

12.6 - AddressBookResource

The AddressBookResource class is a proof-of-concept class that shows a true RESTful API using the Juneau REST toolkit. It provides examples of the following:

Pointing a browser to the resource shows the following:

12.6.1 - Classes

The code is straightforward, consisting of the following classes:

  • package-info.java - Used to define XML namespaces for POJOs in this package.
  • IAddressBook - An interface describing the address book.
  • AddressBook - A data structure consisting of a list of Persons.
  • Person, Address - In-memory representations of people and addresses.
  • CreatePerson, CreateAddress - POJOs for creating and updating people and address through the REST interface.
  • AddressBookResource - The REST resource class.
  • For the sake of brevity, bean properties are defined as public fields instead of the normal getters/setters. Also, the examples are not the most efficient design and are not thread safe.

The package-info.java file is used to define XML and RDF namespaces on beans and properties in this package. Here you define a default XML and RDF namespaces and URL mappings for namespace short-names used throughout this package. It should be noted that these features are entirely optional, and there are often several ways of defining these namespaces.

package-info.java

// XML and RDF namespaces used in this package @Xml(ns="ab", namespaces={ @XmlNs(name="ab", uri="http://www.apache.org/addressBook/"), @XmlNs(name="per", uri="http://www.apache.org/person/"), @XmlNs(name="addr", uri="http://www.apache.org/address/"), @XmlNs(name="mail", uri="http://www.apache.org/mail/") } ) @Rdf(ns="ab", namespaces={ @RdfNs(name="ab", uri="http://www.apache.org/addressBook/"), @RdfNs(name="per", uri="http://www.apache.org/person/"), @RdfNs(name="addr", uri="http://www.apache.org/address/"), @RdfNs(name="mail", uri="http://www.apache.org/mail/") } ) package org.apache.juneau.examples.addressBook; import org.apache.juneau.xml.annotation.*;

Our address book uses the following interface:

IAddressBook.java

/** * Interface used to help illustrate proxy interfaces. * See {@link SampleRemoteableServlet}. */ public interface IAddressBook { /** Return all people in the address book */ List<Person> getPeople(); /** Return all addresses in the address book */ List<Address> getAddresses(); /** Create a person in this address book */ Person createPerson(CreatePerson cp) throws Exception; /** Find a person by id */ Person findPerson(int id); /** Find an address by id */ Address findAddress(int id); /** Find a person by address id */ Person findPersonWithAddress(int id); /** Remove a person by id */ Person removePerson(int id); }

Notes
  • You interface an interface for our address book so that you can later use it to demonstrate the proxy interface support.

The AddressBook class is our address book. It maintains a list of Person objects with some additional convenience methods:

AddressBook.java

/** Address book bean */ public class AddressBook extends LinkedList<Person> implements IAddressBook { // The URL of this resource private URI uri; /** Bean constructor - Needed for instantiating on client side */ public AddressBook () {} /** Normal constructor - Needed for instantiating on server side */ public AddressBook (URI uri) {...} @Override /* IAddressBook */ public List<Person> getPeople() { return this; } @Override /* IAddressBook */ public Person createPerson(CreatePerson cp) throws Exception { Person p = new Person(uri, cp); add(p); return p; } @Override /* IAddressBook */ public Person findPerson(int id) { for (Person p : this) if (p.id == id) return p; return null; } @Override /* IAddressBook */ public Address findAddress(int id) { for (Person p : this) for (Address a : p.addresses) if (a.id == id) return a; return null; } @Override /* IAddressBook */ public Person findPersonWithAddress(int id) { for (Person p : this) for (Address a : p.addresses) if (a.id == id) return p; return null; } @Override /* IAddressBook */ public List<Address> getAddresses() { Set<Address> s = new LinkedHashSet<Address>(); for (Person p : this) for (Address a : p.addresses) s.add(a); return new ArrayList<Address>(s); } @Override /* IAddressBook */ public Person removePerson(int id) { Person p = findPerson(id); if (p != null) remove(p); return p; } /** Utility method */ public static Calendar toCalendar(String birthDate) throws Exception { Calendar c = new GregorianCalendar(); c.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US).parse(birthDate)); return c; } }

Notes
  • The @Xml(elementName="addressBook") annotation tells the toolkit that when serialized as XML, the element name is <addressBook>. Without this annotation, the element would revert to the generalized <array> tag.
  • The separate constructors are implementation specific and are needed because you're going to be using this class in two ways, since you'll be demonstrating the client code as well as the server code, and it eliminates having to define separate client-side and server-side POJOs:
    1. The normal constructor is used to programmatically create this object in the REST servlet code.
    2. The no-arg constructor is used by the Juneau parsers to construct this object in our client side code.

The Person bean is defined as follows:

Person.java

/** Person bean */ @Xml(ns="per") @Rdf(prefix="per") @Bean(typeName="person") public class Person { private static int nextPersonId = 1; // Bean properties. @Rdf(beanUri=true) public URI uri; public URI addressBookUri; public String id; public String name; @BeanProperty(swap=CalendarSwap.Medium.class) public Calendar birthDate; public LinkedList<Address> addresses = new LinkedList<Address>(); /** Bean constructor - Needed for instantiating on server side */ public Person() {} /** Normal constructor - Needed for instantiating on client side */ public Person(URI addressBookUri, CreatePerson cp) throws Exception { this.id = nextPersonId++; this.addressBookUri = addressBookUri; if (addressBookUri != null) this.uri = addressBookUri.resolve("people/" + id); this.name = cp.name; this.birthDate = cp.birthDate; for (CreateAddress ca : cp.addresses) this.addresses.add(new Address(addressBookUri, uri, ca)); } /** Extra read-only bean property */ public int getAge() { return new GregorianCalendar().get(Calendar.YEAR) - birthDate.get(Calendar.YEAR); } /** Convenience method - Add an address for this person */ public Address createAddress(CreateAddress ca) throws Exception { Address a = new Address(addressBookUri, uri, ca); addresses.add(a); return a; } /** Extra method (for method invocation example) */ public String sayHello(String toPerson, int age) { return name + " says hello to " + toPerson + " who is " + age + " years old"; } }

Notes
  • The ns="per" annotations override the default "ab" namespace defined on the package. It applies to this class and all properties of this class.
  • The @Rdf(beanUri=true) annotation identifies the uri property as the resource URI for this resource. This property has special meaning for the RDF serializer. The RDF serializer uses this property for the value of the rdf:resource attribute.
  • The @BeanProperty(swap=CalendarSwap.Medium.class) annotation causes the date field to be serialized in the format "MM dd, yyyy". This could have also been specified globally on the resource level through the {@link org.apache.juneau.rest.annotation.RestResource#properties} annotation.

The Address bean is defined as follows:

Address.java

/** * Address bean */ @Xml(prefix="addr") @Rdf(prefix="addr") @Bean(typeName="address") public class Address { private static int nextAddressId = 1; // Bean properties @Rdf(beanUri=true) public URI uri; public URI personUri; public int id; @Xml(prefix="mail") @Rdf(prefix="mail") public String street, city, state; @Xml(prefix="mail") @Rdf(prefix="mail") public int zip; public boolean isCurrent; /** Bean constructor - Needed for instantiating on client side */ public Address() {} /** Normal constructor - Needed for instantiating on server side */ public Address(URI addressBookUri, URI personUri, CreateAddress ca) throws Exception { this.id = nextAddressId++; if (addressBookUri != null) this.uri = addressBookUri.resolve("addresses/" + id); this.personUri = personUri; this.street = ca.street; this.city = ca.city; this.state = ca.state; this.zip = ca.zip; this.isCurrent = ca.isCurrent; } }

Notes
  • This class shows how the namespace can be overridden at the property level through the @Xml(ns="mail") annotation.

The CreatePerson bean is used as the input data for creating a person.

CreatePerson.java

/** Bean for creating a new person */ @Xml(ns="per") @Rdf(ns="addr") @Bean(typeName="person") public class CreatePerson { // Bean properties public String name; @BeanProperty(swap=CalendarSwap.Medium.class) public Calendar birthDate; public LinkedList<CreateAddress> addresses; /** Bean constructor - Needed for instantiating on server side */ public CreatePerson() {} /** Normal constructor - Needed for instantiating on client side */ public CreatePerson(String name, Calendar birthDate, CreateAddress...addresses) {...} }

The CreateAddress bean is used as the input data for creating an address.

CreateAddress.java

/** Bean for creating a new address */ @Xml(ns="addr") @Rdf(ns="addr") @Bean(typeName="address") public class CreateAddress { // Bean properties @Xml(ns="mail") @Rdf(ns="mail") public String street, city, state; @Xml(ns="mail") @Rdf(ns="mail") public int zip; public boolean isCurrent; /** Bean constructor -Needed for instantiating on server side */ public CreateAddress() {} /** Normal constructor - Needed for instantiating on client side */ public CreateAddress(String street, String city, String state, int zip, boolean isCurrent) {...} }

The AddressBookResource class is our REST resource class.

AddressBookResource.java

/** * Proof-of-concept resource that shows off the capabilities of working with POJO resources. * Consists of an in-memory address book repository. */ @RestResource( path="/addressBook", messages="nls/AddressBookResource", // Links on the HTML rendition page. // "request:/..." URIs are relative to the request URI. // "servlet:/..." URIs are relative to the servlet URI. // "$C{...}" variables are pulled from the config file. htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java" } ), // Allow INIT as a method parameter. allowedMethodParams="*", // Properties that get applied to all serializers and parsers. properties={ // Use single quotes. @Property(name=SERIALIZER_quoteChar, value="'"), // Make RDF/XML readable. @Property(name=RDF_rdfxml_tab, value="5"), // Make RDF parsable by adding a root node. @Property(name=RDF_addRootProperty, value="true"), // Make URIs absolute so that we can easily reference them on the client side. @Property(name=SERIALIZER_uriResolution, value="ABSOLUTE") // Make the anchor text on URLs be just the path relative to the servlet. @Property(name=HTML_uriAnchorText, value="SERVLET_RELATIVE") }, // Our stylesheet for the HTML rendition. stylesheet="styles/devops.css", // Support GZIP encoding on Accept-Encoding header. encoders=GzipEncoder.class, // Swagger info. contact="{name:'John Smith',email:'john@smith.com'}", license="{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}", version="2.0", termsOfService="You're on your own.", tags="[{name:'Java',description:'Java utility',externalDocs:{description:'Home page', url:'http://juneau.apache.org'}}]", externalDocs="{description:'Home page',url:'http://juneau.apache.org'}" ) public class AddressBookResource extends BasicRestServletJena { private static final long serialVersionUID = 1L; // The in-memory address book private AddressBook addressBook; @Override /* Servlet */ public void init() { try { // Create the address book addressBook = new AddressBook(java.net.URI.create("servlet:/")); // Add some people to our address book by default addressBook.createPerson( 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) ) ); addressBook.createPerson( new CreatePerson( "George Walker Bush", toCalendar("Jul 6, 1946"), new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true), new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false) ) ); } catch (Exception e) { throw new RuntimeException(e); } } /** * [GET /] * Get root page. */ @RestMethod(name=GET, path="/", converters=Queryable.class ) public Link[] getRoot() throws Exception { return new Link[] { new Link("people", "people"), new Link("addresses", "addresses") }; } /** * [GET /people/*] * Get all people in the address book. * Traversable transforming enabled to allow nodes in returned POJO tree to be addressed. * Introspectable transforming enabled to allow public methods on the returned object to be invoked. */ @RestMethod(name=GET, path="/people/*", converters={Traversable.class,Queryable.class,Introspectable.class} ) public AddressBook getAllPeople() throws Exception { return addressBook; } /** * [GET /people/{id}/*] * Get a single person by ID. * Traversable transforming enabled to allow nodes in returned POJO tree to be addressed. * Introspectable transforming enabled to allow public methods on the returned object to be invoked. */ @RestMethod(name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class,Introspectable.class} ) public Person getPerson(@Path int id) throws Exception { return findPerson(id); } /** * [GET /addresses/*] * Get all addresses in the address book. */ @RestMethod(name=GET, path="/addresses/*", converters={Traversable.class,Queryable.class} ) public List<Address> getAllAddresses() throws Exception { return addressBook.getAddresses(); } /** * [GET /addresses/{id}/*] * Get a single address by ID. */ @RestMethod(name=GET, path="/addresses/{id}/*", converters={Traversable.class,Queryable.class} ) public Address getAddress(@Path int id) throws Exception { return findAddress(id); } /** * [POST /people] * Create a new Person bean. */ @RestMethod(name=POST, path="/people", guards=AdminGuard.class ) public Redirect createPerson(@Body CreatePerson cp) throws Exception { Person p = addressBook.createPerson(cp); return new Redirect("people/{0}", p.id); } /** * [POST /people/{id}/addresses] * Create a new Address bean. */ @RestMethod(name=POST, path="/people/{id}/addresses", guards=AdminGuard.class ) public Redirect createAddress(@Path int id, @Body CreateAddress ca) throws Exception { Person p = findPerson(id); Address a = p.createAddress(ca); return new Redirect("addresses/{0}", a.id); } /** * [DELETE /people/{id}] * Delete a Person bean. */ @RestMethod(name=DELETE, path="/people/{id}", guards=AdminGuard.class, ) public String deletePerson(@Path int id) throws Exception { addressBook.removePerson(id); return "DELETE successful"; } /** * [DELETE /addresses/{id}] * Delete an Address bean. */ @RestMethod(name=DELETE, path="/addresses/{id}", guards=AdminGuard.class ) public String deleteAddress(@Path int addressId) throws Exception { Person p = addressBook.findPersonWithAddress(addressId); if (p == null) throw new RestException(SC_NOT_FOUND, "Person not found"); Address a = findAddress(addressId); p.addresses.remove(a); return "DELETE successful"; } /** * [PUT /people/{id}/*] * Change property on Person bean. */ @RestMethod(name=PUT, path="/people/{id}/*", guards=AdminGuard.class ) public String updatePerson(RestRequest req, @Path int id, @PathRemainder String remainder) throws Exception { try { Person p = findPerson(id); PojoRest r = new PojoRest(p); ClassMeta<?> cm = r.getClassMeta(remainder); Object in = req.getBody().asType(cm); r.put(remainder, in); return "PUT successful"; } catch (Exception e) { throw new RestException(SC_BAD_REQUEST, "PUT unsuccessful").initCause(e); } } /** * [PUT /addresses/{id}/*] * Change property on Address bean. */ @RestMethod(name=PUT, path="/addresses/{id}/*", guards=AdminGuard.class ) public String updateAddress(RestRequest req, @Path int id, @PathRemainder String remainder) throws Exception { try { Address a = findAddress(id); PojoRest r = new PojoRest(a); ClassMeta<?> cm = r.getClassMeta(remainder); Object in = req.getBody().asType(cm); r.put(remainder, in); return "PUT successful"; } catch (Exception e) { throw new RestException(SC_BAD_REQUEST, "PUT unsuccessful").initCause(e); } } /** * [INIT /] * Reinitialize this resource. */ @RestMethod(name="INIT", path="/", guards=AdminGuard.class ) public String doInit() throws Exception { init(); return "OK"; } /** * [GET /cognos] * Get data in Cognos/XML format */ @RestMethod(name=GET, path="/cognos") public DataSet getCognosData() throws Exception { // The Cognos metadata Column[] items = { new Column("name", "xs:String", 255), new Column("age", "xs:int"), new Column("numAddresses", "xs:int") .addPojoSwap( new PojoSwap<Person,Integer>() { @Override /* PojoSwap */ public Integer swap(BeanSession session, Person p) { return p.addresses.size(); } } ) }; return new DataSet(items, addressBook, this.getBeanContext()); } /** * [OPTIONS /*] * View resource options */ @Override /* BasicRestServlet */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); } /** Convenience method - Find a person by ID */ private Person findPerson(int id) throws RestException { Person p = addressBook.findPerson(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Person not found"); return p; } /** Convenience method - Find an address by ID */ private Address findAddress(int id) throws RestException { Address a = addressBook.findAddress(id); if (a == null) throw new RestException(SC_NOT_FOUND, "Address not found"); return a; } }

Notes
  • The @RestResource.messages() annotation identifies org/apache/juneau/samples/addressbook/nls/AddressBookResource.properties as the resource bundle for localized message for this class.
  • You are setting XML_enableNamespaces to true to enable XML namespaces. By default, XML namespace support is disabled per {@link org.apache.juneau.xml.XmlSerializer#XML_enableNamespaces}, so you have to explicitly enable it on our serializers.
  • The XML_autoDetectNamespaces setting is needed to get the XML serializer to add xmlns attributes to the root elements. This causes the XML serializer to scan the POJO objects for namespaces in order to populate the root element. There are other ways to do this, such as explicitly specifying the XML_defaultNamespaceUris setting at either the resource or method level, which might be preferred in high-performance environments. However, XML_autoDetectNamespaces produces the simplest code for our example.
  • The updatePerson() and updateAddress() methods use a guard to only allow administrators access. For the sample code, the guard does nothing. It's up to the implementer to decide how to restrict access.
  • The updatePerson() and updateAddress() methods use the {@link org.apache.juneau.utils.PojoRest} class to locate and update individual nodes in a POJO tree using the path remainder on the request.
  • The doInit() method shows an example of an overloaded method using the @RestMethod(name=INIT) annotation.
  • The getOptions() method shows the default OPTIONS page augmented with some additional information.

The OPTIONS page uses the servlet resource bundle to specify the labels so that they're globalizable.

AddressBookResource.properties

title = AddressBook sample resource description = Proof-of-concept resource that shows off the capabilities of working with POJO resources getRoot.summary = Get root page getRoot.description = Jumping off page for top-level Person and Address beans. doInit.summary = Reinitialize this resource doInit.description = Resets the address book to the original contents. doInit.res.200.description = Returns the string "OK" getAllPeople.summary = Get all people in the address book getAllPeople.res.200.description = Returns a serialized List<Person> getAllPeople.res.200.examples = {'text/json':"[\n\t{\n\t\turi:'http://hostname/addressBook/person/1',\n\t\taddressBookUri:'http://localhost/addressBook',\n\t\tid:1,\n\t\tname:'John Smith',\n\t\tbirthDate:'Jan 1, 2000',\n\t\taddresses:[\n\t\t\t{\n\t\t\t\turi:'http://localhost/addressBook/addresses/1',\n\t\t\t\tpersonUri:'http://localhost/addressBook/people/1',\n\t\t\t\tid:1,\n\t\t\t\tstreet:'101 Main St',\n\t\t\t\tcity:'Anywhere',\n\t\t\t\tstate:'NY',\n\t\t\t\tzip:12345,\n\t\t\t\tisCurrent:true\n\t\t\t}\n\t\t]\n\t}\n]"} getPerson.summary = Get a single person by ID getPerson.req.path.id.description = Person ID getPerson.req.path.id.type = integer getPerson.res.200.description = Returns a serialized Person bean getPerson.res.200.examples = {'text/json':"{\n\turi:'http://hostname/addressBook/person/1',\n\taddressBookUri:'http://localhost/addressBook',\n\tid:1,\n\tname:'John Smith',\n\tbirthDate:'Jan 1, 2000',\n\taddresses:[\n\t\t{\n\t\t\turi:'http://localhost/addressBook/addresses/1',\n\t\t\tpersonUri:'http://localhost/addressBook/people/1',\n\t\t\tid:1,\n\t\t\tstreet:'101 Main St',\n\t\t\tcity:'Anywhere',\n\t\t\tstate:'NY',\n\t\t\tzip:12345,\n\t\t\tisCurrent:true\n\t\t}\n\t]\n\}"} getPerson.res.404.description = Person ID not found getAllAddresses.summary = Get all addresses in the address book getAllAddresses.res.200.description = Returns a serialized List<Address> getAllAddresses.res.200.examples = {'text/json':"[\n\t{\n\t\turi:'http://localhost/addressBook/addresses/1',\n\t\tpersonUri:'http://localhost/addressBook/people/1',\n\t\tid:1,\n\t\tstreet:'101 Main St',\n\t\tcity:'Anywhere',\n\t\tstate:'NY',\n\t\tzip:12345,\n\t\tisCurrent:true\n\t}\n]"} getAddress.summary = Get a single address by ID getAddress.req.path.id.description = Address ID getAddress.req.path.id.type = integer getAddress.res.200.description = Returns a serialized Address bean getAddress.res.200.examples = {'text/json':"{\n\turi:'http://localhost/addressBook/addresses/1',\n\tpersonUri:'http://localhost/addressBook/people/1',\n\tid:1,\n\tstreet:'101 Main St',\n\tcity:'Anywhere',\n\tstate:'NY',\n\tzip:12345,\n\tisCurrent:true\n}"} getAddress.res.404.description = Address ID not found createPerson.summary = Create a new Person bean createPerson.req.body.description = Serialized CreatePerson bean createPerson.req.body.schema = {example:"{\n\tname:'John Smith',\n\tbirthDate:'Jan 1, 2000',\n\taddresses:[\n\t\t{\n\t\t\tstreet:'101 Main St',\n\t\t\tcity:'Anywhere',\n\t\t\tstate:'NY',\n\t\t\tzip:12345,\n\t\t\tisCurrent:true\n\t\t}\n\t]\n\}"} createPerson.res.307.header.Location.description = URL of new person createAddress.summary = Create a new Address bean createAddress.req.path.id.description = Person ID createAddress.req.path.id.type = integer createAddress.req.body.schema = {example:"{\n\tstreet:'101 Main St',\n\tcity:'Anywhere',\n\tstate:'NY',\n\tzip:12345,\n\tisCurrent:true\n}"} createAddress.res.307.header.Location.description = URL of new address deletePerson.summary = Delete a Person bean deletePerson.req.path.id.description = Person ID deletePerson.req.path.id.type = integer deletePerson.res.200.description = Returns the string "DELETE successful" deletePerson.res.404.description = Person ID not found deleteAddress.summary = Delete an Address bean deleteAddress.req.path.id.description = Address ID deleteAddress.res.200.description = Returns the string "DELETE successful" deleteAddress.res.404.description = Address ID not found updatePerson.summary = Change property on Person bean updatePerson.req.path.id.description = Person ID updatePerson.req.path.id.type = integer updatePerson.req.body.description = Any object matching the field updatePerson.res.200.description = Returns the string "PUT successful" updatePerson.res.400.description = Invalid object type used updatePerson.res.404.description = Person ID not found updateAddress.summary = Change property on Address bean updateAddress.req.path.id.description = Address ID updateAddress.req.path.id.type = integer updateAddress.req.body.description = Any object matching the field updateAddress.res.200.description = Returns the string "PUT successful" updateAddress.res.400.description = Invalid object type used updateAddress.res.404.description = Address ID not foundv getOptions.summary = View resource options getCognosData.summary = Get data in Cognos/XML format getCognosData.res.200.description = Returns a serialized DataSet otherNotes = GZip support enabled. Public methods can be invoked by using the &Method URL parameter. 'text/cognos+xml' support available under root resource only

12.6.2 - Demo

Pointing a browser to the resource shows the results of running the getRoot() method:

Clicking the people link shows you the result of running the getAllPeople() method:

Notice how the URI properties automatically became hyperlinks.

Also notice how the dates are formatted as readable strings. This was from the transform you added to the Calendar property.

Let's see what the output looks like in other formats:

JSON
Lax JSON
XML

Notice how our XML_enableNamespaces and XML_autoDetectNamespaces settings result in namespaces being used.

Also notice how the @BeanProperty(uri=true) annotations caused the uri properties to become XML attributes instead of elements.

RDF/XML

Notice how the @BeanProperty(uri=true) annotations are used to identify values for rdf:about values.

Also notice how URI properties are serialized as rdf:resource attributes.

Now lets look at the schema outputs that can be rendered that show information about the POJO classes themselves.

HTML Schema
JSON Schema
XML Schema

Now let's see what else you can do.

Clicking on the first personUri link executes the getPerson() method, which renders a serialized Person object:

Clicking on the OPTIONS link on the page shows you the Swagger doc generated from our annotations and resource bundle properties:

12.6.3 - Traversable

Because you added the Traversable converter to the getPerson method, you can also address child nodes in the POJO model through path remainders:



12.6.4 - Queryable

The Queryable converter on the getAllPeople() method allows us to perform search/view/sort functions against the data structure before serialization:

Show only the name and addresses columns
Show only names that start with 'B*'
Show only entries with age greater than 60

12.6.5 - Introspectable

The Introspectable converter on the getPerson method allows us to invoke public methods on the addressed POJO (in this case, public methods on the String class):

12.6.6 - ClientTest

The ClientTest class is provided to demonstrate how POJOs can be serialized and parsed through the REST interface using the RestClient class.

You'll notice that the class is a stand-alone executable that can be invoked as a plain Java process.

ClientTest.java

/** * Sample client code for interacting with AddressBookResource */ public class ClientTest { public static void main(String[] args) { try { System.out.println("Running client test..."); // Create a client to handle XML requests and responses. RestClient client = RestClient.create().build(); RestClient xmlClient = RestClient.create().serializer(XmlSerializer.DEFAULT) .parser(XmlParser.DEFAULT).build(); String root = "http://localhost:10000/addressBook"; // Get the current contents of the address book AddressBook ab = client.doGet(root + "/people").getResponse(AddressBook.class); System.out.println("Number of entries = " + ab.getPeople().size()); // Same, but use XML as the protocol both ways ab = xmlClient.doGet(root + "/people").getResponse(AddressBook.class); System.out.println("Number of entries = " + ab.getPeople().size()); // Delete the existing entries for (Person p : ab.getPeople()) { 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 + "/people").getResponse(AddressBook.class); System.out.println("Number of entries = " + ab.getPeople().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 + "/people?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); } catch (Exception e) { e.printStackTrace(); } } // Utility method public static Calendar toCalendar(String birthDate) throws Exception { Calendar c = new GregorianCalendar(); c.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(birthDate)); return c; } }

The output from running this code is the following:

Running client test... 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:9081/sample/addressBook/people/3 Created person George Walker Bush, uri = http://localhost:9081/sample/addressBook/people/4 Created address http://localhost:9081/sample/addressBook/addresses/7 Created address http://localhost:9081/sample/addressBook/addresses/8 Changed name, response = PUT successful New name = Barack Hussein Obama

12.6.7 - Browser Tips

The Juneau architecture is designed to make it easy to debug REST resources using nothing more than a browser. The same actions done programmatically in the last section can also be done using URLs. By default, you can override the HTTP Method and Content through GET parameters, as shown below:

// Delete the existing entries http://localhost:10000/addressBook/people/1?method=DELETE http://localhost:10000/addressBook/people/2?method=DELETE // Add 1st person again http://localhost:10000/addressBook/people?method=POST&content={name:'Barack Obama',birthDate:'Aug 4, 1961',addresses:[{street:'1600 Pennsylvania Ave',city:'Washington',state:'DC',zip:20500,isCurrent:true},{street:'5046 S Greenwood Ave',city:'Chicago',state:'IL',zip:60615,isCurrent:false}]} // Add 2nd person again http://localhost:10000/addressBook/people?method=POST&content={name:'George Walker Bush',birthDate:'Jul 6, 1946'} http://localhost:10000/addressBook/people/4/addresses?method=POST&content={street:'43 Prairie Chapel Rd',city:'Crawford',state:'TX',zip:76638,isCurrent:true} http://localhost:10000/addressBook/people/4/addresses?method=POST&content={street:'1600 Pennsylvania Ave',city:'Washington',state:'DC',zip:20500,isCurrent:false} // Change name of 1st person http://localhost:10000/addressBook/people/3/name?method=PUT&content="'Barack Hussein Obama'"

The ability to overload methods is enabled through the RestResource.allowMethodParam() setting.

12.7 - SampleRemoteableServlet

The SampleRemoteableServlet class shows examples of the following:

The RemoteableServlet class has a single abstract method, {@link org.apache.juneau.rest.remoteable.RemoteableServlet#getServiceMap()}, that defines interface keys and POJO values.

The SampleRemoteableServlet exposes the AddressBook bean from the previous example as a service.

@RestResource( path="/remoteable", messages="nls/SampleRemoteableServlet", title="Remoteable Service Proxy API", description="Sample class showing how to use remoteable proxies. The list below are exposed services that can be retrieved using RestClient.getProxyInterface(Class).", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ), // Allow us to use method=POST from a browser. allowedMethodParams="*" ) public class SampleRemoteableServlet extends RemoteableServlet { AddressBook addressBook = new AddressBook(); @Override /* RemoteableServlet */ protected Map<Class<?>,Object> getServiceMap() throws Exception { Map<Class<?>,Object> m = new LinkedHashMap<Class<?>,Object>(); // In this simplified example, you expose the same POJO service under two different interfaces. // One is IAddressBook which only exposes methods defined on that interface, and // the other is AddressBook itself which exposes all methods defined on the class itself. m.put(IAddressBook.class, addressBook); m.put(AddressBook.class, addressBook); return m; } }

Pointing a browser to the resource shows the following:

Clicking the hyperlinks on each shows you the list of methods that can be invoked on that service. Note that the IAddressBook link shows that you can only invoke methods defined on that interface, whereas the AddressBook link shows ALL public methods defined on that class. Since AddressBook extends from LinkedList, you may notice familiar collections framework methods listed.



Proxy interfaces are then retrieved using the {@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class)} method.

The client side code for invoking this method is shown below:

// Create a RestClient using JSON for serialization, and point to the server-side remoteable servlet. RestClient client = RestClient.create() .rootUrl("http://localhost:10000/remoteable") .build(); // Create a proxy interface. IAddressBook ab = client.getRemoteableProxy(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new CreatePerson("Test Person", AddressBook.toCalendar("Aug 1, 1999"), new CreateAddress("Test street", "Test city", "Test state", 12345, true)) );

12.8 - TempDirResource

The TempDirResource class shows examples of the following:

Pointing a browser to the resource shows the following:

Pointing a browser to the upload link shows a form entry page:

TempDirResource.java

/** * Sample resource that extends DirectoryResource to open up the temp directory as a REST resource. */ @RestResource( path="/tempDir", messages="nls/TempDirResource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS", "upload: servlet:/upload" } ), properties={ @Property(name="DirectoryResource.rootDir", value="$S{java.io.tmpdir}"), @Property(name="DirectoryResource.allowViews", value="true"), @Property(name="DirectoryResource.allowDeletes", value="true"), @Property(name="DirectoryResource.allowPuts", value="false") }, stylesheet="styles/devops.css" ) public class TempDirResource extends DirectoryResource { private static final long serialVersionUID = 1L; /** * [GET /upload] - Display the form entry page for uploading a file to the temp directory. */ @RestMethod(name=GET, path="/upload") public ReaderResource getUploadPage(RestRequest req) throws IOException { return req.getClasspathReaderResource("TempDirUploadPage.html", true); } /** * [POST /upload] - Upload a file as a multipart form post. * Shows how to use the Apache Commons ServletFileUpload class for handling multi-part form posts. */ @RestMethod(name=POST, path="/upload", matchers=TempDirResource.MultipartFormDataMatcher.class) public Redirect uploadFile(RestRequest req) throws Exception { ServletFileUpload upload = new ServletFileUpload(); FileItemIterator iter = upload.getItemIterator(req); while (iter.hasNext()) { FileItemStream item = iter.next(); if (item.getFieldName().equals("contents")) { File f = new File(getRootDir(), item.getName()); IOPipe.create(item.openStream(), new FileOutputStream(f)).closeOut().run(); } } return new Redirect(); // Redirect to the servlet root. } /** Causes a 404 if POST isn't multipart/form-data */ public static class MultipartFormDataMatcher extends RestMatcher { @Override /* RestMatcher */ public boolean matches(RestRequest req) { String contentType = req.getContentType(); return contentType != null && contentType.startsWith("multipart/form-data"); } } }

TempDirResource.properties

#-------------------------------------------------------------------------------- # TempDirResource labels #-------------------------------------------------------------------------------- title = Temp Directory View Service description = View and download files in the '$S{java.io.tmpdir}' directory.

Note how a system property variable can be defined in the properties file.

TempDirUploadPage.html
TODO - Needs update

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style type='text/css'> @import '$R{servletURI}/style.css'; </style> </head> <body> <h1>$R{resourceTitle}</h1> <h2>$R{resourceDescription}</h2> <div class='data'> <form id='form' action='$R{servletURI}/upload' method='POST' target='buff' enctype="multipart/form-data"> <input name="contents" type="file"><button type="submit">Submit</button> </form> </div> </body> </html>

Note how the HTML file contains localized variables for the servlet label and description.

12.9 - AtomFeedResource

The AtomFeedResource class shows examples of the following:

Pointing a browser to the resource shows the following:

True ATOM feeds require using an Accept:text/xml header:

Other languages, such as JSON are also supported:

AtomFeedResource.java

/** * Sample resource that shows how to generate ATOM feeds. */ @RestResource( path="/atom", messages="nls/AtomFeedResource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ), properties={ @Property(name=SERIALIZER_quoteChar, value="'"), @Property(name=RDF_rdfxml_tab, value="5"), @Property(name=RDF_addRootProperty, value="true") }, encoders=GzipEncoder.class ) public class AtomFeedResource extends BasicRestServletJena { private static final long serialVersionUID = 1L; private Feed feed; // The root resource object @Override /* Servlet */ public void init() { try { feed = new Feed() .setTitle(new Text("text", "Juneau ATOM specification")) .setSubTitle(new Text("html", "Decribes <em>stuff</em> about Juneau")) .setUpdated(parseDateTime("2016-01-02T03:04:05Z")) .setId(new Id("tag:juneau.apache.org")) .addLinks( new Link("alternate", "text/html", "http://juneau.apache.org/").setHreflang("en"), new Link("self", "application/atom+xml", "http://juneau.apache.org/feed.atom") ) .setRights(new Text("Copyright (c) ...")) .setGenerator(new Generator("Juneau").setUri(new URI("http://juneau.apache.org/")).setVersion("1.0")) .addEntries( new Entry() .setTitle(new Text("Juneau ATOM specification snapshot")) .addLinks( new Link("alternate", "text/html", "http://juneau.apache.org/juneau.atom"), new Link("enclosure", "audio/mpeg", "http://juneau.apache.org/audio/juneau_podcast.mp3").setLength(12345) ) .setId(new Id("tag:juneau.apache.org")) .setUpdated(parseDateTime("2016-01-02T03:04:05Z")) .setPublished(parseDateTime("2016-01-02T03:04:05Z")) .addAuthors(new Person("James Bognar").setUri(new URI("http://juneau.apache.org/")).setEmail("james.bognar@apache.org")) .addContributors( new Person("Barry M. Caceres") ) .setContent( new Content() .setLang("en") .setBase(new URI("http://www.apache.org/")) .setType("xhtml") .setText("<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><i>[Update: Juneau supports ATOM.]</i></p></div>") ) ); } catch (Exception e) { throw new RuntimeException(e); } } /** * GET request handler */ @RestMethod(name=GET, path="/") public Feed getFeed() throws Exception { return feed; } /** * PUT request handler. * Replaces the feed with the specified content, and then mirrors it as the response. */ @RestMethod(name=PUT, path="/") public Feed setFeed(@org.apache.juneau.rest.annotation.Content Feed feed) throws Exception { this.feed = feed; return feed; } }

12.10 - DockerRegistryResource

The DockerRegistryResource class shows examples of the following:

Pointing a browser to the resource shows the following:

Clicking the search link provides you with the search results against the Docker registry:

DockerRegistryResource.java

/** * Sample resource that shows how to mirror query results from a Docker registry. */ @RestResource( path="/docker", title="Sample Docker resource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class DockerRegistryResource extends BasicRestServlet { private static final long serialVersionUID = 1L; // Get registry URL from examples.cfg file. private String registryUrl = getConfig().getString("DockerRegistry/url"); RestClient rc = RestClient.create().build(); /** [GET /] - Show child resources. */ @SuppressWarnings("nls") @RestMethod(name=GET, path="/") public ResourceDescription[] getChildren(RestRequest req) { return new ResourceDescription[] { new ResourceDescription(req, "search", "Search Registry") }; } /** * PUT request handler. * Replaces the feed with the specified content, and then mirrors it as the response. */ @RestMethod(name=GET, path="/search") public QueryResults query(@Query("q") String q) throws Exception { String url = registryUrl + "/search" + (q == null ? "" : "?q=" + q); return rc.doGet(url).getResponse(QueryResults.class); } public static class QueryResults { public int num_results; public String query; public List<DockerImage> results; } public static class DockerImage { public String name, description; } }

The Docker registry URL is specified in the examples.cfg file:

examples.cfg

#================================================================================ # DockerRegistryResource properties #================================================================================ [DockerRegistry] url = http://clmdocker02.ratl.swg.usma.apache.org:5000/v1

12.11 - TumblrParserResource

The TumblrParserResource class shows examples of the following:

Pointing a browser at a Tumblr blog name, such as ibmblr causes a REST call to be make to the Tumblr blog and the results to be parsed:

TumblrParserResource.java

@RestResource( path="/tumblrParser", messages="nls/TumblrParserResource", title="Tumblr parser service", description="Specify a URL to a Tumblr blog and parse the results.", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class TumblrParserResource extends BasicRestServlet { private static final long serialVersionUID = 1L; @RestMethod(name=GET, path="/") public String getInstructions() throws Exception { return "Append the Tumblr blog name to the URL above (e.g. /tumblrParser/mytumblrblog)"; } @RestMethod(name=GET, path="/{blogName}") public ObjectList parseBlog(@Path String blogName) throws Exception { ObjectList l = new ObjectList(); RestClient rc = RestClient.create().build(); String site = "http://" + blogName + ".tumblr.com/api/read/json"; ObjectMap m = rc.doGet(site).getResponse(ObjectMap.class); int postsTotal = m.getInt("posts-total"); for (int i = 0; i < postsTotal; i += 20) { m = rc.doGet(site + "?start=" + i + "&num=20&transform=text").getResponse(ObjectMap.class); ObjectList ol = m.getObjectList("posts"); for (int j = 0; j < ol.size(); j++) { ObjectMap om = ol.getObjectMap(j); String type = om.getString("type"); Entry e = new Entry(); e.date = om.getString("date"); if (type.equals("link")) e.entry = new Link(om.getString("link-text"), om.getString("link-url")); else if (type.equals("audio")) e.entry = new ObjectMap().append("type","audio").append("audio-caption", om.getString("audio-caption")); else if (type.equals("video")) e.entry = new ObjectMap().append("type","video").append("video-caption", om.getString("video-caption")); else if (type.equals("quote")) e.entry = new ObjectMap().append("type","quote").append("quote-source", om.getString("quote-source")).append("quote-text", om.getString("quote-text")); else if (type.equals("regular")) e.entry = om.getString("regular-body"); else if (type.equals("photo")) e.entry = new Img(om.getString("photo-url-250")); else e.entry = new ObjectMap().append("type", type); l.add(e); } } return l; } public static class Entry { public String date; public Object entry; } }

12.12 - PhotosResource

The PhotosResource class shows examples of the following:

The resource consists of a simple registry of images with integer IDs.

It is initialized with a single entry, which can be accessed through a GET request.

PhotosResource.java

/** * Sample resource that allows images to be uploaded and retrieved. */ @RestResource( path="/photos", messages="nls/PhotosResource", title="Photo REST service", description="Use a tool like Poster to upload and retrieve jpeg and png images.", htmldoc=@HtmlDoc( navlinks={ "options: servlet:/?method=OPTIONS" } ) ) public class PhotosResource extends BasicRestServlet { // Our cache of photos private Map<Integer,Photo> photos = new TreeMap<Integer,Photo>(); @Override /* Servlet */ public void init() { try { // Preload an image. InputStream is = getClass().getResourceAsStream("averycutedog.jpg"); BufferedImage image = ImageIO.read(is); Photo photo = new Photo(0, image); photos.put(photo.id, photo); } catch (IOException e) { throw new RuntimeException(e); } } /** Bean class for storing photos */ public static class Photo { private int id; BufferedImage image; Photo(int id, BufferedImage image) { this.id = id; this.image = image; } public URI getURI() throws URISyntaxException { return new URI("photos/"+id); } public int getID() { return id; } } /** GET request handler for list of all photos */ @RestMethod(name=GET, path="/") public Collection<Photo> getAllPhotos(RestRequest req, RestResponse res) throws Exception { res.setPageTitle("Photo REST service"); res.setPageText("Use a tool like Poster to upload and retrieve jpeg and png images."); return photos.values(); } /** GET request handler for single photo */ @RestMethod(name=GET, path="/{id}", serializers=ImageSerializer.class) public BufferedImage getPhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.get(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return p.image; } /** PUT request handler */ @RestMethod(name=PUT, path="/{id}", parsers=ImageParser.class) public String addPhoto(RestRequest req, @Path int id, @Body BufferedImage image) throws Exception { photos.put(id, new Photo(id, image)); return "OK"; } /** POST request handler */ @RestMethod(name=POST, path="/", parsers=ImageParser.class) public Photo setPhoto(RestRequest req, @Body BufferedImage image) throws Exception { int id = photos.size(); Photo p = new Photo(id, image); photos.put(id, p); return p; } /** DELETE request handler */ @RestMethod(name=DELETE, path="/{id}") public String deletePhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.remove(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return "OK"; } /** OPTIONS request handler */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); } /** Serializer for converting images to byte streams */ @Produces("image/png,image/jpeg") public static class ImageSerializer extends OutputStreamSerializer { @Override /* Serializer */ public void serialize(Object o, OutputStream out, SerializerSession session) throws IOException, SerializeException { RenderedImage image = (RenderedImage)o; String mediaType = ctx.getMediaType(); ImageIO.write(image, mediaType.substring(mediaType.indexOf('/')+1), out); } } /** Parser for converting byte streams to images */ @Consumes("image/png,image/jpeg") public static class ImageParser extends InputStreamParser { @Override /* Parser */ public <T> T parse(InputStream in, ClassMeta<T> type, ParserSession session) throws ParseException, IOException { BufferedImage image = ImageIO.read(in); return (T)image; } } }

12.13 - JsonSchemaResource

The JsonSchemaResource class shows examples of the following:

The resource consists of a pre-initialized {@link org.apache.juneau.dto.jsonschema.Schema} object. Pointing a browser to the resource shows the following:

For true JSON-Schema, you need to specify the header Accept: text/json:

JsonSchemaResource.java

/** * Sample resource that shows how to serialize JSON-Schema documents. */ @RestResource( path="/jsonSchema", messages="nls/JsonSchemaResource", title="Sample JSON-Schema document", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class JsonSchemaResource extends BasicRestServletJena { private static final long serialVersionUID = 1L; private Schema schema; // The schema document @Override /* Servlet */ public void init() { try { schema = new Schema() .setId("http://example.com/sample-schema#") .setSchemaVersionUri("http://json-schema.org/draft-04/schema#") .setTitle("Example Schema") .setType(JsonType.OBJECT) .addProperties( new SchemaProperty("firstName", JsonType.STRING), new SchemaProperty("lastName", JsonType.STRING), new SchemaProperty("age", JsonType.INTEGER) .setDescription("Age in years") .setMinimum(0) ) .addRequired("firstName", "lastName"); } catch (Exception e) { throw new RuntimeException(e); } } /** GET request handler */ @RestMethod(name=GET, path="/") public Schema getSchema() throws Exception { return schema; } /** * PUT request handler. * Replaces the schema document with the specified content, and then mirrors it as the response. */ @RestMethod(name=PUT, path="/") public Schema setSchema(@Body Schema schema) throws Exception { this.schema = schema; return schema; } }

12.14 - SqlQueryResource

The SqlQueryResource class shows examples of the following:

The example uses embedded Derby to create a database whose name is defined in the external configuration files.

Pointing a browser to the resource shows the following:

Running a query results in the following output:

SqlQueryResource.java

/** * Sample resource that shows how Juneau can serialize ResultSets. */ @RestResource( path="/sqlQuery", messages="nls/SqlQueryResource", title="SQL query service", description="Executes queries against the local derby '$C{SqlQueryResource/connectionUrl}' database", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class SqlQueryResource extends BasicRestServlet { private static final long serialVersionUID = 1L; private Config cf = getConfig(); private String driver = cf.getString("SqlQueryResource/driver"); private String connectionUrl = cf.getString("SqlQueryResource/connectionUrl"); private boolean allowUpdates = cf.getBoolean("SqlQueryResource/allowUpdates", false), allowTempUpdates = cf.getBoolean("SqlQueryResource/allowTempUpdates", false), includeRowNums = cf.getBoolean("SqlQueryResource/includeRowNums", false); @Override /* Servlet */ public void init() { try { Class.forName(driver).newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } /** GET request handler - Display the query entry page. */ @RestMethod(name=GET, path="/") public ReaderResource doGet(RestRequest req) throws IOException { return req.getClasspathReaderResource("SqlQueryResource.html", true); } /** POST request handler - Execute the query. */ @RestMethod(name=POST, path="/") public List<Object> doPost(@Body PostInput in) throws Exception { List<Object> results = new LinkedList<Object>(); // Don't try to submit empty input. if (StringUtils.isEmpty(in.sql)) return results; if (in.pos < 1 || in.pos > 10000) throw new RestException(SC_BAD_REQUEST, "Invalid value for position. Must be between 1-10000"); if (in.limit < 1 || in.limit > 10000) throw new RestException(SC_BAD_REQUEST, "Invalid value for limit. Must be between 1-10000"); // Create a connection and statement. // If these fails, let the exception transform up as a 500 error. Connection c = DriverManager.getConnection(connectionUrl); c.setAutoCommit(false); Statement st = c.createStatement(); String sql = null; try { for (String s : in.sql.split(";")) { sql = s.trim(); if (! sql.isEmpty()) { Object o = null; if (allowUpdates || (allowTempUpdates && ! sql.matches("(?:i)commit.*"))) { if (st.execute(sql)) { ResultSet rs = st.getResultSet(); o = new ResultSetList(rs, in.pos, in.limit, includeRowNums); } else { o = st.getUpdateCount(); } } else { ResultSet rs = st.executeQuery(sql); o = new ResultSetList(rs, in.pos, in.limit, includeRowNums); } results.add(o); } } if (allowUpdates) c.commit(); else if (allowTempUpdates) c.rollback(); } catch (SQLException e) { c.rollback(); throw new RestException(SC_BAD_REQUEST, "Invalid query: {0}", sql).initCause(e); } finally { c.close(); } return results; } /** The parsed form post */ public static class PostInput { public String sql; public int pos = 1, limit = 100; } }

SqlQueryResource.html
TODO - Needs update

<html> <head> <style type='text/css'> @import '$R{servletURI}/style.css'; </style> <script> // Quick and dirty function to allow tabs in textarea. function checkTab(e) { if (e.keyCode == 9) { var t = e.target; var ss = t.selectionStart, se = t.selectionEnd; t.value = t.value.slice(0,ss).concat('\t').concat(t.value.slice(ss,t.value.length)); e.preventDefault(); } } // Load results from IFrame into this document. function loadResults(b) { var doc = b.contentDocument || b.contentWindow.document; var data = doc.getElementById('data') || doc.getElementsByTagName('body')[0]; document.getElementById('results').innerHTML = data.innerHTML; } </script> </head> <body> <h1>SQL Query API</h1> <div class='data'> <form action='sqlQuery' method='POST' target='buf'> <table> <tr> <th>Position (1-10000):</th> <td><input name='pos' type='number' value='1'></td> <th>Limit (1-10000):</th> <td><input name='limit' type='number' value='100'></td> <td><button type='submit'>Submit</button><button type='reset'>Reset</button></td> </tr> <tr> <td colspan="5"> <textarea name='sql' style='width:100%;height:200px;font-family:Courier;font-size:9pt;' onkeydown='checkTab(event)'></textarea> </td> </tr> </table> </form> <br> <div id='results'> </div> </div> <iframe name='buf' style='display:none' onload="parent.loadResults(this)"></iframe> </body> </html>

samples.cfg

#================================================================================ # SqlQueryResource properties #================================================================================ [SqlQueryResource] driver = org.apache.derby.jdbc.EmbeddedDriver connectionUrl = jdbc:derby:C:/testDB;create=true allowTempUpdates = true includeRowNums = true

12.15 - ConfigResource

12.16 - LogsResource

The {@link org.apache.juneau.microservice.resources.LogsResource} class is a reusable predefined resource.
It provides a REST interface for the log files generated by the microservice.

Pointing a browser to the resource shows the following:

The highlighted links show the contents of the log file with color highlighting:

The parsed links parse the log file and return the entries as serialized POJOs:

13 - Security Best-Practices

Security is always an ongoing concern in any library. If you discover any security vulnerabilities in this code, please refer to the instructions found here:

13.1 - juneau-marshall

Demarshalling vulnerabilities

One common security vulnerability is the ability to create arbitrary Java object instances through crafted user input. For example, support for constructing POJOs based on an input attribute defining a fully-qualified class name like "{class:'com.foo.MyBean',...}"

Fortunately, Juneau does not support an open-ended "class attribute. As a rule, it should not be possible to create arbitrary POJOs by any of the parsers. The demarshalled object types are inferred via reflection of the class objects passed in through the parser method (e.g. JsonParser.DEFAULT.parse(input, MyBean.class)). As long as the Class object passed into this method is not constructed from user-generated input, it should be free from demarshalling vulnerabilities.

The following example shows a potential vector that circumvents the restriction above:

// Don't do this! Class c = Class.forName(someUserInputString); JsonParser.DEFAULT.parse(input, c); // Oops! Security hole!

Juneau does support something similar to a "class" attribute that allows you to define the POJO type at runtime. This is the "type" attribute. The difference is that it's not possible to specify fully-qualified class names in "type" attributes, and instead can only specify type keys defined through bean dictionaries. Instead of serializing the fully-qualified class names in the output, we instead serialize type names that represent those POJO types.
i.e. instead of "class='com.foo.MyBean'", we instead serialize "type='MyBeanIdentifier'".
Since bean types are defined at compile time, it's impossible to instantiate arbitrary POJOs.

POJO types of generalized input are also inferred through swaps. Again, since the POJO types are hardcoded at compile time, these should not be subject to demarshalling vulnerabilities. However, it is possible to circumvent this through your swap implementation as shown below:

// Don't do this! public class MyInsecureSwap extends PojoSwap<ObjectMap,Object> { public Object swap(BeanSession session, ObjectMap input) throws Exception { // Security hole! return Class.forName(input.getString("class")).newInstance(); } }

Note that the {@link org.apache.juneau.jso.JsoParser}, a thin layer of the Juneau Parser API written on top of plain-old Java Object Serialization which itself is vulnerable to demarshalling issues. Due to this, the JSO parser is not included in any of the default REST servlet implementations. Be especially careful when using this parser, particularly if you want to use it for handing application/x-java-serialized-object input through REST servlets.

All other parsers (JSON, URL-Encoding, MessagePack, etc...) work the same way in determining POJO types, so should be safe from demarshalling vulnerabilities.

Dependent libraries

When accessing security vulnerabilities of any library, dependent libraries must also be taken into account:

13.2 - juneau-svl

Care must be used when defining new {@link org.apache.juneau.svl.Var Vars} using the SVL API since mistakes could potentially expose system properties, environment variables, or even file system files.

For recap, the SVL support allows you to embed variables of the form "$X{key}" inside strings that get resolved to other strings. The resolved strings themselves can also contain variables that also get recursively resolved.

An example of a potential security hole is shown below that could potentially expose any file on a file system through a REST request:

public String doUnsafeGet(RestRequest req) { // Security hole! return req.getVarResolver().resolve("$RQ{foo}"); }

This code is simply echoing the value of the foo query parameter. Now say for example that a bad actor passes in the query string "foo=$F{/some/file/on/file/system}". The $F variable allows you to resolve the contents of files using SVL, and is provided by default using the built-in variable resolver returned by the RestRequest object. You've potentially just exposed the contents of that file through your REST interface.

In reality, the above security hole does not exist because of the following restrictions:

Even though the built-in Juneau variables are safe, special care is needed when defining your own custom variables. If your variable resolves user input in any way, it's HIGHLY recommended that you override the {@link org.apache.juneau.svl.Var#allowNested()} and {@link org.apache.juneau.svl.Var#allowRecurse()} methods to prevent recursive handling of variables.

13.3 - juneau-rest-server

Denial of service attacks can be alleviated through the {@link org.apache.juneau.rest.annotation.RestResource#maxInput() maxInput()} setting. Arbitrarily-large input will trigger an exception before causing out-of-memory errors. The default value for this setting is 100MB.

Since the parsers do not use intermediate DOMs and instead parse directly into Java objects, deeply nested data structures will almost always trigger stack overflow errors long before memory consumption becomes an issue. However, this is NOT true of the RDF parsers that use an intermediate DOM. If parsing RDF, you may want to consider lowering the max-input value above.

14 - Release Notes

What's new in each release

7.1.1 (TBD)

TBD

juneau-microservice

7.1.0 (TBD)

Version 7.1.0 is a major update with major implementation refactoring across all aspects of the product.

juneau-marshall
juneau-svl
juneau-config
juneau-dto
juneau-rest-server
juneau-rest-client
Documentation

7.0.1 (Dec 24, 2017)

This release is a minor update. It includes the following prereq updates:

juneau-marshall
juneau-svl
juneau-rest-server
juneau-microservice-server

7.0.0 (Oct 25, 2017)

This release ups the Java prerequisite to Java 7.

juneau-marshall
juneau-dto
juneau-rest-server

6.4.0 (Oct 5, 2017)

The major change in this release is the project structure.

The library now consists of the following artifacts found in the Maven group "org.apache.juneau":

CategoryMaven ArtifactsDescriptionPrereqs
Juneau Core juneau-marshall Serializers and parsers for:
  • JSON
  • XML
  • HTML
  • UON
  • URL-Encoding
  • MessagePack
  • SOAP/XML
  • CSV
  • BSON (coming soon)
  • YAML (coming soon)
  • Protobuf (coming soon)
  • Java 6
juneau-marshall-rdf Serializers and parsers for:
  • RDF/XML
  • RDF/XML-Abbrev
  • N-Triple
  • Turtle
  • N3
  • Java 6
  • Apache Jena 2.7.1
juneau-dto Data Transfer Objects for:
  • HTML5
  • Atom
  • Cognos
  • JSON-Schema
  • Swagger 2.0
  • Java 6
juneau-svl Simple Variable Language API
  • Java 6
juneau-config Configuration file API
  • Java 6
Juneau REST juneau-rest-server REST Servlet API
  • Java 6
  • Servlet 3.1
juneau-rest-server-jaxrs Optional JAX-RS support
  • Java 6
  • JAX-RS 2.0
juneau-rest-client REST Client API
  • Java 6
  • Apache HttpClient 4.5
Juneau Microservice juneau-microservice-server REST Microservice Server API
  • Java 8
  • Eclipse Jetty 9.4.3
juneau-microservice-template Developer template project
  • Java 8
  • Eclipse Jetty 9.4.3
Examples juneau-examples-core Core code examples
juneau-examples-rest REST code examples
Juneau All juneau-all Combination of the following:
  • juneau-marshall
  • juneau-dto
  • juneau-svl
  • juneau-config
  • juneau-rest-server
  • juneau-rest-client
  • Java 6
  • Servlet 3.1
  • Apache HttpClient 4.5
juneau-marshall
juneau-dto
juneau-rest-server
juneau-microservice
org.apache.juneau.rest.examples

6.3.1 (Aug 1, 2017)

Juneau 6.3.1 is a minor release.

org.apache.juneau
org.apache.juneau.rest
org.apache.juneau.rest.examples

6.3.0 (Jun 30, 2017)

Juneau 6.3.0 is a major update with significant new functionality for defining proxy interfaces against arbitrary 3rd-party REST interfaces.

org.apache.juneau
org.apache.juneau.rest
org.apache.juneau.rest.client
org.apache.juneau.microservice
org.apache.juneau.examples.rest

6.2.0 (Apr 28, 2017)

Juneau 6.2.0 is a major update.

org.apache.juneau
org.apache.juneau.rest
org.apache.juneau.rest.client
org.apache.juneau.microservice

6.1.0 (Feb 25, 2017)

Juneau 6.1.0 is a major update.

In particular, this release cleans up the {@link org.apache.juneau.BeanContext} API to match the {@link org.apache.juneau.PropertyStore}/{@link org.apache.juneau.Context}/{@link org.apache.juneau.Session} paradigm previously used in the serializer and parser APIs. It also makes several improvements to the HTML and XML serialization support and introduces HTML5 DTO beans.

org.apache.juneau
org.apache.juneau.rest

6.0.1 (Jan 3, 2017)

Juneau 6.0.1 is a minor update.

org.apache.juneau

6.0.0 (Oct 3, 2016)

Juneau 6.0.0 is a major update.

The major change is rebranding from "Juno" to "Juneau" in preparation for donation to the Apache Foundation.

org.apache.juneau
org.apache.juneau.rest
org.apache.juneau.rest.client

5.2.0.1 (Mar 23, 2016)

Juno 5.2.0.1 is a moderate update.

com.ibm.team.juno
Server
Client

5.2.0.0 (Dec 30, 2015)

Juno 5.2.0.0 is a major update. Major changes have been made to the microservice architecture and config INI file APIs.

Core
Client
Server
Microservice
Samples
Documentation Updates

5.1.0.20 (Sept 5, 2015)

Juno 5.1.0.20 is a moderate update. The biggest improvement is the ability to associate external INI config files with REST servlets using the ConfigFile functionality.

Core
Server
Microservice

5.1.0.19 (Aug 15, 2015)

Juno 5.1.0.19 is a minor update in terms of core functionality. But it introduces a Microservices project for building REST microservices and docker containers.

Core
Client
Server

5.1.0.18 (Aug 5, 2015)

Juno 5.1.0.18 is a minor update affecting the server component only.

Server

5.1.0.17 (Aug 3, 2015)

Juno 5.1.0.17 is a major update.

Core
Server
Samples

5.1.0.16 (June 28, 2015)

Juno 5.1.0.16 is a moderate update.

Core
Server
Samples

5.1.0.15 (May 24, 2015)

Juno 5.1.0.15 is a minor update.

Core
Server
Client

5.1.0.14 (May 10, 2015)

Juno 5.1.0.14 is a moderate update.

The major addition is support for {@link org.apache.juneau.rest.remoteable Remoteable Services}, the ability to invoke server-side POJO methods through client-side proxy interfaces.

Core
Client
Server

5.1.0.13 (Apr 24, 2015)

Juno 5.1.0.13 is a minor update.

Core
Server
Client

5.1.0.12 (Mar 28, 2015)

Juno 5.1.0.12 is a minor update.

Core
Client

5.1.0.11 (Feb 14, 2015)

Juno 5.1.0.11 is a moderate update.

Core
Server
Client
Other changes

5.1.0.10 (Dec 23, 2014)

Juno 5.1.0.10 is a moderate update.

Core
Server
Client
Samples

5.1.0.9 (Dec 1, 2014)

Juno 5.1.0.9 is a major update. There weren't very many code changes, but the source has been made a part of Jazz Foundation. This required some restructuring of the project. The project on Jazz Hub will eventually be discontinued. However, the libraries on IBM Community Source will continue to be updated regularly.

5.1.0.8 (Oct 25, 2014)

Juno 5.1.0.8 is a moderate update, focused primarily on performance improvements.

5.1.0.7 (Oct 5, 2014)

Juno 5.1.0.7 is a moderate update.

5.1.0.6 (Sept 21, 2014)

Juno 5.1.0.6 is a moderate update.

5.1.0.5 (Sept 1, 2014)

Juno 5.1.0.5 is a moderate update.

5.1.0.4 (Aug 25, 2014)

Juno 5.1.0.4 is a minor update.

5.1.0.3 (Jun 28, 2014)

Juno 5.1.0.3 is a moderate update.

Core API updates
REST Server API updates

5.1.0.2 (Apr 27, 2014)

Juno 5.1.0.2 is a minor update.

5.1.0.1 (Jan 25, 2014)

Juno 5.1.0.1 is a minor update.

5.1.0.0 (Jan 18, 2014)

Juno 5.1.0.0 is a major update.

Major changes
Other changes

5.0.0.36 (Dec 18, 2013)

Juno 5.0.0.36 is a minor update.

5.0.0.35 (Nov 26, 2013)

Juno 5.0.0.35 is a minor update.

5.0.0.34 (Nov 10, 2013)

Juno 5.0.0.34 is a moderate update.

5.0.0.33 (Oct 20, 2013)

Juno 5.0.0.33 is a moderate update.

5.0.0.32 (Oct 5, 2013)

Juno 5.0.0.32 is a moderate update.

5.0.0.31 (Aug 9, 2013)

Juno 5.0.0.31 is a moderate update.

5.0.0.30 (Aug 8, 2013)

Juno 5.0.0.30 is a minor update.

5.0.0.29 (Aug 2, 2013)

Juno 5.0.0.29 is a moderate update.

5.0.0.28 (July 9, 2013)

Juno 5.0.0.28 is a moderate update.

5.0.0.27 (July 7, 2013)

Juno 5.0.0.27 is a moderate update.

5.0.0.26 (Jun 5, 2013)

Juno 5.0.0.26 is a minor update.

5.0.0.25 (May 11, 2013)

Juno 5.0.0.25 is a minor update.

Core API updates
Server API updates

5.0.0.24 (May 9, 2013)

Juno 5.0.0.24 is a major update.

Core API updates
Documentation updates

5.0.0.23 (Apr 14, 2013)

Juno 5.0.0.23 is a minor update.

5.0.0.22 (Apr 12, 2013)

Juno 5.0.0.22 is a minor update.

Core API changes
REST Servlet API changes

5.0.0.21 (Apr 9, 2013)

Juno 5.0.0.21 is a minor update.

Core API changes
Servlet API changes

5.0.0.20 (Apr 7, 2013)

Juno 5.0.0.20 is a major update.

Core API changes
REST server API changes
REST client API changes

5.0.0.19 (Apr 1, 2013)

Juno 5.0.0.19 is a minor update.

5.0.0.18 (Mar 27, 2013)

Juno 5.0.0.18 is a moderate update.

The biggest change is the introduction of the {@link org.apache.juneau.jena.RdfSerializer} class that uses Jena to generate RDF/XML, RDF/XML-ABBREV, N-Tuple, N3, and Turtle output.

This code should be considered prototype-quality, and subject to change in the future.
There are plans of adding an equivalent RdfParser class in the future, so the serializer logic may need to be tweaked to allow POJOs to be reconstituted correctly in the parser.

The RdfXmlSerializer class should be considered deprecated for now.
However, I'm keeping it around, since it's considerably faster and uses far less memory than the Jena-based serializer since it serializes directly from POJOs to RDF/XML.
It may or may not be removed in the future depending on demand.

Other changes

5.0.0.17 (Mar 25, 2013)

Juno 5.0.0.17 is a minor update.

5.0.0.16 (Mar 25, 2013)

Juno 5.0.0.16 is a minor update.

5.0.0.15 (Mar 24, 2013)

Juno 5.0.0.15 is a moderate update.

5.0.0.14 (Mar 23, 2013)

Juno 5.0.0.14 is a major update.

The biggest change is that the RestSerializer, RestParser, RestSerializerGroup, and RestParserGroup classes have been eliminated entirely.
Instead, the existing {@link org.apache.juneau.serializer.Serializer}, {@link org.apache.juneau.parser.Parser}, {@link org.apache.juneau.serializer.SerializerGroup}, and {@link org.apache.juneau.parser.ParserGroup} classes of the core API have been augmented to replace them.

Adoptions will be required if you have previously used these classes.

Core API changes
REST server API changes
REST client API changes

5.0.0.13 (Mar 14, 2013)

Juno 5.0.0.13 is a minor update.

Core API changes
REST server API changes

5.0.0.12 (Mar 10, 2013)

Juno 5.0.0.12 is a minor update.

Core API changes
REST server API changes

5.0.0.11 (Mar 8, 2013)

Juno 5.0.0.11 is a moderate update.

REST server API changes

5.0.0.10 (Mar 7, 2013)

Juno 5.0.0.10 is a minor update.

Core API changes
REST server API changes

5.0.0.9 (Feb 26, 2013)

Juno 5.0.0.9 is a moderate update.

Core API changes
REST server API changes
Other notes

5.0.0.8 (Jan 30, 2013)

Juno 5.0.0.8 is a minor update.

5.0.0.7 (Jan 20, 2013)

Juno 5.0.0.7 is a major update.

Core API updates
REST client updates
REST server updates

5.0.0.6 (Oct 30, 2012)

Juno 5.0.0.6 is a minor update that fixes a small bug in 5.0.0.5.

5.0.0.5 (Oct 29, 2012)

Juno 5.0.0.5 is a major update.

5.0.0.4 (Oct 7, 2012)

Juno 5.0.0.4 is a minor update.

5.0.0.3 (Oct 3, 2012)

Juno 5.0.0.3 is a minor update.

5.0.0.2 (Sept 28, 2012)

Juno 5.0.0.2 is a minor update.

5.0.0.1 (Jun 14, 2012)

Juno 5.0.0.1 is a moderate update.

5.0.0.0 (Jun 11, 2012)

Version 5.0 marks a major release milestone for the Juno/JJSON library. It is now available for download from iRAM under the name "Juno (previously JJSON)". The Juno Starters Guide has been updated to reflect new functionality in this release.