About

A single cohesive framework consisting of the following parts:

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

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

Features
Prerequisites

We've strived to keep prerequisites to an absolute minimum.

Components

The library consists of 4 components/jars and a single uber-jar that contains everything.

Juneau Core

Core library includes easy-to-use and customizable serializers and parsers. The examples here provide a small taste of what's possible.

The default serializers can often be used to serialize POJOs in a single line of code:

// A simple bean public class Person { public String name = "John Smith"; public int age = 21; } // Serialize a bean to JSON, XML, or HTML Person p = new Person(); // Produces: // "{name:'John Smith',age:21}" String laxJson = JsonSerializer.DEFAULT_LAX.serialize(p); // Produces: // "{"name":"John Smith","age":21}" String strictJson = JsonSerializer.DEFAULT.serialize(p); // Produces: // <object> // <name>John Smith</name> // <age>21</age> // </object> String xml = XmlSerializer.DEFAULT_SIMPLE.serialize(p); // Produces: // <table> // <tr><td>name</td><td>John Smith</td></tr> // <tr><td>age</td><td>21</td></tr> // </table> String html = HtmlSerializer.DEFAULT.serialize(p); // Same as Html, but wraps it in HTML and BODY elements with page title/description/links: String htmlDoc = HtmlDocSerializer.DEFAULT.serialize(p); // Produces: // name='John+Smith'&age=21 String urlEncoding = UrlEncodingSerializer.DEFAULT.serialize(p); // Produces: // (name='John Smith',age=21) String uon = UonSerializer.DEFAULT.serialize(p); // Produces: // 82 A4 name AA 4A John Smith 68 A3 age 15 byte[] messagePack = MsgPackSerializer.DEFAULT.serialize(p); // Produces: // <rdf:RDF // xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" // xmlns:jp="http://www.apache.org/juneaubp/" // xmlns:j="http://www.apache.org/juneau/"> // <rdf:Description> // <jp:name>John Smith</jp:name> // <jp:age>21</jp:age> // </rdf:Description> // </rdf:RDF> String rdfXml = RdfSerializer.DEFAULT_XMLABBREV.serialize(p); // Produces: // @prefix jp: <http://www.apache.org/juneaubp/> . // @prefix j: <http://www.apache.org/juneau/> . // [] jp:age "21" ; // jp:name "John Smith" . String rdfN3 = RdfSerializer.DEFAULT_N3.serialize(p); // Produces: // _:A3bf53c85X3aX157cf407e2dX3aXX2dX7ffd <http://www.apache.org/juneaubp/name> "John Smith" . // _:A3bf53c85X3aX157cf407e2dX3aXX2dX7ffd <http://www.apache.org/juneaubp/age> "21" . String rdfNTriple = RdfSerializer.DEFAULT_NTRIPLE.serialize(p);

Parsing back into POJOs is equally simple for any of the supported languages shown above (JSON shown here):

// Use one of the predefined parsers. ReaderParser parser = JsonParser.DEFAULT; // Parse a JSON object (creates a generic ObjectMap). String json = "{name:'John Smith',age:21}"; 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 bean. json = "{name:'John Smith',age:21}"; Person p4 = parser.parse(json, Person.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> m5 = 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>> m6 = 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> l7 = parser.parse(json, LinkedList.class, Integer.class); int[] i7 = parser.parse(json, int[].class); // Parse arbitrary input into ObjectMap or ObjectList objects // (similar to JSONObject/JSONArray but generalized for all languages). json = "{name:'John Smith',age:21}"; ObjectMap m8a = parser.parse(json, ObjectMap.class); int age = m8a.getInt("age"); ObjectMap m8b = (ObjectMap)parser.parse(json, Object.class); // Equivalent. json = "[1,true,null]"; ObjectList l9a = parser.parse(json, ObjectList.class); boolean b = l9a.getBoolean(1); ObjectList l9b = (ObjectList)parser.parse(json, Object.class); // Equivalent.

Features


Serializers and parsers are builder-based. Build from scratch or clone existing instances. Lots of configuration options available for all the languages.

