Apache Juneau Overview

Table of Contents
  1. Juneau - What is it?

  2. Juneau Core (org.apache.juneau)

    1. Serializers

    2. Parsers

    3. SerializerGroups and ParserGroups

    4. ObjectMap and ObjectList

    5. Configurable Properties

    6. Transforms

      1. PojoSwaps

      2. Swap methods

      3. BeanFilters and @Bean annotations

    7. Bean Name and Dictionaries

      1. Bean Subtypes

    8. POJO Categories

    9. Simple Variable Language

    10. Configuration Files

    11. Supported Languages

  3. Juneau Data Transfer Objects (org.apache.juneau.dto)

    1. HTML5

    2. Atom

    3. Swagger

    4. JSON-Schema

  4. Juneau Server (org.apache.juneau.rest)

  5. Juneau Client (org.apache.juneau.rest.client)

  6. Remoteable services (org.apache.juneau.rest.remoteable)

  7. Juneau Microservices (org.apache.juneau.microservice)

  8. Samples

    1. Installing in Eclipse

    2. Running in Eclipse

    3. Building and Running from Command-Line

    4. MANIFEST.MF

    5. RootResources

    6. HelloWorldResource

    7. MethodExampleResource

    8. UrlEncodedFormResource

    9. RequestEchoResource

    10. AddressBookResource

      1. Classes

      2. Demo

      3. Traversable

      4. Queryable

      5. Introspectable

      6. ClientTest

      7. Browser Tips

    11. SampleRemoteableServlet

    12. TempDirResource

    13. AtomFeedResource

    14. DockerRegistryResource

    15. TumblrParserResource

    16. PhotosResource

    17. JsonSchemaResource

    18. SqlQueryResource

    19. ConfigResource

    20. LogsResource

  9. Cookbook Examples

    1. Core API

    2. Server API

      1. Apply a transform that changes the format of doubles

      2. Apply transforms to a subset of serializers or parsers

    3. Client API

    4. Microservice API

  10. Best Practices

  11. Important Documentation Links

  12. Release Notes

1 - Juneau - What is it?

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 2016, the code was donated to the Apache Foundation under the project Apache Juneau.

Features
  1. Extensive and extensible support for a large variety of POJOs, including structured data (beans) and unstructured data (Maps and Collections).

  2. Serialization support:

    • JSON (including variants)
    • XML
    • HTML
    • URL-Encoding
    • UON (URL-Encoded Object Notation)
    • MessagePack
    • RDF/XML
    • RDF/XML-Abbrev
    • N-Triple
    • Turtle
    • N3
    • SOAP/XML
  3. Parsing support:

    • JSON (including lax syntax, comments, concatenated strings)
    • XML
    • HTML
    • URL-Encoding
    • UON (URL-Encoded Object Notation)
    • MessagePack
    • RDF/XML
    • RDF/XML-Abbrev
    • N-Triple
    • Turtle
    • N3
  4. Data Transfer Objects:

    • HTML5
    • ATOM
    • Swagger
    • Cognos
    • JSON-Schema

    DTOs can be used with any serializers and parsers (e.g. ATOM as JSON).

  5. Serialization of POJO meta-models (e.g. the POJO class structure itself) to:

    • JSON-Schema
    • XML-Schema
    • HTML-Schema
  6. Serializers/parsers require only Java 6+. (RDF support requires Jena 2.7.1+)

  7. REST APIs require only Java 6+ and JEE 1.3+. (JAX/RS integration component requires JAX/RS provider)

Components

Juneau ships as a single Java library called juneau.jar.

Juneau requires Java 6+. The majority of the code has no other dependencies except for the following packages:

OSGi bundles are also provided that break down Juneau into the following components:

The following zip files are also provided:

A note about examples

Many of the examples below use beans with public field properties instead of standard getters/setters. This is to simplify the examples.

2 - Juneau Core (org.apache.juneau)

The core packages of Juneau contains serializers and parsers for converting POJOs to and from a wide variety of content types. It uses a common API for defining serializers and parsers.

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.

2.1 - Serializers

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 = new JsonSerializer() .setSimpleMode(true) .setQuoteChar('\''); // Clone an existing serializer and modify it to use single-quotes JsonSerializer serializer = JsonSerializer.DEFAULT.clone() .setQuoteChar('\''); // 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).

Additional Information

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);

In the example above, we're parsing "lax" JSON (single quotes, unquoted attributes). The JSON parser can handle any valid JSON syntax (such as quoted or unquoted attributes, single or double quotes).
It can also handle JSON fragements and embedded Javascript comments. Many of the JSON examples provided will use lax syntax which is easier to read since we don't have to deal with escapes.

Additional Information

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 = new SerializerGroup() .append(JsonSerializer.class, UrlEncodingSerializer.class); .setUseIndentation(true) .addPojoSwaps(CalendarSwap.ISO8601DT.class); // 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 = new ParserGroup() .append(JsonSerializer.class, UrlEncodingSerializer.class); .addTransforms(CalendarSwap.ISO8601DT.class); 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.

Additional Information

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.

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.

// 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);

As a general rule, if you do not specify a target type during parsing, or if the target type cannot be determined through reflection, the parsers automatically generate ObjectMaps and ObjectLists.

Additional Information

2.5 - Configurable Properties

Serializers and parsers have a wide variety of configurable properties.
For example, the following code shows how to configure a JSON serializer:

JsonSerializer s = new JsonSerializer() .setUseIndentation(true) .setUseWhitespace(true) .setSimpleMode(true) .setQuoteChar('\'');

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);

Serializers and parsers can be locked to prevent further modification to the properties. They can also be cloned to copy the configuration of other serializers and parsers.

// Clone and customize an existing serializer. JsonSerializer s = JsonSerializer.DEFAULT_LAX .clone() .setQuoteChar('"'); // Lock it so that the configuration cannot be changed. s.lock();

Additional Information

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