// Create a serializer from scratch using a builder JsonSerializer serializer = new JsonSerializerBuilder() .simple() // Simple mode .sq() // Use single quotes .pojoSwaps( // Swap unserializable classes with surrogate POJOs IteratorSwap.class, // Iterators swapped with lists ByteArrayBase64Swap.class, // byte[] swapped with base-64 encoded strings CalendarSwap.ISO8601DT.class // Calendars swapped with ISO8601-compliant strings ) .beanFilters(MyBeanFilter.class) // Control how bean properties are handled .timeZone(TimeZone.GMT) // For serializing Calendars .locale(Locale.JAPAN) // For timezone-specific serialization .sortCollections(true) // For locale-specific serialization .sortProperties(true) // Various behavior settings .trimNullProperties(true) .trimStrings(true) .methodVisibility(PROTECTED) // Control which fields/methods are serialized .beanDictionary( // Adds type variables for resolution during parsing MyBeanA.class, MyBeanB.class ) .debug(true) // Add debug output .build(); // Clone an existing serializer and modify it to use single-quotes JsonSerializer serializer = JsonSerializer.DEFAULT.builder() .sq() .build();




Many POJOs such as primitives, beans, collections, arrays, and classes with various known constructors and methods are serializable out-of-the-box. For other objects, "transforms" allow you to perform various mutations on them before serialization and after parsing.

Additional Information


UON (URL-Encoded Object Notation) allows JSON-like data structures (OBJECT, ARRAY, NUMBER, BOOLEAN, STRING, NULL) in HTTP constructs (query parameters, form parameters, headers, URL parts) without violating RFC2396. This allows POJOs to be converted directly into HTTP constructs.

( id=1, name='John+Smith', uri=http://sample/addressBook/person/1, addressBookUri=http://sample/addressBook, birthDate=1946-08-12T00:00:00Z, addresses=@( ( uri=http://sample/addressBook/address/1, personUri=http://sample/addressBook/person/1, id=1, street='100+Main+Street', city=Anywhereville, state=NY, zip=12345, isCurrent=true ) ) )




Lots of shortcuts are provided throughout the API to simplify tasks, and the APIs are often useful for debugging and logging purposes as well...

// Create JSON strings from scratch using fluent-style code. String jsonObject = new ObjectMap().append("foo","bar").toString(); String jsonArray = new ObjectList().append("foo").append(123).append(null).toString(); // Create maps and beans directly from JSON. Map<String,Object> myMap = new ObjectMap("{foo:'bar'}"); List<Object> myList = new ObjectList("['foo',123,null]"); // Load a POJO from a JSON file. MyPojo myPojo = JsonParser.DEFAULT.parse(new File("myPojo.json")); // Serialize POJOs and ignore exceptions (great for logging) String json = JsonSerializer.DEFAULT_LAX.toString(myPojo); // Dump a POJO to the console. JsonSerializer.DEFAULT_LAX.println(myPojo); // Delayed serialization. // (e.g. don't serialize an object if it's not going to be logged). logger.log(FINE, "My POJO was: {0}", new StringObject(myPojo)); logger.log(FINE, "My POJO in XML was: {0}", new StringObject(XmlSerializer.DEFAULT, myPojo)); String message = new StringMessage("My POJO in {0}: {1}", "JSON", new StringObject(myPojo)).toString(); // Create a 'REST-like' wrapper around a POJO. // Allows you to manipulate POJO trees using URIs and GET/PUT/POST/DELETE commands. PojoRest pojoRest = new PojoRest(myPojo); pojoRest.get(String.class, "addressBook/0/name"); pojoRest.put("addressBook/0/name", "John Smith");




SerializerGroup and ParserGroup 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 SerializerGroupBuilder() .append(JsonSerializer.class, UrlEncodingSerializer.class); .ws() // or .setUseWhitespace(true) .pojoSwaps(CalendarSwap.ISO8601DT.class) .build(); // Find the appropriate serializer by Accept type and serialize our POJO to the specified writer. // Fully RFC2616 compliant. 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 ParserGroupBuilder() .append(JsonParser.class, UrlEncodingParser.class); .pojoSwaps(CalendarSwap.ISO8601DT.class) .build(); Person p = pg.getParser("text/json").parse(myReader, Person.class);

Additional Information


DTO libraries

Data Transfer Object libraries are provided for a variety of languages that allow you to serialize commonly-used documents.

HTML5 documents and fragments can be constructed using the HTML5 DTOs and HTML or XML serializers:

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>

ATOM feeds can be constructed using the ATOM DTOs and XML serializer:

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") .entries( entry("tag:juneau.sample.com,2013:1.2345", "Juneau ATOM specification snapshot", "2016-01-02T03:04:05Z") .published("2016-01-02T03:04:05Z") .content( content("xhtml") .lang("en") .base("http://www.apache.org/") .text("<div><p><i>[Update: Juneau supports ATOM.]</i></p></div>") ) ); // Serialize to ATOM/XML String atomXml = XmlSerializer.DEFAULT.serialize(feed);

<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> <subtitle type='html'> Describes <em>stuff</em> about Juneau </subtitle> <entry> <id> tag:juneau.apache.org </id> <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>

Swagger documents can be constructed using the Swagger DTOs and JSON serializer:

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

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

Note that these DTOs can also be serialized to any of the other supported languages such as JSON or MessagePack! And they can be parsed back into their original objects!

Additional Information


Juneau Server

The REST server API builds upon the SerializerGroup and ParserGroup classes to provide annotated REST servlets that automatically negotiate the HTTP media types for you.
Developers simply work with requests, responses, headers, path variables, query parameters, and form data as POJOs.
Allows you to create sophisticated REST interfaces using tiny amounts of code.

The end goal is to provide simple and flexible yet sophisticated REST interfaces that allow POJOs to be automatically represented as different content types depending on whatever the particular need:

A simple example that supports all languages:

@RestResource( path="/systemProperties", title="System properties resource" ) public class SystemPropertiesResource extends RestServletDefault { @RestMethod(name="GET", path="/") public Map getSystemProperties(@Query("sort") boolean sort) throws Throwable { if (sort) return new TreeMap(System.getProperties()); return System.getProperties(); } @RestMethod(name="GET", path="/{propertyName}") public String getSystemProperty(@Path String propertyName) throws Throwable { return System.getProperty(propertyName); } @RestMethod(name="PUT", path="/{propertyName}", guards=AdminGuard.class) public String setSystemProperty(@Path String propertyName, @Body String value) { System.setProperty(propertyName, value); return "OK"; } @RestMethod(name="POST", path="/", guards=AdminGuard.class) public String setSystemProperties(@Body java.util.Properties newProperties) { System.setProperties(newProperties); return "OK"; } @RestMethod(name="DELETE", path="/{propertyName}", guards=AdminGuard.class) public String deleteSystemProperty(@Path String propertyName) { System.clearProperty(propertyName); return "OK"; } }

A more sophisticated example of the same resource using various features, including information for fully populating the Swagger documentation, guards for restricting access to particular methods, customizing supported content types and serialization options, adding g-zip compression, and adding customized branding for the HTML views.

@RestResource( path="/systemProperties", title="System properties resource", description="REST interface for performing CRUD operations on system properties.", // Widget used for content-type pull-down menu. widgets={ ContentTypeMenuItem.class }, // Links on the HTML rendition page. // "request:/..." URIs are relative to the request URI. // "servlet:/..." URIs are relative to the servlet URI. htmldoc=@HtmlDoc( // Custom navigation links. links={ "up: request:/..", "options: servlet:/?method=OPTIONS", "form: servlet:/formPage", "$W{ContentTypeMenuItem}", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/SystemPropertiesResource.java" }, // Custom page text in aside section. aside={ "<div style='max-width:800px' class='text'>", " <p>Shows standard GET/PUT/POST/DELETE operations and use of Swagger annotations.</p>", "</div>" }, // Custom CSS styles applied to HTML view. style={ "aside {display:table-caption;}" } ), // Set serializer, parser, and REST context properties. properties={ @Property(name=SERIALIZER_quoteChar, value="'") }, // Add compression support. encoders=GzipEncoder.class, // Augment Swagger information. swagger=@ResourceSwagger( 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 RestServlet { @RestMethod( name="GET", path="/", summary="Show all system properties", description="Returns all system properties defined in the JVM.", // Augment Swagger information. swagger=@MethodSwagger( 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(); } ... }

In HTML, our resource looks like this:

When combined with the support for HTML5 beans, simple HTML forms can be constructed for easy input and output using nothing more than Java:

import static org.apache.juneau.dto.html5.HtmlBuilder.*; @RestMethod( name="GET", path="/formPage", summary="Form entry page", description="A form post page for setting a single system property value.", guards=AdminGuard.class ) public Form getFormPage() { return form().method("POST").action("formPagePost").children( h4("Set system property"), "Name: ", input("text").name("name"), br(), "Value: ", input("text").name("value"), br(), br(), button("submit","Click me!").style("float:right") ); } @RestMethod( name="POST", path="/formPagePost", description="Accepts a simple form post of a system property name/value pair.", guards=AdminGuard.class ) public Redirect formPagePost(@FormData("name") String name, @FormData("value") String value) { System.setProperty(name, value); return new Redirect("servlet:/"); // Redirect to the servlet top page. }

The REST API is built on top of Servlets, making them easy to deploy in any JEE environment.

REST Java methods can return any of the following objects:
POJOs, Readers, InputStreams, ZipFiles, Redirects, Streamables, and Writables.

Or add your own handlers for other types.

REST Java methods can be passed any of the following objects in any order:

It's up to you how you want to define your REST methods. As a general rule, there are 3 broad approaches typically used:

Methodology #1 - Annoted parameters

This approach uses annotated parameters for retrieving input from the request.

@RestMethod(name="GET", path="/example1/{p1}/{p2}/{p3}/*") public String example1( @Method String method, // HTTP method. @Path String p1, // Path variables. @Path int p2, @Path UUID p3, @Query("q1") int q1, // Query parameters. @Query("q2") String q2, @Query("q3") UUID q3, @PathRemainder String remainder, // Path remainder after pattern match. @Header("Accept-Language") String lang, // Headers. @Header("Accept") String accept, @Header("DNT") int doNotTrack ) { // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); return output; }

Methodology #2 - Low-level request/response objects

This approach uses low-level request/response objects to perform the same as above.

@RestMethod(name="GET", path="/example2/{p1}/{p2}/{p3}/*") public String example2( RestRequest req, // A direct subclass of HttpServletRequest. RestResponse res // A direct subclass of HttpServletResponse. ) { // HTTP method. String method = req.getMethod(); // Path variables. RequestPathMatch path = req.getPathMatch(); String p1 = path.get("p1", String.class); int p2 = path.get("p2", int.class); UUID p3 = path.get("p3", UUID.class); // Query parameters. RequestQuery query = req.getQuery(); int q1 = query.get("q1", 0, int.class); String q2 = query.get("q2", String.class); UUID q3 = query.get("q3", UUID.class); // Path remainder after pattern match. String remainder = req.getPathMatch().getRemainder(); // Headers. String lang = req.getHeader("Accept-Language"); String accept = req.getHeader("Accept"); int doNotTrack = req.getHeaders().get("DNT", int.class); // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); res.setOutput(output); // Or use getWriter(). }

Methodology #3 - Intermediate-level API objects

This approach is sort of the middle ground where you get access functional area APIs.

@RestMethod(name="GET", path="/example3/{p1}/{p2}/{p3}/*") public String example3( HttpMethod method, // HTTP method. RequestPathMatch path, // Path variables. RequestQuery query, // Query parameters. RequestHeaders headers, // Headers. AcceptLanguage lang, // Specific header classes. Accept accept ) { // Path variables. String p1 = path.get("p1", String.class); int p2 = path.get("p2", int.class); UUID p3 = path.get("p3", UUID.class); // Query parameters. int q1 = query.get("q1", 0, int.class); String q2 = query.get("q2", String.class); UUID q3 = query.get("q3", UUID.class); // Path remainder after pattern match. String remainder = path.getRemainder(); // Headers. int doNotTrack = headers.get("DNT", int.class); // Send back a simple String response String output = String.format( "method=%s, p1=%s, p2=%d, p3=%s, remainder=%s, q1=%d, q2=%s, q3=%s, lang=%s, accept=%s, dnt=%d", method, p1, p2, p3, remainder, q1, q2, q3, lang, accept, doNotTrack); res.setOutput(output); }

All three are completely equivalent. It's up to your own coding preferences which methodology you use.



Lifecycle hooks allow you to hook into lifecycle events of the servlet or REST call. Like @RestMethod methods, the list of parameters are specified by the developer.

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

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

Or if you want to intercept REST calls:

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

The hook events can be broken down into two categories:



Auto-generated OPTIONS pages are constructed from Swagger DTO beans, here shown serialized as HTML:

Swagger documentation can be populated from annotations (as above), resource bundles, or Swagger JSON files.

The page shown above is implemented on the RestServletDefault class in the method below which shows that it's doing nothing more than serializing a Swagger bean which is constructed in the RestRequest object:

@RestMethod(name="OPTIONS", path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); }




Navigatable hierarchies of REST resources are easy to set up either programmatically or through annotations.
The following example is the RootResources class from the REST examples showing how to construct a grouping of resources using the children() annotation:

@RestResource( path="/", title="Root resources", description="Example of a router resource page.", widgets={ PoweredByApache.class, ContentTypeMenuItem.class }, htmldoc=@HtmlDoc( links={ "options: ?method=OPTIONS", "$W{ContentTypeMenuItem}", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/RootResources.java" }, aside={ "<div style='max-width:400px' class='text'>", " <p>This is an example of a 'router' page that serves as a jumping-off point to child resources.</p>", " <p>Resources can be nested arbitrarily deep through router pages.</p>", " <p>Note the options link provided that lets you see the generated swagger doc for this page.</p>", " <p>Also note the source link on these pages to view the source code for the page.</p>", " <p>All content on pages in the UI are serialized POJOs. In this case, it's a serialized array of beans with 2 properties, 'name' and 'description'.</p>", " <p>Other features (such as this aside) are added through annotations.</p>", "</div>" }, footer="$W{PoweredByApache}" ), children={ HelloWorldResource.class, PetStoreResource.class, SystemPropertiesResource.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, ConfigResource.class, LogsResource.class, DockerRegistryResource.class, ShutdownResource.class } ) public class RootResources extends RestServletGroupDefault { /* No code needed! */ }

The above resource when rendered in HTML shows how easy it is to discover and navigate to child resources using a browser:

Resources can be nested arbitrarily deep. The @RestResource and @RestMethod annotations can be applied to any classes, not just servlets. The only requirement is that the top-level resource be a subclass of RestServlet as a hook into the servlet container.

The juneau-examples-rest project includes various other examples that highlight some of the capabilities of the REST servlet API.
For example, the PetStoreResource class shows some advanced features such as using POJO renders and converters, and HTML widgets.

The beans being serialized are shown here:

// Our bean class. public class Pet { @Html(link="servlet:/{id}") // Creates a hyperlink in HTML view. @NameProperty // Links the parent key to this bean. public int id; public String name; public Kind kind; @BeanProperty(format="$%.2f") // Renders price in dollars. public float price; @BeanProperty(swap=DateSwap.RFC2822D.class) // Renders dates in RFC2822 format. public Date birthDate; public int getAge() { Calendar c = new GregorianCalendar(); c.setTime(birthDate); return new GregorianCalendar().get(Calendar.YEAR) - c.get(Calendar.YEAR); } } @Html(render=KindRender.class) // Render as an icon in HTML. public static enum Kind { CAT, DOG, BIRD, FISH, MOUSE, RABBIT, SNAKE } public static class KindRender extends HtmlRender<Kind> { @Override public Object getContent(SerializerSession session, Kind value) { return new Img().src("servlet:/htdocs/"+value.toString().toLowerCase()+".png"); } @Override public String getStyle(SerializerSession session, Kind value) { return "background-color:#FDF2E9"; } }

The QUERY menu item shows the capabilities of Converters which are post-processors that work to filter POJOs after they've been returned by your Java method.
In this case, we're using the Queryable converter that allows us to perform search/view/sort/paging against collections of beans:

The drop-down menu items are implemented through "widgets" which allow you to embed arbitrary HTML, Javascript, and CSS in the HTML view of the page.

@RestMethod( name="GET", path="/", summary="The complete list of pets in the store", // Add 'query' and 'content-types' menu items. widgets={ QueryMenuItem.class, ContentTypeMenuItem.class, StyleMenuItem.class }, // Add our converter for POJO query support. converters=Queryable.class, // Add our menu items in the nav links. htmldoc=@HtmlDoc( links={ "up: request:/..", "options: servlet:/?method=OPTIONS", "$W{QueryMenuItem}", "$W{ContentTypeMenuItem}", "$W{StyleMenuItem}", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/PetStoreResource.java" } ) ) public Collection<Pet> getPets() {

HTML views are highly customizable with abilities such as defining your own look-and-feel and even allowing you to define your own templates.

For example, the PetStore page above rendered in one of the other predefined stylesheets:



Automatic error handling is provided for a variety of conditions:

Other features include:

Additional Information


Juneau Client

The REST client API allows you to access REST interfaces using POJOs as well:

// Create a reusable JSON client. RestClient client = new RestClientBuilder().build(); // The address of the root resource. String url = "http://localhost:10000/systemProperties"; // Do a REST GET against a remote REST interface and convert // the response to an unstructured ObjectMap object. Map m1 = client.doGet(url).getResponse(TreeMap.class); // Add some new system properties. // Use XML as the transport medium. client = new RestClientBuilder(XmlSerializer.class, XmlParser.class).build(); Properties p = new Properties(); p.load(reader); int returnCode = client.doPost(url + "/systemProperties", p).execute();

The client API uses the same serializers and parsers (and subsequently their flexibilty and configurability) as the server side to marshall POJOs back and forth.



The remote proxy interface API allows you to invoke server-side POJO methods on the client side using REST (i.e. RPC over REST):

// Get an interface proxy. IAddressBook ab = restClient.getRemoteableProxy(IAddressBook.class); // Invoke a method on the server side and get the returned result. Person p = ab.createPerson( new Person( "John Smith", "Aug 1, 1999", new Address("My street", "My city", "My state", 12345, true) ) );

There are two ways to expose remoteable proxies on the server side:

  1. Extending from RemoteableServlet.
  2. Using a @RestMethod(name="PROXY") annotation on a Java method.

The RemoteableServlet class is a simple specialized servlet with an abstract getServiceMap() method to define the server-side POJOs:

@RestResource( path="/remote" ) public class SampleRemoteableServlet extends RemoteableServlet { // Our server-side POJO. AddressBook addressBook = new AddressBook(); @Override /* RemoteableServlet */ protected Map<Class<?>,Object> getServiceMap() throws Exception { Map<Class<?>,Object> m = new LinkedHashMap<Class<?>,Object>(); // In this simplified example, we expose the same POJO service under two different interfaces. // One is IAddressBook which only exposes methods defined on that interface, and // the other is AddressBook itself which exposes all methods defined on the class itself (dangerous!). m.put(IAddressBook.class, addressBook); m.put(AddressBook.class, addressBook); return m; } }

The @RestMethod(name="PROXY") approach is easier if you only have a single interface you want to expose. You simply define a Java method whose return type is an interface, and return the implementation of that interface:

// Our exposed proxy object. @RestMethod(name="PROXY", path="/addressbookproxy/*") public IAddressBook getProxy() { return addressBook; }

In either case, the proxy communications layer is pure REST. Parameters passed in on the client side are serialized as an HTTP POST, parsed on the server side, and then passed to the invocation method. The returned POJO is then marshalled back as an HTTP response.

In most cases, you'll want to use JSON or MessagePack as your communications layer since these are the most efficent. Although remoteable proxies work perfectly well for any of the other supported languages. For example, RPC over Turtle!

The parameters and return types of the Java methods can be any of the supported serializable and parsable types in POJO Categories. This ends up being WAY more flexible than other proxy interfaces since Juneau can handle so may POJO types out-of-the-box. Most of the time you don't even need to modify your existing Java implementation code.

The RemoteableServlet class itself shows how sophisticated REST interfaces can be built on the Juneau RestServlet API using very little code. The RemoteableServlet class itself consists of only 53 lines of code, yet is a sophisticated discoverable and self-documenting REST interface. And since the remote proxy API is built on top of REST, it can be debugged using just a browser.



Remoteable proxies can also be used to define interface proxies against 3rd-party REST interfaces. This is an extremely powerful feature that allows you to quickly define easy-to-use interfaces against virtually any REST interface.

Similar in concept to remoteable services defined above, but in this case we simply define our interface with special annotations that tell us how to convert input and output to HTTP headers, query parameters, form post parameters, or request/response bodies.

@Remoteable public interface MyProxyInterface { @RemoteMethod(httpMethod="POST", path="/method") String doMethod(@Header("E-Tag") UUID etag, @Query("debug") boolean debug, @Body MyPojo pojo); } RestClient client = new RestClientBuilder().build(); MyProxyInterface p = client.getRemoteableProxy(MyProxyInterface.class, "http://hostname/some/rest/interface"); String response = p.doMethod(UUID.generate(), true, new MyPojo());

Additional Information


Config

The config file API allows you to interact with INI files using POJOs. A sophisticated variable language is provided for referencing environment variables, system properties, other config file entries, and a host of other types.

#-------------------------- # 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}}

You're probably wondering "why INI files?" The beauty of these INI files is that they're easy to read and modify, yet sophisticated enough to allow you to store arbitrary-complex data structures and retrieve them as simple values or complex POJOs:

// Load our config file ConfigFile f = new ConfigFileBuilder().build("MyIniFile.cfg"); 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");

Values are LAX JSON (i.e. unquoted attributes, single quotes) except for top-level strings which are left unquoted. Any parsable object types are supported as values (e.g. arrays, collections, beans, swappable objects, enums, etc...).

One of the more powerful aspects of the REST servlets is that you can pull values directly from config files by using the "$C" variable in annotations.
For example, the HTML stylesheet for your REST servlet can be defined in a config file like so:

@RestResource( path="/myResource", config="$S{my.config.file}", // Path to config file (here pulled from a system property) stylesheet="$C{MyResourceSettings/myStylesheet}" // Stylesheet location pulled from config file. ) public class MyResource extends RestServlet {

Other features:

Additional Information


Juneau Microservice

The microservice API combines all the features above with a built-in Jetty server to produce a lightweight REST service packaged as three simple files:

The microservice API was originally designed for and particularly suited for use in Docker containers.

REST microservices can also be started programmatically in existing code:

RestMicroservice myRestService = new RestMicroservice() .setConfig("microservice.cfg", false) .setJettyXml("my-jetty.xml"); myRestService.start(); URI uri = myRestService.getURI();

The provided microservice.cfg template file gives you a starting point for defining your microservice:

#================================================================================ # Basic configuration file for SaaS microservices # Subprojects can use this as a starting point. #================================================================================ #================================================================================ # REST settings #================================================================================ [REST] # The location of the jetty.xml file to use for configuring Jetty. jettyXml = jetty.xml # Stylesheet to use for HTML views. # The default options are: # - styles/juneau.css # - styles/devops.css # Other stylesheets can be referenced relative to the servlet package or working # directory. stylesheet = styles/devops.css # What to do when the config file is saved. # Possible values: # NOTHING - Don't do anything. # RESTART_SERVER - Restart the Jetty server. # RESTART_SERVICE - Shutdown and exit with code '3'. saveConfigAction = RESTART_SERVER #================================================================================ # Logger settings # See FileHandler Java class for details. #================================================================================ [Logging] # The directory where to create the log file. # Default is "." logDir = logs # The name of the log file to create for the main logger. # The logDir and logFile make up the pattern that's passed to the FileHandler # constructor. # If value is not specified, then logging to a file will not be set up. logFile = microservice.%g.log # Whether to append to the existing log file or create a new one. # Default is false. append = # The SimpleDateFormat format to use for dates. # Default is "yyyy.MM.dd hh:mm:ss". dateFormat = # The log message format. # The value can contain any of the following variables: # {date} - The date, formatted per dateFormat. # {class} - The class name. # {method} - The method name. # {logger} - The logger name. # {level} - The log level name. # {msg} - The log message. # {threadid} - The thread ID. # {exception} - The localized exception message. # Default is "[{date} {level}] {msg}%n". format = # The maximum log file size. # Suffixes available for numbers. # See ConfigFile.getInt(String,int) for details. # Default is 1M. limit = 10M # Max number of log files. # Default is 1. count = 5 # Default log levels. # Keys are logger names. # Values are serialized Level POJOs. levels = { org.apache.juneau:'INFO' } # Only print unique stack traces once and then refer to them by a simple 8 character hash identifier. # Useful for preventing log files from filling up with duplicate stack traces. # Default is false. useStackTraceHashes = true # The default level for the console logger. # Default is WARNING. consoleLevel = #================================================================================ # System properties #-------------------------------------------------------------------------------- # These are arbitrary system properties that are set during startup. #================================================================================ [SystemProperties] # Configure Jetty for StdErrLog Logging org.eclipse.jetty.util.log.class = org.eclipse.jetty.util.log.StrErrLog # Jetty logging level org.eclipse.jetty.LEVEL = WARN

Various predefined reusable REST resource classes are provided for accessing log files, viewing and editing the config file, etc... These allow you to quickly put together sophisticated REST microservices.

Additional Information


More information

*ALL* user documentation for this project is maintained in its Javadocs. If it's not in the Javadocs, it doesn't exist.

The top-level Overview document is a good starting point for developers wishing to get up-to-speed quickly.

*** fín ***