2.6 - 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:

Annotations are also provided that allow you to use transformations directly on class definitions:

2.6.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 very easy to understand. Simply put, they can be thought of as '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 public String swap(BeanSession session, Date o) { return format.format(o); } /** Converts an ISO8601 string to a Date object. */ @Override public Date unswap(BeanSession session, String o, ClassMeta hint) throws ParseException { try { return format.parse(o); } catch (java.text.ParseException e) { throw new ParseException(e); } } }

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. Serializer serializer = new JsonSerializer().addPojoSwaps(MyDateSwap.class); String json = serializer.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 parser = new JsonParser().addPojoSwaps(MyDateSwap.class); MyBean bean = parser.parse(json, MyBean.class); int day = bean.date.getDay(); // == 3

Several PojoSwaps are already provided for common Java objects:

  • {@link org.apache.juneau.transforms}
    • {@link org.apache.juneau.transforms.ByteArrayBase64Swap}
    • {@link org.apache.juneau.transforms.CalendarSwap}
    • {@link org.apache.juneau.transforms.DateSwap}
    • {@link org.apache.juneau.transforms.EnumerationSwap}
    • {@link org.apache.juneau.transforms.IteratorSwap}
    • {@link org.apache.juneau.transforms.ReaderSwap}
    • {@link org.apache.juneau.transforms.XMLGregorianCalendarSwap}

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

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

2.6.2 - 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.6.3 - BeanFilters and @Bean annotations

{@link org.apache.juneau.transform.BeanFilter BeanFilters} are used to control aspects of how beans are handled during serialization and parsing. They allow you to control various aspects of beans, such as...

  • Which properties to include or exclude.
  • Property order.
  • Property naming conventions.
  • Overriding reading and writing of properties.

In practice, however, it's simpler to use the {@link org.apache.juneau.annotation.Bean @Bean} and {@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotations on your bean classes. The annotations are functionally equivalent to the bean filter class.

// 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 filters are defined through {@link org.apache.juneau.transform.BeanFilterBuilder BeanFilterBuilders}. The programmatic equivalent to the the annotation above would be:

public class MyAddressBeanFilter extends BeanFilterBuilder { // Must provide a no-arg constructor! public MyAddressBeanFilter() { super(Address.class); // The bean class that this filter applies to. setIncludeProperties("street,city,state"); // The properties we want exposed. } }

Bean filters are added to serializers and parsers using the addBeanFilters(Class...) method. For example:

// Create a new JSON serializer and associate a bean filter with it. Serializer serializer = new JsonSerializer().addBeanFilters(MyAddressBeanFilter.class);

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 addBeanFilter(Class...) 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 { ... } // Create a new JSON serializer that only exposes street,city,state on Address bean. Serializer serializer = new JsonSerializer().addBeanFilters(AddressInterface.class);

Additional Information
  • {@link org.apache.juneau.transform}

2.7 - 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 dictionary 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(typeDictionary={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 defined at two levels:

Type names do not need to be universally unique. However, they must be unique within a dictionary.

The following reserved words cannot be used as type names: object, array, number, boolean, null.

Serialized type names are DISABLED by default. They must be enabled on the serializer using the {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_addBeanTypeProperties} configuration property.

The "_type" property name can be overridden using the {@link org.apache.juneau.BeanContext#BEAN_beanTypePropertyName} configuration property.

2.7.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.8 - POJO Categories

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

GroupDescriptionExamplesCan
serialize?
Can
parse?
1 Java 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, 5] objects.
Map, Collection, and array values are group [1, 2, 3a, 4a, 5] 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, 6] objects.
Map, Collection, and array values are group [3b, 4, 5, 6] objects.
  • HashSet<Bean,Integer>
  • TreeMap<Integer,Reader>
yes no
3 Java Beans      
3a With standard properties
These are beans that have no-arg constructors and one or more properties defined by public getter and setter methods or public fields.
Property values are group [1, 2, 3a, 4a, 5] objects.
  yes yes
3b With non-standard properties or not true beans
These include true beans that have no-arg constructors and one or more properties defined by getter and setter methods or properties, but property types include group [3b, 4b, 5, 6] 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
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, 3a] objects
For example, a swap that converts a {@code Date} to a {@code String}.
  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}.
  yes no
5 Objects with standardized static methods and/or constructors for converting to another POJO that's serializable.
     
5a Objects with standardized static T valueOf(String)/static T fromString(String) methods, or constructors with a String argument.
During serialization, objects are converted to strings using the toString() method. During parsing, strings are converted to objects using one of these static methods or constructors.
java.util.UUID yes yes
5b Objects with standardized Object swap(BeanSession)/static T unswap(BeanSession,Object) methods, or constructors with an Object argument where the objects are any object on this list.
During serialization, normal objects are converted to swapped objects using the swap() method. During parsing, swapped objects are converted to normal objects using the static method or constructor.
  yes yes
6 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

Serializers are designed to work on tree-shaped POJO models. These are models where there are no referential loops (e.g. leaves with references to nodes, or nodes in one branch referencing nodes in another branch). There is a serializer setting {@code detectRecursions} to look for and handle these kinds of loops (by setting these references to null), but it is not enabled by default since it introduces a moderate performance penalty.

2.9 - Simple Variable Language

The {@link org.apache.juneau.svl} package 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}".

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

// 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}}");

SVL is a large topic on it's own. It is used extensively in the ConfigFile, REST and Microservice APIs.

Additional Information

2.10 - Configuration Files

The {@link org.apache.juneau.ini} package contains a powerful API for creating and using INI-style config files.

An example of an INI file:

# Default section key1 = 1 key2 = true key3 = 1,2,3 key4 = http://foo # Section 1 [Section1] key1 = 2 key2 = false key3 = 4,5,6 key4 = http://bar

This class can be used to easily access contents of the file:

int key1; boolean key2; int[] key3; URL key4; // Load our config file ConfigFile f = ConfigMgr.DEFAULT.get("MyIniFile.cfg"); // Read values from default section key1 = f.getInt("key1"); key2 = f.getBoolean("key2"); key3 = f.getObject(int[].class, "key3"); key4 = f.getObject(URL.class, "key4"); // Read values from section #1 key1 = f.getInt("Section1/key1"); key2 = f.getBoolean("Section1/key2"); key3 = f.getObject(int[].class, "Section1/key3"); key4 = f.getObject(URL.class, "Section1/key4");

The interface also allows a config file to be easily constructed programmatically:

// Construct the sample INI file programmatically ConfigFile cf = ConfigMgr.DEFAULT.create("MyIniFile.cfg") .addLines(null, "# Default section", "key1 = 1", "key2 = true", "key3 = 1,2,3", "key4 = http://foo", "") .addHeaderComments("Section1", "# Section 1") .addLines("Section1", "key1 = 2", "key2 = false", "key3 = 4,5,6", "key4 = http://bar") .save();

The following is equivalent, except that it uses {@link org.apache.juneau.ini.ConfigFile#put(String, Object)} to set values:

// Construct the sample INI file programmatically ConfigFile cf = ConfigMgr.DEFAULT.create("MyIniFile.cfg") .addLines(null, "# Default section") .addHeaderComments("section1", "# Section 1"); cf.put("key1", 1); cf.put("key2", true); cf.put("key3", new int[]{1,2,3}); cf.put("key4", new URL("http://foo")); cf.put("Section1/key1", 2); cf.put("Section1/key2", false); cf.put("Section1/key3", new int[]{4,5,6}); cf.put("Section1/key4", new URL("http://bar")); cf.save();

The config file looks deceptively simple, the config file API is a very powerful feature with many capabilities, including:

Example:

#-------------------------- # 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 # 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 = $ARG{myarg} # The first command-line argument firstArg = $ARG{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,$ARG{0}}} # A POJO with embedded variables aBean2 = {foo:'$ARG{0}',baz:$C{MySection/anInt}}

// Java code for accessing config entries above. ConfigFile cf = Microservice.getConfig(); int anInt = cf.getInt("MySection/anInt"); boolean aBoolean = cf.getBoolean("MySection/aBoolean"); int[] anIntArray = cf.getObject(int[].class, "MySection/anIntArray"); URL aURL = cf.getObject(URL.class, "MySection/aURL"); MyBean aBean = cf.getObject(MyBean.class, "MySection/aBean"); Locale locale = cf.getObject(Locale.class, "MySection/locale"); String path = cf.getString("MySection/path"); String mainClass = cf.getString("MySection/mainClass"); int sameAsAnInt = cf.getInt("MySection/sameAsAnInt"); String myArg = cf.getString("MySection/myArg"); String firstArg = cf.getString("MySection/firstArg");

Additional Information

2.11 - Supported Languages

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

Additional Information

3 - Juneau Data Transfer Objects (org.apache.juneau.dto)

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

3.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 fragements.

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

3.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) 2016, Apache Foundation") .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 = new XmlSerializer.SqReadable(); // 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) 2016, Apache Foundation </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

3.3 - Swagger

TODO

3.4 - JSON-Schema

TODO

4 - Juneau Server (org.apache.juneau.rest)

The Juneau REST Server API provides servlet-based REST resources on top of existing POJOs.

The API automatically detects Accept header of requests and converts POJOs to any of the supported languages. The toolkit is extensible and also allows for support of user-defined content types.

Automatic built-in support is provided for negotiation of response charsets and gzip encoding.

The following is an example of a REST API used to view and set JVM system properties.

@RestResource( path="/systemProperties", title="System properties resource", description="REST interface for performing CRUD operations on system properties.", properties={ @Property(name=SERIALIZER_quoteChar, value="'"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS'}"), }, stylesheet="styles/devops.css", encoders=GzipEncoder.class, 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 SystemPropertiesResource extends RestServletDefault { @RestMethod( name="GET", path="/", summary="Show all system properties", description="Returns all system properties defined in the JVM.", parameters={ @Parameter(in="query", name="sort", description="Sort results alphabetically.", _default="false") }, responses={ @Response(value=200, description="Returns a map of key/value pairs.") } ) public Map getSystemProperties(@Query("sort") boolean sort) throws Throwable { if (sort) return new TreeMap(System.getProperties()); return System.getProperties(); } @RestMethod( name="GET", path="/{propertyName}", summary="Get system property", description="Returns the value of the specified system property.", parameters={ @Parameter(in="path", name="propertyName", description="The system property name.") }, responses={ @Response(value=200, description="The system property value, or null if not found.") } ) public String getSystemProperty(@Path String propertyName) throws Throwable { return System.getProperty(propertyName); } @RestMethod( name="PUT", path="/{propertyName}", summary="Replace system property", description="Sets a new value for the specified system property.", guards=AdminGuard.class, parameters={ @Parameter(in="path", name="propertyName", description="The system property name."), @Parameter(in="body", description="The new system property value."), }, responses={ @Response(value=302, headers={ @Parameter(name="Location", description="The root URL of this resource.") } ), @Response(value=403, description="User is not an admin.") } ) public Redirect setSystemProperty(@Path String propertyName, @Body String value) { System.setProperty(propertyName, value); return new Redirect(); } @RestMethod( name="POST", path="/", summary="Add an entire set of system properties", description="Takes in a map of key/value pairs and creates a set of new system properties.", guards=AdminGuard.class, parameters={ @Parameter(in="path", name="propertyName", description="The system property key."), @Parameter(in="body", description="The new system property values.", schema="{example:{key1:'val1',key2:123}}"), }, responses={ @Response(value=302, headers={ @Parameter(name="Location", description="The root URL of this resource.") } ), @Response(value=403, description="Unauthorized: User is not an admin.") } ) public Redirect setSystemProperties(@Body java.util.Properties newProperties) { System.setProperties(newProperties); return new Redirect(); } @RestMethod( name="DELETE", path="/{propertyName}", summary="Delete system property", description="Deletes the specified system property.", guards=AdminGuard.class, parameters={ @Parameter(in="path", name="propertyName", description="The system property name."), }, responses={ @Response(value=302, headers={ @Parameter(name="Location", description="The root URL of this resource.") } ), @Response(value=403, description="Unauthorized: User is not an admin") } ) public Redirect deleteSystemProperty(@Path String propertyName) { System.clearProperty(propertyName); return new Redirect(); } @RestMethod( name="OPTIONS", path="/*", summary="Show resource options", description="Show resource options as a Swagger doc" ) public Swagger getOptions(RestRequest req) { return req.getSwagger(); } }

The resource above is deployed like any other servlet, in this way:

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.3"> <servlet> <servlet-name>SystemPropertiesService</servlet-name> <servlet-class>org.apache.juneau.examples.rest.SystemPropertiesService</servlet-class> </servlet> <servlet-mapping> <servlet-name>SystemPropertiesService</servlet-name> <url-pattern>/systemProperties</url-pattern> </servlet-mapping> </web-app>

Pointing your browser to the resource renders the POJOs as HTML (since that's what the browser specifies in the Accept header).

One of the most useful aspects of using this API is the self-discovering, self-documenting OPTIONS pages. These are constructed automatically using reflection, augmented with information pulled from annotations (as shown above), resource bundles, or Swagger JSON files:

Arbitrarily complex POJO models can be serialized using any of the supported serializers, and content can be parsed using any of the supported parsers.

The juneau-examples-rest project contains various REST resource examples in an easy-to-use REST microservice. One of these is AddressBookResource which serializes AddressBook objects defined below (some code omitted):

/** package-info.java */ @XmlSchema( prefix="ab", xmlNs={ @XmlNs(prefix="ab", namespaceURI="http://www.apache.org/addressBook/"), @XmlNs(prefix="per", namespaceURI="http://www.apache.org/person/"), @XmlNs(prefix="addr", namespaceURI="http://www.apache.org/address/"), @XmlNs(prefix="mail", namespaceURI="http://www.apache.org/mail/") } ) package org.apache.juneau.examples.addressBook; import org.apache.juneau.xml.annotation.*;   /** Address book bean */ @Bean(typeName="addressBook") public class AddressBook extends LinkedList<Person> {}   /** Person bean */ @Xml(prefix="per") @Bean(typeName="person") public class Person { // Bean properties @Rdf(beanUri=true) public URI uri; public URI addressBookUri; public int id; public String name; @BeanProperty(swap=CalendarSwap.Medium.class) public Calendar birthDate; public LinkedList<Address> addresses; }   /** Address bean */ @Xml(prefix="addr") @Bean(typeName="address") public class Address { // Bean properties @Rdf(beanUri=true) public URI uri; public URI personUri; public int id; @Xml(prefix="mail") public String street, city, state; @Xml(prefix="mail") public int zip; public boolean isCurrent; }

The framework allows you to override header values through GET parameters, so that you can specify the ACCEPT header to see each type. Adding &plainText=true forces the response Content-Type to be text/plain.

HTML

Also, localization can be tested by passing in an Accept-Language header.

HTML/french
JSON
XML
Simple XML
URL-Encoding
UON
RDF/XML
RDF/N3
RDF/N-Tuple
RDF/Turtle

The Server API is an exhaustive topic on its own. Refer to the additional information for an in-depth examination of the API.

Additional Information

5 - Juneau Client (org.apache.juneau.rest.client)

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. RestClient client = new RestClient(JsonSerializer.class, JsonParser.class); // 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); // Add a person to the address book. // Use XML as the transport medium. client = new RestClient(XmlSerializer.class, XmlSerializer.class); Person p = new Person("Joe Smith", 21); int returnCode = client.doPost(url + "/entries", p).run();

The Client API is also an exhaustive topic on its own. Refer to the additional information for an in-depth examination of the API.

Additional Information

6 - Remoteable Services (org.apache.juneau.rest.remoteable)

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.

Remoteable Services are implemented through a combination of the Server and Client libraries.

In this example, you have the following interface defined that you 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 = new RestClient(JsonSerializer.class,JsonParser.class) .setRemoteableServletUri("https://localhost:9080/juneau/sample/remoteable"); // 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 made directly from a browser with no coding involved.

Additional Information

7 - Juneau Microservices (org.apache.juneau.microservice)

WARNING - The microservice API is still in beta. It may be replaced with an OSGi-based architecture.

The microservice-project.zip file contains a standalone Eclipse project that can be used to quickly create REST microservices as standalone executable jars without the need of an application server. They start almost instantly and are started through a simple java command:

java -jar microservice.jar

When you execute this command, you can point your browser to http://localhost:10000 to bring up the REST interface:

Microservices combine all the functionality of the core, server, and client APIs to provide truly powerful and easy-to-use REST interfaces with minimal overhead.

Additional Information

8 - Samples

The microservice-samples-project.zip file is a zipped eclipse project that 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.

8.1 - Installing in Eclipse

Follow these instructions to create the Samples project in Eclipse:

  1. Download the latest microservice-samples-project .zip file (e.g. microservice-samples-project-5.2.zip).
  2. In your Eclipse workspace, go to File->Import->General->Existing Projects into Workspace and click Next.

  3. Select the .zip file and click Finish.

  4. In your workspace, you can now see the following project:

The important elements in this project are:

At this point you're ready to start the microservice from your workspace.

8.2 - Running in Eclipse

The microservice-samples-project.launch file is already provided to allow you to quickly start the Samples microservice.

Go to Run->Run Configurations->Java Application->microservice-samples.project and click Run.

In your console view, you can see the following output:

Now open your browser and point to http://localhost:10000. You can see the following:

You have now started a REST interface on port 10000.

8.3 - Building and Running from Command-Line

The build.xml file is a very basic ANT script for building the Samples microservice into an executable jar.

To build the Samples microservice, right-click build.xml and select Run As->Ant Build. Once complete (which takes approximately 1 second), if you refresh the project, you can see the following new directory:

If you open up a command prompt in the build/microservice folder, you can start your microservice as follows:

If you get an error message saying java.net.BindException: Address already in use, this means that the microservice is already running elsewhere, so it cannot bind to port 10000.

8.4 - MANIFEST.MF

The META-INF/MANIFEST.MF file is used to describe the microservice. If you open it, you'll see the following:

Manifest-Version: 1.0 Main-Class: org.apache.juneau.microservice.RestMicroservice Rest-Resources: org.apache.juneau.examples.rest.RootResources Main-ConfigFile: examples.cfg Class-Path: lib/commons-codec-1.9.jar lib/commons-io-1.2.jar lib/commons-logging-1.1.1.jar lib/httpclient-4.5.jar lib/httpcore-4.4.1.jar lib/httpmime-4.5.jar lib/javax.servlet-api-3.0.jar lib/jetty-all-8.1.0.jar lib/juneau-all-5.2.jar lib/org.apache.commons.fileupload_1.3.1.jar lib/derby.jar lib/jena-core-2.7.1.jar lib/jena-iri-0.9.2.jar lib/log4j-1.2.16.jar lib/slf4j-api-1.6.4.jar lib/slf4j-log4j12-1.6.4.jar

Notes

If you modify the manifest file and get NoClassDefFoundErrors, ensure that the classpath entries contain trailing spaces.

8.5 - 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", properties={ @Property(name=HTMLDOC_links, value="{options:'$R{servletURI}?method=OPTIONS',source:'$R{servletURI}/source?classes=(org.apache.juneau.rest.samples.RootResources)'}") }, 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 ResourceGroup { 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.RestServlet#getTitle(RestRequest)} and {@link org.apache.juneau.rest.RestServlet#getDescription(RestRequest)} 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()} 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 Resource {

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 overriding the {@link org.apache.juneau.rest.RestServlet#createChildren()} method.

Note that these router pages can be arbitrarily nested deep. You can define many levels of router pages for arbitrarily hierarchical REST interfaces.

Let's step back and describe what's going on here:
During servlet initialization of the RootResources object, the toolkit looks for the @RestResource.children() annotation. If it finds it, it instantiates instances of each class and recursively performs servlet initialization on them. It then associates the child resource with the parent by the name specified by the @RestResource.path() annotation on the child class. When a request for the child URL (/helloWorld) is received, the RootResources servlet gets the request and sees that the URL remainder matches one of its child resources. It then forwards the request to the child resource for processing. The request passed to the child resource is the same as if the child resource had been deployed independently (e.g. path-info, resource-URI, and so forth).

8.6 - 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", properties={ @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS'}") } ) public class HelloWorldResource extends Resource { 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:

8.7 - 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", properties={ @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.MethodExampleResource)'}") } ) public class MethodExampleResource extends Resource { private static final long serialVersionUID = 1L; /** Example GET request that redirects to our example method */ @RestMethod(name="GET", path="/") public Redirect doGetExample() throws Exception { return new Redirect("example1/xxx/123/{0}/xRemainder?p1=123&p2=yyy", UUID.randomUUID()); } /** Example GET request using annotated attributes */ @RestMethod(name="GET", path="/example1/{a1}/{a2}/{a3}/*", rc={200}) public String doGetExample1( @Method String method, @Path String a1, @Path int a2, @Path UUID a3, @Query("p1") int p1, @Query("p2") String p2, @Query("p3") UUID p3, @PathRemainder String remainder, @Header("Accept-Language") String lang, @Header("Accept") String accept, @Header("DNT") int doNotTrack ) { String output = String.format( "method=%s, a1=%s, a2=%d, a3=%s, remainder=%s, p1=%d, p2=%s, p3=%s, lang=%s, accept=%s, dnt=%d", method, a1, a2, a3, remainder, p1, p2, p3, lang, accept, doNotTrack); return output; } /** Example GET request using methods on RestRequest and RestResponse */ @RestMethod(name="GET", path="/example2/{a1}/{a2}/{a3}/*", rc={200}) public void doGetExample2(RestRequest req, RestResponse res) throws Exception { String method = req.getMethod(); // Attributes (from URL pattern variables) String a1 = req.getPathParameter("a1", String.class); int a2 = req.getPathParameter("a2", int.class); UUID a3 = req.getPathParameter("a3", UUID.class); // Optional GET parameters int p1 = req.getQueryParameter("p1", int.class, 0); String p2 = req.getQueryParameter("p2", String.class); UUID p3 = req.getQueryParameter("p3", UUID.class); // URL pattern post-match String remainder = req.getPathRemainder(); // Headers String lang = req.getHeader("Accept-Language"); int doNotTrack = req.getHeader("DNT", int.class); // Send back a simple String response String output = String.format( "method=%s, a1=%s, a2=%d, a3=%s, remainder=%s, p1=%d, p2=%s, p3=%s, lang=%s, dnt=%d", method, a1, a2, a3, remainder, p1, p2, p3, lang, doNotTrack); res.setOutput(output); } }

The class consists of 3 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.RestServletDefault} class defines the following Java method:

RestServletDefault.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( properties={ @Property(name=HTMLDOC_links, value="{options:'?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.

8.8 - 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 Resource { private static final long serialVersionUID = 1L; /** GET request handler */ @RestMethod(name="GET", path="/") public ReaderResource doGet(RestRequest req) throws IOException { return req.getReaderResource("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#getReaderResource(String,boolean)} method pulls in the following file located in the same package as the class:

UrlEncodedForm.html

<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> <h3 class='title'>$R{servletTitle}</h3> <h5 class="description">$R{servletDescription}</h5> <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{servletTitle} and $R{servletDescription} resolve to the values returned by {@link org.apache.juneau.rest.RestRequest#getServletTitle()} and {@link org.apache.juneau.rest.RestRequest#getServletDescription()}.

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:

Additional Information

8.9 - 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", properties={ @Property(name=SERIALIZER_maxDepth, value="10"), @Property(name=SERIALIZER_detectRecursions, value="true"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.RequestEchoResource)'}") }, 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 Resource { /** GET request handler */ @RestMethod(name="GET", path="/*", converters={Queryable.class,Traversable.class}) public HttpServletRequest doGet(RestRequest req, @Properties ObjectMap properties) { // Set the HtmlDocSerializer title programmatically. // This sets the value for this request only. properties.put(HTMLDOC_title, "Contents of HttpServletRequest object"); // 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 overriding the {@link org.apache.juneau.rest.RestServlet#createSerializers(ObjectMap,Class[],Class[])} method, as follows:

Hypothetical RequestEchoResource.createSerializers() method

/** Override the default rest serializers to add some transforms */ @Override protected SerializerGroup createSerializers(ObjectMap properties, Class[] beanFilters, Class[] pojoSwaps) { // You'll just reuse the parent serializer group SerializerGroup serializerGroup = super.createSerializers(properties, beanFilters, pojoSwaps); // 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. serializerGroup .addBeanFilters(HttpServletRequest.class, HttpSession.class, ServletContext.class) .addPojoSwaps(EnumerationSwap.class) .setMaxDepth(10) .setDetectRecursions(true); .setProperty(HTMLDOC_links, "{...}"); // Return the updated group return serializerGroup; }

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.

8.10 - 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:

8.10.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 shortnames 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", properties={ @Property(name=REST_allowMethodParam, value="*"), @Property(name=HTML_uriAnchorText, value=TO_STRING), @Property(name=SERIALIZER_quoteChar, value="'"), @Property(name=RDF_rdfxml_tab, value="5"), @Property(name=RDF_addRootProperty, value="true"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.addressbook.AddressBookResource,org.apache.juneau.examples.addressbook.Address,org.apache.juneau.examples.addressbook.AddressBook,org.apache.juneau.examples.addressbook.CreateAddress,org.apache.juneau.examples.addressbook.CreatePerson,org.apache.juneau.examples.addressbook.IAddressBook,org.apache.juneau.examples.addressbook.Person)'}"), // Resolve all relative URIs so that they're relative to this servlet! @Property(name=SERIALIZER_relativeUriBase, value="$R{servletURI}"), }, stylesheet="styles/devops.css", encoders=GzipEncoder.class, 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 ResourceJena { 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("")); // 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) throws Exception { try { Person p = findPerson(id); String pathRemainder = req.getPathRemainder(); PojoRest r = new PojoRest(p); ClassMeta<?> cm = r.getClassMeta(pathRemainder); Object in = req.getBody(cm); r.put(pathRemainder, 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) throws Exception { try { Address a = findAddress(id); String pathInfo = req.getPathInfo(); PojoRest r = new PojoRest(a); ClassMeta<?> cm = r.getClassMeta(pathInfo); Object in = req.getBody(cm); r.put(pathInfo, 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 /* RestServletJenaDefault */ @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.XmlSerializerContext#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 explicitely 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

8.10.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:

8.10.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:



8.10.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

8.10.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):

8.10.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 standalone 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 = new RestClient(JsonSerializer.DEFAULT, JsonParser.DEFAULT); RestClient xmlClient = new RestClient(XmlSerializer.DEFAULT, XmlParser.DEFAULT); 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

8.10.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 {@link org.apache.juneau.rest.RestServletContext#REST_allowMethodParam} property.

8.11 - 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", properties={ @Property(name=HTMLDOC_title, value="Remoteable Service Proxy API"), @Property(name=HTMLDOC_description, value="Sample class showing how to use remoteable proxies. The list below are exposed services that can be retrieved using RestClient.getProxyInterface(Class)."), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.SampleRemoteableServlet)'}"), // Allow us to use method=POST from a browser. @Property(name=REST_allowMethodParam, value="*") } ) 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.



As good practice, you'll want to use interfaces to prevent all public methods from being exposed.

The {@link org.apache.juneau.rest.client.RestClient#setRemoteableServletUri(String)} method is used to specify the location of the remoteable services servlet running on the server. 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 = new RestClient(JsonSerializer.class,JsonParser.class) .setRemoteableServletUri("http://localhost:10000/remoteable"); // 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)) );

Additional Information

8.12 - 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", 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"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS',upload:'upload',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.TempDirResource,org.apache.juneau.rest.samples.DirectoryResource)'}"), }, 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.getReaderResource("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

<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> <h3 class='title'>$R{servletTitle}</h3> <h5 class="description">$R{servletDescription}</h5> <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.

Additional Information

8.13 - 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", properties={ @Property(name=SERIALIZER_quoteChar, value="'"), @Property(name=RDF_rdfxml_tab, value="5"), @Property(name=RDF_addRootProperty, value="true"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.AtomFeedResource)'}") }, encoders=GzipEncoder.class ) public class AtomFeedResource extends ResourceJena { 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) 2016, Apache Foundation")) .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; } }

Additional Information

8.14 - 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", properties={ @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.AtomFeedResource)'}") } ) public class DockerRegistryResource extends Resource { private static final long serialVersionUID = 1L; // Get registry URL from examples.cfg file. private String registryUrl = getConfig().getString("DockerRegistry/url"); RestClient rc = new RestClient(JsonSerializer.DEFAULT, JsonParser.DEFAULT); /** [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

Additional Information

8.15 - 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", properties={ @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.TumblrParserResource)'}"), @Property(name=HTMLDOC_title, value="Tumblr parser service"), @Property(name=HTMLDOC_description, value="Specify a URL to a Tumblr blog and parse the results.") } ) public class TumblrParserResource extends Resource { 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 = new RestClient(JsonSerializer.class, JsonParser.class); 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; } }

8.16 - 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", properties={ @Property(name=HtmlDocSerializerContext.HTMLDOC_links, value="{options:'?method=OPTIONS'}"), @Property(name=HtmlDocSerializerContext.HTMLDOC_title, value="Photo REST service"), @Property(name=HtmlDocSerializerContext.HTMLDOC_description, value="Use a tool like Poster to upload and retrieve jpeg and png images.") } ) public class PhotosResource extends RestServletDefault { // 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.setProperty(HtmlDocSerializerContext.HTMLDOC_title, "Photo REST service"); res.setProperty(HtmlDocSerializerContext.HTMLDOC_description, "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 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 public <T> T parse(InputStream in, ClassMeta<T> type, ParserSession session) throws ParseException, IOException { BufferedImage image = ImageIO.read(in); return (T)image; } } }

8.17 - 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", properties={ @Property(name=HTMLDOC_title, value="Sample JSON-Schema document"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.JsonSchemaResource)'}") } ) public class JsonSchemaResource extends ResourceJena { 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; } }

8.18 - 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", properties={ @Property(name=HTMLDOC_title, value="SQL query service"), @Property(name=HTMLDOC_description, value="Executes queries against the local derby '$C{SqlQueryResource/connectionUrl}' database"), @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS',source:'$R{servletParentURI}/source?classes=(org.apache.juneau.rest.samples.SqlQueryResource)'}"), } ) public class SqlQueryResource extends Resource { private static final long serialVersionUID = 1L; private ConfigFile 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.getReaderResource("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

<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> <h3 class='title'>SQL Query API</h3> <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

8.19 - ConfigResource

The {@link org.apache.juneau.microservice.resources.ConfigResource} class is a reusable resource defined in the {@link org.apache.juneau.microservice} API. It provides a REST interface for reading and altering the microservice config file.

Pointing a browser to the resource shows the following:

An edit page is provided for altering the raw config file:

The {@link org.apache.juneau.ini.ConfigFile} class is a serializable POJO, which makes the resource relatively straighforward to implement.

ConfigResource.java

/** * Shows contents of the microservice configuration file. */ @RestResource( path="/config", title="Configuration", description="Contents of configuration file.", properties={ @Property(name=HTMLDOC_links, value="{up:'$R{requestParentURI}',options:'$R{servletURI}?method=OPTIONS',edit:'$R{servletURI}/edit'}"), } ) public class ConfigResource extends Resource { private static final long serialVersionUID = 1L; /** * [GET /] - Show contents of config file. * * @return The config file. * @throws Exception */ @RestMethod(name="GET", path="/", description="Show contents of config file.") public ConfigFile getConfigContents() throws Exception { return getConfig(); } /** * [GET /edit] - Show config file edit page. * * @param req The HTTP request. * @return The config file as a reader resource. * @throws Exception */ @RestMethod(name="GET", path="/edit", description="Show config file edit page.") public ReaderResource getConfigEditPage(RestRequest req) throws Exception { // Note that you don't want variables in the config file to be resolved, // so you need to escape any $ characters that you see. req.setAttribute("contents", getConfig().toString().replaceAll("\\$", "\\\\\\$")); return req.getReaderResource("ConfigEdit.html", true); } /** * [GET /{section}] - Show config file section. * * @param section The section name. * @return The config file section. * @throws Exception */ @RestMethod(name="GET", path="/{section}", description="Show config file section.", parameters={ @Parameter(in="path", name="section", description="Section name.") } ) public ObjectMap getConfigSection(@Path("section") String section) throws Exception { return getSection(section); } /** * [GET /{section}/{key}] - Show config file entry. * * @param section The section name. * @param key The section key. * @return The value of the config file entry. * @throws Exception */ @RestMethod(name="GET", path="/{section}/{key}", description="Show config file entry.", parameters={ @Parameter(in="path", name="section", description="Section name."), @Parameter(in="path", name="key", description="Entry name.") } ) public String getConfigEntry(@Path("section") String section, @Path("key") String key) throws Exception { return getSection(section).getString(key); } /** * [POST /] - Sets contents of config file from a FORM post. * * @param contents The new contents of the config file. * @return The new config file contents. * @throws Exception */ @RestMethod(name="POST", path="/", description="Sets contents of config file from a FORM post.", parameters={ @Parameter(in="formData", name="contents", description="New contents in INI file format.") } ) public ConfigFile setConfigContentsFormPost(@FormData("contents") String contents) throws Exception { return setConfigContents(new StringReader(contents)); } /** * [PUT /] - Sets contents of config file. * * @param contents The new contents of the config file. * @return The new config file contents. * @throws Exception */ @RestMethod(name="PUT", path="/", description="Sets contents of config file.", parameters={ @Parameter(in="body", description="New contents in INI file format.") } ) public ConfigFile setConfigContents(@Body Reader contents) throws Exception { ConfigFile cf2 = ConfigMgr.DEFAULT.create().load(contents); return getConfig().merge(cf2).save(); } /** * [PUT /{section}] - Add or overwrite a config file section. * * @param section The section name. * @param contents The new contents of the config file section. * @return The new section. * @throws Exception */ @RestMethod(name="PUT", path="/{section}", description="Add or overwrite a config file section.", parameters={ @Parameter(in="path", name="section", description="Section name."), @Parameter(in="body", description="New contents for section as a simple map with string keys and values.") } ) public ObjectMap setConfigSection(@Path("section") String section, @Body Map<String,String> contents) throws Exception { getConfig().setSection(section, contents); return getSection(section); } /** * [PUT /{section}/{key}] - Add or overwrite a config file entry. * * @param section The section name. * @param key The section key. * @param value The new value. * @return The new value. * @throws Exception */ @RestMethod(name="PUT", path="/{section}/{key}", description="Add or overwrite a config file entry.", parameters={ @Parameter(in="path", name="section", description="Section name."), @Parameter(in="path", name="key", description="Entry name."), @Parameter(in="body", description="New value as a string.") } ) public String setConfigSection(@Path("section") String section, @Path("key") String key, @Body String value) throws Exception { getConfig().put(section, key, value, false); return getSection(section).getString(key); } private ObjectMap getSection(String name) { ObjectMap m = getConfig().getSectionMap(name); if (m == null) throw new RestException(SC_NOT_FOUND, "Section not found."); return m; } }

ConfigEdit.html

<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> <h3 class='title'>$R{servletTitle}</h3> <h5 class='description'>Edit config file</h5> <p class='links'><a href='$R{requestParentURI}'>up</a> - <a href='$R{servletURI}?method=OPTIONS'>options</a></p> <form id='form' action='$R{servletURI}' method='POST' enctype='application/x-www-form-urlencoded'> <div class='data'> <table> <tr><td colspan='2' align='right'><button type='submit'>Submit</button><button type='reset'>Reset</button></td></tr> <tr><th colspan='2'>Contents</th></tr> <tr><td colspan='2'><textarea name='contents' rows='40' cols='120' style='white-space: pre; word-wrap: normal; overflow-x: scroll;'>$SA{contents}</textarea></td></tr> </table> </div> </form> </body> </html>

8.20 - LogsResource

The {@link org.apache.juneau.microservice.resources.LogsResource} class is a reusable resource defined in the {@link org.apache.juneau.microservice} API. 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:

9 - Cookbook Examples

9.1 - Core API

TODO topics
  1. Creating generic JSON objects
  2. Defining XML namespaces

9.2 - Server API

9.2.1 - Apply a transform that changes the format of doubles

The {@link org.apache.juneau.rest.annotation.RestResource#pojoSwaps()} annotation can be used to add POJO swaps to all the serializers and parsers registered with a servlet.

In this example, you define a POJO swap that converts doubles to localized-format strings using the NumberFormat Java class.

@RestResource( pojoSwaps={ MyRestService.DoubleSwap.class } ) public class MyRestService extends JazzDefaultRestResource { private static final NumberFormat FORMAT = NumberFormat.getInstance(); public static class DoubleSwap extends PojoSwap<Double,String> { @Override /* PojoSwap */ public String swap(BeanSession session, Double o) throws SerializeException { return FORMAT.format(o); } }

9.2.2 - Apply transforms to a subset of serializers or parsers

The {@link org.apache.juneau.rest.RestServlet#createSerializers(ObjectMap,Class[],Class[])} and {@link org.apache.juneau.rest.RestServlet#createParsers(ObjectMap,Class[],Class[])} methods are the servlet methods that get called during servlet initialization to create the serializer and parser groups. These methods can be overridden to customize individual serializers and parsers in a way that can't be done using annotations.

In this example, you want to apply the swap from the previous example to change the rendered format for doubles. However, in this case, you apply the swao to only the HTML serializer.

@Override protected SerializerGroup createSerializers(ObjectMap properties, Class[] beanFilters, Class[] pojoSwaps) throws Exception { SerializerGroup g = super.createSerializers(properties, beanFilters, pojoSwaps); g.getSerializer("text/html").addPojoSwaps(DoubleSwap.class); return g; }

TODO topics
  1. Packaging as WAR files
  2. Customizing OPTIONS pages
  3. Rendering form entry pages
  4. Using the ZipFileList response handler
  5. Implementing console-output pages in HTML
  6. Using configuration files
  7. Making a bean traversable
  8. Using the Queryable converter
  9. Sending raw output
  10. Retrieving raw input
  11. Accessing request query parameters
  12. Accessing request path variables
  13. Accessing request content
  14. Accessing request header values
  15. Accessing the path pattern remainder
  16. Creating ResourceGroup pages
  17. Using matchers to define multiple Java methods to the same path pattern
  18. Using the Remoteable API
  19. Sending a redirect request
  20. Changing the stylesheet used by the HTML serializer
  21. Using the Introspector API to invoke methods on Java objects through REST calls
  22. Customizing serializers and parsers at the method level
  23. Accessing config file values
  24. Accessing request query parameters on URL-Encoded FORM posts without triggering HTML body to be read
  25. Accessing localized messages
  26. Defining your own response handlers
  27. Guarding access to a servlet or method
  28. Handling servlet initialization errors
  29. Handling exceptions that occur during response processing
  30. Customizing logging
  31. Creating an ATOM feed
  32. Creating a REST API against a file system
  33. Creating a Docker REST API
  34. Creating a REST API for storing and retrieving images
  35. Creating a REST API for echoing requests
  36. Creating a Tumblr REST API
  37. Creating a Cloudant REST API
  38. Using onPreCall() to intercept requests before processing
  39. Using onPostCall() to intercept requests after processing
  40. Creating child resources programmatically
  41. Defining default request headers
  42. Defining default response headers
  43. Defining your own var-resolver variables
  44. Serving up static files inside the /htdocs embedded package
  45. Defining MIME types of files in the /htdocs folder using the createMimitypesFileTypeMap() method
  46. Defining the title and description of a servlet programmatically using getDescription() and getTitle().
  47. Setting properties programmatically using RestServlet.setProperty()
  48. Setting and saving config file properties
  49. Defining your own abstract subclass of RestServlet or RestServletDefault
  50. Adding GZip support
  51. Accessing environment variables in config files

9.3 - Client API

9.4 - Microservice API

10 - 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.SerializerContext#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.

  4. 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.

11 - Important Document Links

All up-to-date Juneau documentation is stored in Javadocs, especially package-level Javadocs. This index provides links to the best jumping-off points for documentation.

Links

12 - Release Notes

What's new in each release

6.1.0 (TBD)

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.ContextFactory}/{@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 {@link org.apache.juneau.ini.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.