juneau 9.0.0 API

Apache Juneau 9.0.0 Documentation

Table of Contents
  1. Overview

    1. Marshallingcreated: 9.0.0

    2. End-to-End RESTcreated: 9.0.0

    3. REST Server

    4. REST Clientcreated: 9.0.0

    5. DTOscreated: 9.0.0

    6. Config Filescreated: 9.0.0

    7. Fluent Assertionscreated: 9.0.0

    8. General Designcreated: 9.0.0

  2. juneau-marshall

    1. Marshallers

    2. Serializers and Parsersupdated: 9.0.0

    3. Bean Contextsupdated: 9.0.0

    4. Java Beans Supportcreated: 8.2.0, updated: 9.0.0

      1. @Bean Annotationupdated: 8.2.0,9.0.0

      2. @Beanp Annotationupdated: 8.1.0,8.1.2,9.0.0

      3. @Beanc Annotationupdated: 8.1.0,8.1.2

      4. @BeanIgnore Annotation

      5. @NameProperty Annotation

      6. @ParentProperty Annotation

      7. POJO Buildersupdated: 9.0.0

      8. Bypass Serialization using Readers and InputStreams

    5. HTTP Part Serializers and Parsersupdated: 8.2.0,9.0.0

    6. Context Settingsupdated: 8.1.3,9.0.0

    7. Context Annotationscreated: 8.1.0, updated: 8.1.3,8.2.0,9.0.0

    8. JsonMap and JsonListupdated: 8.2.0

    9. Complex Data Typescreated: 9.0.0

    10. SerializerSets and ParserSetsupdated: 9.0.0

    11. Swapsupdated: 9.0.0

      1. Default Swaps

      2. Auto-detected swapscreated: 8.1.0

      3. Per-media-type Swapsupdated: 8.1.0,8.2.0

      4. One-way Swaps

      5. @Swap Annotationupdated: 8.0.0,9.0.0

      6. Templated Swaps

      7. Surrogate Classes

    12. Dynamically Applied Annotationscreated: 8.1.3, updated: 9.0.0

    13. Bean Names and Dictionariesupdated: 9.0.0

      1. Bean Subtypes

    14. Virtual Beansupdated: 9.0.0

    15. Non-Tree Models and Recursion Detectionupdated: 9.0.0

    16. Parsing into Generic Modelsupdated: 8.2.0

    17. Reading Continuous Streamsupdated: 9.0.0

    18. URIsupdated: 9.0.0

    19. Comparison with Jacksonupdated: 9.0.0

    20. POJO Categories

    21. Simple Variable Language

      1. SVL Variablesupdated: 8.0.0,8.1.0

      2. VarResolvers and VarResolverSessionsupdated: 9.0.0

      3. VarResolver.DEFAULTcreated: 8.1.0

      4. Other Notes

    22. Encoderscreated: 9.0.0

    23. Object Toolscreated: 9.0.0

    24. JSON Details

      1. JSON Methodology

      2. JSON Serializersupdated: 9.0.0

      3. JSON 5updated: 9.0.0

      4. JSON Parsersupdated: 9.0.0

      5. @Json Annotation

    25. JSON-Schema Support

    26. XML Details

      1. XML Methodology

      2. XML Serializersupdated: 9.0.0

      3. XML Parsersupdated: 9.0.0

      4. @Bean(typeName) Annotation

      5. @Xml(childName) Annotation

      6. @Xml(format) Annotation

      7. Namespaces

    27. HTML Details

      1. HTML Methodology

      2. HTML Serializersupdated: 9.0.0

      3. HTML Parsersupdated: 9.0.0

      4. @Html Annotation

      5. @Html(render) Annotation

      6. HtmlDocSerializerupdated: 9.0.0

      7. BasicHtmlDocTemplate

      8. Custom Templates

    28. HTML-Schema Support

    29. UON Details

      1. UON Methodology

      2. UON Serializersupdated: 9.0.0

      3. UON Parsersupdated: 9.0.0

    30. URL-Encoding Details

      1. URL-Encoding Methodology

      2. URL-Encoding Serializersupdated: 9.0.0

      3. URL-Encoding Parsersupdated: 9.0.0

      4. @UrlEncoding Annotation

    31. MessagePack Details

      1. MessagePack Serializersupdated: 9.0.0

      2. MessagePack Parsersupdated: 9.0.0

    32. OpenAPI Detailsupdated: 8.2.0

      1. OpenAPI Methodologyupdated: 8.2.0

      2. OpenAPI Serializersupdated: 8.2.0,9.0.0

      3. OpenAPI Parsersupdated: 8.2.0,9.0.0

    33. Best Practices

  3. juneau-marshall-rdf

  4. juneau-dto

    1. HTML5

    2. Atom

    3. Swagger

    4. Swagger UI

  5. juneau-config

    1. Overviewupdated: 9.0.0

      1. Syntax Rules

    2. Reading Entriesupdated: 9.0.0

      1. POJOsupdated: 9.0.0

      2. Arrays

      3. Java Collection Framework Objectsupdated: 9.0.0

      4. Binary Dataupdated: 9.0.0

    3. Variablesupdated: 9.0.0

      1. Logic Variables

    4. Modded/Encoded Entriesupdated: 9.0.0

    5. Sectionsupdated: 9.0.0

    6. Setting Values

      1. File System Changes

      2. Custom Entry Serialization

      3. Setting Values in Bulk

    7. Listeners

    8. Serializingupdated: 9.0.0

    9. Importsupdated: 8.1.0

    10. Config Storesupdated: 9.0.0

      1. MemoryStore

      2. FileStoreupdated: 9.0.0

      3. Custom ConfigStoresupdated: 9.0.0

      4. ConfigStore Listeners

    11. Read-only Configsupdated: 9.0.0

    12. Closing Configs

    13. System Default Configcreated: 8.0.0, updated: 8.1.0

  6. juneau-assertionscreated: 9.0.0

    1. Overviewcreated: 9.0.0

  7. juneau-rest-commoncreated: 9.0.0

    1. Helper Classescreated: 9.0.0

    2. Annotationscreated: 9.0.0

    3. HTTP Headerscreated: 9.0.0

    4. HTTP Partscreated: 9.0.0

    5. HTTP Entities and Resourcescreated: 9.0.0

    6. HTTP Responsescreated: 9.0.0

    7. Remote Proxy Interfacescreated: 9.0.0

  8. juneau-rest-serverupdated: 9.0.0

    1. Overviewcreated: 9.0.0

    2. @Rest-Annotated Classesupdated: 8.1.2,9.0.0

      1. Predefined Classesupdated: 9.0.0

      2. Child Resourcesupdated: 9.0.0

      3. Path Variablesupdated: 9.0.0

      4. Deploymentupdated: 9.0.0

      5. Lifecycle Hooksupdated: 9.0.0

    3. @RestOp-Annotated Methodsupdated: 9.0.0

      1. Inferred HTTP Methods and Pathsupdated: 9.0.0

      2. Java Method Parametersupdated: 9.0.0

      3. Java Method Return Typesupdated: 9.0.0

      4. Java Method Throwable Typesupdated: 9.0.0

      5. Path Patternsupdated: 9.0.0

      6. Matchersupdated: 9.0.0

      7. Overloading HTTP Methods

      8. Additional Informationupdated: 9.0.0

    4. HTTP Partsupdated: 9.0.0

      1. Part Marshallersupdated: 8.1.0,9.0.0

      2. HTTP Part Annotationsupdated: 8.1.0,9.0.0

      3. Default Partscreated: 9.0.0

      4. @Request Beansupdated: 8.1.0,9.0.0

      5. @Response Beansupdated: 8.1.0,9.0.0

      6. HTTP Part APIscreated: 9.0.0

    5. Marshallingupdated: 9.0.0

    6. Form Postsupdated: 9.0.0

    7. Guardsupdated: 9.0.0

    8. Convertersupdated: 9.0.0

    9. Localized Messagesupdated: 8.2.0,9.0.0

    10. Encodersupdated: 9.0.0

    11. Configuration Filesupdated: 9.0.0

    12. SVL Variablesupdated: 9.0.0

    13. Static filesupdated: 9.0.0

    14. Client Versioningupdated: 9.0.0

    15. Swagger

      1. BasicRestServlet/BasicRestObjectupdated: 8.1.0,9.0.0

      2. Basic Swagger Infoupdated: 9.0.0

      3. Tags

      4. Operations

      5. Parametersupdated: 9.0.0

      6. Responses

      7. Modelsupdated: 9.0.0

      8. SwaggerUI.css

    16. REST method execution statisticscreated: 8.1.3, updated: 9.0.0

    17. @HtmlDocConfigupdated: 8.1.0,9.0.0

      1. User Interfaces (UI) vs. Developer Interfaces (DI)

      2. Widgetsupdated: 9.0.0

      3. Predefined Widgetsupdated: 9.0.0

      4. UI Customizationupdated: 9.0.0

      5. Stylesheetsupdated: 8.1.0,9.0.0

    18. Logging / Debuggingcreated: 9.0.0

    19. HTTP Status Codesupdated: 9.0.0

    20. Built-in Parametersupdated: 9.0.0

    21. Using with OSGi

    22. RestContextcreated: 9.0.0

    23. RestOpContextcreated: 9.0.0

    24. Response Processorscreated: 9.0.0

    25. REST/RPCupdated: 8.0.0,9.0.0

    26. Serializing URIsupdated: 9.0.0

    27. Utility Beanscreated: 9.0.0

    28. Using with HTML Beanscreated: 9.0.0

    29. Other Notes

  9. juneau-rest-server-springbootcreated: 8.0.0, updated: 9.0.0

    1. Overviewcreated: 8.0.0, updated: 9.0.0

  10. juneau-rest-clientupdated: 9.0.0

    1. POJO Marshallingcreated: 8.2.0, updated: 9.0.0

    2. Request Partscreated: 8.2.0, updated: 9.0.0

    3. Request Contentcreated: 8.2.0, updated: 9.0.0

    4. Response Statuscreated: 8.1.0, updated: 9.0.0

    5. Response Headerscreated: 8.2.0, updated: 9.0.0

    6. Response Contentcreated: 8.2.0, updated: 9.0.0

    7. Custom Call Handlerscreated: 8.2.0, updated: 9.0.0

    8. Interceptorscreated: 8.2.0

    9. REST Proxiescreated: 8.2.0, updated: 9.0.0

      1. @Remoteupdated: 9.0.0

      2. @RemoteOpupdated: 9.0.0

      3. @Contentupdated: 9.0.0

      4. @FormDataupdated: 9.0.0

      5. @Queryupdated: 9.0.0

      6. @Headerupdated: 9.0.0

      7. @Pathupdated: 9.0.0

      8. @Requestupdated: 9.0.0

      9. @Responseupdated: 9.0.0

      10. Dual-purpose (end-to-end) interfacescreated: 8.0.0

    10. Logging and Debuggingcreated: 8.2.0, updated: 9.0.0

    11. Customizing HttpClientcreated: 8.2.0, updated: 9.0.0

    12. Extending RestClientcreated: 8.2.0

    13. Authenticationupdated: 8.2.0

      1. BASIC Authentication

      2. FORM-based Authentication

      3. OIDC Authentication

  11. juneau-rest-mockcreated: 8.1.0, updated: 8.2.0

    1. MockRestClientcreated: 8.2.0, updated: 9.0.0

  12. juneau-microservice-corecreated: 8.1.0

    1. Microservice Overviewcreated: 8.0.0

    2. Lifecycle Methodscreated: 8.0.0

    3. Argscreated: 8.0.0

    4. Manifestcreated: 8.0.0

    5. Configcreated: 8.0.0

    6. System propertiescreated: 8.0.0

    7. VarResolvercreated: 8.0.0

    8. Console Commandscreated: 8.0.0

    9. Listenerscreated: 8.0.0

  13. juneau-microservice-jettycreated: 8.1.0

    1. Overviewcreated: 8.0.0

    2. Lifecycle Methodscreated: 8.0.0

    3. Resource Classescreated: 8.0.0

    4. Predefined Resource Classescreated: 8.0.0

    5. Configcreated: 8.0.0

    6. Jetty.xml filecreated: 8.0.0

    7. UI Customizationcreated: 8.0.0

    8. Extending JettyMicroservicecreated: 8.0.0

  14. my-jetty-microservicecreated: 8.1.0

    1. Installing in Eclipsecreated: 8.0.0

    2. Running in Eclipsecreated: 8.0.0

    3. Building and Running from Command-Linecreated: 8.0.0

  15. my-springboot-microservicecreated: 8.0.0

    1. Installing in Eclipsecreated: 8.0.0

    2. Running in Eclipsecreated: 8.0.0

    3. Building and Running from Command-Linecreated: 8.0.0

  16. juneau-petstorecreated: 8.2.0, updated: 9.0.0

    1. Running the Pet Store Appcreated: 9.0.0

    2. juneau-petstore-apicreated: 9.0.0

    3. juneau-petstore-clientcreated: 9.0.0

    4. juneau-petstore-servercreated: 9.0.0

  17. Security Best-Practices

    1. juneau-marshallcreated: 8.2.0

    2. juneau-svlcreated: 8.2.0

    3. juneau-rest-servercreated: 8.2.0

  18. v9.0 Migration Guide

1 - Overview

About

Apache Juneau™ is a single cohesive Java ecosystem for marshalling Java objects to a wide variety of language types and creating annotation-based REST end-to-end server and client APIs.

The Juneau ecosystem consists of the following parts:

CategoryMaven ArtifactsDescriptionPrereqs
juneau-core juneau-marshall
  • Serializers and parsers for JSON (various flavors), XML, HTML, URL-Encoding, UON, OpenAPI, PlainText, CSV, SOAP, and MessagePack.
  • Apache HttpCore 4.4
juneau-marshall-rdf
  • Serializers and parsers for RDF/XML (various flavors), N3, NTriple, and Turtle.
  • Apache HttpCore 4.4
  • Apache Jena 2.7.1
juneau-dto
  • Data Transfer Objects for HTML5, Atom, Cognos, JSON-Schema, and Swagger
  • None
juneau-config
  • Configuration File API
  • None
juneau-assertions
  • Fluent-style assertions API
  • None
juneau-rest juneau-rest-common
  • REST APIs common to client and server side.
  • Apache HttpCore 4.4
juneau-rest-server
  • REST Servlet API
  • Servlet 3.1+
juneau-rest-server-springboot
  • REST Spring Boot integration
  • Spring Boot 2.0+
juneau-rest-client
  • REST Client API
  • Apache HttpClient 4.5
juneau-rest-mock
  • REST Testing API
  • Apache HttpClient 4.5
my-springboot-microservice
  • Spring Boot developer template
  • Spring Boot 2.0+
juneau-examples juneau-examples-core
  • Core code examples
juneau-examples-rest
  • REST code examples
juneau-all juneau-all Combination of the following:
  • juneau-marshall
  • juneau-dto
  • juneau-config
  • juneau-assertions
  • juneau-rest-common
  • juneau-rest-server
  • juneau-rest-client
  • Servlet 3.1+
  • Apache HttpClient 4.5+

The current version of Juneau is 9.0.0. The easiest way to pull in the library is through the following maven dependency:

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-all</artifactId> <version>9.0.0</version> </dependency>

If you would like to work with the bleeding-edge code, you can access the 9.0.1 version through the following repository:

<pluginRepositories> <pluginRepository> <id>apache.snapshots</id> <url>http://repository.apache.org/snapshots/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </pluginRepository> </pluginRepositories>

Each of the components are also packaged as stand-alone OSGi modules.

Features
  • Fast memory-efficient serialization.
  • Fast, safe, memory-efficient parsing. Parsers are not susceptible to deserialization attacks.
  • KISS is our mantra! No auto-wiring. No code generation. No dependency injection. Just add it to your classpath and use it. Extremely simple unit testing!
  • Enjoyable to use
  • Tiny - ~1MB
  • Exhaustively tested
  • Lots of up-to-date documentation and examples
  • Minimal library dependencies making it ideal for use in uber-jars.
  • Built on top of Servlet and Apache HttpClient APIs that allow you to use the newest HTTP/2 features such as request/response multiplexing and server push.
  • All modules work with Java 8 through at least 18.

1.1 - Marshallingcreated: 9.0.0

The juneau-marshall library includes easy-to-use and highly customizable serializers and parsers based around a common API. It allows you to marshall Java POJOs directly to and from a wide variety of language types without the need for intermediate Document Object Models making them extremely efficient.

Supported languages include:

  • JSON
  • XML
  • HTML
  • UON
  • URL-Encoding
  • MessagePack
  • OpenAPI
  • SOAP/XML
  • CSV
  • YAML (coming soon)
  • RDF/XML
  • RDF/XML-Abbrev
  • N-Triple
  • Turtle
  • N3

  • The marshalling support can be thought of as similar to Jackson except for support of a wide variety of languages. Additionally, JSON marshalling is about 20% faster than Jackson yet supports the same usecases.

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; } // Produces: // "{"name":"John Smith","age":21}" String json = Json.of(new Person());

Parsing back into POJOs is equally simple for any of the supported languages. Language fragments are also supported.

// Parse a JSON object as a bean. String json = "{\"name\":\"John Smith\","\age\":21}"; Person person = Json.to(json, Person.class);

Marshalling support is provided for a wide variety of POJO types including:

  • Primitives and primitive objects
  • Beans
  • Java Collections Framework objects (e.g. Collections, Maps)
  • Arrays
  • POJOs
Serializer/Parser Builders

Marshallers like the one shown above are pairings of serializers and parsers. Serializers and parsers are builder-based using fluent methods allowing you to quickly create, clone, and modify them in single lines of code.

// Create a serializer from scratch programmatically using a builder. JsonSerializer serializer = JsonSerializer .create() .simple() // Simple mode .sq() // Use single quotes .timeZone(TimeZone.GMT) // For timezone-specific serialization .locale(Locale.JAPAN) // For locale-specific serialization .sortCollections() .sortProperties() .keepNullProperties() .trimStrings() .beanMethodVisibility(PROTECTED) // Control which fields/methods are serialized .beanDictionary( // Adds type variables for resolution during parsing MyBeanA.class, MyBeanB.class ) .debug() // Debug mode .build();

Many POJOs such as primitives, beans, collections, arrays, and classes with various known constructors and methods are serializable out-of-the-box.

Swaps allow you to replace non-serializable objects with serializable equivalents. The org.apache.juneau.swaps package contains a variety of predefined swaps.

// Create a serializer from scratch programmatically using a builder. JsonSerializer serializer = JsonSerializer .create() .swaps( // 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 ) .build();

Any POJO that doesn't fit into the category of a bean/collection/array/primitive and doesn't have a swap associated with it is converted to simple strings. By default, various instance and static methods and constructors on POJO classes are automatically detected and supported for marshalling a POJO to and from a string.

Bean Annotations

Beans and POJO classes, methods, fields, and constructors can also be annotated with a variety of annotations to customize how they are marshalled:

// Sort bean properties by name. // Exclude city/state from marshalling. @Bean(sort=true, excludeProperties="city,state") public class Address { ... } // Specify an implementation class for an interface. @Marshalled(implClass=AutomobileImpl.class) public interface Automobile { ... }

As a general rule, any capabilities provided by bean annotations can be programmatically specified via the builder APIs. This allows the marshallers to be used equivalently on either your own code that you have access to, or external code where you only have access to binaries.

Configuration Annotations

Serializers and parsers can also be configured using annotations.

@BeanConfig(sortProperties="true") @SerializerConfig(quoteChar="'") @RdfConfig(rdfxml_tab="5", addRootProperty="true") public class MyAnnotatedClass {...} // Create a serializer configured using annotations. JsonSerializer serializer = JsonSerializer .create() .applyAnnotations(MyAnnotatedClass.class) .build();

Config annotations are extensively used in the REST Servlet APIs to configure how POJOs are marshalled through REST interfaces.

Config variables also support embedded variables for resolving settings at runtime.

// Sort properties depending on value of system property "sortProperties". @BeanConfig(sortProperties="$S{sortProperties,false}")

Default values for config settings can be overridden via system properties or environment variables. For example, the system property "BeanContext.sortProperties" or environment variable "BEANCONTEXT_SORTPROPERTIES" can be used to set the default value for the sort properties setting.

Bean annotations can also be programmatically attached to POJOs using config annototations like so:

@Bean(onClass=Address.class, sort=true, excludeProperties="city,state") public class MyAnnotatedClass {...}

JSON 5 Marshalling

The Json5Serializer class can be used to serialized POJOs into JSON 5 notation.

JSON 5 is similar to JSON except for the following:

  • JSON attributes are only quoted when necessary.
  • Uses single-quotes for quoting.
Examples:

// Some free-form JSON. Map map = JsonMap.of( "foo", "x1", "_bar", "x2", " baz ", "x3", "123", "x4", "return", "x5", "", "x6" );

// Serialized to standard JSON { "foo": "x1", "_bar": "x2", " baz ": "x3", "123": "x4", "return": "x5", "": "x6" }

// Serialized to JSON 5 { foo: 'x1', _bar: 'x2', ' baz ': 'x3', // Quoted due to embedded spaces. '123': 'x4', // Quoted to prevent confusion with number. 'return': 'x5', // Quoted because it's a keyword. '': 'x6' // Quoted because it's an empty string. }

JSON 5 is still valid Javascript. The advantage to simplified JSON is you can represent it in a Java String in minimal form with minimal escaping. This is particularly useful in cases such as unit testing where you can easily validate POJOs by simplifying them to JSON 5 and do a simple string comparison.

WriterSerializer serializer = Json5Serializer.DEFAULT; assertString(serializer.toString(myPojo)).is("{foo:'bar',baz:123}");

UON Marshalling

The Marshalling API also supports UON (URL-Encoded Object Notation). It 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 these HTTP constructs which is not possible in other languages such as JSON.

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

OpenAPI Marshalling

The Marshalling API also supports schema-based OpenAPI serialization. It allows HTTP parts to be marshalled to-and-from POJOs based on OpenAPI schema definitions.

import static org.apache.juneau.httpart.HttpPartSchema.*; // Schema - Pipe-delimited list of comma-delimited longs. HttpPartSchema schema = tArrayPipes().items( tArrayCsv().items( tInt64() ) ).build(); // Our value to serialize Object value = new long[][]{{1,2,3},{4,5,6},{7,8,9}}; // Produces "1,2,3|4,5,6|7,8,9" String output = OpenApi.of(schema, value);

Schema-based serialization and parsing is used heavily in both the server and client REST APIs with built-in schema validations support in various HTTP part annotations.

// REST server method with HTTP parts using schema validation. @RestGet public void doGet( @Query(name="myParam", schema=@Schema(min=1, max=32)) int myParam, @Header("MyHeader", schema=@Schema(pattern="foo.*")) String p2 ) {...}

JsonMap/JsonList

The JsonMap and JsonList collections classes allow you to programmatically build generic JSON data structures. They are similar in concept to JSONObject and JSONArray that you find in other JSON marshalling APIs but can be used to generate DOMs in any of the supported languages.

// Create JSON strings from scratch using fluent-style code. String myMap = JsonMap.create().append("foo","bar").asJson(); String myList = JsonList.of("foo", 123, null, jsonObject).asJson(); // Parse directly from JSON into generic DOMs. Map<String,Object> myMap = JsonMap.ofJson("{foo:'bar'}"); List<Object> myList = JsonList.ofJson("['foo',123,null]");

These classes provide lots of convenience methods including:

  • Methods for direct marshalling to/from any of the other supported languages.
  • Methods for quick conversions to other data types including collections, beans, arrays, etc...
Serializer and Parser Sets

SerializerSet and ParserSet 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. SerializerSet serializerSet = SerializerSet .create() .add(JsonSerializer.class, UrlEncodingSerializer.class); .forEach(x -> x.swaps(CalendarSwap.ISO8601DT.class)) .forEachWS(x -> x.useWhitespace()) .build(); // Find the appropriate serializer by Accept type and serialize our POJO to the specified writer. // Fully RFC2616 compliant. serializerSet .getSerializer("text/invalid, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0") .serialize(person, myWriter); // Construct a new parser group with configuration parameters that get applied to all parsers. ParserSet parserSet = ParserSet .create() .add(JsonParser.class, UrlEncodingParser.class); .forEach(x -> x.swaps(CalendarSwap.ISO8601DT.class)) .build(); Person person = parserSet .getParser("text/json") .parse(myReader, Person.class);

SVL Variables

The 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 SVL variables are used widely throughout various annotations defined in Juneau allowing many features to be configured via external sources such as configuration files or environment variables/system properties. The SVL APIs are extensible allowing for the addition of new types of variables.

1.2 - End-to-End RESTcreated: 9.0.0

The juneau-rest-server and juneau-rest-client libraries provide server and client side REST capabilities that can be used by themselves, or together to create simplified yet sophisticated Java-based REST communications layers that completely hide away the complexities of the REST protocol.

A typical pattern is to define a REST API on the server side:

@Rest(path="/petstore") public class PetStoreRest { @RestPost(path="/pets", guards=AdminGuard.class) public Ok addPet( @Content CreatePet createPetBean, @Header("E-Tag") UUID etag, @Query("debug") boolean debug ) throws BadRequest, Unauthorized, InternalServerError { // Process request here. return Ok.OK; // Standard 400-OK response. } }

Then define a Java interface that can be provided to consumers of your API to access your REST API:

@Remote(path="/petstore") public interface PetStoreClient { @RemotePost("/pets") Ok addPet( @Content CreatePet createPet, @Header("E-Tag") UUID etag, @Query("debug") boolean debug ) throws BadRequest, Unauthorized, InternalServerError; }

Note that you may choose to have your service class implement your interface. The REST libraries will happily look for annotations defined on methods of parent classes and interfaces. It's up to you how you want to design it.

Finally, the RestClient class is used to construct a remote proxy to our REST service:

// Use a RestClient with default JSON 5 support and BASIC auth. RestClient client = RestClient.create().json5().basicAuth(...).build(); // Instantiate our proxy interface. PetStoreClient store = client.getRemote(PetStoreClient.class, "http://localhost:10000"); // Use it to create a pet. CreatePet createPet = new CreatePet("Fluffy", 9.99); Pet pet = store.addPet(createPet, UUID.randomUUID(), true);

The call above translates to the following REST call:

POST http://localhost:10000/petstore/pets?debug=true HTTP/1.1 Accept: application/json Content-Type: application/json Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== E-Tag: 475588d4-0b27-4f56-9296-cc683251d314 { name: 'Fluffy', price: 9.99 }

It looks simplistic but the server and client APIs are highly sophisticated libraries that allow you to perform complex tasks using very little code.

1.3 - REST Server

@Rest-Annotated Resources

A REST resource is simply a Java class annotated with Rest. The most common case is a class that extends BasicRestServlet, which itself is simply an extension of HttpServlet which allows it to be deployed as a servlet.

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

This is what it looks like in a browser.

http://localhost:10000/helloWorld

  • Parsers for request bodies are selected based on the request Content-Type header.
  • Serializers for response bodies are selected based on the request Accept header.
    • In this case, it's the HtmlDocSerializer serializer based on the browser's default Accept header that's asking for HTML.
  • REST resource classes and methods can be annotated with configuration annotations for the serializers and parsers (such as @HtmlConfig and @BeanConfig shown above).
  • Annotations such as the title, summary, and descriptions shown above are used for auto-generated Swagger UI pages (described later).
REST Children

Child Resources are REST servlets or objects that are linked to parent resources through the @Rest(children) annotation.

Example:

/** Parent Resource */ @Rest( path="/parent", children={ MyChildResource.class } ) public MyParentResource extends BasicRestServlet {...}

/** Child Resource */ @Rest( path="/child" // Path relative to parent resource. ) // Note that we don't need to extend from RestServlet. public MyChildResource implements BasicRestObject { ... }

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

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

Predefined Configuration Interfaces

The servlets in the previous section implemented the BasicUniversalConfig which simply defines a preconfigured set of annotations that get inherited by the child classes:

/** * Predefined configuration for a REST resource that supports all languages * and provides common default configuration values. */ @Rest( // Default serializers for all Java methods in the class. serializers={ HtmlDocSerializer.class, HtmlStrippedDocSerializer.class, HtmlSchemaDocSerializer.class, JsonSerializer.class, Json5Serializer.class, JsonSchemaSerializer.class, XmlDocSerializer.class, UonSerializer.class, UrlEncodingSerializer.class, OpenApiSerializer.class, MsgPackSerializer.class, SoapXmlSerializer.class, PlainTextSerializer.class, CsvSerializer.class }, // Default parsers for all Java methods in the class. parsers={ JsonParser.class, Json5Parser.class, XmlParser.class, HtmlParser.class, UonParser.class, UrlEncodingParser.class, OpenApiParser.class, MsgPackParser.class, PlainTextParser.class, CsvParser.class } ) public interface BasicUniversalConfig extends DefaultConfig, DefaultHtmlConfig {}

/** * Predefined REST configuration that defines common default values for all configurations. */ @Rest( // Configuration file. config="$S{j.configFile,$E{J_CONFIG_FILE,SYSTEM_DEFAULT}}", // Standard fields. path="", roleGuard="", rolesDeclared="", // Configuration beans. converters={}, encoders={IdentityEncoder.class}, guards={}, parsers={}, partParser=OpenApiParser.class, partSerializer=OpenApiSerializer.class, responseProcessors={ ReaderProcessor.class, InputStreamProcessor.class, ThrowableProcessor.class, HttpResponseProcessor.class, HttpResourceProcessor.class, HttpEntityProcessor.class, ResponseBeanProcessor.class, PlainTextPojoProcessor.class, SerializedPojoProcessor.class }, restOpArgs={ AttributeArg.class, ContentArg.class, FormDataArg.class, HasFormDataArg.class, HasQueryArg.class, HeaderArg.class, HttpServletRequestArgs.class, HttpServletResponseArgs.class, HttpSessionArgs.class, InputStreamParserArg.class, MethodArg.class, ParserArg.class, PathArg.class, QueryArg.class, ReaderParserArg.class, RequestBeanArg.class, ResponseBeanArg.class, ResponseHeaderArg.class, ResponseCodeArg.class, RestContextArgs.class, RestSessionArgs.class, RestOpContextArgs.class, RestOpSessionArgs.class, RestRequestArgs.class, RestResponseArgs.class, DefaultArg.class }, serializers={}, // Configurable settings. allowedHeaderParams="$S{j.allowedHeaderParams,$E{J_ALLOWED_HEADER_PARAMS,Accept,Content-Type}}", allowedMethodHeaders="$S{j.allowedMethodHeaders,$E{J_ALLOWED_METHOD_HEADERS,}}", allowedMethodParams="$S{j.allowedMethodParams,$E{J_ALLOWED_METHOD_PARAMS,HEAD,OPTIONS}}", clientVersionHeader="$S{j.clientVersionHeader,$E{J_CLIENT_VERSION_HEADER,Client-Version}}", debug="$S{j.debug,$E{J_DEBUG,}}", debugOn="$S{j.debugOn,$E{J_DEBUG_ON,}}", defaultAccept="$S{j.defaultAccept,$E{J_DEFAULT_ACCEPT,}}", defaultCharset="$S{j.defaultCharset,$E{J_DEFAULT_CHARSET,UTF-8}}", defaultContentType="$S{j.defaultContentType,$E{J_DEFAULT_CONTENT_TYPE,}}", defaultRequestAttributes="$S{j.defaultRequestAttributes,$E{J_DEFAULT_REQUEST_ATTRIBUTES,}}", defaultRequestHeaders="$S{j.defaultRequestHeaders,$E{J_DEFAULT_REQUEST_HEADERS,}}", defaultResponseHeaders="$S{j.defaultResponseHeaders,$E{J_DEFAULT_RESPONSE_HEADERS,}}", disableContentParam="$S{j.disableContentParam,$E{J_DISABLE_CONTENT_PARAM,false}}", maxInput="$S{j.maxInput,$E{J_MAX_INPUT,1000000}}", messages="$S{j.messages,$E{J_MESSAGES,}}", renderResponseStackTraces="$S{j.renderResponseStackTraces,$E{J_RENDER_RESPONSE_STACK_TRACES,false}}", uriAuthority="$S{j.uriAuthority,$E{J_URI_AUTHORITY,}}", uriContext="$S{j.uriContext,$E{J_URI_CONTEXT,}}", uriRelativity="$S{j.uriRelativity,$E{J_URI_RELATIVITY,}}", uriResolution="$S{j.uriResolution,$E{J_URI_RESOLUTION,}}", // Metadata settings. consumes={}, description="", produces={}, siteName="$S{j.siteName,$E{J_SITE_NAME,}}", swagger=@Swagger, title="$S{j.title,$E{J_TITLE,}}", // Injectable/overridable beans. beanStore=BeanStore.Void.class, // Defaults to BeanStore. callLogger=CallLogger.Void.class, // Defaults to BasicCallLogger. debugEnablement=DebugEnablement.Void.class, // Defaults to BasicDefaultEnablement. fileFinder=FileFinder.Void.class, // Defaults to BasicFileFinder. staticFiles=StaticFiles.Void.class, // Defaults to BasicStaticFiles. swaggerProvider=SwaggerProvider.Void.class, // Defaults to BasicSwaggerProvider. // Overridable context classes. contextClass=RestContext.class, restChildrenClass=RestChildren.class, restOpContextClass=RestOpContext.class, restOperationsClass=RestOperations.class ) @BeanConfig( // When parsing generated beans, ignore unknown properties // that may only exist as getters and not setters. ignoreUnknownBeanProperties="true", ignoreUnknownEnumValues="true" ) @SerializerConfig( // Enable automatic resolution of URI objects to root-relative values. uriResolution="ROOT_RELATIVE" ) public interface DefaultConfig {}

/** * Predefined REST configuration that defines common default values the HTML Doc serializer. */ @HtmlDocConfig( // Default page header contents. header={ "<h1>$RS{title}</h1>", // Use @Rest(title) "<h2>$RS{operationSummary,description}</h2>", // Use either @RestOp(summary) or @Rest(description) "$C{REST/header}" // Extra header HTML defined in external config file. }, // Basic page navigation links. navlinks={ "up: request:/.." }, // Default stylesheet to use for the page. // Can be overridden from external config file. // Default is DevOps look-and-feel (aka Depression look-and-feel). stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}", // Default contents to add to the <head> section of the HTML page. // Use it to add a favicon link to the page. head="$C{REST/head}", // No default page footer contents. // Can be overridden from external config file. footer="$C{REST/footer}", // By default, table cell contents should not wrap. nowrap="true" ) public interface DefaultHtmlConfig {}

The org.apache.juneau.rest.config package contains other basic configurations for use. Annotations are aggregated from child-to-parent order allowing for these basic configurations to be extended and modified, or you can create your own annotations from scratch.

REST Group Pages

The BasicRestServletGroup class provides a default "router" page for child resources when a parent resource is nothing more than a grouping of child resources.

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

/** * Sample REST resource showing how to implement a "router" resource page. */ @Rest( path="/", title="Root resources", description="Example of a router resource page.", children={ HelloWorldResource.class, PetStoreResource.class, DtoExamples.class, ConfigResource.class, LogsResource.class, ShutdownResource.class } ) public class RootResources extends BasicRestServletGroup { // NO CODE!!! }

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

http://localhost:10000

REST Resource Methods

The real power behind the REST server API is the ability to define Java methods as REST endpoints.

Example:

@RestPost(path="/pets", guards=AdminGuard.class) public Ok addPet( @Content CreatePet createPetBean, @Header("E-Tag") UUID etag, @Query("debug") boolean debug ) throws BadRequest, Unauthorized, InternalServerError { // Process request. return Ok.OK; }

Java methods on @Rest-annotated classes have the following format:

@RestOp(method="...", path="...") <config-annotations> public <return-type> method(<args>) throws <throwables> { ... }

The various parts require their own topics to fully appreciate the scope of abilities but the following is a summary:

Deploying as a Servlet

The BasicRestServlet class is the entry point for your REST resources. It extends directly from HttpServlet and is deployed like any other servlet (such as a standard web.xml file).

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

Most developers are not going to be using the RestServlet class itself, and instead will extend from one of the preconfigured default servlets such as BasicRestServlet and BasicRestServletGroup which provides universal language support, basic instrumentation, and auto-generated Swagger UI.

Deploying in Spring Boot

The BasicSpringRestServlet class is typically entry point for your REST resources when working within a Spring Boot environment. It extends from SpringRestServlet which provides additional capabilities including:

  • Your REST resources can be defined as injectable Spring beans.
  • Various capabilities within the REST Server library (e.g. logging, instrumentation, call handling, API extensions) can be defined via Spring beans and automatically pulled into the framework.

Most developers are not going to be using the RestServlet class itself, and instead will extend from one of the preconfigured default servlets such as BasicSpringRestServlet and BasicSpringRestServletGroup that have the same capabilites as the BasicRestServlet and BasicRestServletGroup counterparts.

Example configuration file:

@Configuration public class MySpringConfiguration { /** * Our root REST bean. * Note that this must extend from SpringRestServlet so that child resources can be * resolved as Spring beans. * All REST objects are attached to this bean using the Rest.children() annotation. */ @Bean public RootResources getRootResources() { return new RootResources(); } /** * Optionally return the HelloWorldResource object as an injectable bean. */ @Bean public HelloWorldResource getHelloWorldResource() { return new HelloWorldResource(); } /** * Map our servlet to a path. */ @Bean public ServletRegistrationBean<Servlet> getRootServlet(RootResources rootResources) { return new ServletRegistrationBean<>(rootResources, "/*"); } }

@Rest( children={ HelloWorldResource.class } ) public class RootResources extends BasicSpringRestServletGroup { // No code! }

Additional Information

1.4 - REST Clientcreated: 9.0.0

Built upon the feature-rich Apache HttpClient library, the Juneau RestClient API adds support for fluent-style REST calls and the ability to perform marshalling of POJOs to and from HTTP parts.

Example:

// Create a basic REST client with JSON support and download a bean. MyBean bean = RestClient.create() .json5() .build() .get(URI) .run() .assertStatus().asCode().is(200) .assertHeader("Content-Type").matchesSimple("application/json*") .getContent().as(MyBean.class);

REST Testing Framework

The MockRestClient class is used for performing serverless unit testing of @Rest-annotated and @Remote-annotated classes. It perform full serialization and parsing of the HTTP request and responses, but bypasses the network layer to significantly improve speed while still performing real testing.

Example:

public class MockTest { // A simple bean with one field. public static class MyBean { public int foo = 1; } // Our REST resource to test. // Simply echos the response. @Rest public static class EchoRest extends BasicRestServlet { @RestPut public MyBean echo(@Content MyBean bean) { return bean; } } // Our JUnit test. @Test public void testEcho() throws Exception { MyBean myBean = new MyBean(); // Do a round-trip on the bean through the REST interface myBean = MockRestClient .create(EchoRest.class) .json5() .build() .put("/echo", myBean) .run() .assertStatus().is(200) .assertContent().is("{foo:1}") .getContent().as(MyBean.class); assertEquals(1, myBean.foo); } }

Additional Information

1.5 - DTOscreated: 9.0.0

The juneau-dto library contains several predefined POJOs for generating commonly-used document types that are designed to be used with the Juneau Marshaller APIs for both serializing and parsing.

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 HtmlBuilder class is a utility class with predefined static methods that allow you to easily construct DTO instances in a minimal amount of code.

Examples:

import static org.apache.juneau.dto.html5.HtmlBuilder.*; // An HTML table Object mytable = table( tr( th("c1"), th("c2") ), tr( td("v1"), td("v2") ) ); String html = Html.of(mytable);

<table> <tr> <th>c1</th> <th>c2</th> </tr> <tr> <td>v1</td> <td>v2</td> </tr> </table>

ATOM

The Juneau ATOM feed DTOs are simply beans with fluent-style setters. The following code shows a feed being created programmatically using the AtomBuilder class.

Example:

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

Swagger

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

Example:

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

SwaggerUI

The SwaggerUI class is a DTO class for generating Swagger user interfaces from Swagger beans.

The PetStore example described later provides an example of auto-generated Swagger JSON:

Using SwaggerUI, we're able to render that JSON as a Swagger user interface when the request is asking for HTML:

1.6 - Config Filescreated: 9.0.0

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

Example configuration file:

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

Config files are accessed through the Config class which are created through the Config.Builder class. Builder creator methods are provided on the Config class:

// Create a Config object Config config = Config.create().name("MyConfig.cfg").build(); // Read values from section #1 int key1 = config.getInt("Section1/key1"); boolean key2 = config.getBoolean("Section1/key2"); int[] key3 = config.getObject("Section1/key3", int[].class); URL key4 = config.getObject("Section1/key4", URL.class);

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

1.7 - Fluent Assertionscreated: 9.0.0

The juneau-assertions module in Juneau is a powerful API for performing fluent style assertions.

Fluent assertions have two types of methods:

  • "asX" methods which perform transformations.
  • "isX" methods which perform assertions.

Multiple transformations and assertions can be performed per statement.

Examples:

import static org.apache.juneau.assertions.Assertions.*; import static org.apache.juneau.assertions.AssertionPredicates.*; // Check the contents of a string. assertString("foo, bar") .asSplit(",") .asTrimmed() .is("foo", "bar"); // Extract a subset of properties from a list of beans and compare using Simplified JSON. List<MyBean> myListOfBeans = ...; assertBeanList(myListOfBeans) .asPropertyMaps("a,b") .asJson().is("[{a:1,b:'foo'}]"); // Perform an arbitrary Predicate check against a bean. MyBean myBean = ...; assertBean(myBean) .is(x -> isValidCheck(x)) // Check that a list of strings has less than 10 entries and the first // 3 entries are [foo, bar*, null] using assertion predicates. List<String> myListOfStrings = ...; assertStringList(myListOfStrings) .asSize().isLt(10) .asFirst(3) .is(eq("foo"),match("bar*"),isNull()) // Check that an exception is thrown and is the specified type and has the specified message. assertThrown(()->myBean.runBadMethod()) .isExists() .isExactType(RuntimeException.class) .asMessage().is("foo");

The Assertions APIs are used throughout the REST client and server APIs for performing inline assertions on REST requests and responses.

Example:

// Create a basic REST client with JSON support and download a bean. MyBean bean = RestClient.create() .json5() .build() .get(URI) .run() .assertStatus().asCode().is(200) .assertHeader("Content-Type").isMatches("application/json*") .getContent().assertValue().asString().isContains("OK") .getContent().as(MyBean.class);

1.8 - General Designcreated: 9.0.0

The Juneau framework uses the design pattern of builders, context, and session objects:

  • Context Builders - Modifiable objects that allow you to define configuration settings for contexts.
  • Contexts - Unmodifiable thread-safe objects meant to be cacheable and reusable.
  • Sessions - Modifiable objects usually meant for one-time use.

This is a general design pattern used throughout the framework including the REST client and server APIs.

The following shows the general pattern for creating sessions:

// Create a reusable context object (in this case a serializer). WriterSerializer serializer = JsonSerializer .create() // Instantiates a context builder. .findFluentSetters() // Sets a configuration value. .build(); // Creates a context. // Create a one-time session object. WriterSerializerSession session = serializer .createSession() // Instantiates a session builder. .useWhitespace() // Sets a session value. .build(); // Creates a session. // Use it. String json = session.serialize(myBean);

Typically developers will not deal with session objects and will just use convenience methods on the context classes themselves that handle creation of sessions:

// Just use serialize method on WriterSerializer class. String json = serializer.serialize(myBean);

Most context objects also have static default instances that can be used in leu of creating new contexts as well:

// Just use one of the static context instances. String json = JsonSerializer.DEFAULT.serialize(myBean);

Most context classes also have the ability to clone and modify existing context objects:

// Clone and modify an existing context object. WriterSerializer serializer = JsonSerializer .DEFAULT .copy() // Instantiates a context builder. .findFluentSetters() // Sets a configuration value. .build(); // Creates a context.

The default values of many context settings can also be set via system properties and environment variables. The javadocs on these settings will identify when this is possible.

The framework makes heavy use of caching of existing context objects with the same builder settings. This is a critical reason why Juneau achieve impressive performance. Using Java reflection to find out all information about a bean type is expensive. By caching context objects, we only need to reflect that bean type once and store that information in the context for reuse by all serializers and parsers that share the same bean context configuration settings.

2 - juneau-marshall

Maven Dependency

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

Java Library

juneau-marshall-9.0.0.jar

OSGi Module

org.apache.juneau.marshall_9.0.0.jar

The juneau-marshall artifact contains the following:

  • Foundation for all serializers and parsers.
  • Implementations for all serializers and parsers except RDF languages.
  • Extensions to Apache HttpCore components used by both client and server APIs.
  • Assertions APIs.
  • Various reusable utilities used throughout the framework.

2.1 - Marshallers

Marshallers are simple pairings of a Serializer and Parser with convenience methods for serializing and parsing POJOs.

Examples:

// Using instance. Json json = new Json(); MyPojo myPojo = json.read(string, MyPojo.class); String string = json.write(myPojo);

// Using DEFAULT instance. MyPojo myPojo = Json.DEFAULT.read(string, MyPojo.class); String string = Json.DEFAULT.write(myPojo);

Juneau comes with the following predefined marshallers:

Each predefined marshaller also includes static convenience from/to methods to make it even easier to perform marshalling on POJOs:

Examples:

// Using shortcut static methods. MyPojo myPojo = Json.to(jsonString, MyPojo.class); String json = Json.of(myPojo);

2.2 - Serializers and Parsersupdated: 9.0.0

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

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

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

// A simple bean public class Person { public String name = "John Smith"; public int age = 21; } // Serialize to JSON, XML, or HTML Person person = new Person(); // Produces: // "{\"name\":\"John Smith\",\"age\":21}" String json = JsonSerializer.DEFAULT.serialize(person); // Produces: // "{name:'John Smith',age:21}" String json = Json5Serializer.DEFAULT.serialize(person); // Produces: // <object> // <name>John Smith</name> // <age>21</age> // </object> String xml = XmlSerializer.DEFAULT.serialize(person); // 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(person); // Produces: // "(name='John Smith',age=21)" String uon = UonSerializer.DEFAULT.serialize(person); // Produces: // "name='John+Smith'&age=21" String urlencoding = UrlEncodingSerializer.DEFAULT.serialize(person); // Produces: // 82 A4 6E 61 6D 65 AA 4A 6F 68 6E 20 53 6D 69 74 68 A3 61 67 65 15 byte[] bytes = MsgPackSerializer.DEFAULT.serialize(person);

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

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

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


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 person = parser.parse(json, Person.class); // Or parse it into a generic Map. Map map = parser.parse(json, Map.class); // Parse a JSON string. json = "'foobar'"; String string = parser.parse(json, String.class); // Parse a JSON number as a Long or Float. json = "123"; Long _long = parser.parse(json, Long.class); Float _float = 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> map2 = 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>> map3 = 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> list = parser.parse(json, LinkedList.class, Integer.class); int[] ints = 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 person = new Person(); parser.parseIntoBean(json, person); // Populate an existing list from a JSON array of numbers. json = "[1,2,3]"; List<Integer> list = new LinkedList<Integer>(); parser.parseIntoCollection(json, list, 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> map = new TreeMap<String,Person>(); parser.parseIntoMap(json, map, 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 fragments 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.

2.3 - Bean Contextsupdated: 9.0.0

At the heart of the marshalling APIs is the Bean Context API that provides a common framework for marshalling beans and POJOs across all serializers and parsers. All serializers and parsers (and their builders) extend from the bean context API classes.

One important feature of the bean context API is the ability to wrap Java beans inside maps to allow properties to be accessed through a Map layer. Although this is used internally by all the serializers and parsers, it's often useful to use this feature by itself.

Example:

// Wrap a bean in a map and do some simple get/set calls. BeanMap<MyBean> myBeanMap = BeanContext.DEFAULT_SESSION.toBeanMap(myBean); myBeanMap.put("myProperty", 123); int myProperty = myBeanMap.get("myProperty", int.class);

The bean context API provides many settings that fine-tune how POJOs should be handled during marshalling.

2.4 - Java Beans Supportcreated: 8.2.0, updated: 9.0.0

Out-of-the-box, Juneau supports marshalling of Java beans with standard public getters and setters, public fields, and fluent setters (e.g. withX naming convention). There are also many settings and annotations that can be used to customize how bean properties are detected. The following is an example of some of the ways to define bean properties:

public class MyBean { // Public field property. public String property1; // Standard public getters/setters. public String getProperty2() {...} public void setProperty2(String value) {...} // With fluent-style setter. public String getProperty3() {...} public MyBean withProperty3(String value) {...} // Read-only property (ignored by parsers). public String getProperty4() {...} // Write-only property (ignored by serializers). public void setProperty5(String value) {...} // Non-standard getters/setters identified by annotation. @Beanp public String property6() {...} @Beanp public void property6(String value) {...} // Non-standard getters/setters identified by annotation with overridden names. @Beanp("property7") public String property7X() {...} @Beanp("property7") public void property7X(String value) {...} // Non-public getters/setters identified by annotation. @Beanp private String getProperty8() {...} @Beanp private void setProperty8(String value) {...} // Ignore a method that looks like a getter. @BeanIgnore public String getNotAProperty() {...} }

Several settings exist to allow you to customize how bean properties are handled by serializers and parsers:

Settings and equivalent annotations are also available to control which properties are marshalled and how they are ordered.

It's common to use the @Bean(properties|p) annotation to force the ordering of properties during marshalling. IBM JVMs keep the ordering of fields and methods in the compiled bytecodebut Oracle JVMs do not and return fields/methods in random order. The @Bean(properties|p) annotation was added to help with this limitation.

// Bean should be marshalled with properties in the specified order. @Bean(properties="foo,bar,baz") public class MyBean { ... }

2.4.1 - @Bean Annotationupdated: 8.2.0,9.0.0

The @Bean annotation is used to tailor how beans are interpreted by the framework.

Bean property inclusion and ordering on a bean class can be done using the @Bean(properties|p) annotation.

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

Bean properties can be excluded using the @Bean(excludeProperties|xp) annotation.

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

Bean properties can be sorted alphabetically using @Bean(sort)

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

The @Bean(propertyNamer) annotation is used to provide customized naming of properties.

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

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

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

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

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

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

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

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

The @Bean(interceptor) annotation and BeanInterceptor class can be used to perform interception and inline handling of bean getter and setter calls.

// Interceptor that strips out sensitive information on Address beans. public class AddressInterceptor extends BeanInterceptor<Address> { @Override public Object readProperty(Address bean, String name, Object value) { if ("taxInfo".equals(name)) return "redacted"; return value; } @Override public Object writeProperty(Address bean, String name, Object value) { if ("taxInfo".equals(name) && "redacted".equals(value)) return TaxInfoUtils.lookup(bean.getStreet(), bean.getCity(), bean.getState()); return value; } } // Register interceptor on bean class. @Bean(interceptor=AddressInterceptor.class) public class Address { public String getTaxInfo() {...} public void setTaxInfo(String value) {...} }

The @Bean(on) and @Bean(onClass) annotations can be used to programmatically attach @Bean annotations to classes.

@Bean(onClass=Address.class, sort=true, excludeProperties="city,state") public class MyAnnotatedClass {...} // Create a serializer configured using annotations. JsonSerializer serializer = JsonSerializer .create() .applyAnnotations(MyAnnotatedClass.class) .build();

2.4.2 - @Beanp Annotationupdated: 8.1.0,8.1.2,9.0.0

The @Beanp annotation is used to tailor how individual bean properties are interpreted by the framework.

The @Beanp(name) annotation is used to override the name of the bean property.

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

The @Name annotation is a shortcut for specifying a bean property name:

public class MyBean { @Name("Bar") public String getFoo() {...} }

If the beanFieldVisibility setting on the bean context excludes this field (e.g. the visibility is set to the default of PUBLIC but the field is PROTECTED), this annotation can be used to force the field to be identified as a property.

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

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

The following shows various ways of using dynamic bean properties.

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

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

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

Note that if you're not interested in these additional properties, you can also use the ignoreUnknownBeanProperties setting to ignore values that don't fit into existing properties.

The @Beanp(value) annotation is a synonym for @Beanp(name). Use it in cases where you're only specifying a name so that you can shorten your annotation.

The following annotations are equivalent:

@Beanp(name="foo") @Beanp("foo")

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

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

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

The @Beanp(params) annotation is for bean properties of type map or collection. It's used to identify the class types of the contents of the bean property object when the general parameter types are interfaces or abstract classes.

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

The @Beanp(properties) annotation is used to limit which child properties are rendered by the serializers. It can be used on any of the following bean property types:

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

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

The @Beanp(format) annotation specifies a String format for converting a bean property value to a formatted string.

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

2.4.3 - @Beanc Annotationupdated: 8.1.0,8.1.2

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

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

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

// Parsing into a read-only bean. String json = "{name:'John Smith',age:45}"; Person person = Json.to(json, Person.class); String name = person.getName(); // "John Smith" int age = person.getAge(); // 45

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

The @Name annotation can also be used instead of @Beanc(properties):

@Beanc public Person(@Name("name") String name, @Name("age") int age) { this.name = name; this.age = age; }

If neither @Beanc(properties) or @Name is used to identify the bean property names, we will try to use the parameter names if they are available in the bytecode.

2.4.4 - @BeanIgnore Annotation

The @BeanIgnore annotation is used to ignore classes, fields, and methods from being interpreted as beans or bean components.

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

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

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

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

2.4.5 - @NameProperty Annotation

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

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

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

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

2.4.6 - @ParentProperty Annotation

The @ParentProperty annotation is used to identify a setter as a method for adding a parent reference to a child object.

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

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

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

2.4.7 - POJO Buildersupdated: 9.0.0

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

A typical builder usage is shown below:

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

The typical code for such a builder using a static inner class is shown below:

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

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

Builders require two parts:

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

The first can be accomplished through any of the following:

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

    public static Builder create() {...}

  • A public constructor on the POJO class that takes in a single parameter that implements the Builder interface.
    The builder class must have a public no-arg constructor.

    public MyBean(Builder builder) {...}

  • A @Builder annotation on the POJO class to explicitly identify it.
    The builder class must have a public no-arg constructor.

    @Builder(Builder.class) public class MyBean {...}

The second can be accomplished through any of the following:

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

    public MyBean build() {...}

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

    public MyBean(Builder builder) {...}

2.4.8 - Bypass Serialization using Readers and InputStreams

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

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

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

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

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

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

2.5 - HTTP Part Serializers and Parsersupdated: 8.2.0,9.0.0

There is a separate set of serializers and parsers for marshalling HTTP parts (query, form-data, headers, path variables, and plain-text request bodies). The distinction is that these are designed to marshall directly to-and-from strings based on Open-API schema information.

// Schema information about our part. HttpPartSchema schema = HttpPartSchema .tArrayPipes() .items( HttpPartSchema .tArrayCsv() .items( HttpPartSchema.tInt64("integer","int64") ) ) .build(); // Our value to serialize Object value = new long[][]{{1,2,3},{4,5,6},{7,8,9}}; // Produces "1,2,3|4,5,6|7,8,9" String output = OpenApi.of(HttpPartType.HEADER, schema, value); // Produces "[[1,2,3],[4,5,6],[7,8,9]] long[][] value = OpenApi.to(HttpPartType.HEADER, schema, output, long[][].class);

The HttpPartSchema class also provides convenience static methods for creation of custom schemas. The equivalent to the schema above can be structured like so:

import static org.apache.juneau.httppart.HttpPartSchema.*; // Schema information about our part. HttpPartSchema schema = tArrayPipes(tArrayCsv(tInt64())).build();

The class hierarchy for the part marshallers are:

2.6 - Context Settingsupdated: 8.1.3,9.0.0

Serializers and parsers have a wide variety of configurable settings. Their builders all extend from the BeanContext.Builder class that allows you to easily construct new instances from scratch or build upon existing instances. For example, the following code shows how to configure a JSON serializer:

WriterSerializer serializer = JsonSerializer .create() // Create a JsonSerializer.Builder .simpleMode() // Simple mode .ws() // Use whitespace .sq() // Use single quotes .sortProperties() // Sort bean properties by name .build(); // Create a JsonSerializer

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 = Json5Serializer.DEFAULT.serialize(myPojo);

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

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

Default values for configurable settings can be set globally using either system properties or environment variables.
For example, the default useWhitespace setting can be set by either the system property "WriterSerializer.useWhitespace" or environment variable "WRITERSERIALIZER_USEWHITESPACE". The builder setters will identify when default values can be set this way.

2.7 - Context Annotationscreated: 8.1.0, updated: 8.1.3,8.2.0,9.0.0

All configurable properties described in the previous section have annotation equivalents that can be applied on classes or methods.

In the section on the REST server API, we describe how to configure serializers and parsers using @XConfig annotations like those shown below:

@Rest( path="/addressBook", title="Address Book REST API" ... ) @SerializerConfig(quoteChar="'") @RdfConfig(rdfxml_tab="5", addRootProperty="true") @BeanConfig(sortProperties="true", examples="Feed: $F{AddressBook_example.json}") @Bean(onClass=Address.class, properties="street,city,state") public class AddressBookResource extends BasicRestServlet { ... }

Config annotations defined on classes and methods can be applied to serializers and parsers using the following methods:

The following example shows how annotations defined on a dummy class can be applied to a serializer:

@SerializerConfig(quoteChar="'") @Bean(on="Address", properties="street,city,state") public static class DummyClass {} WriterSerializer serializer = JsonSerializer.create().applyAnnotations(DummyClass.class).build(); String json = serializer.toString(addressBean);

Config annotations are provided for all serializers and parsers:

Annotations normally applied to bean classes/methods/fields/parameters can also be programmatically attatched to beans by using the "on" or "onClass" annotation values as seen on the @Bean annotation in the example above. These include:

Annotations can also be applied directly to serializers and parsers using the following method:

The following example shows a concrete implementation of an annotation can be applied to a serializer:

public class Address {...} Bean ba = new BeanAnnotation("Address").properties("street,city,state"); WriterSerializer serializer = JsonSerializer.create().annotations(ba).build(); String json = serializer.toString(addressBean); // Will print street,city,state

Concrete annotation implementations are provided for all annotations.

Any number of matching config or concrete annotations can be applied. They are applied in the order they are provided to the context. Therefore any values can be overridden. Config and concrete annotations also override any class or method level annotations

@Bean(properties="street,city") // Will be overridden public class Address {...} Bean beanAnnotation = new BeanAnnotation("Address").properties("street,city,state"); WriterSerializer serializer = JsonSerializer.create().annotations(beanAnnotation).build(); String json = serializer.toString(addressBean); // Will print street,city,state

2.8 - JsonMap and JsonListupdated: 8.2.0

The JsonMap and JsonList 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 JsonMap and JsonList 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 JsonMap.writeTo(java.io.Writer) or JsonList.writeTo(java.io.Writer) methods.
  2. Passing them to one of the Serializer serialize methods.
  3. Simply calling the JsonMap.asJson()/JsonMap.toString() or JsonList.asString()/JsonList.toString() methods which will serialize it as Simplified JSON.

Any valid JSON can be parsed into an unstructured model consisting of generic JsonMap and JsonList objects. (Any valid XML can also be parsed into an unstructured model)

// Parse an arbitrary JSON document into an unstructered data model // consisting of JsonMaps, JsonLists, and java primitive objects. String json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}"; JsonMap map = Json.to(json, JsonMap.class); // Use JsonMap API to extract data from the unstructured model. int johnSmithAge = map.getMap("a").getInt("age"); // Convert it back into JSON. json = Json.of(map); // Or convert it to XML. String xml = Xml.of(map); // Or just use toString() or asJson(). json = map.toString(); json = map.asJson();

The JsonMap and JsonList classes have many convenience features:

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

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 JsonMaps and JsonLists.

2.9 - Complex Data Typescreated: 9.0.0

The Juneau parsers have the ability to parse into complex data types that consist of multidimensional arrays and nested maps and collections using the methods below:

Arrays are simple enough and can be constructed using the first method:

String json = "[1,2,3]"; int[] array = Json.to(json, int[].class);

For data types consisting of nested collections an maps such as Map<String,List<MyBean>>, you need to use the second parse method that allows you to define the parameter types of the collections classes. For example:

String json = "{foo:[{bar:'baz'}]}"; TreeMap<String,List<MyBean>> map = Json.to( json, // Input being parsed. TreeMap.class, // Top-level data type. String.class, // Key type of map. LinkedList.class, // Value type of map. MyBean.class // Value type of list. );

Collection classes are assumed to be followed by zero or one objects indicating the element type.
Map classes are assumed to be followed by zero or two meta objects indicating the key and value types.
The arguments can be arbitrarily long to indicate arbitrarily complex data structures.

Similar methods for converting to complex types can be found on the RequestContent and RequestHttpPart classes, and the BeanSession.convertToType(Object,Type,Type...) method.

2.10 - SerializerSets and ParserSetsupdated: 9.0.0

On top of the serializers and parsers are the SerializerSet and ParserSet classes. These classes allow serializers and parsers to be grouped and retrieved by W3C-compliant HTTP Accept and Content-Type values...

// Construct a new serializer group with configuration parameters that get applied // to all serializers. SerializerSet serializers = SerializerSet.create() .add(JsonSerializer.class, UrlEncodingSerializer.class) .forEach(x -> x.swaps(TemporalCalendarSwap.IsoLocalDateTime.class)) .forEachWS(x -> x.ws()) // or .useWhitespace(true) .build(); // Find the appropriate serializer by Accept type and serialize our POJO to the // specified writer. serializers .getSerializer("text/invalid, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0") .serialize(myPerson, myWriter); // Construct a new parser group with configuration parameters that get applied to all parsers. ParserSet parsers = ParserSet.create() .add(JsonSerializer.class, UrlEncodingSerializer.class) .forEach(x -> x.swaps(CalendarSwap.IsoLocalDateTime.class)) .build(); Person myPerson = parsers .getParser("text/json") .parse(myReader, Person.class);

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

2.11 - Swapsupdated: 9.0.0

Swaps are a critical component of Juneau. They allow the serializers and parsers to handle Java objects that wouldn't normally be serializable.

Swaps are, simply put, 'object swappers' that swap in serializable objects for non-serializable ones during serialization, and vis-versa during parsing. Some examples of non-serializable objects 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 ObjectSwap that will swap in a bean of a particular type with a map containing customized key-value pairs:

// Sample swap for converting a bean to a specialized map of key-value pairs. public class MyBeanSwap extends ObjectSwap<MyBean,JsonMap> { // Converts a bean to a generic map. @Override /* ObjectSwap */ public JsonMap swap(BeanSession session, MyBean bean) { return JsonMap.of("foo", bean.getBar()); } // Converts the generic map back into a bean. @Override /* ObjectSwap */ public MyBean unswap(BeanSession session, JsonMap map, ClassMeta<?> hint) throws Exception { MyBean bean = new MyBean(); bean.setBar(map.getString("foo")); return bean; } }

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

// Create a new JSON serializer with our swap. WriterSerializer serializer = JsonSerializer.create().simple().swaps(MyBeanSwap.class).build(); String json = serializer.serialize(new MyBean()); // Create a JSON parser with our swap. ReaderParser parser = JsonParser.create().swaps(MyBeanSwap.class).build(); MyBean bean = parser.parse(json, MyBean.class);

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

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

The following example shows the BASE64 swap in use:

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

The BeanContextable.Builder.swap(Class,Class,ThrowingFunction) and BeanContextable.Builder.swap(Class,Class,ThrowingFunction,ThrowingFunction) methods are another way to define swaps by using functions.

// Use a function to convert beans to strings. WriterSerializer serializer = JsonSerializer .create() .simple() .swap(MyBean.class, String.class, x -> myBeanStringifier(x)) .build();

2.11.1 - Default Swaps

By default, all serializers and parsers have built in ObjectSwaps defined for the following common data types:

Various other swaps are provided in the org.apache.juneau.swaps package.

2.11.2 - Auto-detected swapscreated: 8.1.0

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

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:

If you want to force a bean-like class to be serialized as a string, you can use the @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() method, where X is any serializable object.
  • public X swap(BeanSession) method, where X is any serializable object.
  • public static MyPojo unswap(X) method, where X is any serializable object.
  • public static MyPojo swap(X,BeanSession) method, where X is any serializable object.

Serializing to and from Maps can be accomplished by defining any of the following methods:

  • public Map toMap() method.
    Can be any type of map with string keys and object vals.
  • public JsonMap toMap() method.
  • public Map toMap(BeanSession) method.
    Can be any type of map with string keys and object vals.
  • public JsonMap toMap(BeanSession) method.
  • public static MyPojo fromMap(Map) method.
    Can be any type of map with string keys and object vals.
  • public static MyPojo fromMap(JsonMap) method.
  • public static MyPojo fromMap(Map,BeanSession) method.
    Can be any type of map with string keys and object vals.
  • public static MyPojo fromMap(JsonMap,BeanSession) method.

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 (BeanSession.getMediaType()).

The following example shows how an HTML5 form template object can be created that gets serialized as a populated 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(dictionary=HtmlBeanDictionary.class) public class FormTemplate { private String action; private int value1; private boolean value2; // Our 'unswap' constructor public FormTemplate(Form form) { this.action = form.getAttr("action"); this.value1 = form.getChild(Input.class, 0) .getAttr(int.class, "value"); this.value2 = form.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.11.3 - Per-media-type Swapsupdated: 8.1.0,8.2.0

Swaps can also be defined per-media-type.

The ObjectSwap.forMediaTypes() method can be overridden to provide a set of media types that the swap is invoked on. It's also possible to define multiple swaps against the same object as long as they're differentiated by media type. When multiple swaps are defined, the best-match media type is used.

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

public class ObjectSwapTest { public static class MyPojo {} public static class MyJsonSwap extends StringSwap<MyPojo> { @Override /* ObjectSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/json"); } @Override /* ObjectSwap */ public String swap(BeanSession session, MyPojo pojo) throws Exception { return "It's JSON!"; } } public static class MyXmlSwap extends StringSwap<MyPojo> { @Override /* ObjectSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/xml"); } @Override /* ObjectSwap */ public String swap(BeanSession session, MyPojo pojo) throws Exception { return "It's XML!"; } } public static class MyOtherSwap extends StringSwap<MyPojo> { @Override /* ObjectSwap */ public MediaType[] forMediaTypes() { return MediaType.forStrings("*/*"); } @Override /* ObjectSwap */ public String swap(BeanSession session, MyPojo pojo) throws Exception { return "It's something else!"; } } @Test public void doTest() throws Exception { SerializerSet serializers = SerializersSet.create() .add(JsonSerializer.class, XmlSerializer.class, HtmlSerializer.class) .forEach(x -> x.swaps(MyJsonSwap.class, MyXmlSwap.class, MyOtherSwap.class)) .forEachWS(x -> x.ws()) .build(); MyPojo myPojo = new MyPojo(); String json = seralizers.getWriterSerializer("text/json").serialize(myPojo); assertEquals("'It\\'s JSON!'", json); String xml = seralizers.getWriterSerializer("text/xml").serialize(myPojo); assertEquals("<string>It's XML!</string>", xml); String html = seralizers.getWriterSerializer("text/html").serialize(myPojo); assertEquals("<string>It's something else!</string>", html); } }

When multiple swaps match the same media type, a best-match algorithm is applied to find the correct swap to use.

In later sections we describe how annotations can be used to shorten this syntax:

@Swap(MyJsonSwap.class) @Swap(MyXmlSwap.class) @Swap(MyOtherSwap.class) public static class MyPojo {} @Swap(mediaTypes="*/json") public static class MyJsonSwap extends ObjectSwap<MyPojo,String> {...} @Swap(mediaTypes="*/xml") public static class MyXmlSwap extends ObjectSwap<MyPojo,String> {...} @Swap(mediaTypes="*/*") public static class MyOtherSwap extends ObjectSwap<MyPojo,String> {...}

2.11.4 - One-way Swaps

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

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

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

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

Here is an example of our one-way swap being used. Note that trying to parse the original object will cause a ParseException to be thrown.

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

2.11.5 - @Swap Annotationupdated: 8.0.0,9.0.0

@Swap can be used to associate a swap class using an annotation. This is often cleaner than using the builder swaps() method since you can keep your swap class near your POJO class.

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

Multiple swaps can be associated with a class using multiple @Swap annotations:

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

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

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

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

public class MyBean { private MyPojo myPojo; // Swap applied to bean property. @Beanp(swap=MyPojoSwap.class) public MyPojo getMyPojo() { return myPojo; } }

When applied to bean properties, the swap annotation need only be applied to either the getter, setter, or field.

The swap annotation can also be applied to the private field of a bean property, like so:

public class MyBean { @Beanp(swap=MyPojoSwap.class) private MyPojo myPojo; public MyPojo getMyPojo() { return myPojo; } public MyBean setMyPojo(MyPojo myPojo) { this.myPojo = myPojo; return this; } }

2.11.6 - Templated Swaps

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

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

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

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

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

2.11.7 - Surrogate Classes

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

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

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

This could be solved with the following ObjectSwap.

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

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

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

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

// Create a JSON serializer that can serialize our unserializable object. WriterSerializer serializer = JsonSerializer .create() .swaps(MySerializableSurrogate.class) .build();

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

2.12 - Dynamically Applied Annotationscreated: 8.1.3, updated: 9.0.0

In the section Swaps, you were introduced to annotations that can be applied to bean classes, methods, fields, and constructors such as @Bean:

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

An alternate way of applying these annotations is to attach them to unrelated classes and methods and then tell your serializer or parser where to find them.

// Unannotated class. public class Address { ... } @Bean(onClass=Address.class, properties="street,city,state") public static class DummyClass {} WriterSerializer serializer = JsonSerializer .create() .applyAnnotations(DummyClass.class) .build(); String json = serializer.toString(addressBean);

The advantage to this approach is it allows you to use Juneau annotations on classes/methods/fields/constructors where you might not have access to the source code, or when you only want to selectively apply the annotation under certain scenarios instead of globally.

For example, the following shows the @Bean annotation being selectively applied on a single REST method (described later in juneau-rest-server):

@RestGet @Bean(onClass=Address.class, properties="street,city,state") public List<Address> getAddresses() {}

Any Juneau annotation that has an on()/onClass() method can be applied dynamically this way. These include:

The valid pattern matches are:

  • Classes:
    • Fully qualified:
      • "com.foo.MyClass"
    • Fully qualified inner class:
      • "com.foo.MyClass$Inner1$Inner2"
    • Simple:
      • "MyClass"
    • Simple inner:
      • "MyClass$Inner1$Inner2"
      • "Inner1$Inner2"
      • "Inner2"
  • Methods:
    • Fully qualified with args:
      • "com.foo.MyClass.myMethod(String,int)"
      • "com.foo.MyClass.myMethod(java.lang.String,int)"
      • "com.foo.MyClass.myMethod()"
    • Fully qualified:
      • "com.foo.MyClass.myMethod"
    • Simple with args:
      • "MyClass.myMethod(String,int)"
      • "MyClass.myMethod(java.lang.String,int)"
      • "MyClass.myMethod()"
    • Simple:
      • "MyClass.myMethod"
    • Simple inner class:
      • "MyClass$Inner1$Inner2.myMethod"
      • "Inner1$Inner2.myMethod"
      • "Inner2.myMethod"
  • Fields:
    • Fully qualified:
      • "com.foo.MyClass.myField"
    • Simple:
      • "MyClass.myField"
    • Simple inner class:
      • "MyClass$Inner1$Inner2.myField"
      • "Inner1$Inner2.myField"
      • "Inner2.myField"
  • Constructors:
    • Fully qualified with args:
      • "com.foo.MyClass(String,int)"
      • "com.foo.MyClass(java.lang.String,int)"
      • "com.foo.MyClass()"
    • Simple with args:
      • "MyClass(String,int)"
      • "MyClass(java.lang.String,int)"
      • "MyClass()"
    • Simple inner class:
      • "MyClass$Inner1$Inner2()"
      • "Inner1$Inner2()"
      • "Inner2()"
  • A comma-delimited list of anything on this list.

2.13 - Bean Names and Dictionariesupdated: 9.0.0

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

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

Bean classes are given names through the @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[]. @Beanp(dictionary={Bar.class,Baz.class}) public Object[] x = new Object[]{new Bar(), new Baz()}; } @Bean(typeName="bar") public class Bar {} @Bean(typeName="baz") public class Baz {}

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

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

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

When serialized as XML, the bean is rendered as:

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

Bean dictionaries are registered through the following:

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

  • Any bean class that specifies a value for @Bean(typeName).
  • Any subclass of BeanDictionaryList containing a collection of bean classes with type name annotations.
  • Any subclass of BeanDictionaryMap containing a mapping of type names to classes without type name annotations.
  • Any array or collection of the objects above.

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

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

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

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

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 Serializer.Builder.addBeanTypes() setting.
2.13.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( dictionary={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:

A1 object = new A1(); object.f1 = "f1"; String json = Json5.of(object); assertEquals("{_type:'A1',f1:'f1',f0:'f0'}", json);

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

A object = Json.to(json, A.class); assertTrue(object instanceof A1);

2.14 - Virtual Beansupdated: 9.0.0

The BeanContext.Builder.disableInterfaceProxies() setting (enabled by default) allows the Juneau parsers to parse content into virtual beans (bean interfaces without implementation classes).

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

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

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

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

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

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

2.15 - Non-Tree Models and Recursion Detectionupdated: 9.0.0

The Juneau Serializer API is designed to be used against POJO tree structures. It expects that there not be loops in the POJO model (e.g. children with references to parents, etc...). If you try to serialize models with loops, you will usually cause a StackOverflowError to be thrown (if BeanTraverseContext.Builder.maxDepth(int) is not reached first).

If you still want to use the Juneau serializers on such models, Juneau provides the BeanTraverseContext.Builder.detectRecursions() setting. It tells the serializer to look for instances of an object in the current branch of the tree and skip serialization when a duplicate is encountered.

For example, let's make a POJO model out of the following classes:

public class A { public B b; } public class B { public C c; } public class C { public A a; }

Now we create a model with a loop and serialize the results.

// Clone an existing serializer and set property for detecting recursions. WriterSerializer serializer = Json5Serializer .DEFAULT_READABLE .copy() .detectRecursions() .build(); // Create a recursive loop. A a = new A(); a.b = new B(); a.b.c = new C(); a.b.c.a = a; // Serialize to JSON. String json = serializer.serialize(a);

What we end up with is the following, which does not serialize the contents of the c field:

{ b: { c: { } } }

Without recursion detection enabled, this would cause a stack-overflow error.

Recursion detection introduces a performance penalty of around 20%. For this reason the setting is disabled by default.

2.16 - Parsing into Generic Modelsupdated: 8.2.0

The Juneau parsers are not limited to parsing back into the original bean classes. If the bean classes are not available on the parsing side, the parser can also be used to parse into a generic model consisting of Maps, Collections, and primitive objects.

You can parse into any Map type (e.g. HashMap, TreeMap) but using JsonMap is recommended since it has many convenience methods for converting values to various types. The same is true when parsing collections. You can use any Collection (e.g. HashSet, LinkedList) or array (e.g. Object[], String[], String[][]) but using JsonList is recommended.

When the map or list type is not specified, or is the abstract Map, Collection, or List types, the parser will use JsonMap and JsonList by default.

For example, given the following JSON:

{ 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 } ] }

We can parse this into a generic JsonMap:

// Parse JSON into a generic POJO model. JsonMap map = Json.to(json, JsonMap.class); // Convert it back to JSON. String json = Json5.of(map);

What we end up with is the exact same output. Even the numbers and booleans are preserved because they are parsed into Number and Boolean objects when parsing into generic models.

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

Once parsed into a generic model, various convenience methods are provided on the JsonMap and JsonList classes to retrieve values:

// Parse JSON into a generic POJO model. JsonMap map = Json.to(json, JsonMap.class); // Get some simple values. String name = map.getString("name"); int id = map.getInt("id"); // Get a value convertable from a String. URI uri = map.get(URI.class, "uri"); // Get a value using a swap. TemporalCalendarSwap swap = new TemporalCalendarSwap.IsoInstant(); Calendar birthDate = map.get(swap, "birthDate"); // Get the addresses. JsonList addresses = map.getList("addresses"); // Get the first address and convert it to a bean. Address address = addresses.get(Address.class, 0);

As a general rule, parsing into beans is often more efficient than parsing into generic models. And working with beans is often less error prone than working with generic models.

2.17 - Reading Continuous Streamsupdated: 9.0.0

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

The JsonParser and UonParser classes can read continuous streams by using the Parser.Builder.unbuffered() setting. This prevents the parsers from using an internal buffer that would read past the end of the currently parsed POJO.

Examples:

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

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

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

The MsgPackParser class doesn't use any internal buffering to begin with, so it can be used with continuous streams without any special properties.

2.18 - URIsupdated: 9.0.0

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

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

// Our bean with properties containing various kinds of URIs. public class TestURIs { public URI a = URI.create("http://www.apache.org/a"), b = URI.create("/b"), c = URI.create("/c/x/y"), d = URI.create("d"), e = URI.create("e/x/y"), f = URI.create(""), g = URI.create("context:/g/x"), h = URI.create("context:/h"), i = URI.create("context:/"), j = URI.create("context:/.."), k = URI.create("servlet:/k/x"), l = URI.create("servlet:/l"), m = URI.create("servlet:/"), n = URI.create("servlet:/.."), o = URI.create("request:/o/x"), p = URI.create("request:/p"), q = URI.create("request:/"), r = URI.create("request:/.."); } // Create a serializer. WriterSerializer serializer = JsonSerializer create() .simple() .uriContext( UriContext.of( "http://foo.com:123", // Authority "/myContext", // Context root "/myServlet", // Servlet path "/myPath" // Path info ) ) .uriResolution(ABSOLUTE) .uriRelativity(RESOURCE) .build(); // Produces: // { // a:'http://www.apache.org/a', // b:'http://foo.com:123/b', // c:'http://foo.com:123/c/x/y', // d:'http://foo.com:123/myContext/myServlet/d', // e:'http://foo.com:123/myContext/myServlet/e/x/y', // f:'http://foo.com:123/myContext/myServlet', // g:'http://foo.com:123/myContext/g/x', // h:'http://foo.com:123/myContext/h', // i:'http://foo.com:123/myContext', // j:'http://foo.com:123' // k:'http://foo.com:123/myContext/myServlet/k/x', // l:'http://foo.com:123/myContext/myServlet/l', // m:'http://foo.com:123/myContext/myServlet', // n:'http://foo.com:123/myContext', // o:'http://foo.com:123/myContext/myServlet/myPath/o/x', // p:'http://foo.com:123/myContext/myServlet/myPath/p', // q:'http://foo.com:123/myContext/myServlet/myPath', // r:'http://foo.com:123/myContext/myServlet' // } String json = serializer.serialize(new TestURIs());

URI resolution is controlled by the following settings:

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

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

2.19 - Comparison with Jacksonupdated: 9.0.0

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

The following charts describe equivalent features between the two libraries:

Annotations
JacksonJuneau
@JsonGetter
@JsonSetter
@Beanp
@JsonAnyGetter
@JsonAnySetter
@Beanp(name="*")
@JsonIgnore
@JsonIgnoreType
@BeanIgnore
@JsonIgnoreProperties({...}) @Bean(excludeProperties|xp)
@JsonAutoDetect(fieldVisibility=...) No equivalent annotation but can be controlled via:
BeanContext.Builder.beanFieldVisibility(Visibility)
BeanContext.Builder.beanMethodVisibility(Visibility)
@JsonCreator
@JsonProperty
@Beanc
@JacksonInject No equivalent.
@JsonSerialize
@JsonDeserialize
Juneau uses swaps to convert non-serializable object to serializable forms:
@Swap
@JsonInclude No equivalent annotation but can be controlled via various settings:
BeanContext
Serializer
@JsonPropertyOrder @Bean(properties="...")
@Bean(sort=x)
@JsonValue
@JsonRawValue
Can be replicated using swaps with Reader swapped values.

2.20 - POJO Categories

In general, Juneau allows for marshalling for a wide variety of POJO types including:

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

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

A separate category exists for POJOs that can be converted to and from Strings. These are used in places such as:

  • Serializing of POJOs to Strings in the REST client API when no serializers are registered.
  • Parsing of POJOs from Strings in the REST server API for "text/plain" requests where "text/plain" is not already mapped to an existing serializer.

As a general rule, all POJOs are converted to Strings using the toString() method. However, there is one exception:

POJOs are convertible from Strings using any of the following (matched in the specified order):

  • Any of the following public static non-deprecated methods:
    • create(String)
    • fromString(String)
    • fromValue(String)
    • valueOf(String)
    • parse(String)
    • parseString(String)
    • forName(String)
    • forString(String)
  • Has a public constructor that takes in a String.

Exceptions exist for the following classes:

POJOs convertible to/from other types

POJOs are also converted to various other types in places such as the Open-API serializers and parsers. In this section, the type being converted to will be referred to as X.

POJOs are considered convertible from X if it has any of the following (matched in the specified order):

  • Any any of the following public static non-deprecated methods:
    • create(X)
    • from*(X)
  • Has a public constructor that takes in an X.
  • The X class has a public non-static no-arg non-deprecated method called to*().

POJOs are considered convertible from X if any of the reverse of above are true.

2.21 - Simple Variable Language

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

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

The VarResolver class is used to resolve variables. The VarResolver.DEFAULT resolver is a reusable instance of this class configured with the following basic variables:

The following logic variables are also provided:

Example:

// Use the default variable resolver to resolve a string that contains // $S (system property) variables. String property = 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 property = VarResolver.DEFAULT.resolve("$E{MYPROPERTY,$S{my.property,not found}}");

2.21.1 - SVL Variablesupdated: 8.0.0,8.1.0

Variables are defined through the Var API. The API comes with several predefined variables and is easily extensible.

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

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

The following shows the class hierarchy of the Var class:

  • Var - Superclass of all vars.
    • SimpleVar - Superclass of all vars that return strings.
      • DefaultingVar - Variables that define a default value if the resolve method returns null.
        • MapVar - Variables that pull values from maps.
      • MultipartVar - Variables that consist of 2 or more comma-delimited arguments.
    • StreamedVar - Superclass of all vars that stream their value to writers.

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

ModuleClassPattern
juneau-svl EnvVariablesVar $E{key[,default]}
SystemPropertiesVar $S{key[,default]}
ArgsVar $A{key[,default]}
ManifestFileVar $MF{key[,default]}
IfVar $IF{arg,then[,else]}
SwitchVar $SW{arg,pattern1:then1[,pattern2:then2...]}
CoalesceVar $CO{arg1[,arg2...]}
PatternMatchVar $PM{arg,pattern}
PatternReplaceVar $PR{arg,pattern,replace}
PatternExtractVar $PE{arg,pattern,groupdIndex}
NotEmptyVar $NE{arg}
UpperCaseVar $UC{arg}
LowerCaseVar $LC{arg}
LenVar $LN{arg[,delimiter]}
SubstringVar $ST{arg,start[,end]}
HtmlWidgetVar $W{name}
juneau-config ConfigVar $C{key[,default]}
juneau-rest-server FileVar $F{path[,default]}}
ServletInitParamVar $I{name[,default]}
LocalizationVar $L{key[,args...]}
RequestAttributeVar $RA{key1[,key2...]}
RequestFormDataVar $RF{key1[,key2...]}
RequestHeaderVar $RH{key1[,key2...]}
RequestPathVar $RP{key1[,key2...]}
RequestQueryVar $RQ{key1[,key2...]}
RequestSwaggerVar $RS{key}
RequestVar $R{key1[,key2...]}
SerializedRequestAttrVar $SA{contentType,key[,default]}
SwaggerVar $SS{key1[,key2...]}
UrlVar $U{uri}
UrlEncodeVar $UE{uriPart}
2.21.2 - VarResolvers and VarResolverSessionsupdated: 9.0.0

The main class for performing variable resolution is VarResolver. Two methods are provided for resolving variables:

Var resolvers can rely on the existence of other objects. For example, ConfigVar relies on the existence of a Config. This is accomplished through the following method:

Beans are accessible through the following method:

Var resolvers can be cloned and extended by using the VarResolver.copy() method. Cloning a resolver will copy it's Var class names and context objects.

Example:

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

2.21.3 - VarResolver.DEFAULTcreated: 8.1.0

VarResolver.DEFAULT is a reusable variable resolver with default support for the following variables:

2.21.4 - Other Notes
  • The escape character '\' can be used when necessary to escape the following characters: $ , { }
  • WARNING: It is possible to cause StackOverflowErrors if your nested variables result in a recursive loop (e.g. the environment variable 'MYPROPERTY' has the value '$E{MYPROPERTY}'). So don't do that!
  • As a general rule, this class tries to be as efficient as possible by not creating new strings when not needed.
    For example, calling the resolve method on a string that doesn't contain variables (e.g. resolver.resolve("foobar")) will simply be a no-op and return the same string.

2.22 - Encoderscreated: 9.0.0

The org.apache.juneau.encoders package defines an API for handling encoding-based matching of Accept-Encoding/Content-Encoding HTTP headers. It consists of the following classes:

EncoderSet

The EncoderSet class represents the set of encoders keyed by codings. It maintains a set of encoders and the codings that they can handle. The getEncoderMatch(String) and EncoderSet.getEncoder(String) methods are then used to find appropriate encoders for specific Accept-Encoding and Content-Encoding header values.

Match ordering

Encoders are tried in the order they appear in the set. The EncoderSet.Builder.add(Class...) / EncoderSet.Builder.add(Encoder...) methods prepend the values to the list to allow them the opportunity to override encoders already in the list.

For example, calling builder.add(E1.class,E2.class).add(E3.class, E4.class) will result in the order E3, E4, E1, E2.

Example:

// Create an encoder group with support for gzip compression. EncoderSet encoders = EncoderSet .create() .add(GzipEncoder.class) .build(); // Should return "gzip" String matchedCoding = encoders.findMatch("compress;q=1.0, gzip;q=0.8, identity;q=0.5, *;q=0"); // Get the encoder Encoder encoder = encoders.getEncoder(matchedCoding);

Encoder API

The Encoder interface is used for enabling decompression on requests and compression on responses, such as support for GZIP compression. It is used to wrap input and output streams within compression/decompression streams.

Encoders are registered with RestServlets through the @Rest(encoders) annotation.

2.23 - Object Toolscreated: 9.0.0

The org.apache.juneau.objecttools package defines convenience utility classes for accessing and manipulating POJOs. It consists of the following classes:

ObjectRest

The ObjectRest class provides the ability to perform standard REST operations (GET, PUT, POST, DELETE) against nodes in a POJO model. Nodes in the POJO model are addressed using URLs.

A POJO model is defined as a tree model where nodes consist of consisting of the following:

  • Maps and Java beans representing JSON objects.
  • Collections and arrays representing JSON arrays.
  • Java beans.

Leaves of the tree can be any type of object.

Use get() to retrieve an element from a JSON tree.
Use put() to create (or overwrite) an element in a JSON tree.
Use post() to add an element to a list in a JSON tree.
Use delete() to remove an element from a JSON tree.

Example:

// Construct an unstructured POJO model JsonMap map = JsonMap.ofJson("" + "{" + " name:'John Smith', " + " address:{ " + " streetAddress:'21 2nd Street', " + " city:'New York', " + " state:'NY', " + " postalCode:10021 " + " }, " + " phoneNumbers:[ " + " '212 555-1111', " + " '212 555-2222' " + " ], " + " additionalInfo:null, " + " remote:false, " + " height:62.4, " + " 'fico score':' > 640' " + "} " ); // Wrap Map inside an ObjectRest object ObjectRest johnSmith = ObjectRest.create(map); // Get a simple value at the top level // "John Smith" String name = johnSmith.getString("name"); // Change a simple value at the top level johnSmith.put("name", "The late John Smith"); // Get a simple value at a deep level // "21 2nd Street" String streetAddress = johnSmith.getString("address/streetAddress"); // Set a simple value at a deep level johnSmith.put("address/streetAddress", "101 Cemetery Way"); // Get entries in a list // "212 555-1111" String firstPhoneNumber = johnSmith.getString("phoneNumbers/0"); // Add entries to a list johnSmith.post("phoneNumbers", "212 555-3333"); // Delete entries from a model johnSmith.delete("fico score"); // Add entirely new structures to the tree JsonMap medicalInfo = JsonMap.ofJson("" + "{" + " currentStatus: 'deceased'," + " health: 'non-existent'," + " creditWorthiness: 'not good'" + "}" ); johnSmith.put("additionalInfo/medicalInfo", medicalInfo);

In the special case of collections/arrays of maps/beans, a special XPath-like selector notation can be used in lieu of index numbers on GET requests to return a map/bean with a specified attribute value.
The syntax is @attr=val, where attr is the attribute name on the child map, and val is the matching value.

Example:

// Get map/bean with name attribute value of 'foo' from a list of items Map map = objectRest.getMap("/items/@name=foo");

  • This class is used in the Traversable REST response converter.
ObjectSearcher

The ObjectSearcher class is designed to provide searches across arrays and collections of maps or beans. It allows you to quickly filter beans and maps using simple yet sophisticated search arguments.

Example:

MyBean[] arrayOfBeans = ...; ObjectSearcher searcher = ObjectSearcher.create(); // Returns a list of beans whose 'foo' property is 'X' and 'bar' property is 'Y'. List<MyBean> result = searcher.run(arrayOfBeans, "foo=X,bar=Y");

The tool can be used against the following data types:

  • Arrays/collections of maps or beans.

The default searcher is configured with the following matcher factories that provides the capabilities of matching against various data types. This list is extensible:

The StringMatcherFactory class provides searching based on the following patterns:

  • "property=foo" - Simple full word match
  • "property=fo*", "property=?ar" - Meta-character matching
  • "property=foo bar"(implicit), "property=^foo ^bar"(explicit) - Multiple OR'ed patterns
  • "property=+fo* +*ar" - Multiple AND'ed patterns
  • "property=fo* -bar" - Negative patterns
  • "property='foo bar'" - Patterns with whitespace
  • "property=foo\\'bar" - Patterns with single-quotes
  • "property=/foo\\s+bar" - Regular expression match

The NumberMatcherFactory class provides searching based on the following patterns:

  • "property=1" - A single number
  • "property=1 2" - Multiple OR'ed numbers
  • "property=-1 -2" - Multiple OR'ed negative numbers
  • "property=1-2","property=-2--1" - A range of numbers (whitespace ignored)
  • "property=1-2 4-5" - Multiple OR'ed ranges
  • "property=<1","property=<=1","property=>1","property=>=1" - Open-ended ranges
  • "property=!1","property=!1-2" - Negation

The TimeMatcherFactory class provides searching based on the following patterns:

  • "property=2011" - A single year
  • "property=2011 2013 2015" - Multiple years
  • "property=2011-01" - A single month
  • "property=2011-01-01" - A single day
  • "property=2011-01-01T12" - A single hour
  • "property=2011-01-01T12:30" - A single minute
  • "property=2011-01-01T12:30:45" - A single second
  • "property=>2011","property=>=2011","property=<2011","property=<=2011" - Open-ended ranges
  • "property=>2011","property=>=2011","property=<2011","property=<=2011" - Open-ended ranges
  • "property=2011 - 2013-06-30" - Closed ranges
  • This class is used in the Queryable REST response converter.
ObjectSorter

The ObjectSorter class is designed to sort arrays and collections of maps or beans.

Example:

MyBean[] arrayOfBeans = ...; ObjectSorter sorter = ObjectSorter.create(); // Returns a list of beans sorted accordingly. List<MyBean> result = sorter.run(arrayOfBeans, "foo,bar-");

The tool can be used against the following data types:

  • Arrays/collections of maps or beans.

The arguments are a simple comma-delimited list of property names optionally suffixed with '+' and '-' to denote ascending/descending order.

  • This class is used in the Queryable REST response converter.
ObjectViewer

The ObjectViewer class is designed to extract properties from collections of maps or beans.

Example:

MyBean[] arrayOfBeans = ...; ObjectViewer viewer = ObjectViewer.create(); // Returns the 'foo' and 'bar' properties extracted into a list of maps. List<Map> result = viewer.run(arrayOfBeans, "foo,bar");

The tool can be used against the following data types:

  • Arrays/collections of maps or beans.
  • Singular maps or beans.
  • This class is used in the Queryable REST response converter.
ObjectPaginator

The ObjectPaginator class is designed to extract sublists from arrays/collections of maps or beans.

Example:

MyBean[] arrayOfBeans = ...; ObjectPaginator paginator = ObjectPaginator.create(); // Returns all rows from 100 to 110. List<MyBean> result = paginator.run(arrayOfBeans, 100, 10);

The tool can be used against the following data types:

  • Arrays/collections of maps or beans.
  • This class is used in the Queryable REST response converter.
ObjectIntrospector

The ObjectIntrospector class is used to invoke methods on Objects using arguments in serialized form.

Example:

String string1 = "foobar"; String string2 = ObjectIntrospector .create(string) .invoke(String.class, "substring(int,int)", "[3,6]"); // "bar"

The arguments passed to the identified method are POJOs serialized in JSON format. Arbitrarily complex arguments can be passed in as arguments.

  • This class is used in the Introspectable REST response converter.
  • This is an extremely powerful but potentially dangerous tool. Use wisely.
ObjectMerger

The ObjectMerger class is used for merging POJOs behind a single interface. This is particularly useful in cases where you want to define beans with 'default' values.

For example, given the following bean classes:

public interface IA { String getX(); void setX(String x); } public class A implements IA { private String x; public A(String x) { this.x = x; } public String getX() { return x; } public void setX(String x) { this.x = x; } }

The getters will be called in order until the first non-null value is returned:

merge = ObjectMerger.merger(IA.class, new A("1"), new A("2")); assertEquals("1", merge.getX()); merge = ObjectMerger.merger(IA.class, new A(null), new A("2")); assertEquals("2", merge.getX()); merge = ObjectMerger.merger(IA.class, new A(null), new A(null)); assertEquals(null, merge.getX());

2.24 - JSON Details

Juneau supports converting arbitrary POJOs to and from JSON using ultra-efficient serializers and parsers. The JSON serializer converts POJOs directly to JSON without the need for intermediate DOM objects using a highly-efficient state machine. Likewise, the JSON parser creates POJOs directly from JSON without the need for intermediate DOM objects.

The following example shows JSON for a typical bean:

Sample Beans

public class Person { // Bean properties public String name; @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

Sample Code

Person person = new Person() .name("John Smith") .birthDate("1946-08-12T00:00:00Z") .addresses( new Address() .street("100 Main Street") .city("Anywhereville") .state(NY) .zip(12345) .isCurrent(true); );

Normal JSON

{ "name": "John Smith", "birthDate": "1946-08-12T00:00:00Z", "addresses": [ { "street": "100 Main Street", "city": "Anywhereville", "state": "NY", "zip": 12345, "isCurrent": true } ] }

Simplified JSON

{ name: 'John Smith', birthDate: '1946-08-12T00:00:00Z', addresses: [ { street: '100 Main Street', city: 'Anywhereville', state: 'NY', zip: 12345, isCurrent: true } ] }

2.24.1 - JSON Methodology

The JSON data type produced depends on the Java object type being serialized.

  • Primitives and primitive objects are converted to JSON primitives.
  • Beans and Maps are converted to JSON objects.
  • Collections and arrays are converted to JSON arrays.
  • Anything else is converted to JSON strings.
Data type conversions:
POJO type JSON type Example Serialized form
String String serialize("foobar"); 'foobar'
Number Number serialize(123); 123
Boolean Boolean serialize(true); true
Null Null serialize(null); null
Beans with properties of any type on this list Object serialize(new MyBean()); {p1:'val1',p2:true}
Maps with values of any type on this list Object serialize(new TreeMap()); {key1:'val1',key2:true}
Collections and arrays of any type on this list Array serialize(new Object[]{1,"foo",true}); [1,'foo',true]

In addition, swaps can be used to convert non-serializable POJOs into serializable forms, such as converting Calendar object to ISO8601 strings, or byte[] arrays to Base-64 encoded strings.

2.24.2 - JSON Serializersupdated: 9.0.0

The JsonSerializer class is used to serialize POJOs into JSON.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.24.3 - JSON 5updated: 9.0.0

The Json5Serializer class can be used to serialized POJOs into JSON 5 notation.

JSON 5 is similar to JSON except for the following:

  • JSON attributes are only quoted when necessary.
  • Uses single-quotes for quoting.
Examples:

// Some free-form JSON. JsonMap map = JsonMap.of( "foo", "x1", "_bar", "x2", " baz ", "x3", "123", "x4", "return", "x5", "", "x6" );

// Serialized to standard JSON { "foo": "x1", "_bar": "x2", " baz ": "x3", "123": "x4", "return": "x5", "": "x6" }

// Serialized to simplified JSON { foo: 'x1', _bar: 'x2', ' baz ': 'x3', // Quoted due to embedded spaces. '123': 'x4', // Quoted to prevent confusion with number. 'return': 'x5', // Quoted because it's a keyword. '': 'x6' // Quoted because it's an empty string. }

The advantage to JSON 5 is you can represent it in a Java String in minimal form with minimal escaping. This is particularly useful in cases such as unit testing where you can easily validate POJOs by simplifying them to JSON 5 and do a simple string comparison.

WriterSerializer serializer = Json5Serializer.DEFAULT; assertEquals("{foo:'bar',baz:123}", serializer.toString(myPojo));

2.24.4 - JSON Parsersupdated: 9.0.0

The JsonParser class is used to parse JSON into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

The JSON parser supports ALL valid JSON, including:

  • Javascript comments.
  • Single or double quoted values.
  • Quoted (strict) or unquoted (non-strict) attributes.
  • JSON fragments (such as string, numeric, or boolean primitive values).
  • Concatenated strings.
2.24.5 - @Json Annotation

The @Json annotation is used to override the behavior of JsonSerializer on individual bean classes or properties.

The annotation can be applied to beans as well as other objects serialized to other types (e.g. strings).

The @Json(wrapperAttr) annotation can be used to wrap beans inside a JSON object with a specified attribute name.

Example:

@Json(wrapperAttr="personBean") public class Person { public String name = "John Smith"; }

The following shows the JSON representation with and without the annotation present:

Without annotation With annotation
{ name: 'John Smith' } { personBean: { name: 'John Smith' } }

2.25 - JSON-Schema Support

Juneau provides the JsonSchemaSerializer class for generating JSON-Schema documents that describe the output generated by the JsonSerializer class. This class shares the same properties as JsonSerializer. For convenience the JsonSerializer.getSchemaSerializer() method has been added for creating instances of schema serializers from the regular serializer instance.

Sample Beans

public class Person { // Bean properties public String name; public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

The code for creating our POJO model and generating JSON-Schema is shown below:

// Get the one of the default schema serializers. JsonSchemaSerializer serializer = JsonSchemaSerializer.DEFAULT_SIMPLE_READABLE; // Get the JSON Schema for the POJO. String jsonSchema = serializer.serialize(new Person()); // This also works. jsonSchema = serializer.serialize(Person.class);

JSON Schema

{ type: 'object', description: 'org.apache.juneau.sample.Person', properties: { name: { type: 'string', description: 'java.lang.String' }, birthDate: { type: 'string', description: 'java.util.Calendar' }, addresses: { type: 'array', description: 'java.util.LinkedList<org.apache.juneau.sample.Address>', items: { type: 'object', description: 'org.apache.juneau.sample.Address', properties: { street: { type: 'string', description: 'java.lang.String' }, city: { type: 'string', description: 'java.lang.String' }, state: { type: 'string', description: 'java.lang.String' }, zip: { type: 'number', description: 'int' }, isCurrent: { type: 'boolean', description: 'boolean' } } } } } }

2.26 - XML Details

Juneau supports converting arbitrary POJOs to and from XML using ultra-efficient serializers and parsers. The XML serializer converts POJOs directly to XML without the need for intermediate DOM objects. Likewise, the XML parser uses a STaX parser and creates POJOs directly without intermediate DOM objects.

Unlike frameworks such as JAXB, Juneau does not require POJO classes to be annotated to produce and consume XML. However, several XML annotations are provided for handling namespaces and fine-tuning the format of the XML produced.

The following example shows XML for a typical bean:

Sample Beans

@Bean(typeName="person") public class Person { // Bean properties public String name; @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } @Bean(typeName="address") public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

Sample Code

Person person = new Person() .name("John Smith") .birthDate("1946-08-12T00:00:00Z") .addresses( new Address() .street("100 Main Street") .city("Anywhereville") .state(NY) .zip(12345) .isCurrent(true); );

Normal XML:

<person> <name>John Smith</name> <birthDate>1946-08-12T04:00:00Z</birthDate> <addresses> <address> <street>100 Main Street</street> <city>Anywhereville</city> <state>NY</state> <zip>12345</zip> <isCurrent>true</isCurrent> </address> </addresses> </person>

Juneau produces JSON-equivalent XML, meaning any valid JSON document can be losslessly converted into an XML equivalent. In fact, all of the Juneau serializers and parsers are built upon this JSON-equivalence.

2.26.1 - XML Methodology

The following examples show how different data types are represented in XML. They mirror how the data structures are represented in JSON.

Simple types

The representation of loose (not a direct bean property value) simple types are shown below:

Data type JSON example XML
string 'foo' <string>foo</string>
boolean true <boolean>true</boolean>
integer 123 <number>123</number>
float 1.23 <number>1.23</number>
null null <null/>
Maps

Loose maps and beans use the element <object> for encapsulation.

_type attributes are added to bean properties or map entries if the type cannot be inferred through reflection (e.g. an Object or superclass/interface value type).

Data type JSON example XML
Map<String,String> { k1: 'v1' k2: null } <object> <k1>v1</k1> <k2 _type='null'/> </object>
Map<String,Number> { k1: 123, k2: 1.23, k3: null } <object> <k1>123</k1> <k2>1.23</k2> <k3 _type='null'/> </object>
Map<String,Object> { k1: 'v1' k2: 123, k3: 1.23, k4: true, k5: null } <object> <k1>v1</k1> <k2 _type='number'>123</k2> <k3 _type='number'>1.23</k3> <k4 _type='boolean'>true</k4> <k5 _type='null'/> </object>
Arrays

Loose collections and arrays use the element <array> for encapsulation.

Data type JSON example XML
String[] [ 'foo' null ] <array> <string>foo</string> <null/> </array>
Number[] [ 123, 1.23, null ] <array> <number>123</number> <number>1.23</number> <null/> </array>
Object[] [ 'foo', 123, 1.23, true, null ] <array> <string>foo</string> <number>123</number> <number>1.23</number> <boolean>true</boolean> <null/> </array>
String[][] [ ['foo', null], null, ] <array> <array> <string>foo</string> <null/> </array> <null/> </array>
int[] [ 123 ] <array> <number>123</number> </array>
boolean[] [ true ] <array> <boolean>true</boolean> </array>
List<String> [ 'foo' null ] <array> <string>foo</string> <null/> </array>
List<Number> [ 123, 1.23, null ] <array> <number>123</number> <number>1.23</number> <null/> </array>
List<Object> [ 'foo', 123, 1.23, true, null ] <array> <string>foo</string> <number>123</number> <number>1.23</number> <boolean>true</boolean> <null/> </array>
Beans
Data type JSON example XML
class MyBean { public String a; public int b; public Object c; // String value public Object d; // Integer value public MyBean2 e; public String[] f; public int[] g; } class MyBean2 { String h; } { a: 'foo', b: 123, c: 'bar', d: 456, e: { h: 'baz' } f: ['qux'] g: [789] } <object> <a>foo</a> <b>123</b> <c>bar</c> <d _type='number'>456</d> <e> <h>baz</h> </e> <f> <string>qux</string> </f> <g> <number>789</number> </g> </object>
Beans with Map properties
Data type JSON example XML
class MyBean { public Map<String,String> a; public Map<String,Number> b; public Map<String,Object> c; } { a: { k1: 'foo' }, b: { k2: 123 }, c: { k3: 'bar', k4: 456, k5: true, k6: null } } <object> <a> <k1>foo</k1> </a> <b> <k2>123</k2> </b> <c> <k3>bar</k3> <k4 _type='number'>456</k4> <k5 _type='boolean'>true</k5> <k6 _type='null'/> </c> </object>
2.26.2 - XML Serializersupdated: 9.0.0

The XmlSerializer class is used to serialize POJOs into XML.

The XmlDocSerializer class is the same but serializes a <?xml?> header at the top of the file.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.26.3 - XML Parsersupdated: 9.0.0

The XmlParser class is used to parse XML into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

2.26.4 - @Bean(typeName) Annotation

The @Bean(typeName) annotation can be used to override the Juneau default name on bean elements. Types names serve two distinct purposes:

  1. To override the element name.
  2. To serve as a class identifier so that the bean class can be inferred during parsing if it cannot automatically be inferred through reflection.
Example
Data type JSON example Without annotation With annotation
@Bean(typeName="X") class MyBean { public String a; public int b; } { a: 'foo', b: 123 } <object> <a>foo</id> <b>123</name> </object> <X> <a>foo</id> <b>123</name> </X>

On bean properties, a _type attribute will be added if a type name is present and the bean class cannot be inferred through reflection.

In the following example, a type attribute is used on property 'b' but not property 'a' since 'b' is of type Object and therefore the bean class cannot be inferred.

Example
Java Without annotation With annotation
class MyBean { public BeanX a = new BeanX(); public Object b = new BeanX(); } @Bean(typeName="X") class BeanX { public String fx = "foo"; } <object> <a> <fx>foo</fx> </a> <b> <fx>foo</fx> </b> </object> <object> <a> <fx>foo</fx> </a> <b _type='X'> <fx>foo</fx> </b> </object>
string, number, boolean, object, array, and null are reserved keywords that cannot be used as type names.

Beans with type names are often used in conjunction with the @Bean(dictionary) and @Beanp(dictionary) annotations so that the beans can be resolved at parse time. These annotations are not necessary during serialization but are needed during parsing in order to resolve the bean types.

The following examples show how type names are used under various circumstances.

Pay special attention to when _type attributes are and are not used.

Examples
Java XML
@Bean(dictionary={BeanX.class}) class BeanWithArrayPropertiesWithTypeNames { public BeanX[] b1 = new BeanX[]{ new BeanX() }; public Object[] b2 = new BeanX[]{ new BeanX() }; public Object[] b3 = new Object[]{ new BeanX() }; } <object> <b1> <X> <fx>foo</fx> </X> </b1> <b2> <X> <fx>foo</fx> </X> </b2> <b3> <X> <fx>foo</fx> </X> </b3> </object>
@Bean(dictionary={BeanX.class}) class BeanWith2dArrayPropertiesWithTypeNames { public BeanX[][] b1 = new BeanX[][]{{ new BeanX() }}; public Object[][] b2 = new BeanX[][]{{ new BeanX() }}; public Object[][] b3 = new Object[][]{{ new BeanX() }}; } <object> <b1> <array> <X> <fx>foo</fx> </X> </array> </b1> <b2> <array> <X> <fx>foo</fx> </X> </array> </b2> <b3> <array> <X> <fx>foo</fx> </X> </array> </b3> </object>
@Bean(dictionary={BeanX.class}) class BeanWithMapPropertiesWithTypeNames { public Map<String,BeanX> b1 = new HashMap<>() {{ put("k1", new BeanX()); }}; public Map<String,Object> b2 = new HashMap<>() {{ put("k2", new BeanX()); }} } <object> <b1> <k1> <fx>foo</fx> </k1> </b1> <b2> <k2 _type='X'> <fx>foo</fx> </k2> </b2> </object>

Bean type names are also used for resolution when abstract fields are used. The following examples show how they are used in a variety of circumstances.

Java XML
@Bean(dictionary={A.class}) class BeanWithAbstractFields { public A a = new A(); public IA ia = new A(); public AA aa = new A(); public Object o = new A(); } interface IA {} abstract class AA implements IA {} @Bean(typeName="A") class A extends AA { public String fa = "foo"; } <object> <a> <fa>foo</fa> </a> <ia _type='A'> <fa>foo</fa> </ia> <aa _type='A'> <fa>foo</fa> </aa> <o _type='A'> <fa>foo</fa> </o> </object>
@Bean(dictionary={A.class}) class BeanWithAbstractArrayFields { public A[] a = new A[]{new A()}; public IA[] ia1 = new A[]{new A()}; public IA[] ia2 = new IA[]{new A()}; public AA[] aa1 = new A[]{new A()}; public AA[] aa2 = new AA[]{new A()}; public Object[] o1 = new A[]{new A()}; public Object[] o2 = new Object[]{new A()}; } <object> <a> <A> <fa>foo</fa> </A> </a> <ia1> <A> <fa>foo</fa> </A> </ia1> <ia2> <A> <fa>foo</fa> </A> </ia2> <aa1> <A> <fa>foo</fa> </A> </aa1> <aa2> <A> <fa>foo</fa> </A> </aa2> <o1> <A> <fa>foo</fa> </A> </o1> <o2> <A> <fa>foo</fa> </A> </o2> </object>
@Bean(dictionary={A.class}) class BeanWithAbstractMapFields { public Map<String,A> a = new HashMap<>() {{ put("k1", new A()); }}; public Map<String,AA> b = new HashMap<>() {{ put("k2", new A()); }}; public Map<String,Object> c = new HashMap<>() {{ put("k3", new A()); }}; } <object> <a> <k1> <fa>foo</fa> </k1> </a> <b> <k2 _type='A'> <fa>foo</fa> </k2> </b> <c> <k3 _type='A'> <fa>foo</fa> </k3> </c> </object>
@Bean(dictionary={A.class}) class BeanWithAbstractMapArrayFields { public Map<String,A[]> a = new LinkedHashMap<>() {{ put("a1", new A[]{new A()}); }}; public Map<String,IA[]> ia = new LinkedHashMap<>() {{ put("ia1", new A[]{new A()}); put("ia2", new IA[]{new A()}); }}; public Map<String,AA[]> aa = new LinkedHashMap<>() {{ put("aa1", new A[]{new A()}); put("aa2", new AA[]{new A()}); }}; public Map<String,Object[]> o = new LinkedHashMap<>() {{ put("o1", new A[]{new A()}); put("o2", new AA[]{new A()}); }}; } <object> <a> <a1> <A> <fa>foo</fa> </A> </a1> </a> <ia> <ia1> <A> <fa>foo</fa> </A> </ia1> <ia2> <A> <fa>foo</fa> </A> </ia2> </ia> <aa> <aa1> <A> <fa>foo</fa> </A> </aa1> <aa2> <A> <fa>foo</fa> </A> </aa2> </aa> <o> <o1> <A> <fa>foo</fa> </A> </o1> <o2> <A> <fa>foo</fa> </A> </o2> </o> </object>

On a side note, characters that cannot be represented in XML 1.0 are encoded using a simple encoding. Note in the examples below, some characters such as '\n', '\t', and '\r' can be represented as XML entities when used in text but not in element names. Other characters such as '\b' and '\f' cannot be encoded in XML 1.0 at all without inventing our own notation. Whitespace characters in element names are encoded as well as whitespace end characters in text.

Java XML
class BeanWithSpecialCharacters { public String a = " \b\f\n\t\r "; } <object> <a>_x0020_ _x0008__x000C_&#x000a;&#x0009;&#x000d; _x0020_</a> </object>
@Bean(typeName=" \b\f\n\t\r ") class BeanWithNamesWithSpecialCharacters { @Beanp(name=" \b\f\n\t\r ") public String a = " \b\f\n\t\r "; } <_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_> <_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_> _x0020_ _x0008__x000C_&#x000a;&#x0009;&#x000d; _x0020_ </_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_> </_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_>

While it's true that these characters CAN be represented in XML 1.1, it's impossible to parse XML 1.1 text in Java without the XML containing an XML declaration. Unfortunately, this, and the uselessness of the XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES setting in Java forced us to make some hard design decisions that may not be the most elegant.

2.26.5 - @Xml(childName) Annotation

The @Xml(childName) annotation can be used to specify the name of XML child elements for bean properties of type collection or array.

Example
Data type JSON example Without annotation With annotation
class MyBean { @Xml(childName="X") public String[] a; @Xml(childName="Y") public int[] b; } { a: ['foo','bar'], b: [123,456] } <object> <a> <string>foo</string> <string>bar</string> </a> <b> <number>123</number> <number>456</number> </b> </object> <object> <a> <X>foo</X> <X>bar</X> </a> <b> <Y>123</Y> <Y>456</Y> </b> </object>
class MyBean { @Xml(childName="child") public int[] a; } { a: [123,456] } <object> <a> <string>foo</string> <string>bar</string> </a> </object> <object> <a> <child>foo</child> <child>bar</child> </a> </object>
2.26.6 - @Xml(format) Annotation

The @Xml(format) annotation can be used to tweak the XML format of a POJO. The value is set to an enum value of type XmlFormat. This annotation can be applied to both classes and bean properties.

The XmlFormat.ATTR format can be applied to bean properties to serialize them as XML attributes instead of elements. Note that this only supports properties of simple types (e.g. strings, numbers, booleans).

Example
Data type JSON example Without annotation With annotation
class MyBean { @Xml(format=XmlFormat.ATTR) public String a; } { a: 'foo' } <object> <a>foo</a> </object> <object a='foo'/>

The XmlFormat.ATTRS format can be applied to bean classes to force all bean properties to be serialized as XML attributes instead of child elements.

Example
Data type JSON example Without annotation With annotation
@Xml(format=XmlFormat.ATTRS) class MyBean { public String a; public int b; } { a: 'foo', b: 123 } <object> <a>foo</a> <b>123</b> </object> <object a='foo' b='123'/>

The XmlFormat.ELEMENT format can be applied to bean properties to override the XmlFormat.ATTRS format applied on the bean class.

Example
Data type JSON example Without annotation With annotation
@Xml(format=XmlFormat.ATTRS) class MyBean { public String a; @Xml(format=XmlFormat.ELEMENT) public int b; } { a: 'foo', b: 123 } <object> <a>foo</a> <b>123</b> </object> <object a='foo'> <b>123</b> </object>

The XmlFormat.ATTRS format can be applied to a single bean property of type Map<String,Object> to denote arbitrary XML attribute values on the element. These can be mixed with other XmlFormat.ATTR annotated properties but there must not be an overlap in bean property names and map keys.

Example
Data type JSON example Without annotation With annotation
class MyBean { @Xml(format=XmlFormat.ATTRS) public Map<String,Object> a; @Xml(format=XmlFormat.ATTR) public int b; } { a: { k1: 'foo', k2: 123, }, b: 456 } <object> <a> <k1>foo</k1> <k2 _type='number'>123</k2> </a> <b>456</b> </object> <object k1='foo' k2='123' b='456'/>

The XmlFormat.COLLAPSED format can be applied to bean properties of type array/Collection. This causes the child objects to be serialized directly inside the bean element. This format must be used in conjunction with @Xml(childName) to differentiate which collection the values came from if you plan on parsing the output back into beans. Note that child names must not conflict with other property names.

Data type JSON example Without annotation With annotation
class MyBean { @Xml(childName="A",format=XmlFormat.COLLAPSED) public String[] a; @Xml(childName="B",format=XmlFormat.COLLAPSED) public int[] b; } { a: ['foo','bar'], b: [123,456] } <object> <a> <string>foo</string> <string>bar</string> </a> <b> <number>123</number> <number>456</number> </b> </object> <object> <A>foo</A> <A>bar</A> <B>123</B> <B>456</B> </object>

The XmlFormat.ELEMENTS format can be applied to a single bean property of either a simple type or array/Collection. It allows free-form child elements to be formed. All other properties on the bean MUST be serialized as attributes.

Data type JSON example With annotation
class MyBean { @Xml(format=XmlFormat.ATTR) public String a; @Xml(format=XmlFormat.ELEMENTS) public String b; } { a: 'foo', b: 'bar' } <object a='foo'> <string>bar</string> </object>
class MyBean { @Xml(format=XmlFormat.ATTR) public String a; @Xml(format=XmlFormat.ELEMENTS) public Object[] b; } { a: 'foo', b: [ 'bar', 'baz', 123, true, null ] } <object a='foo'> <string>bar</string> <string>baz</string> <number>123</number> <boolean>true</boolean> <null/> </object>

The XmlFormat.MIXED format is similar to XmlFormat.ELEMENTS except elements names on primitive types (string/number/boolean/null) are stripped from the output. This format particularly useful when combined with bean dictionaries to produce mixed content. The bean dictionary isn't used during serializationbut it is needed during parsing to resolve bean types.

The XmlFormat.MIXED_PWS format identical to XmlFormat.MIXED except whitespace characters are preserved in the output.

Data type JSON example Without annotations With annotations
class MyBean { @Xml(format=XmlFormat.MIXED) @Beanp(dictionary={MyBeanX.class, MyBeanY.class}) public Object[] a; } @Bean(typeName="X") class MyBeanX { @Xml(format=XmlFormat.ATTR) public String b; } @Bean(typeName="Y") class MyBeanY { @Xml(format=XmlFormat.ATTR) public String c; } { a: [ 'foo', { _type:'X', b:'bar' } 'baz', { _type:'Y', b:'qux' }, 'quux' ] } <object> <a> <string>foo</string> <object> <b>bar</b> </object> <string>baz</string> <object> <b>qux</b> </object> <string>quux</string> </a> </object> <object>foo<X b='bar'/>baz<Y c='qux'/>quux</object>

Whitespace (tabs and newlines) are not added to MIXED child nodes in readable-output mode. This helps ensures strings in the serialized output can be losslessly parsed back into their original forms when they contain whitespace characters. If the XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES setting was not useless in Java, we could support lossless readable XML for MIXED content. But as of Java 8, it still does not work.

XML suffers from other deficiencies as well that affect MIXED content. For example, <X></X> and <X/> are equivalent in XML and indistinguishable by the Java XML parsers. This makes it impossible to differentiate between an empty element and an element containing an empty string. This causes empty strings to get lost in translation. To alleviate this, we use the constructs "_xE000_" to represent an empty string, and "_x0020_" to represent leading and trailing spaces.

The examples below show how whitespace is handled under various circumstances:

Data type XML
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT) public String a = null; } <X/>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT) public String a = ""; } <X>_xE000_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT) public String a = " "; } <X>_x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT) public String a = " "; } <X>_x0020__x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT) public String a = " foobar "; } <X>_x0020_ foobar _x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT_PWS) public String a = null; } <X/>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT_PWS) public String a = ""; } <X>_xE000_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT_PWS) public String a = " "; } <X> </X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT_PWS) public String a = " "; } <X> </X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.TEXT_PWS) public String a = " foobar "; } <X> foobar </X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED) public String[] a = null; } <X/>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED) public String[] a = {""}; } <X>_xE000_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED) public String[] a = {" "}; } <X>_x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED) public String[] a = {" "}; } <X>_x0020__x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED) public String[] a = {" foobar "}; } <X>_x0020_ foobar _x0020_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED_PWS) public String[] a = null; } <X/>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED_PWS) public String[] a = {""}; } <X>_xE000_</X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED_PWS) public String[] a = {" "}; } <X> </X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED_PWS) public String[] a = {" "}; } <X> </X>
@Bean(typeName="X") class MyBean { @Xml(format=XmlFormat.MIXED_PWS) public String[] a = {" foobar "}; } <X> foobar </X>

It should be noted that when using MIXED, you are not guaranteed to parse back the exact same content since side-by-side strings in the content will end up concatenated when parsed.

The XmlFormat.TEXT format is similar to XmlFormat.MIXED except it's meant for solitary objects that get serialized as simple child text nodes. Any object that can be serialize to a String can be used. The XmlFormat.TEXT_PWS is the same except whitespace is preserved in the output.

Data type JSON example Without annotations With annotations
class MyBean { @Xml(format=XmlFormat.TEXT) public String a; } { a: 'foo' } <object> <a>foo</a> </object> <object>foo</object>

The XmlFormat.XMLTEXT format is similar to XmlFormat.TEXT except it's meant for strings containing XML that should be serialized as-is to the document. Any object that can be serialize to a String can be used. During parsing, the element content gets parsed with the rest of the document and then re-serialized to XML before being set as the property value. This process may not be perfect (e.g. double quotes may be replaced by single quotes, etc...).

Data type JSON example With TEXT annotation With XMLTEXT annotation
class MyBean { @Xml(format=XmlFormat.XMLTEXT) public String a; } { a: 'Some <b>XML</b> text' } <object>Some &lt;b&gt;XML&lt;/b&gt; text</object> <object>Some <b>XML</b> text</object>
2.26.7 - Namespaces

Let's go back to the example of our original Person bean class but add some namespace annotations:

Sample Beans

@Xml(prefix="per") @Bean(typeName="person") public class Person { // Bean properties public String name; @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } @Xml(prefix="addr") @Bean(typeName="address") public class Address { // Bean properties @Xml(prefix="mail") public String street, city; @Xml(prefix="mail") public StateEnum state; @Xml(prefix="mail") public int zip; public boolean isCurrent; // Getters/setters omitted }

The namespace URLs can either be defined as part of the @Xml annotation, or can be defined at the package level with the @XmlSchema annotation. Below shows it defined at the package level:

package-info.java

@XmlSchema( prefix="ab", // Default namespace 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;

Sample Code

Person person = new Person() .name("John Smith") .birthDate("1946-08-12T00:00:00Z") .addresses( new Address() .street("100 Main Street") .city("Anywhereville") .state(NY) .zip(12345) .isCurrent(true); ); // Create a new serializer with readable output, this time with namespaces enabled. // Note that this is identical to XmlSerializer.DEFAULT_NS_SQ_READABLE. XmlSerializer serializer = XmlSerializer.create().ns().ws().sq().build(); String xml = serializer.serialize(p);

Now when we run this code, we'll see namespaces added to our output:

<per:person> <per:name>John Smith</per:name> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses> <addr:address> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

Enabling the XmlSerializer.XML_addNamespaceUrisToRootsetting results in the namespace URLs being added to the root node:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' > <per:name>John Smith</per:name> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses> <addr:address> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

We can simplify the output by setting the default namespace on the serializer so that all the elements do not need to be prefixed:

// Create a new serializer with readable output, this time with namespaces enabled. XmlSerializer serializer = XmlSerializer.create().ws().sq().ns() .defaultNamespaceUri("http://www.apache.org/person/") .build();

This produces the following equivalent where the elements don't need prefixes since they're already in the default document namespace:

<person xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' > <name>John Smith</name> <birthDate>1946-08-12T04:00:00Z</birthDate> <addresses> <addr:address> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </addresses> </person>

By default, the XML serializer class will make a first-pass over the data structure to look for namespaces defined on classes and bean properties. In high-performance environments, you may want to consider disabling auto-detection and providing your own explicit list of namespaces to the serializer to avoid this scanning step using XmlSerializer.Builder.disableAutoDetectNamespaces().

The following code will produce the same output as before but will perform slightly better since it avoids this pre-scan step.

// Create a new serializer with readable output, this time with namespaces enabled. XmlSerializer serializer = XmlSerializer.create() .ws() .sq() .autoDetectNamespaces(false) .namespaces("{per:'http://www.apache.org/person/'}") .build();

2.27 - HTML Details

Juneau supports converting arbitrary POJOs to and from HTML. Built on top of the existing XML parser, it also uses a STaX parser and creates POJOs directly without intermediate DOM objects.

The primary use case for HTML serialization is rendering POJOs in easy-to-read format in REST interfaces.

2.27.1 - HTML Methodology

The following examples show how different data types are represented in HTML. They mirror how the data structures are represented in JSON.

Simple types

The representation for simple types mirror those produced by the XML serializer. Tags are added to help differentiate data types when they cannot be inferred through reflection. These tags are ignored by browsers and treated as plain text.

Data type JSON example HTML
string 'foo' <string>foo</string>
boolean true <boolean>true</boolean>
integer 123 <number>123</number>
float 1.23 <number>1.23</number>
null null <null/>
Maps

Maps and beans are represented as tables.

The _type attribute is added to differentiate between objects (maps/beans) and arrays (arrays/collections).

Data type JSON example HTML
Map<String,String> { k1: 'v1' k2: null } <table _type='object'> <tr> <td>k1</td> <td>v1</td> </tr> <tr> <td>k2</td> <td><null/></td> </tr> </table>
Map<String,Number> { k1: 123, k2: 1.23, k3: null } <table _type='object'> <tr> <td>k1</td> <td>123</td> </tr> <tr> <td>k2</td> <td>1.23</td> </tr> <tr> <td>k3</td> <td><null/></td> </tr> </table>
Map<String,Object> { k1: 'v1' k2: 123, k3: 1.23, k4: true, k5: null } <table _type='object'> <tr> <td>k1</td> <td>v1</td> </tr> <tr> <td>k2</td> <td><number>123</number></td> </tr> <tr> <td>k3</td> <td><number>1.23</number></td> </tr> <tr> <td>k4</td> <td><boolean>true</boolean></td> </tr> <tr> <td>k5</td> <td><null/></td> </tr> </table>
Arrays

Collections and arrays are represented as ordered lists.

Data type JSON example HTML
String[] [ 'foo' null ] <ul> <li>foo</li> <li><null/></li> </ul>
Number[] [ 123, 1.23, null ] <ul> <li>123</li> <li>1.23</li> <li><null/></li> </ul>
Object[] [ 'foo', 123, 1.23, true, null ] <ul> <li>foo</li> <li><number>123</number></li> <li><number>1.23</number></li> <li><boolean>true</boolean></li> <li><null/></li> </ul>
String[][] [ ['foo', null], null, ] <ul> <li> <ul> <li>foo</li> <li><null/></li> </ul> </li> <li><null/></li> </ul>
int[] [ 123 ] <ul> <li>123</li> </ul>
boolean[] [ true ] <ul> <li>true</li> </ul>
Collections
Data type JSON example HTML
List<String> [ 'foo' null ] <ul> <li>foo</li> <li><null/></li> </ul>
List<Number> [ 123, 1.23, null ] <ul> <li>123</li> <li>1.23</li> <li><null/></li> </ul>
List<Object> [ 'foo', 123, 1.23, true, null ] <ul> <li>foo</li> <li><number>123</number></li> <li><number>1.23</number></li> <li><boolean>true</boolean></li> <li><null/></li> </ul>
Beans
Data type JSON example HTML
class MyBean { public String a; public int b; public Object c; // String value public Object d; // Integer value public MyBean2 e; public String[] f; public int[] g; } class MyBean2 { String h; } { a: 'foo', b: 123, c: 'bar', d: 456, e: { h: 'baz' } f: ['qux'] g: [789] } <table _type='object'> <tr> <td>a</td> <td>foo</td> </tr> <tr> <td>b</td> <td>123</td> </tr> <tr> <td>c</td> <td>bar</td> </tr> <tr> <td>d</td> <td><number>456</number></td> </tr> <tr> <td>e</td> <td> <table _type='object'> <tr> <td>h</td> <td>qux</td> </tr> </table> </td> </tr> <tr> <td>f</td> <td> <ul> <li>baz</li> </ul> </td> </tr> <tr> <td>g</td> <td> <ul> <li>789</li> </ul> </td> </tr> </table>
Beans with Map properties
Data type JSON example HTML
class MyBean { public Map<String,String> a; public Map<String,Number> b; public Map<String,Object> c; } { a: { k1: 'foo' }, b: { k2: 123 }, c: { k3: 'bar', k4: 456, k5: true, k6: null } } <table _type='object'> <tr> <td>a</td> <td> <table _type='object'> <tr> <td>k1</td> <td>foo</td> </tr> </table> </td> </tr> <tr> <td>b</td> <td> <table _type='object'> <tr> <td>k2</td> <td>123</td> </tr> </table> </td> </tr> <tr> <td>c</td> <td> <table _type='object'> <tr> <td>k3</td> <td>bar</td> </tr> <tr> <td>k4</td> <td><number>456</number></td> </tr> <tr> <td>k5</td> <td><boolean>true</boolean></td> </tr> <tr> <td>k6</td> <td><null/></td> </tr> </table> </td> </tr> </table>
2.27.2 - HTML Serializersupdated: 9.0.0

The HtmlSerializer class is used to serialize POJOs into HTML.

The HtmlDocSerializer class is the same but wraps the serialized POJO inside a document template consisting of header, nav, aside, and footer sections.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.27.3 - HTML Parsersupdated: 9.0.0

The HtmlParser class is used to parse HTML into POJOs. They can also parse the contents produced by HtmlDocSerializer.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

2.27.4 - @Html Annotation

The @Html annotation can be used to customize how POJOs are serialized to HTML on a per-class/field/method basis.

The @Html(link) annotation adds a hyperlink to a bean property when rendered as HTML.

Example:

public class FileSpace { // Add a hyperlink to this bean property. @Html(link="servlet:/drive/{drive}") public String getDrive() {...} }

The @Html(anchorText) annotation is used to specify the anchor text of a hyperlink.

Example:

// Produces <a ...>CLICK ME!</a> when serialized to HTML. public class FileSpace { // Add a hyperlink to this bean property. @Html(link="servlet:/drive/{drive}", anchorText="CLICK ME!") public String getDrive() {...} }

The @Html(format) annotation is used to specify what format to use for HTML elements. For example, the HTML beans defined in the org.apache.juneau.dto.html5 package use format=XML so that the beans get serialized as standard XML:

Example:

// Parent class of all HTML DTO beans. @Html(format=XML) public abstract class HtmlElement {...}

The @Html(noTableHeaders) annotation is used to prevent beans from being serialized with table headers.

The @Html(noTables) annotation is used to force beans to be serialized as trees instead of tables

2.27.5 - @Html(render) Annotation

The @Html(render) annotation allows for custom rendering of bean property values when serialized as HTML. Using this class, you can alter the CSS style and HTML content of the bean property.

The following example shows two render classes that customize the appearance of the pctFull and status columns in the code below:

import static org.apache.juneau.dto.html5.HtmlBuilder.*; // Our bean class public class FileSpace { private final String drive; private final long total, available; public FileSpace(String drive, long total, long available) { this.drive = drive; this.total = total; this.available = available; } @Html(link="drive/{drive}") public String getDrive() { return drive; } public long getTotal() { return total; } public long getAvailable() { return available; } @Html(render=FileSpacePctRender.class) public float getPctFull() { return ((100 * available) / total); } @Html(render=FileSpaceStatusRender.class) public FileSpaceStatus getStatus() { float pf = getPctFull(); if (pf < 80) return FileSpaceStatus.OK; if (pf < 90) return FileSpaceStatus.WARNING; return FileSpaceStatus.SEVERE; } }

// Possible values for the getStatus() method public enum FileSpaceStatus { OK, WARNING, SEVERE; }

// Custom render for getPctFull() method public class FileSpacePctRender extends HtmlRender<Float> { @Override public String getStyle(SerializerSession session, Float value) { if (value < 80) return "background-color:lightgreen;text-align:center"; if (value < 90) return "background-color:yellow;text-align:center"; return "background-color:red;text-align:center;border:;animation:color_change 0.5s infinite alternate"; } @Override public Object getContent(SerializerSession session, Float value) { if (value >= 90) return div( String.format("%.0f%%", value), style("@keyframes color_change { from { background-color: red; } to { background-color: yellow; }") ); return String.format("%.0f%%", value); } }

// Custom render for getStatus() method public class FileSpaceStatusRender extends HtmlRender<FileSpaceStatus> { @Override public String getStyle(SerializerSession session, FileSpaceStatus value) { return "text-align:center"; } @Override public Object getContent(SerializerSession session, FileSpaceStatus value) { switch (value) { case OK: return img().src(URI.create("servlet:/htdocs/ok.png")); case WARNING: return img().src(URI.create("servlet:/htdocs/warning.png")); default: return img().src(URI.create("servlet:/htdocs/severe.png")); } } }

2.27.6 - HtmlDocSerializerupdated: 9.0.0

HtmlDocSerializer is an extension of HtmlSerializer that wraps serialized POJOs in a complete HTML document.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

This class is used extensively in the creation of POJO-based user interfaces in the REST API.

Example:

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

The HtmlDocSerializer.Builder.template(Class) setting defines a template for the HTML page being generated. The default template is described next.

2.27.7 - BasicHtmlDocTemplate

The BasicHtmlDocTemplate class defines a default template for HTML documents created by HtmlDocSerializer.

The HTML document created by this template consists of the following structure:

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

2.27.8 - Custom Templates

Custom page templates can be created by implementing the HtmlDocTemplate interface and associating it with your HtmlDocSerializer using the HtmlDocSerializer.Builder.template(Class) setting.

The interface implementation is open-ended allowing you to define the contents of the page any way you wish.

2.28 - HTML-Schema Support

The HtmlSchemaSerializer class is the HTML-equivalent to the JsonSchemaSerializer class. It's used to generate HTML versions of JSON-Schema documents that describe the output generated by the JsonSerializer class.

Sample Beans

public class Person { // Bean properties public String name; public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

The code for creating our POJO model and generating HTML-Schema is shown below:

// Get the one of the default schema serializers. HtmlSchemaSerializer serializer = HtmlSchemaSerializer.DEFAULT_SIMPLE_READABLE; // Get the HTML Schema for the POJO. String htmlSchema = serializer.serialize(new Person()); // This also works. htmlSchema = serializer.serialize(Person.class);

The result is the HTML table shown below:

type object
properties
name
type string
birthDate
type string
addresses
type array
items
type object
properties
street
type string
city
type string
state
type string
enum
  • AL
  • PA
  • NC
zip
type integer
format int32
isCurrent
type boolean

2.29 - UON Details

Juneau supports converting arbitrary POJOs to and from UON strings using ultra-efficient serializers and parsers. The serializer converts POJOs directly to UON strings without the need for intermediate DOM objects using a highly-efficient state machine. Likewise, the parser creates POJOs directly from UON strings without the need for intermediate DOM objects.

Juneau uses UON (URL-Encoded Object Notation) for representing POJOs. The UON specification can be found here.

The following example shows JSON for a typical bean:

Sample Beans

public class Person { // Bean properties public String name; @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

Sample Code

Person person = new Person() .name("John Smith") .birthDate("1946-08-12T00:00:00Z") .addresses( new Address() .street("100 Main Street") .city("Anywhereville") .state(NY) .zip(12345) .isCurrent(true); );

UON

( name='John+Smith', birthDate='1946-08-12T00:00:00Z', addresses=@( ( street='100 Main Street', city=Anywhereville, state=NY, zip=12345, isCurrent=true ) ) )

2.29.1 - UON Methodology
General methodology:
Java typeJSON equivalentUON
Maps/beans OBJECT a1=(b1=x1,b2=x2) a1=(b1=(c1=x1,c2=x2))
Collections/arrays ARRAY a1=@(x1,x2) a1=@(@(x1,x2),@(x3,x4)) a1=@((b1=x1,b2=x2),(c1=x1,c2=x2))
Booleans BOOLEAN a1=true&a2=false
int/float/double/... NUMBER a1=123&a2=1.23e1
null NULL a1=null
String STRING a1=foobar a1='true' a1='null' a1='123' a1=' string with whitespace ' a1='string with ~'escaped~' quotes'

Refer to the UON specification for a complete set of syntax rules.

2.29.2 - UON Serializersupdated: 9.0.0

The UonSerializer class is used to serialize POJOs into UON.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.29.3 - UON Parsersupdated: 9.0.0

The UonParser class is used to parse UON into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

2.30 - URL-Encoding Details

Juneau supports converting arbitrary POJOs to and from URL-encoded strings using ultra-efficient serializers and parsers. The serializer converts POJOs directly to URL-encoded strings without the need for intermediate DOM objects using a highly-efficient state machine. Likewise, the parser creates POJOs directly from URL-encoded strings without the need for intermediate DOM objects.

Juneau uses UON (URL-Encoded Object Notation) for representing POJOs as URL-Encoded values in key-value pairs. The UON specification can be found here.

The following example shows JSON for a typical bean:

Sample Beans

public class Person { // Bean properties public String name; @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate; public List<Address> addresses; // Getters/setters omitted } public class Address { // Bean properties public String street, city; public StateEnum state; public int zip; public boolean isCurrent; // Getters/setters omitted }

Sample Code

Person person = new Person() .name("John Smith") .birthDate("1946-08-12T00:00:00Z") .addresses( new Address() .street("100 Main Street") .city("Anywhereville") .state(NY) .zip(12345) .isCurrent(true); );

URL-Encoding

name='John+Smith' &birthDate='1946-08-12T00:00:00Z' &addresses=@( ( street='100 Main Street', city=Anywhereville, state=NY, zip=12345, isCurrent=true ) )

2.30.1 - URL-Encoding Methodology
General methodology:
Java typeJSON equivalentUON
Maps/beans OBJECT a1=(b1=x1,b2=x2) a1=(b1=(c1=x1,c2=x2))
Collections/arrays ARRAY a1=@(x1,x2) a1=@(@(x1,x2),@(x3,x4)) a1=@((b1=x1,b2=x2),(c1=x1,c2=x2))
Booleans BOOLEAN a1=true&a2=false
int/float/double/... NUMBER a1=123&a2=1.23e1
null NULL a1=null
String STRING a1=foobar a1='true' a1='null' a1='123' a1=' string with whitespace ' a1='string with ~'escaped~' quotes'

Refer to the UON specification for a complete set of syntax rules.

2.30.2 - URL-Encoding Serializersupdated: 9.0.0

The UrlEncodingSerializer class is used to serialize POJOs into URL-Encoding.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.30.3 - URL-Encoding Parsersupdated: 9.0.0

The UrlEncodingParser class is used to parse URL-Encoding into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

2.30.4 - @UrlEncoding Annotation

The @UrlEncoding annotation is used to override the behavior of UrlEncodingSerializer on individual bean classes or properties.

The expandedParams setting is used to force bean properties of type array or Collection to be expanded into multiple key/value pairings.

2.31 - MessagePack Details

Juneau supports converting arbitrary POJOs to and from MessagePack using ultra-efficient serializers and parsers.

MessagePack is a compact binary form of JSON. The serialization support for MessagePack mirrors that of JSON.

2.31.1 - MessagePack Serializersupdated: 9.0.0

The MsgPackSerializer class is used to serialize POJOs into MessagePack.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

The following pre-configured serializers are provided for convenience:

2.31.2 - MessagePack Parsersupdated: 9.0.0

The MsgPackParser class is used to parse MessagePack into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following pre-configured parsers are provided for convenience:

2.32 - OpenAPI Detailsupdated: 8.2.0

Juneau supports converting arbitrary POJOs to and from strings using OpenAPI-based schema rules.

The relevant classes for using OpenAPI-based serialization are:

The HttpPartSchema class is used to define the formatting and validations for a POJO. It's used in conjunction with the serializer and parser to produce and consume HTTP parts based on OpenAPI rules.

Later in the rest-server and rest-client sections, we also describe how the following annotations can be applied to method parameters and class types to define the schema for various HTTP parts:

2.32.1 - OpenAPI Methodologyupdated: 8.2.0

Unlike the other Juneau serializers and parsers that convert input and output directly to-and-from POJOs, the OpenAPI serializers and parsers use intermediate objects based on the type and format of the schema.

The following table shows the "natural" intermediate type of the object based on the type/format:

TypeFormatIntermediate Java Type
string or empty byte
binary
binary-spaced
byte[]
date
date-time
Calendar
uon No intermediate type.
(serialized directly to/from POJO)
empty String
boolean empty Boolean
integer int32 Integer
int64 Long
number float Float
double Double
array empty Arrays of intermediate types on this list.
uon No intermediate type.
(serialized directly to/from POJO)
object empty Map<String,Object>
uon No intermediate type.
(serialized directly to/from POJO)

The valid POJO types for serializing/parsing are based on the intermediate types above. As a general rule, any POJOs that are the intermediate type or transformable to or from the intermediate type are valid POJO types.

For example, the following POJO type can be transformed to and from a byte array.

// Sample POJO class convertable to and from a byte[]. public class MyPojo { // Constructor used by parser. public MyPojo(byte[] value) {...} // toX method used by serializer. public byte[] toBytes() {...} }

This example shows how that POJO can be converted to a BASE64-encoded string.

// Construct a POJO. MyPojo myPojo = ...; // Define a schema. HttpPartSchema schema = HttpPartSchema.tByte().build(); // Convert POJO to BASE64-encoded string. HttpPartSerializer serializer = OpenApiSerializer.DEFAULT; String httpPart = serializer.serialize(schema, myPojo); // Convert BASE64-encoded string back into a POJO. HttpPartParser parser = OpenApiParser.DEFAULT; myPojo = parser.parse(schema, httpPart, MyPojo.class);

In addition to defining format, the schema also allows for validations of the serialized form.

// Construct a POJO. MyPojo myPojo = ...; // Define a schema. // Serialized string must be no smaller than 100 characters. HttpPartSchema schema = HttpPartSchema.tByte().minLength(100).build(); // Convert POJO to BASE64-encoded string. HttpPartSerializer serializer = OpenApiSerializer.DEFAULT; String httpPart; try { httpPart = serializer.serialize(schema, myPojo); } catch (SchemaValidationException e) { // Oops, output too small. } // Convert BASE64-encoded string back into a POJO. HttpPartParser parser = OpenApiParser.DEFAULT; try { myPojo = parser.parse(schema, httpPart, MyPojo.class); } catch (SchemaValidationException e) { // Oops, input too small. }

It looks simple but the implementation is highly sophisticated being able to serialize and parse and validate using complex schemas.

The next sections go more into depth on serializing and parsing various POJO types.

2.32.2 - OpenAPI Serializersupdated: 8.2.0,9.0.0

The OpenApiSerializer class is used to convert POJOs to HTTP parts.

The class hierarchy for the builder of this serializer is:

Refer to the builder javadocs for configurable settings.

Later we'll describe how to use HTTP-Part annotations to define OpenAPI schemas for serialization and parsing of HTTP parts. The following example is a preview showing an HTTP body defined as pipe-delimited list of comma-delimited numbers (e.g. "1,2,3|4,5,6|7,8,9"):

@RestPost("/2dLongArray") public void post2dLongArray( @Content( schema=@Schema( type="array", collectionFormat="pipes", items=@Items( type="array", collectionFormat="csv", items=@SubItems( type="integer", format="int64", minimum="0", maximum="100" minLength=1, maxLength=10 ) ) minLength=1, maxLength=10 ) ) Long[][] body ) {...} // Alternate shortened format. @RestPost("/2dLongArray") public void post2dLongArray( @Content( schema=@Schema( t="array", cf="pipes", i=@Items( t="array", cf="csv", i=@SubItems( t="integer", f="int64", min="0", max="100" minl=1, maxl=10 ) ) minl=1, maxl=10 ) ) Long[][] body ) {...}

Under-the-covers, this gets converted to the following schema object:

HttpPartSchema schema = HttpPartSchema.create() .items( HttpPartSchema.create() .type("array") .collectionFormat("pipes") .items( HttpPartSchema.create() .type("array") .collectionFormat("csv") .items( HttpPartSchema.create() .type("integer") .format("int64") .minimum(0) .maximum(100) .minLength(1) .maxLength(10) ) ) ) .build();

Various convenience methods exist for shortening this code.

import static org.apache.juneau.http.HttpPartSchema.*; HttpPartSchema schema = tArrayPipes( tArrayCsv( tInt64().minimum(0).maximum(100).minLength(1).maxLength(10) ) ).build();

The following code shows how the schema above can be used to create our pipe+csv list of numbers:

// Our POJO being serialized. Long[][] input = .... // Convert POJO to a string. try { String httpPart = OpenApi.of(schema, input); } catch (SchemaValidationException e) { // Oops, one of the restrictions were not met. }

As a general rule, any POJO convertible to the intermediate type for the type/format of the schema can be serialized using the OpenAPI serializer. Here are the rules of POJO types allowed for various type/format combinations:

TypeFormatValid parameter types
string or empty byte
binary
binary-spaced
  • byte[] (default)
  • InputStream
  • Reader - Read into String and then converted using String.getBytes().
  • Object - Converted to String and then converted using String.getBytes().
  • Any POJO transformable to a byte[] via the following methods:
    • public byte[] toBytes() {...}
    • public byte[] toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a byte[] via an ObjectSwap.
date
date-time
  • Calendar (default)
  • Date
  • Any POJO transformable to a Calendar via the following methods:
    • public Calendar toCalendar() {...}
    • public Calendar toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a Calendar via an ObjectSwap.
uon
empty
  • String (default)
  • Any POJO transformable to a String via the following methods:
    • public String toString() {...}
  • Any POJO transformable to a String via an ObjectSwap.
boolean empty
  • Boolean (default)
  • boolean
  • String - Converted to a Boolean.
  • Any POJO transformable to a Boolean via the following methods:
    • public Boolean toBoolean() {...}
    • public Boolean toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a Boolean via an ObjectSwap.
integer int32
  • Integer (default)
  • int
  • String - Converted to an String.
  • Any POJO transformable to an Integer via the following methods:
    • public Integer toInteger() {...}
    • public Integer toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to an Integer via an ObjectSwap.
int64
  • Long (default)
  • long
  • String - Converted to a Long.
  • Any POJO transformable to a Long via the following methods:
    • public Long toLong() {...}
    • public Long toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a Long via an ObjectSwap.
number float
  • Float (default)
  • float
  • String - Converted to a Float.
  • Any POJO transformable to a Float via the following methods:
    • public Float toFloat() {...}
    • public Float toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a Float via an ObjectSwap.
double
  • Double (default)
  • double
  • String - Converted to a Double.
  • Any POJO transformable to a Double via the following methods:
    • public Double toDouble() {...}
    • public Double toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to a Double via an ObjectSwap.
array empty
  • Arrays or Collections of any defaults on this list.
  • Any POJO transformable to arrays of the default types (e.g. Integer[], Boolean[][], etc...).
    For example:
    • public Boolean[][] toFoo() {...} (any method name starting with "to")
  • Any POJO transformable to arrays of the default types via an ObjectSwap
uon
object empty
  • Map<String,Object> (default)
  • Beans with properties of anything on this list.
  • Any POJO transformable to a map via an ObjectSwap
uon

For arrays, an example of "Any POJO transformable to arrays of the default types" is:

// Sample POJO class convertable to a Long[][]. public class MyPojo { // toX method used by serializer. public Long[][] to2dLongs() {...} }

In the example above, our POJO class can be used to create our pipe-delimited list of comma-delimited numbers:

// Our POJO being serialized. MyPojo input = .... // Convert POJO to a string. try { String httpPart = OpenApi.of(schema, input); } catch (SchemaValidationException e) { // Oops, one of the restrictions were not met. }

The object type is not officially part of the OpenAPI standard. However, Juneau supports serializing Maps and beans to HTTP parts using UON notation.

The following shows an example of a bean with several properties of various types.

public class MyBean { private static byte[] FOOB = "foo".getBytes(); public String f1 = "foo"; public byte[] f2 = FOOB; public byte[] f3 = FOOB; public byte[] f4 = FOOB; public Calendar f5 = parseIsoCalendar("2012-12-21T12:34:56Z"); public String f6 = "foo"; public int f7 = 1; public Long f8 = 2l; public float f9 = 1.0; public Double f10 = 1.0; public Boolean f11 = true; public Object fExtra = "1"; }

We define the following schema:

import static org.apache.juneau.httppart.HttpPartSchema.*; HttpPartSchema schema = tObject() .prop("f1", tString()) .prop("f2", tByte()) .prop("f3", tBinary()) .prop("f4", tBinarySpaced()) .prop("f5", tDateTime()) .prop("f6", tUon()) .prop("f7", tInteger()) .prop("f8", tInt64()) .prop("f9", tNumber()) .prop("f10", tDouble()) .prop("f11", tBoolean()) .ap(tInteger()) .build();

Then we serialize our bean:

String httpPart = OpenApi.of(schema, new MyBean());

The results of this serialization is shown below:

( f1=foo, f2=Zm9v, f3=666F6F, f4='66 6F 6F', f5=2012-12-21T12:34:56Z, f6=foo, f7=1, f8=2, f9=1.0, f10=1.0, f11=true, fExtra=1 )

The following is an example of a bean with various array property types:

public class MyBean { private static byte[] FOOB = "foo".getBytes(); public String[] f1 = {"a,b",null}, public byte[][] f2 = new byte[][]{FOOB,null}, public byte[][] f3 = new byte[][]{FOOB,null}, public byte[][] f4 = new byte[][]{FOOB,null}, public Calendar[] f5 = new Calendar[]{parseIsoCalendar("2012-12-21T12:34:56Z"),null}, public String[] f6 = {"a","b",null}, public int[] f7 = new int[]{1,2,null}, public Integer[] f8 = new Integer[]{3,4,null}, public float[] f9 = new float[]{1f,2f,null}, public Float[] f10 = new Float[]{3f,4f,null}, public Boolean[] f11 = new Boolean[]{true,false,null}, public Object[] fExtra = new Object[]{1,"2",null}; }

For this bean, we define the following schema:

import static org.apache.juneau.httppart.HttpPartSchema.*; HttpPartSchema schema = tObject() .prop("f1", tArray(tString())) .prop("f2", tArray(tByte())) .prop("f3", tArray(tBinary())) .prop("f4", tArray(tBinarySpaced())) .prop("f5", tArray(tDateTime())) .prop("f6", tArray(tUon())) .prop("f7", tArray(tInteger())) .prop("f8", tArray(tInt64())) .prop("f9", tArray(tNumber())) .prop("f10", tArray(tDouble())) .prop("f11", tArray(tBoolean())) .ap(tArray(tInteger())) .build();

Serializing this bean produces the following output:

( f1=@('a,b',null), f2=@(Zm9v,null), f4=@(2012-12-21T12:34:56Z,null), f5=@(666F6F,null), f6=@('66 6F 6F',null), f7=@(a,b,null), f8=@(1,2,null), f9=@(3,4,null), f10=@(1.0,2.0,null), f11=@(3.0,4.0,null), f12=@(true,false,null), fExtra=@(1,2,null) )

  • Array properties can also use CSV/SSV/PIPES for array notation.
    Various notations can be mixed throughout.
  • Schemas and POJOs can be defined arbitrarily deep.
  • Schemas are optional. They can be skipped or partially defined.
  • We make our best attempt to convert the input to the matching type. However, you will get SerializeExceptions if you attempt an impossible conversion. (e.g. trying to serialize the string "foo" as a boolean).
2.32.3 - OpenAPI Parsersupdated: 8.2.0,9.0.0

The OpenApiParser class is used to convert HTTP parts back into POJOs.

The class hierarchy for the builder of this parser is:

Refer to the builder javadocs for configurable settings.

The following is the previous example of a schema that defines the format of a pipe-delimited list of comma-delimited numbers (e.g. "1,2,3|4,5,6|7,8,9"):

import static org.apache.juneau.httppart.HttpPartSchema.*; HttpPartSchema schema = tArrayPipes( tArrayCsv( tInt64().min(0).max(100).minl(1).maxl(10) ) ).build();

The following code shows how the schema above can be used to parse our input into a POJO:

// Our input being parsed. String input = "1,2,3|4,5,6|7,8,9" // Convert string to a POJO. try { Long[][] pojo = OpenApi.to(schema, input, Long[][].class); } catch (SchemaValidationException e) { // Oops, one of the restrictions were not met. }

As a general rule, any POJO convertible from the intermediate type for the type/format of the schema can be parsed using the OpenAPI parser. Here are the rules of POJO types allowed for various type/format combinations:

TypeFormatValid parameter types
string or empty byte
binary
binary-spaced
date
date-time
uon
empty
  • String (default)
  • Object - Returns the default String.
  • Any POJO transformable from a String (via constructors, static create methods, or swaps).
boolean empty
integer int32
  • Integer (default)
  • Any subclass of Number
  • Any primitive number: (e.g int, float...)
  • String
  • Object - Returns the default Integer.
  • Any POJO transformable from an Integer (via constructors, static create methods, or swaps).
int64
  • Long (default)
  • Any subclass of Number
  • Any primitive number: (e.g int, float...)
  • String
  • Object - Returns the default Long.
  • Any POJO transformable from an Long (via constructors, static create methods, or swaps).
number float
  • Float (default)
  • Any subclass of Number
  • Any primitive number: (e.g int, float...)
  • String
  • Object - Returns the default Float.
  • Any POJO transformable from an Float (via constructors, static create methods, or swaps).
double
  • Double (default)
  • Any subclass of Number
  • Any primitive number: (e.g int, float...)
  • String
  • Object - Returns the default Double.
  • Any POJO transformable from an Double (via constructors, static create methods, or swaps).
array empty
  • Arrays or Collections of anything on this list.
  • Any POJO transformable from arrays of the default types (e.g. Integer[], Boolean[][], etc...).
uon
object empty
  • Map<String,Object> (default)
  • Beans with properties of anything on this list.
  • Maps with string keys.
uon

Additionally, any of the type above can also be wrapped as Optionals.

For arrays, an example of "Any POJO transformable from arrays of the default types" is:

// Sample POJO class convertable from a Long[][]. public class MyPojo { // Constructor used by parser. public MyPojo(Long[][] from2dLongs) {...} }

In the example above, our POJO class can be constructed from our pipe-delimited list of comma-delimited numbers:

// Our input being parsed. String input = "1,2,3|4,5,6|7,8,9" // Convert string to a POJO. try { MyPojo pojo = OpenApi.to(schema, input, MyPojo.class); } catch (SchemaValidationException e) { // Oops, one of the restrictions were not met. }

Just like serialization, the object type is not officially part of the OpenAPI standard but Juneau supports parsing HTTP parts in UON notation to Maps and beans.

The following shows an example of a bean with several properties of various types.

public class MyBean { public String f1; public byte[] f2; public byte[] f3; public byte[] f4; public Calendar f5; public String f6; public int f7; public Long f8; public float f9; public Double f10; public Boolean f11; public Object fExtra; }

We define the following schema again:

import static org.apache.juneau.httppart.HttpPartSchema.*; HttpPartSchema schema = tObject() .prop("f1", tString()) .prop("f2", tByte()) .prop("f3", tBinary()) .prop("f4", tBinarySpaced()) .prop("f5", tDateTime()) .prop("f6", tUon()) .prop("f7", tInteger()) .prop("f8", tInt64()) .prop("f9", tNumber()) .prop("f10", tDouble()) .prop("f11", tBoolean()) .ap(tInteger()) .build();

Then we parse our input into our POJO:

String input = "(f1=foo,f2=Zm9v,f3=666F6F,f4='66 6F 6F',f5=2012-12-21T12:34:56Z,f6=foo," + "f7=1,f8=2,f9=1.0,f10=1.0,f11=true,fExtra=1)"; MyBean bean = OpenApi.to(schema, input, MyBean.class);

Note that serializing into generic Object properties would have produced similar results:

public class MyBean { public Object f1; public Object f2; public Object f3; public Object f4; public Object f5; public Object f6; public Object f7; public Object f8; public Object f9; public Object f10; public Object f11; public Object fExtra; }

We can also parse into Maps as well:

String input = "(f1=foo,f2=Zm9v,f3=666F6F,f4='66 6F 6F',f5=2012-12-21T12:34:56Z,f6=foo," + "f7=1,f8=2,f9=1.0,f10=1.0,f11=true,fExtra=1)"; JsonMap map = OpenApi.to(schema, input, JsonMap.class);

  • Array properties can also use CSV/SSV/PIPES for array notation.
    Various notations can be mixed throughout.
  • Schemas and POJOs can be defined arbitrarily deep.
  • Schemas are optional. They can be skipped or partially defined.
  • We make our best attempt to convert the output to the matching type. However, you will get ParseExceptions if you attempt an impossible conversion. (e.g. trying to parse the string "foo" into a boolean).

2.33 - 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 BeanTraverseContext.Builder.detectRecursions() option can cause a performance penalty of around 20%.
    Therefore, it's recommended that this option be used only when necessary.
  3. In general, JSON serialization and parsing is about 20% faster than XML. JSON is also more compact than XML.
    MessagePack is fastest and most compact of all.
  4. The RDF parsers are SLOW.
    RDF simply isn't efficient with node traversal, so creating tree structures out of RDF models is highly inefficient.
  5. The Parser methods that take in ClassMeta parameters are slightly faster than methods that take in Class or Object parameters, since the latter methods involve hash lookups to resolve to ClassMeta parameters.

3 - juneau-marshall-rdf

Maven Dependency

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

Java Library

juneau-marshall-rdf-9.0.0.jar

OSGi Module

org.apache.juneau.marshaller.rdf_9.0.0.jar

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

  • RDF/XML
  • RDF/XML-Abbrev
  • N-Triple
  • Turtle
  • N3

4 - juneau-dto

Maven Dependency

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

Java Library

juneau-dto-9.0.0.jar

OSGi Module

org.apache.juneau.dto_9.0.0.jar

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

4.1 - HTML5

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

The 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 common HTML DOM objects.

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 = Html.of(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 = Html.of(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 = Html.of(myform); <form action='/submit' method='POST'> Position (1-10000): <input name='pos' type='number' value='1'/><br/> Limit (1-10000): <input name='pos' type='number' value='100'/><br/> <button type='submit'>Submit</button> <button type='reset'>Reset</button> </form>

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

The HtmlParser class can be used convert these HTML documents back into POJOs.

Other serializers and parsers (e.g. JsonSerializer) can be used to represent these POJOs in languages other than HTML.

4.2 - Atom

The Juneau ATOM feed DTOs are simply beans with fluent-style setters. The following code shows a feed being created programmatically using the 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") .setSubtitle(text("html").setText("Describes <em>stuff</em> about Juneau")) .setLinks( link("alternate", "text/html", "http://juneau.apache.org").setHreflang("en"), link("self", "application/atom+xml", "http://juneau.apache.org/feed.atom") ) .setRights("Copyright (c) ...") .setGenerator( generator("Juneau").setUri("http://juneau.apache.org/").setVersion("1.0") ) .setEntries( entry("tag:juneau.sample.com,2013:1.2345", "Juneau ATOM specification snapshot", "2016-01-02T03:04:05Z") .setLinks( link"alternate", "text/html", "http://juneau.apache.org/juneau.atom"), link("enclosure", "audio/mpeg", "http://juneau.apache.org/audio/juneau_podcast.mp3").setLength(1337) ) .setPublished("2016-01-02T03:04:05Z") .setAuthors( person("Jane Smith").setUri("http://juneau.apache.org/").setEmail("janesmith@apache.org") ) .setContributors( person("John Smith") ) .setContent( content("xhtml") .setLang("en") .setBase("http://www.apache.org/") .setText("<div><p><i>[Update: Juneau supports ATOM.]</i></p></div>") ) );

To serialize this to ATOM, use the XmlSerializer class:

Example with no namespaces

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

Results

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

The XmlParser class can be used convert these Atom documents back into POJOs.

Other serializers and parsers (e.g. JsonSerializer) can be used to represent these POJOs in languages other than XML.

4.3 - Swagger

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

The SwaggerBuilder class is a utility class with predefined static methods that allow you to easily construct DTO instances in a minimal amount of code.

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

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

This document can be generated by the following Java code:

static import org.apache.juneau.dto.swagger.SwaggerBuilder.*; Swagger swagger = swagger() .setSwagger("2.0") .setInfo( info("Swagger Petstore", "1.0.0") .setDescription("This is a sample server Petstore server.") .setTermsOfService("http://swagger.io/terms/") .setContact( contact().setEmail("apiteam@swagger.io") ) .setLicense( license("Apache 2.0").setUrl("http://www.apache.org/licenses/LICENSE-2.0.html") ) ) .setHost("petstore.swagger.io") .setBasePath("/v2") .setTags( tag("pet").setDescription("Everything about your Pets") .setExternalDocs( externalDocumentation("http://swagger.io", "http://swagger.io") ) ) .setSchemes("http") .setPath("/pet", "post", operation() .setTags("pet") .setSummary("Add a new pet to the store") .setDescription("") .setOperationId("addPet") .setConsumes(MediaType.JSON, MediaType.XML) .setProduces(MediaType.JSON, MediaType.XML) .setParameters( parameterInfo("body", "body") .setDescription("Pet object that needs to be added to the store") .setRequired(true) ) .setResponse(405, responseInfo("Invalid input")) ); // Serialize using JSON serializer. String swaggerJson = Json.of(swagger); // Or just use toString(). String swaggerJson = swagger.toString();

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

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

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

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

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

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

4.4 - Swagger UI

The SwaggerUI class is a DTO class for generating Swagger user interfaces from Swagger beans.

The PetStore example described later provides an example of auto-generated Swagger JSON:

Using SwaggerUI, we're able to render that JSON as a Swagger user interface when the request is asking for HTML:

The class itself is nothing more than a POJO swap that swaps out Swagger beans with Div elements:

public class SwaggerUI extends ObjectSwap<Swagger,Div> { @Override public MediaType[] forMediaTypes() { // Only use this swap when the Accept type is HTML. return new MediaType[] {MediaType.HTML}; } @Override public Div swap(BeanSession beanSession, Swagger swagger) throws Exception { ... } }

The BasicRestServlet class (describe later) shows how this swap is used in the REST interface to generate the Swagger UI shown above:

@Rest( // Allow OPTIONS requests to be simulated using ?method=OPTIONS query parameter. allowedMethodParams="OPTIONS", ... ) @BeanConfig( // POJO swaps to apply to all serializers/parsers. swaps={ // Use the SwaggerUI swap when rendering Swagger beans. SwaggerUI.class } ) public abstract class BasicRestServlet extends RestServlet implements BasicRestConfig { /** * [OPTIONS /*] - Show resource options. */ @RestOp( method=OPTIONS, path="/*", summary="Swagger documentation", description="Swagger documentation for this resource." ) @HtmlDocConfig( // Override the nav links for the swagger page. navlinks={ "back: servlet:/", "json: servlet:/?method=OPTIONS&Accept=text/json&plainText=true" }, // Never show aside contents of page inherited from class. aside="NONE" ) public Swagger getOptions(RestRequest req) { // Localized Swagger for this resource is available through the RestRequest object. return req.getSwagger(); } }

5 - juneau-config

Maven Dependency

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

Java Library

juneau-config-9.0.0.jar

OSGi Module

org.apache.juneau.config_9.0.0.jar

5.1 - Overviewupdated: 9.0.0

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

Example configuration file:

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

Config files are accessed through the Config class which are created through the Config.Builder class. Builder creator methods are provided on the Config class:

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

Once instantiated, reading values from the config are simple:

// Read values from section #1 int key1 = config.get("Section1/key1").asInteger().orElse(-1); boolean key2 = config.get("Section1/key2").asBoolean().orElse(false); int[] key3 = config.get("Section1/key3").as(int[].class).orElse(null); URL key4 = config.get("Section1/key4").as(URL.class).orElse(null);

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

  • Support for storing and retrieving any of the following data types:
    • Primitives
    • POJOs
    • Beans
    • Arrays, Maps, and Collections of anything
    • Binary data
  • Transactional modifications with commit/rollback capabilities.
  • A listener API.
  • Filesystem watcher integration allowing changes on the file system to be reflected in real-time.
  • Modifications through the Config class (e.g. add/remove/modify sections and keys, add/remove comments and whitespace, etc...) DO NOT cause loss of formatting in the file.
    All existing whitespace and comments are preserved for you!
  • Value encoding for added security.
  • Support for SVL variables.
  • Directly populate beans from config sections.
  • Accessing config sections through Java interface proxies.
  • An extensible storage API allows you to write your own config storage (e.g. storage in databases or the cloud).
5.1.1 - Syntax Rules
  • Each config file contains zero or more sections containing zero or more entries:

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

  • Unicode escapes can be used in values.

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

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

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

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

    key1 = 1 # A comment about this entry

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

    valueContainingPound = Value containing \u0023 character


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

    multiLineValue = line 1, line 2, line 3


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

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

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

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

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

    / \ [ ] = #

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

5.2 - Reading Entriesupdated: 9.0.0

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

Entries are accessed via the Config.get(String) method which returns the following bean:

The most common case for configuration values are primitives.

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

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

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

Numbers can also use hexadecimal and octal notation:

hex1 = 0x12FE hex2 = 0X12FE octal1 = 01234

Strings with newlines are treated as multi-line values that get broken into separate lines:

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

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

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

5.2.1 - POJOsupdated: 9.0.0

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

  • Objects convertible from Strings.
  • Beans.

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

# A POJO key4 = http://bar

// Read values from section #1 URL key4 = config.get("Section1/key4").as(URL.class).orElse(null);

Beans are represented as JSON 5 by default:

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

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

The default serializer and parser is registered on the Config through the following methods:

5.2.2 - Arrays

The asStringArray() method allows you to retrieve comma-delimited lists of values:

key1 = foo, bar, baz

String[] key1 = config.get("key1").asStringArray().orElse(null);

String arrays can also be represented in JSON when the registered parser is a JSON parser:

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

String[] key1 = config.get("key1").asStringArray().orElse(null);

Primitive arrays can also be retrieved using the as() and to() methods:

key1 = [1,2,3]

int[] key1 = config.get("key1").as(int[].class).orElse(null);

Arrays of POJOs can also be retrieved using the methods as well:

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

Address[] addresses = config.get("addresses").as(Address[].class).orElse(null);

5.2.3 - Java Collection Framework Objectsupdated: 9.0.0

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

Examples are shown below:

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

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

List<Address> addresses = config.get("addresses").as(ArrayList.class, Address.class).orElse(null);

Oftentimes, it might be useful to parse into the JsonList and JsonMap classes that provide the various convenience methods for working with JSON-like data structures:

JsonMap map = config.get("key1").asMap().orElse(null); JsonList list = config.get("key2").asList().orElse(null);

5.2.4 - Binary Dataupdated: 9.0.0

Entries can also be accessed as binary data. Binary data can be represented in 3 formats:

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

The binary data format is controlled via the following setting:

For example:

key = Zm9vYmFycw==

byte[] bytes = config.get("key").asBytes().orElse(null);

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

key = Zm9vYm Fycw==

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

5.3 - Variablesupdated: 9.0.0

Config files can contain variables that get resolved dynamically using the previously-described VarResolver API.

Example:

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

Config config = Config.create().build(); Locale locale = config.get("MySection/locale").as(Locale.class).orElse(null); String path = config.get("MySection/path").asString().orElse(null); int sameAsAnInt = config.get("MySection/sameAsAnInt").asInteger().orElse(null); ABean bean = config.get("MySection/aBean").as(ABean.class).orElse(null);

By default, Configs use the VarResolver.DEFAULT variable resolver which provides support for the following variables and constructs:

The variable resolver is controlled via the following setting:

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

5.3.1 - Logic Variables

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

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

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

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

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

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

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

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

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

5.4 - Modded/Encoded Entriesupdated: 9.0.0

The following method can be used to associates entry modifiers to a config:

Mods are used to modify values before being persisted. This can be used to replace or encode sensitive information. They are denoted by a single character that gets appended between angle brackets on the property name (e.g. key<X>). Multiple modifiers can be denoted by multiple characters (e.g. key<XYZ>) and are applied/removed in the order denoted.

The framework comes built-in with a simple xor-encode mod tied to the '*' character. The following shows how it is used:

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

Custom encoders can be used to provide your own encoding support by implementing the Mod class.

Unmodified values are encoded when the file is saved using the Config.commit() method. They can also be encoded immediately by calling Config.applyMods() which can typically be done during JVM startup to immediately encode any unencoded passwords in the file.

5.5 - Sectionsupdated: 9.0.0

Config sections can be retrieved in-bulk using the Config.getSection(String) method. It returns the following bean:

The asMap() method allows you to access a section as simple key/value pairs.

Example:

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

// Example usage Config config = Config.create("MyConfig.cfg").build(); JsonMap map = config.getSection("MyAddress").asMap().get(); String street = map.getString("street"); String city = map.getString("city"); String state = map.getString("state"); int zip = map.getInt("zip");

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

Config files can also be used to directly populate beans using asBean() or writeToBean().

Example:

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

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

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

Config sections can also be accessed via interface proxies using Section.asInterface(Class).

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

Example:

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

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

5.6 - Setting Values

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

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

The method Config.set(String,Object,Serializer,String,String,List) can be used for adding comments and pre-lines to entries, and specifying encoded values.

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

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

The last 4 arguments in Config.set(String,Object,Serializer,String,String,List) are optional in that if you pass null, the current value will not be overwritten. To unset the same-line comment, you should pass in a blank string. To remove pre-lines, you should pass in an empty list.

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

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

# Section comment 1 # Section comment 2 [NewSection]

Changes made to the configuration are transactional in nature. They are kept in memory until you call the Config.commit() method. Until then, you still see the modified values when you call any of the getters but the modified values exist only in memory.

Changes can be rolled back using the Config.rollback() method.

5.6.1 - File System Changes

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

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

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

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

5.6.2 - Custom Entry Serialization

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

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

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

The value can then be retrieved using the equivalent parser:

Address myAddress = config.get("key1").as(XmlParser.DEFAULT, Address.class).orElse(null);

5.6.3 - Setting Values in Bulk

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

Changes can then be committed using the Config.commit() method.

5.7 - Listeners

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

The ConfigEventListener interface consists of the following method:

The ConfigEvent class provides access to all the information about the updated entry:

The listener method is triggered:

  • After Config.commit() is called.
  • When the file changes on the file system.

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

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

5.8 - Serializingupdated: 9.0.0

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

Both methods are thread safe.

5.9 - Importsupdated: 8.1.0

Configurations can import values from other configurations using the following syntax:

# Import values from configuration 'ParentConfig' <ParentConfig> # Our normal section [Section1] ...

A configuration can contain zero or more imports anywhere in the file. However, for clarity, imports should normally be placed in the default section of the configuration file. The resolved configuration is retrieved from the configuration store used for the child configuration.

Configuration imports can be nested arbitrarily deep.

Example:

# MyConfig contents <ParentConfig1>

# ParentConfig1 contents <ParentConfig2>

# ParentConfig2 contents [Foo] bar = baz

// Java code Config config = Config.create("MyConfig").build(); String foo = config.get("Foo/bar").get(); // == "baz"

Values can be overridden by child configurations.

Example:

# MyConfig contents <ParentConfig1> [Foo] bar = baz

# ParentConfig1 contents <ParentConfig2> [Foo] bar = qux

# ParentConfig2 contents [Foo] bar = quux

Config config = Config.create("MyConfig").build(); String foo = config.get("Foo/bar").get(); // == "baz"

Changes made to imported configurations are automatically reflected in the child configuration and partake in the listener API as if the entries were part of the child configuration. Only non-overridden values trigger listener events. For example, if an imported configuration defines a value for "Foo/bar" and the child configuration does not, modifications to "Foo/bar" value in the parent configuration will trigger a listener event in the child config. However, if the child configuration does also specify a value for "Foo/bar", a change to the parent "Foo/bar" will NOT trigger a listener event because the value ends up not being changed from the perspective of the child configuration.

Values can be overwritten in child configurations but the values will only be set in that configuration and not the imported configuration.

Dynamically adding an import will cause change events to be generated for imported values.

# MyConfig contents starting empty

# ParentConfig contents [Foo] bar = baz

// Create our configuration. Config config = Config.create("MyConfig").build(); // Create a listener that sets a flag if "Foo/bar" is set. final boolean[] triggered = new boolean[1]; ConfigEventListener listener = new ConfigEventListener() { public void onConfigChange(ConfigEvents events) { triggered[0] = events.isKeyModified("Foo", "bar")); } }; config.addListener(listener); // Dynamically add an import to ParentConfig in the default section. config.setImport("", "ParentConfig"); config.commit(); // The new import statement should have triggered a config changes for imported values. assertTrue(triggered[0]);

Dynamically removing an import has the same effect as removing keys and generates REMOVE_ENTRY events.

Note that when dynamically adding or removing imports, overridden keys in the child config will be filtered from the change events.

5.10 - Config Storesupdated: 9.0.0

Configuration files are stored in entities called Stores.

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

Read is self-explanatory:

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

Write is slightly trickier:

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

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

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

Two configuration stores are provided by default:

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

Example:

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

The default store used is FileStore.DEFAULT which defines the execution directory as the file system directory to store and retrieve files.

5.10.1 - MemoryStore

The MemoryStore class is simply an in-memory storage location for configuration files. There is no hard persistence and is used primarily for testing purposes.

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

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

5.10.2 - FileStoreupdated: 9.0.0

The FileStore is the typical store used for configuration files. It provides the following configurable settings:

Example:

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

5.10.3 - Custom ConfigStoresupdated: 9.0.0

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

The example below shows a starting point for an implementation based on polling a relational database. The source can be found here: SqlStore. Completing it is left as an exercise:

Example Store Class:

public class SqlStore extends ConfigStore { private final String jdbcUrl; private final String tableName, nameColumn, valueColumn; private final Timer watcher; private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>(); protected SqlStore(ConfigStore.Builder builder) { super(builder); this.jdbcUrl = builder.jdbcUrl; this.tableName = builder.tableName; this.nameColumn = builder.nameColumn; this.valueColumn = builder.valueColumn; int pollInterval = builder.pollInterval; TimerTask timerTask = new TimerTask() { @Override public void run() { SqlStore.this.poll(); } }; this.watcher = new Timer("MyTimer"); watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 1000); } synchronized void poll() { // Loop through all our entries and find the latest values. for (Map.Entry<String,String> e : cache.entrySet()) { String name = e.getKey(); String cacheContents = e.getValue(); String newContents = getDatabaseValue(name); // Change detected! if (! cacheContents.equals(newContents)) update(name, newContents); } } // Reads the value from the database. protected String getDatabaseValue(String name) { // Implement me! return null; } @Override /* ConfigStore */ public boolean exists(String name) { // Implement me! return false; } @Override /* ConfigStore */ public synchronized String read(String name) { String contents = cache.get(name); if (contents == null) { contents = getDatabaseValue(name); update(name, contents); } return contents; } @Override /* ConfigStore */ public synchronized String write(String name, String expectedContents, String newContents) { // This is a no-op. if (StringUtils.eq(expectedContents, newContents)) return null; String currentContents = read(name); if (expectedContents != null && StringUtils.ne(currentContents, expectedContents)) return currentContents; update(name, newContents); // Success! return null; } @Override /* ConfigStore */ public synchronized SqlStore update(String name, String newContents) { cache.put(name, newContents); super.update(name, newContents); // Trigger any listeners. return this; } @Override /* Closeable */ public synchronized void close() { if (watcher != null) watcher.cancel(); } }

5.10.4 - ConfigStore Listeners

The ConfigStore class has the following listener methods:

Note that this is a different listener than ConfigEventListener. In this case, we're just listening for changed files:

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

5.11 - Read-only Configsupdated: 9.0.0

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

Example:

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

This causes all methods that make modifications to throw UnsupportedOperationException.

5.12 - Closing Configs

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

Example:

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

5.13 - System Default Configcreated: 8.0.0, updated: 8.1.0

Each JVM has a system default config. This is a configuration file that serves as the default configuration for the system. It's accessed using the following static methods:

If you do not specify a system default config, one will be automatically searched for. The search is done in the following order:

  1. If the system property "juneau.configFile" is set, we search for this file in first the home directory and then the classpath.
  2. In the home directory:
    1. <jar-name>.cfg
    2. Any file that end with .cfg. First one matched alphabetically is used.
  3. In the context classpath root package (i.e. inside the jar itself):
    1. <jar-name>.cfg
    2. juneau.cfg
    3. default.cfg
    4. application.cfg
    5. app.cfg
    6. settings.cfg
    7. application.properties

Later in the section on REST resources, we describe how to associate configurations with REST resources using the @Rest(config) annotation. The system default configuration can be referenced with the keyword SYSTEM_DEFAULT like so:

// Always use system default. @Rest(config="SYSTEM_DEFAULT") // Use system property if set or the system default if not. @Rest(config="$S{juneau.configFile,SYSTEM_DEFAULT}")

By default, all properties in the system default configuration are automatically set as system properties. This can be disabled by setting the system property "juneau.disableAutoSystemProps" to "true".

6 - juneau-assertionscreated: 9.0.0

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-assertions</artifactId> <version>9.0.0</version> </dependency>

Java Library

juneau-assertions-9.0.0.jar

OSGi Module

org.apache.juneau.assertions_9.0.0.jar

The org.apache.juneau.assertions package in Juneau is a powerful API for performing fluent style assertions. It is used to implement built-in assertion methods on both the server and client side APIs. But it can also be used standalone for testing.

6.1 - Overviewcreated: 9.0.0

The org.apache.juneau.assertions package in Juneau is a powerful API for performing fluent style assertions. It is used throughout the REST client and server APIs for performing inline assertions on REST requests and responses.

Example:

// Create a basic REST client with JSON support and download a bean. MyBean bean = RestClient.create() .json5() .build() .get(URI) .run() .assertStatus().asCode().is(200) .assertHeader("Content-Type").isMatches("application/json*") .getContent().assertValue().asString().isContains("OK") .getContent().as(MyBean.class);

  • The REST API is described later in the documentation.

The assertions API is designed to be used in both code (as it's done in the REST APIs) or for standalone use in unit tests.

The Assertions class provides various static methods for invoking assertions on a variety of object types for simplified unit testing.

Assertions have 3 categories of methods:

  • Testing methods (isX methods)
  • Transform methods (asX methods)
  • Configuration methods (setX methods)
Examples:

import static org.apache.juneau.assertions.Assertions.*; import static org.apache.juneau.assertions.AssertionPredicates.*; // Check the contents of a string. assertString("foo, bar") .asSplit(",") .asTrimmed() .isHas("foo", "bar"); // Extract a subset of properties from a list of beans and compare using Simplified JSON. List<MyBean> myListOfBeans = ...; assertBeanList(myListOfBeans) .asPropertyMap("a,b") .asJson().is("[{a:1,b:'foo'}]"); // Perform an arbitrary Predicate check against a bean. MyBean myBeans = ...; assertBean(myBeans) .is(x -> isValidCheck(x)) // Check that a list of strings has less than 10 entries and the first // 3 entries are [foo, bar*, null] using assertion predicates. List<String> myListOfStrings = ...; assertStringList(myListOfStrings) .asSize().isLt(10) .asFirst(3) .isEach(eq("foo"),match("bar*"),isNull()) // Check that an exception is thrown and is the specified type and has the specified message. assertThrown(()->myBean.runBadMethod()) .exists() .isExactType(RuntimeException.class) .asMessage().is("foo");

Testing methods (isX methods) perform an assertion on the specified value and throws a AssertionError if the test fails. Otherwise, the method returns the original assertion object to allow you to chain the command.

Example:

// Test a string. assertString(myString) .isNotNull() // Perform test and returns original FluentStringAssertion. .isNotEmpty(); // Perform test and returns original FluentStringAssertion.

Transform methods (asX methods) allow you to convert assertions of one type to another type or to convert the tested value to some other form wrapped in another assertion.

Example:

// Customize the behavior of an assertion. assertString(myString) .asString() // Converts to a FluentIntegerAssertion. .isLt(100) // Runs test and returns original FluentStringAssertion. .asUc() // Converts string to uppercase and returns a new FluentStringAssertion. .isContains("FOO"); // Runs test and returns original FluentStringAssertion.

Configuration methods (setX methods) allow you to tailor the behavior of assertions when they fail. They always return the same assertion object. Transformed assertions inherit the configurations of the created-by assertions. Configuration methods start with set.

Example:

// Customize the behavior of an assertion. assertString(myString) .setMsg("My string was null. Value was {VALUE}.") // Custom error message when error occurs. .setOut(myPrintWriter) // Print error message to a separate writer. .setThrowable(MyAssertionThrowable.class) // Throw a customized assertion exception. .isNotNull();

The following shows the class hierarchy for the IntegerAssertion class showing the general design pattern for assertion classes:

In the design, the "Fluent" classes (e.g. FluentIntegerAssertion) allow you to specify the object that gets returned when the test method is executed. When used in the RestClient class for example, the return object is the RestResponse object so that you can perform multiple fluent operations against that object. The "Normal" classes (e.g. IntegerAssertion) are simply subclasses of the fluent equivalent which return the assertion itself, meaning the test method returns the original IntegerAssertion so that multiple tests can be performed per assertion.

For more information about the capabilities of the Assertions API, refer to the methods on the Assertions methods above.

7 - juneau-rest-commoncreated: 9.0.0

Maven Dependency

<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-rest-common</artifactId> <version>9.0.0</version> </dependency>

Java Library

juneau-rest-common-9.0.0.jar

OSGi Module

org.apache.juneau.rest.common_9.0.0.jar

The org.apache.juneau.http package contains a slew of useful extensions to the Apache HttpComponents libraries and define APIs used extensively in the REST server and client APIs.

These APIs extend from the Apache HttpComponents libraries and can be used with libraries based on it such as Apache HttpClient. The REST Client API described later is built on top of Apache HttpClient and many of the classes defined in this package make up integral components of that API. Likewise, the APIs defined here are also used in the REST Server APIs also described later.

7.1 - Helper Classescreated: 9.0.0

The org.apache.juneau.http.header package contains various convenience classes for creating standard HTTP components using static imports.

HttpHeaders

The HttpHeaders class contains many convenience static methods and fields for working with standard HTTP request and response headers and header lists.

Example:

import static org.apache.juneau.http.HttpHeaders.*; HeaderList headers = headerList( // Arbitrary list of headers CONTENTTYPE_TEXT_XML, // Static constants contentType("text/xml") // Predefined headers contentType(() -> "text/xml") // Predefined headers with supplied values stringHeader("Content-Type", "text/xml") // Freeform headers stringHeader("Content-Type", () -> "text/xml") // Freeform headers with supplied values );

This class is vast in scope and covers all request and response headers defined in RFC2616.

In addition to the predefined headers, various methods are provided for free-form headers. Each accepts either static values or values from Suppliers:

The serializedHeader methods allows for headers serialized using schema-based serializers such as the OpenAPI serializer.

Static methods are also provided for instantiating Header-annotated or other HttpComponent-defined header classes:

Example:

import static org.apache.juneau.http.HttpHeaders.*; ContentType contentType = header(ContentType.class, "text/xml");

Lists of headers can be produced with the following methods:

The capabilities of the HeaderList class is described later.

HttpParts

The HttpParts class contains convenience static methods for generating query/form-data/path parts and part lists.

Example:

import static org.apache.juneau.http.HttpParts.*; PartList formData = partList( // Arbitrary list of parts stringPart("Name", "Bill") // Freeform part integerPart("Age", () -> calculateAge()) // Freeform part with supplied value );

The following methods are provided for creating parts. Each accepts either static values or values from Suppliers:

The serializedPart methods allows for parts serialized using schema-based serializers such as the OpenAPI serializer.

Lists of parts can be produced with the following methods:

The capabilities of the PartList class is described later.

HttpEntities

The HttpEntities class contains convenience static methods for generating HTTP message entities. Returned objects extend from org.apache.http.HttpEntity but provides the following additional features:

  • Caching.
  • Fluent setters.
  • Fluent assertions.
  • Externally-supplied/dynamic content.

The following methods are provided for creating entities. Each accepts either static values or values from Suppliers and returns builders:

HTTP entities are automatically supported in both the server and client REST APIs for requests and responses.

Example:

import static org.apache.juneau.http.HttpResources.*; @RestDelete(path="/{id}") public HttpEntity helloWold(...) { return stringEntity("Hello!").contentType("text/plain"); }

HttpResources

The HttpResources class contains convenience static methods for generating HTTP message resources. Returned objects extend from HttpResource which extends from HttpEntity but with additional arbitrary headers.

The following methods are provided for creating entities. Each accepts either static values or values from Suppliers and are in the form of builders.

The most common location where resources are used are as returned types of REST operation methods described later.

Example:

import static org.apache.juneau.http.HttpResources.*; @RestDelete(path="/{id}") public HttpResource helloWold(...) { return stringResource("Hello!").contentType("text/plain").header("Cache-Control", "none"); }

HttpResponses

The HttpResponses class contains convenience static methods for standard HTTP responses. Returned objects extend from org.apache.http.HttpResponse and are in the form of builders.

The following methods are provided for creating entities:

The most common location where these responses are used are in REST operation methods described later.

Example:

import static org.apache.juneau.http.HttpResponses.*; import static org.apache.juneau.http.HttpHeaders.*; @RestDelete(path="/{id}") public Ok doDelete(...) throws Unauthorized { if (/* user not authorized*/) throw unauthorized(); return ok().content("Delete was successful"); }

7.2 - Annotationscreated: 9.0.0

The org.apache.juneau.http.annotation package contains annotations for defining both server and client side APIs. The server-side APIs also use it for producing auto-generated Swagger documentation through the REST API itself.

These annotations are used in a variety of places in the server and client side REST interfaces, especially for remote proxies. These will be described later in those sections.

7.3 - HTTP Headerscreated: 9.0.0

The org.apache.juneau.http.header package contains implementations of org.apache.http.Header for all common HTTP headers.

These headers extend from the following classes that provide data-type specific functionality:

These subclasses provide various convenience methods to allow for easy fluent-style coding.

Examples

// Validates the response body content is not expired. restClient .get(URL) .run() .getHeader("Expires").asDateHeader().assertZonedDateTime().isLessThan(new Date());

HeaderList

The HeaderList class is a list of HTTP headers.

Example

// Construct using builder. HeaderList headers = HeaderList .create() .append(Accept.of("text/xml")) .append("Content-Type", ()->getDynamicContentTypeFromSomewhere()); // Construct using convenience creator. HeaderList headers = HeaderList.of(Accept.TEXT_XML, ContentType.TEXT_XML);

Static methods are provided on HttpHeaders to further simplify creation of header lists.

import static org.apache.juneau.http.HttpHeaders.*; HeaderList headers = headerList(accept("text/xml"), contentType("text/xml"));

The builder class supports setting default header values (i.e. add a header to the list if it isn't otherwise in the list). Note that this is different from simply setting a value twice as using default values will not overwrite existing headers.
The following example notes the distinction:

headers = HeaderList .create() .set(Accept.TEXT_PLAIN) .set(Accept.TEXT_XML); assertObject(headers).isString("[Accept: text/xml]"); headers = HeaderList .create() .set(Accept.TEXT_PLAIN) .setDefault(Accept.TEXT_XML); assertObject(headers).isString("[Accept: text/plain]");

Various methods are provided for iterating over the headers in this list to avoid array copies.

In general, try to use these over the getAll() / getAll(String) methods that require array copies.

The get(String) method is special in that it will collapse multiple headers with the same name into a single comma-delimited list (see RFC 2616 Section 4.2 for rules).

The get(Class) and get(String,Class) methods are provided for working with Header-annotated beans.

Example

HeaderList headers = HeaderList.of(Accept.TEXT_JSON, Accept.TEXT_XML); // Returns "text/json, text/xml" Accept accept = headers.get(Accept.class);

By default, header names are treated as case-insensitive. This can be changed using the caseSensitive(boolean) method.

A VarResolver can be associated with this builder to create header values with embedded variables that are resolved at runtime.

Example

// Create a header list with dynamically-resolving values pulled from a system property. System.setProperty("foo", "bar"); HeaderList headers = HeaderList .create() .resolving() .append("X1", "$S{foo}") .append("X2", ()->"$S{foo}"); assertObject(headers).isString("[X1: bar, X2: bar]");

The HeaderList object can be extended to defined pre-packaged lists of headers which can be used in various annotations throughout the framework.

Example

// A predefined list of headers. public class MyHeaderList extends HeaderList { public MyHeaderList() { super(Accept.TEXT_XML, ContentType.TEXT_XML); } } // Use it on a remote proxy to add headers on all requests. @Remote(path="/petstore", headerList=MyHeaderList.class) public interface PetStore { @RemotePost("/pets") Pet addPet( @Content CreatePet createPet, @Header("E-Tag") UUID etag, @Query("debug") boolean debug ); }

7.4 - HTTP Partscreated: 9.0.0

The org.apache.juneau.http.part package contains implementations of org.apache.http.NameValuePair to be used for query/form-data/path parts and part lists.

PartList

The PartList class is a list of HTTP parts (form-data, query-parameters, path-parameters).

Example

PartList parts = PartList .create() .append(MyPart.of("foo")) .append("Bar", ()->getDynamicValueFromSomewhere());

Convenience creators are provided for creating lists with minimal code:

PartList parts = PartList.of(BasicIntegerPart.of("foo", 1));

Static methods are provided on HttpParts to further simplify creation of part lists.

import static org.apache.juneau.http.HttpParts.*; PartList parts = partList(integerPart("foo", 1), booleanPart("bar", false));

The builder class supports setting default part values (i.e. add a part to the list if it isn't otherwise in the list). Note that this is different from simply setting a value twice as using default values will not overwrite existing parts.
The following example notes the distinction:

parts = PartList .create() .set("Foo", "bar") .set("Foo", "baz"); assertObject(parts).isString("foo=baz"); parts = PartList .create() .set("Foo", "bar") .setDefault("Foo", "baz"); assertObject(parts).isString("foo=bar");

Various methods are provided for iterating over the parts in this list to avoid array copies.

In general, try to use these over the getAll() / getAll(String) methods that require array copies.

Similar to the way multiple headers can be collapsed into a single value, the get(String) method is special in that it will collapse multiple parts with the same name into a single comma-delimited list.

The get(Class) and get(String,Class) methods are provided for working with FormData / Query / Path-annotated beans.

Example

MyQueryBean foo = parts.get(MyQueryBean.class);

A VarResolver can be associated with this builder to create part values with embedded variables that are resolved at runtime.

Example

// Create a part list with dynamically-resolving values pulled from a system property. System.setProperty("foo", "bar"); PartList parts = PartList .create() .resolving() .append("X1", "$S{foo}") .append("X2", ()->"$S{foo}"); assertObject(parts).isString("X1=bar&X2=bar");

The PartList object can be extended to defined pre-packaged lists of parts which can be used in various annotations throughout the framework.

Example

// A predefined list of parts. public class MyPartList extends PartList { public MyPartList() { super(BasicIntegerPart.of("foo",1), BasicBooleanPart.of("bar",false)); } }

7.5 - HTTP Entities and Resourcescreated: 9.0.0

The org.apache.juneau.http.entity package contains implementations of org.apache.http.HttpEntity. These are entities that can be sent or received with an HTTP message. They consist of a message body and the headers Content-Type, Content-Length, and Content-Encoding.

The org.apache.juneau.http.resource package contains implementations of HttpResource which are extensions of org.apache.http.HttpEntity with arbitrary headers added beyond the standard content headers.

Example

import static org.apache.juneau.http.HttpEntities.*; byte[] payload = {...}; // Create via type builder. HttpEntity entity = ByteArrayEntity .create() .content(payload) .contentType(ContentType.APPLICATION_OCTET_STREAM); // Create via HttpEntities. HttpEntity entity = byteArrayEntity(payload, ContentType.APPLICATION_OCTET_STREAM);

HTTP entities and resources can be used by both the server and client side APIs described in later sections.

Server-side example:

// REST endpoint that simply echos an HTTP entity. @RestPost(path="/entity") public HttpEntity echoMyEntity(HttpEntity entity) { return entity; } // REST endpoint that serves up a static file. @RestGet(path="/resource/{fileName}") public HttpResource getStaticFile(@Path String fileName, Locale locale) { getContext().getStaticFiles().resolve(fileName, locale).orElseThrow(NotFound::new); }

Client-side example:

// REST client that uses the echo REST endpoint above. HttpEntity entity = byteArrayEntity(...); entity = RestClient.create() .build() .rootUrl(URI) .post("/entity", entity) .run() .assertStatus().asCode().is(200) .getContent().as(ByteArrayEntity.class);

7.6 - HTTP Responsescreated: 9.0.0

The org.apache.juneau.http.response package contains predefined org.apache.http.HttpResponse implementations for most standard HTTP responses.

These are built upon existing HttpComponents APIs:

The most common location where these responses are used are in REST operation methods described later.

Example:

@RestDelete(path="/{id}") public Ok doDelete(@Path int id) throws NotFound, Unauthorized { pojoService.delete(pojoService.find(id).orElseThrow(NotFound::new)); return Ok.OK; }

The following classes are also provided for constructing your own custom responses:

7.7 - Remote Proxy Interfacescreated: 9.0.0

The org.apache.juneau.http.remote package contains the annotations used for defining client-side remote proxies.

See Proxies for more information on use of these annotations.

8 - juneau-rest-serverupdated: 9.0.0

Maven Dependency

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

Java Library

juneau-rest-server-9.0.0.jar

OSGi Module

org.apache.juneau.rest.server_9.0.0.jar

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

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

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

Features
  • Deployable in standard Servlet containers.
  • Deployable in Spring Boot environments with full support for injected beans.
  • Serializes POJOs to JSON, XML, HTML, URL-Encoding, UON, RDF/XML, N-Triple, Turtle, N3, SOAP, or Java-serialized-object based on value of Accept header.
    No user code is required to handle these types.
    • Extensible design that provides ability to override existing content type handlers, or add the ability to handle other kinds of content types.
  • Parses content of POST/PUT request bodies to POJOs.
  • Automatic built-in ability to serialize POJO metadata to JSON+SCHEMA, XML+SCHEMA, or HTML+SCHEMA based on Accept header.
  • Automatic negotiation of output Writer based on HTTP headers.
    • Automatic handling of Accept-Charset header for all character sets supported by the JVM.
    • Automatic handling of Accept-Encoding header with registered encoders.
  • Automatic error handling.
    • Automatic 401 errors (Unauthorized) on failed guards.
    • Automatic 404 errors (Not Found) on unmatched path patterns.
    • Automatic 405 errors (Method Not Implemented) on unimplemented methods.
    • Automatic 406 errors (Not Acceptable) when no matching serializer was found to handle the Accept header.
    • Automatic 412 errors (Precondition Failed) when all matchers failed to match.
    • Automatic 415 errors (Unsupported Media Type) when no matching parser was found was found to handle the Content-Type header.
    • Automatic 500 errors on uncaught exceptions.
  • Support for parsing all HTTP parts (headers, query, formData, path variables) using Swagger formatting rules and validations.
    Not limited to simple POJOs but rather you can represent arbitrarily-complex POJOs in any HTTP part using UON notation.
  • Auto-created Swagger JSON and Swagger UI available through OPTIONS requests of resources.
  • Various useful debugging features that make debugging using a browser extremely simple...
    • Ability to pass HTTP header values as URL GET parameters (e.g. &Accept=text/xml).
    • Ability to pass HTTP content on PUT/POST requests as a URL GET parameter (e.g. &content=(foo=bar)).
    • Ability to simulate non-GET requests using a &method GET parameter (e.g. &method=POST).
    • Ability to force "text/plain" on response using GET parameter &plainText=true.
  • Ability to implement overloaded HTTP methods through the use of the &method attribute (e.g. &method=FOO).
  • Ability to match URL patterns (e.g. /foo/{fooId}/bar/{barId}) against URLs (e.g. /foo/123/bar/456/bing).
  • Ability to associate guards at the resource or method levels through annotations.
    Typically useful for security but can be used for a variety of purposes.
  • Ability to associate converters at the resource or method levels through annotations.
    Typically useful for performing conversions on input and output, such as for supporting older input and output formats.

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

8.1 - Overviewcreated: 9.0.0

A REST resource is simply a Java class annotated with @Rest. The most common case is a class that extends BasicRestServlet, which itself is simply an extension of HttpServlet which allows it to be deployed as a servlet.

Juneau has two sample applications for demonstrating how to use the REST API, one using Jetty and one using Spring Boot:

The org.apache.juneau.examples.rest.springboot application is described in the section SpringBoot Overview.

The Jetty application consists of the following application class that registers our top-level servlet:

public class App { public static void main(String[] args) throws Exception { JettyMicroservice .create() .args(args) .servlet(RootResources.class) .build() .start() .startConsole() .join(); } }

The root resources class is an example of a router page that is used to attach children to:

@Rest( title="Root resources", description="Example of a router resource page.", children={ HelloWorldResource.class, DtoExamples.class, UtilityBeansResource.class, HtmlBeansResource.class, ConfigResource.class, ShutdownResource.class } ) @HtmlDocConfig( widgets={ ContentTypeMenuItem.class }, navlinks={ "api: servlet:/api", "stats: servlet:/stats", "$W{ContentTypeMenuItem}", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/RootResources.java" }, aside={ "<div 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 <span class='link'>API</span> link provided that lets you see the generated swagger doc for this page.</p>", " <p>Also note the <span class='link'>STATS</span> link to view runtime statistics on this page.</p>", " <p>Also note the <span class='link'>SOURCE</span> link 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>" }, asideFloat="RIGHT" ) @SerializerConfig( // For testing purposes, we want to use single quotes in all the serializers so it's easier to do simple // String comparisons. // You can apply any of the Serializer/Parser/BeanContext settings this way. quoteChar="'" ) public class RootResources extends BasicRestServletGroup { private static final long serialVersionUID = 1L; }

This is what it looks like in a browser:

HTML representation
JSON representation

The HelloWorldResource class is our basic example of a child REST resource:

@Rest( title="Hello World", description="An example of the simplest-possible resource", path="/helloWorld" ) @HtmlDocConfig( aside={ "<div style='max-width:400px' class='text'>", " <p>This page shows a resource that simply response with a 'Hello world!' message</p>", " <p>The POJO serialized is a simple String.</p>", "</div>" } ) public class HelloWorldResource extends BasicRestObject { @RestGet(path="/*", summary="Responds with \"Hello world!\"") public String sayHello() { return "Hello world!"; } }

This is what it looks like in a browser:

HTML representation

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

8.2 - @Rest-Annotated Classesupdated: 8.1.2,9.0.0

The @Rest annotation is the primary way of defining and configuring REST resource classes. The functionality of the class itself is covered in detail in the topics below.

The @Rest annotation in inheritable from parents and interfaces of resource classes. When multiple annotations are defined at different levels, the annotation values are combined. This is a particularly useful feature because it allows you to define your own configured parent resource classes that can be extended by all your child resources so that they all share common settings.

8.2.1 - Predefined Classesupdated: 9.0.0

The following example represents the bare-minimum needed for deploying a top-level REST endpoint with basic JSON marshalling support:

@Rest( path="/mypath", serializers=JsonSerializer.class, parsers=JsonParser.class ) public class MyResource extends RestServlet { @RestGet(path="/") public Object getPojo() { ... } }

The RestServlet class provides all the logic for starting up your REST application when the servlet container calls init(ServletConfig). On startup, it scans your class for annotations and sets up all of your serializers and parsers. It then does this recursively for all child resources.

Users will typically not extend directly from RestServlet. Instead, several classes are provided by the framework to provide additional functionality and to handle different use-cases. Users will typically extend from one of these Basic* classes:

The RestServlet class itself is not configured with any serializers or parsers. However, it does provide several convenience methods to be aware of:

The Basic* classes implement the BasicRestOperations interface which defines common endpoints for swagger documentation, statistics, and serving static files:

public interface BasicRestOperations { @RestGet(path="/api/*") public Swagger getSwagger(RestRequest req); @RestGet(path="/htdocs/*") public HttpResource getHtdoc(@Path String path, Locale locale); @RestGet(path="favicon.ico") public HttpResource getFavIcon(); @RestGet(path="/stats") public RestContextStats getStats(RestRequest req); @RestOp(method=ANY, path="/error") public void error(); }

The Basic* classes also implement BasicJsonConfig interface which provides basic JSON marshalling support. Other config interfaces are available as well to quickly provide different types of marshalling support. Note that these interfaces do not define any methods but rather simply provide a set of commonly-used annotations so that you don't need to define them on all your classes.

For example, if you want to provide a resource that supports all languages in Juneau, simply add the BasicUniversalConfig interface like so:

@Rest(...) public class MyResource extends RestServlet implements BasicUniversalConfig { ... }

The *Group classes implement the BasicGroupOperations interface which provides an additional REST endpoint for listing and navigating child resources:

public interface BasicGroupOperations { @RestGet(path="/") public ChildResourceDescriptions getChildren(RestRequest req); }

The *Spring* classes are meant to be used in Spring Boot environments so that you can take full advantage of the Spring Framework for injecting dependencies on child resources and helper classes.

The *Object* classes provide the same functionality as the servlet classes but do not extend from HttpServlet. This becomes important in Spring Boot environments where you may want to define child resources as Spring Beans but don't want Spring Boot to auto-detect them as servlets to be deployed as top-level resources. This is less important in standard servlet containers that don't auto-deploy servlets. In those environments, you can also use servlet classes for child resources.

The following is a breakdown of which classes you will use in different cases:

8.2.2 - Child Resourcesupdated: 9.0.0

Child Resources are REST servlets or objects that are linked to parent resources through the @Rest(children) annotation.

Example:

/** Parent Resource */ @Rest( path="/parent", children={MyChildResource.class} ) public MyRootResources extends BasicRestServletGroup {...}

/** Child Resource */ @Rest( path="/child" // Path relative to parent resource. ) // Note that we don't need to extend from RestServlet. public MyChildResource extends BasicRestObject {...}

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

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

As explained earlier, child REST objects typically extend from BasicRestObject or BasicRestObjectGroup and not from one of the servlet classes. They also technically don't even need to extend from those classes and can instead just be a normal class annotated with the bare-minimum @Rest and @RestOp annotations.

8.2.3 - Path Variablesupdated: 9.0.0

The path can contain variables that get resolved to @Path parameters or access through the RestRequest.getPathParams() method.

Example:

@Rest( path="/myResource/{foo}/{bar}" ) public class MyResource extends BasicRestServlet { @RestPost("/{baz}") public void String doX(@Path String foo, @Path int bar) { ... } }

Variables can be used on either top-level or child resources and can be defined on multiple levels. Path variables resolved in parent resource paths are also available to the child resources.

All variables in the path must be specified or else the target will not resolve and a 404 will result.
8.2.4 - Deploymentupdated: 9.0.0

REST resources are deployed in the following ways:

  • Deployed in a J2EE container as a servlet.
  • Deployed in a Spring Boot environment as a servlet.
  • Deployed as a child of another REST resource.

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

  • When deployed as a child of another resource, you MAY extend from one of the servlet classes but it's not necessary. The only requirement is that the class be annotated with @Rest and have one of the following constructors if they aren't already Spring Beans:
    • public T()
    • public T(RestContext.Builder)

Deployment in a servlet container is typically done by adding a servlet entry for the top-level resources to the JEE web.xml.

Deployment in a Spring Boot environment involves defining your top-level resources as Spring Beans. Top-level resources must extend from BasicSpringRestServlet or BasicSpringRestServletGroup so that Juneau can hook into the injection framework provided by Spring. Child resource CAN be defined as injected Spring Beans as well but it is not a requirement.

Example Spring Boot Configuration

@SpringBootApplication @Controller public class SpringBootAppConfig { @Bean public MyRootResources getRootResources() { ... } @Bean public MyChildResource getMyChildResource() { ... } @Bean public ServletRegistrationBean<Servlet> getRootServlet(RootResources rootResources) { return new ServletRegistrationBean<>(rootResources, "/*"); } }

8.2.5 - Lifecycle Hooksupdated: 9.0.0

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

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

@Rest(...) public class MyResource extends BasicRestObject { // Our database. private Map<Integer,Object> myDatabase; @RestInit public void initMyDatabase(RestContext.Builder builder) throws Exception { myDatabase = new LinkedHashMap<>(); } }

Or if you want to intercept REST calls:

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

The following lifecycle annotations are provided.

  • Resource lifecycle events:
  • REST call lifecycle events:
    • RestStartCall - At the beginning of a REST call.
    • RestPreCall - Right before the @RestOp method is invoked.
    • RestPostCall - Right after the @RestOp method is invoked.
    • RestEndCall - At the end of the REST call after the response has been flushed.

8.3 - @RestOp-Annotated Methodsupdated: 9.0.0

REST Java methods are identified on REST servlets using the @RestOp annotation. The annotation allows the framework to identify the available REST methods through reflection.

Example:

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

The following specialized annotations are also provided for specific HTTP methods:

Example:

@RestGet(path="/") public String sayHello() { return "Hello world!"; }

8.3.1 - Inferred HTTP Methods and Pathsupdated: 9.0.0

When the name and/or path values are not specified, their values are inferred from the Java method name.

The HTTP method can be inferred from the Java method by starting the method name with any of the following:

  • get
  • put
  • post
  • delete
  • options
  • head
  • trace
  • patch

If path is not defined, it's inferred from the Java method name (minus the prefix above).

Examples:

// Method="GET", path="/foo" @RestOp public String getFoo() {...}

// Method="DELETE", path="/foo" @RestOp public String deleteFoo() {...}

// Method="GET", path="/foo" // "GET" is default @RestOp public String foo() {...}

// Method="GET", path="/" @RestOp(path="/") public String foo() {...}

// Method="GET", path="/" @RestOp public String get() {...}

// Method="POST", path="/" @RestOp public String post() {...}

If name and path are both specified, the Java method name can be anything.

8.3.2 - Java Method Parametersupdated: 9.0.0

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

In Spring Boot environments, any available Spring Beans can also be passed in as parameters.

Example:

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

Additional parameter types can be defined via the annotation Rest.restOpArgs() or by calling RestContext.Builder.restOpArgs(Class...).

Example:

@Rest( restOpArgs={ MyOpArg.class } // Option #1 - Via annotation ) public class MyResource extends BasicRestObject { // Option #2 - Programmatically @RestInit public void init(RestContext.Builder builder) { builder.restOpArgs(MyOpArg.class); } }

8.3.3 - Java Method Return Typesupdated: 9.0.0

The return type of the Java method can be any serializable POJO as defined in POJO Categories. It can also be void if the method is not sending any output (e.g. a request redirect) or is setting the output using the RestResponse.setContent(Object) method.

Example:

@RestGet public String doGet() { return "Hello World!"; }

In addition to POJOs, the following return types are also supported:

REST Java methods can also generate a response via the following:

Example:

// Equivalent method 1 @RestGet("/example1/{personId}") public Person doGet1(@Path("personId") UUID personId) { Person person = getPersonById(personId); return person; } // Equivalent method 2 @RestGet("/example2/{personId}") public void doGet2(RestResponse res, @Path("personId") UUID personId) { Person person = getPersonById(personId); res.setContent(person); }

Additional parameter types can be defined via the annotation Rest.responseProcessors() or by calling RestContext.Builder.responseProcessors(Class...).

Example:

@Rest( responseProcessors={ MyResponseProcessor.class } // Option #1 - Via annotation ) public class MyResource extends BasicRestObject { // Option #2 - Programmatically @RestInit public void init(RestContext.Builder builder) { builder.responseProcessors(MyResponseProcessor.class); } }

8.3.4 - Java Method Throwable Typesupdated: 9.0.0

Annotated Java methods can throw any of the following:

All other throwables get processed as follows:

  • Processed as 400/Bad Request:
  • Processed as 401/Unauthorized:
    • Any class named "*AccessDenied*" or "*Unauthorized*"
  • Processed as 404/Not Found:
    • Any class named "*Empty*" or "*NotFound*"
  • Anything else processed as 500/Internal Server Error.
Example:

@RestGet("/user/login") public Ok login( @FormData("username") String username, @FormData("password") String password ) throws Unauthorized { if (! isOK(username, password)) throw new Unauthorized("You're not welcome!"); return Ok.OK; }

8.3.5 - Path Patternsupdated: 9.0.0

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

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

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

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

// Try first @RestGet("/foo/bar") public void method1() { ... } // Try second @RestGet("/foo/{bar}") public void method2(...) { ... } // Try third @RestGet("/foo/*") public void method3(...) { ... } // Try last @RestGet("/*") public void method4(...) { ... }

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

The following example shows the distinction.

@RestGet("/*") public void doGet(@Path("/*") String remainder) { // URL path pattern can have remainder accessible through req.getRemainder(). } @RestPut("/") public void doPut() { // URL path pattern must match exactly and will cause a 404 error if a remainder exists. }

8.3.6 - Matchersupdated: 9.0.0

RestMatchers are used to allow multiple Java methods to be tied to the same HTTP method and path but differentiated by some request attribute such as a specific header value.

Example:

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

The interface for matchers is simple:

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

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

Through the use of the built-in "method" GET parameter, you can implement requests beyond the basic REST http method types.

For example, the URL "/sample/foo?method=BAR" will cause the following method to be invoked...

@RestOp(method="BAR") public void doBar(RestRequest req, RestResponse res) { // Handle BAR requests }

To support overloaded methods, the @Rest(allowedMethodParams) setting must be enabled on your servlet.

@Rest( // Allow &method parameter on BAR requests allowedMethodParams="BAR" )

8.3.8 - Additional Informationupdated: 9.0.0

8.4 - HTTP Partsupdated: 9.0.0

In previous examples we showed the ability to pass in annotated parameters on RestOp-annotated methods to parse standard HTTP parts:

Example:

@RestGet("/example1/{a1}/*") public String doGetExample1( @Path("a1") String a1, @Query("p1") int p1, @HasQuery("p2") boolean hasP3, @Path("/*") String remainder, @Header("Accept-Language") String lang ) { // Do something with all of those }

Annotations are provided for both request and response HTTP parts.

The annotations used for defining the schema for request HTTP parts are:

These annotation can be used on method parameters or on the parameter types themselves, or a combination of both.

  • When defined on types, annotations are aggregated from parent to child with child values taking precedence. When defined on both, annotations are aggregated with values on parameters taking precedence.
8.4.1 - Part Marshallersupdated: 8.1.0,9.0.0

Juneau comes with three basic marshaller types for serializing and parsing Header, Query, Form, and Path parts:

By default, the REST API uses the OpenAPI serializer and parser which allows for schema-based marshalling. You also have the option to use UON marshalling which is schema-less but allows for JSON-equivalent data structures (object/array/primitives/...) using URL-encoding notation. This can be done by overriding the part marshallers through the following APIs:

The OpenAPI marshallers themselves also have the ability to support UON notation for individual parts via the schema itself:

@Query(..., schema=@Schema(format="uon")) Map<Integer,MyBean> myMap

8.4.2 - HTTP Part Annotationsupdated: 8.1.0,9.0.0

The following annotations allow for defining part schemas based on the OpenAPI standard.

The @Header/@Query/ @FormData/@Path annotations can be used on parameters of @RestOp-annotated methods to get access to request headers, query parameters, form-data parameters, and path parts.

The most typical scenario is to simply use the value field to define parameter names:

Example:

@RestGet public void doGet( @Query("p1") int p1, @Query("p2") String p2, @Query("p3") UUID p3) {...}

This is functionally equivalent to the following code:

@RestGet public void doGet(RestRequest req) { RequestQueryParams query = req.getQueryParams(); int p1 = query.get("p1").asInteger().orElse(0); String p2 = query.get("p2").orElse(null); UUID p3 = query.get("p3").as(UUID.class).orElse(null); }

The special name "*" (or blank) can be used to represent all values. When used, the data type must be a Map or bean.

Examples:

// Multiple values passed as a map. @RestGet public void doGet(@Query("*") Map<String,Object> map) {...}

// Same but name "*" is inferred. @RestGet public void doGet(@Query Map<String,Object> map) {...}

// Multiple values passed as a bean. @RestGet public void doGet(@Query MyQueryBean bean) {...}

The @Content annotation is used to identify POJOs to be used as the body of an HTTP request.

Examples:

// Defined on parameter @RestPost public void addPet(@Content Pet pet) {...}

// Defined on POJO class @RestPost public void addPet(Pet pet) {...} @Content public class Pet {...}

This is functionally equivalent to the following code:

@RestPost public void addPet(RestRequest req) { Pet pet = req.getContent().as(Pet.class); ... }

In addition to @Content-annotated parameters/types, the body of an HTTP request can be retrieved by passing in parameters of the following types (matched in the specified order):

  1. Reader
    @Content annotation is optional.
    Content-Type is ignored.
  2. InputStream
    @Content annotation is optional.
    Content-Type is ignored.
  3. Any Parseable POJO type.
    Content-Type is required to identify correct parser.
  4. Objects convertible from Reader by having one of the following non-deprecated methods:
    • public T(Reader in) {...}
    • public static T create(Reader in) {...}
    • public static T fromReader(Reader in) {...}
    Content-Type must not be present or match an existing parser so that it's not parsed as a POJO.
  5. Objects convertible from InputStream by having one of the following non-deprecated methods:
    • public T(InputStream in) {...}
    • public static T create(InputStream in) {...}
    • public static T fromInputStream(InputStream in) {...}
    Content-Type must not be present or match an existing parser so that it's not parsed as a POJO.
  6. Objects convertible from String by having one of the following non-deprecated methods:
    • public T(String in) {...}
    • public static T create(String in) {...}
    • public static T fromString(String in) {...}
    • public static T parse(String in) {...}
    • public static T parseString(String in) {...}
    • public static T forName(String in) {...}
    • public static T forString(String in) {...}
    Note that this also includes all enums.
  7. Any Optional of anything on this list.

When used in combination with the mutable Value object, the @StatusCode and @Header annotations can be used on parameters @RestOp-annotated methods to to define to response codes and headers.

Example:

@RestGet("/user/login") public void login( @FormData("username") String username, @FormData("password") String password, @StatusCode Value<Integer> status, @Header("My-Response-Header") Value<String> myResponseHeader ) { if (isValid(username, password)) { status.set(200); myResponseHeader.set("Welcome " + username + "!"); } else { status.set(401); } }

This is functionally equivalent to the following code:

@RestGet("/user/login") public void doGet(RestRequest req, RestResponse res) { RequestFormParams form = req.getFormParams(); String username = form.get("username").orElse(null); String password = form.get("password").orElse(null); if (isValid(username, password) { res.setStatus(200); res.setHeader("My-Response-Header", "Welcome " + username + "!"); } else { res.setStatus(401); } }

The default registered part marshallers, OpenApiSerializer and OpenApiParser, are used to marshall POJOs using schemas defined via the @Schema annotation.

For example, the following shows how a pipe-delimited list of comma-delimited numbers (e.g. "1,2,3|4,5,6|7,8,9") can be converted to a 2-dimensional array of Longs:

@RestGet("/testQuery1") public void testQuery1( @Query("queryParamName") @Schema( collectionFormat="pipes", items=@Items( collectionFormat="csv", type="integer", format="int64", minimum="0", maximum="100" minLength=1, maxLength=10 ), minLength=1, maxLength=10 ) Long[][] queryParameter ) {...} // Same but using condensed notation. @RestGet("/testQuery2") public void testQuery2( @Query("queryParamName") @Schema( cf="pipes", minl=1, maxl=10, i=@Items(cf="csv", t="integer", f="int64", min="0", max="100", minl=1, maxl=10) ) Long[][] queryParameter ) {...}

Schema-based marshalling works for both request and response parts. Input will be converted based on the types and formats defined in the schema definition. Input validations such as minLength/maxLength that don't match the input will result in automatic 400 Bad Request responses.

The part and schema annotations are also used for supplying swagger information about the HTTP part. This information is used to populate the auto-generated Swagger documentation and UI.

Example:

@Query("name") @Schema( description="Pet name", required=true )

SVL Variables (e.g. "$L{my.localized.variable}") are supported on annotation fields as well. Among other things, this allow for annotation values to be defined externally and the ability to produce localized swagger documents based on the Accept-Language on a request.

Example:

@Schema( description="$L{PetNameDescription}" )

The @Content annotation can also be used to parse HTTP request bodies using OpenAPI schemas when the body content type matches the OpenApiParser parser via the header Content-Type: text/openapi.

The following shows the same for a request body:

@RestPost("/testContent") public void testContent( @Content @Schema( items=@Items( collectionFormat="pipes", items=@SubItems( collectionFormat="csv", type="integer", format="int64", minimum="0", maximum="100" minLength=1, maxLength=10 ) ), minLength=1, maxLength=10 ) Long[][] content ) {...}

The list of valid POJO types for parameters depends on type and format of the value or items/entries of the value. For example, instead of Longs in the example above, we could also define a 2-dimensional array of POJOs convertible from Longs:

// Content is a 2-dimensional array of POJOs convertible from Longs: @RestPost("/example1") public void testContent(@Content(...) MyPojo1[][] content) {...} public class MyPojo1 { public MyPojo1(Long input) {...} }

// Content is a POJO that takes in a Long array: @RestPost("/example2") public void testContent(@Content(...) MyPojo2[] content) {...} public class MyPojo2 { public MyPojo2(Long[] input) {...} }

// Content is a POJO that takes in the whole 2-dimensional array: @RestPost("/example3") public voidtestContent@Content(...) MyPojo3 content) {...} public class MyPojo3 { public MyPojo3(Long[][] input) {...} }

As you can see, the complexity of possible input types expands significantly. For more information about valid parameter types, see OpenAPI Parsers.

8.4.3 - Default Partscreated: 9.0.0

By default, HTTP parts that don't have value (such as missing query parameters) end up with null values:

@RestPost("/example") public String doGetExample1( @Query("p1") int p1, @FormData("f1") MyBean f1, @Header("Accept-Language") AcceptLanguage h1 ) { if (p1 == null) p1 = -1; if (f1 == null) f1 = DEFAULT_BEAN; if (h1 == null) h1 = AcceptLanguage.of("en"); }

You have several options to provide default values for HTTP parts. The most common is to simply use Optional parameters and handle default values programmatically:

@RestPost("/example") public String doGetExample1( @Query("p1") Optional<Integer> p1, @FormData("f1") Optional<MyBean> f1, @Header("Accept-Language") Optional<AcceptLanguage> h1 ) { int _p1 = p1.orElse(-1); Bean _f1 = f1.orElse(DEFAULT_BEAN); AcceptLanguage _h1 = h1.orElse(AcceptLanguage.of("en")); }

You can also specify default values on the annotations:

@RestPost("/example") public String doGetExample1( @Query(name="p1", def="-1") int p1, @FormData(name="f1", def="foo=bar,baz=qux") MyBean f1, @Header(name="Accept-Language", def="en") AcceptLanguage lang ) { ... }

A third option is to specify default values via the Rest and RestOp annotations.

Example:

// Servlet with default headers @Rest( // Assume "text/json" Accept value when Accept not specified defaultRequestHeaders={"Accept: text/json"}, // Add a version header attribute to all responses defaultResponseHeaders={"X-Version: 1.0"} ) public MyRestServlet extends BasicRestServlet { ... }

Default parts can also be specified programmatically through any of the following methods:

8.4.4 - @Request Beansupdated: 8.1.0,9.0.0

The @Request annotation can used to define proxy interfaces against HTTP requests in combination with the following annotations used on methods:

Example:

@RestPut("/pets/{petId}") public void addPet(UpdatePetBean updatePet) {...} @Request public interface UpdatePetBean { @Path // {petId} inferred. int getPetId(); @Query("verbose") boolean isDebug(); @Header("*") Map<String,Object> getAllHeaders(); @Content Pet getPet(); }

The example above is identical in behavior to specifying individual annotated parameters on the @RestOp-annotated method:

@RestPut("/pets/{petId}") public void addPet( @Path("petId") int petId, @Query("verbose") boolean debug, @Header("*") Map<String,Object> allHeaders, @Content UpdatePetBean pet ) {...}

The return types of the getters must be the supported parameter types for the HTTP-part annotation used. Schema-based serialization and parsing is used just as if used as individual parameter types. Annotations used are the exact same used on REST parameters and have all the same feature support including automatic Swagger validation and documentation. Part names can either be explicitly specified or automatically inferred from the getter names.

Example:

@Request public interface MyRequest { // Schema-based query parameter "pipedCdlInts": // Pipe-delimited list of comma-delimited lists of integers. @Query @Schema( collectionFormat="pipes" items=@Items( items=@SubItems( collectionFormat="csv" type="integer", minimum=1, maximum=100 ), maximumLength=10 ) ) int[][] getPipedCdlInts(); }

For clarity, the @Request annotation can be defined on the parameter, class, or both.

Example:

@RestPut("/pets/{petId}") public void addPet(@Request UpdatePetBean updatePet) {...} @Request public interface UpdatePetBean {...}

8.4.5 - @Response Beansupdated: 8.1.0,9.0.0

The Response annotation can be used to define beans that return HTTP response parts via annotations and methods. They are used in combination with the following annotations:

Response beans can either be returned or thrown from @RestOp-annotated methods.

  • As a general convention, response beans with return codes <400 should be defined as regular classes and >=400 should be defined as exceptions.

The following example shows the @Response annotation used to define an exception for an invalid login attempt:

// Our annotated normal response. @Response @StatusCode(200) @Schema( description="User was good." // Description show in Swagger ) public class ValidLogin { public ValidLogin() { ... } // Response bean converted to output based on Accept header. @Content public WelcomeMessageBean getContent() { return new WelcomeMessageBean(); } }

// Our annotated exception. @Response @StatusCode(401) @Schema( description="Invalid username or password provided" // Description show in Swagger ) public class InvalidLogin extends Exception { public InvalidLogin() { super("Invalid username or password."); // Message sent in response } @Header("My-Message") public String getMyMessage() { return "Nope!"; } }

// Our REST method that throws an annotated exception. @RestGet("/user/login") public ValidLogin login( @FormData("username") String username, @FormData("password") String password ) throws InvalidLogin { if (isValid(username, password)) { return new ValidLogin(); } throw new InvalidLogin(); }

Custom exceptions can also extend from one of the predefined HTTP exceptions such as the Unauthorized exception:

// Our annotated exception. @Response @Schema( description="Invalid username or password provided" // Overridden from parent class ) public class InvalidLogin extends Unauthorized { public InvalidLogin() { super("Invalid username or password."); } } // Parent predefined exception class. @Response @StatusCode(401) @Schema( description="Unauthorized" ) public class Unauthorized extends RestException {...}

8.4.6 - HTTP Part APIscreated: 9.0.0

Request HTTP parts can also be retrieved programmatically through the following classes that can be passed in as parameters or access through RestRequest bean:

Example:

@RestPost(...) public Object myMethod(RequestHeaders headers) { // Add a default value. headers.addDefault("ETag", DEFAULT_UUID); // Get a header value as a POJO. UUID etag = headers.get("ETag").as(UUID.class).get(); // Get a header as a standard HTTP part. ContentType contentType = headers.get(ContentType.class).orElse(ContentType.TEXT_XML); }

Built in to these APIs are various convenience methods such as converting parts to different types or inline fluent assertions:

Example:

// Inline fluent assertion and retrieval. String foo = request .getHeader("Foo") .assertString().contains("bar") .get();

8.5 - Marshallingupdated: 9.0.0

Juneau uses Parsers and Serializers for marshalling HTTP request and response bodies to POJOs using the Content-Type header to match the best parser and the Accept header to match the best serializer.

Serializers and parsers can be associated with REST servlets using the following annotations:

Request bodies are parsed and passed in via @Content-annotated parameters, and response bodies are returned or thrown by @RestOp-annotated methods and serialized.

Example:

@Rest( serializers={JsonParser.class, HtmlSerializer.class}, parsers={JsonParser.class, HtmlParser.class} ) public class MyResource { // Override at the method level. @RestPost(parsers={XmlParser.class}) public MyPojo myMethod(@Content MyPojo myPojo) { // Do something with your parsed POJO. // Then return it and serialize the POJO. } }

The following classes provide common default serializers and parsers that can be used as-is or augmented by child classes:

Serializers and parsers can also be defined programmatically using an INIT hook method like shown below:

@Rest public class MyResource { @RestInit public void init(RestContext.Builder builder) { builder.serializers().add(JsonSerializer.class, HtmlSerializer.class); builder.parsers().add(JsonParser.class, HtmlParser.class); } }

They can also be defined through custom REST contexts and builders.

Config annotations allow you to define serializer and parser properties using specialized annotations at either the class or operation levels:

@Rest( ... ) @BeanConfig(sortProperties="true") @SerializerConfig(trimNulls="true") @JsonConfig(escapeSolidus="true") public class MyResource extends BasicRestServlet { @RestPost @BeanConfig(sortProperties="false") @SerializerConfig(trimNulls="false") public MyPojo myMethod(@Content MyPojo myPojo) { ... } }

Swaps are associated serializers and parsers registered on a REST resource via the BeanConfig annotation on either the class or method level:

// Servlet with transforms applied @Rest( ... ) @BeanConfig( swaps={ // Calendars should be serialized/parsed as ISO8601 date-time strings TemporalCalendarSwap.IsoInstant.class, // Byte arrays should be serialized/parsed as BASE64-encoded strings ByteArraySwap.Base64.class }, beanFilters={ // Subclasses of MyInterface will be treated as MyInterface objects. // Bean properties not defined on that interface will be ignored. MyInterface.class } ) public MyResource extends BasicRestServlet {...}

Config annotations are defined for all serializers and parsers:

  • PhotosResource - An example of a REST resource that uses a custom serializer and parser.

8.6 - Form Postsupdated: 9.0.0

HTTP form posts can be handled two ways:

  1. By parsing the entire HTTP body into a POJO using the registered UrlEncodingParser
  2. By access the form post entries as HTTP parts.

The following example shows the first approach of handling an application/x-www-form-urlencoded request of the form "aString=foo&aNumber=123&aDate=2001-07-04T15:30:45Z" and loading it into a simple bean.

// A simple bean. public static class FormInputBean { public String aString; public int aNumber; @Swap(TemporalCalendarSwap.IsoLocalDateTime.class) public Calendar aDate; }

@Rest(...) public class MyRestResource extends BasicRestServlet { // Our form input endpoint. @RestPost("/") public Object doPost(@Content FormInputBean input) { // Just mirror back the request return input; } }

The next example shows handling it as individual parts:

// Our form input endpoint. @RestPost("/") public Object doPost( @FormData("aString") String aString, @FormData("aNumber") int aNumber, @FormData("aDate") Calendar aDate) { ... }

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

If you're using form input beans, DO NOT use the @FormData attribute or ServletRequest.getParameter(String) method since this will cause the underlying JEE servlet to parse the HTTP body as a form post.
Your input bean will end up being null since there won't be any content left after the servlet has parsed the body of the request.
This applies to WHENEVER you use @Content or RestRequest.getContent()
If you want to be able to consume url-encoded form post bodies as POJOs in Spring Boot, you'll need to add the following Spring Bean to your configuration to prevent Spring Boot from automatically consuming the body itself:

@SpringBootApplication @Controller public class SpringBootAppConfig { @Bean public FilterRegistrationBean<HiddenHttpMethodFilter> registration(HiddenHttpMethodFilter filter) { FilterRegistrationBean<HiddenHttpMethodFilter> reg = new FilterRegistrationBean<>(filter); reg.setEnabled(false); return reg; } }

Multi-part Form Posts

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

The following is an example that uses the File Upload library to allow files to be uploaded as multipart form posts.

Example:

@Rest( path="/tempDir" ) public class TempDirResource extends DirectoryResource { @RestPost(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 file = new File(getRootDir(), item.getName()); IOPipe.create(item.openStream(), new FileOutputStream(file)).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"); } }

The following shows using the HttpServletRequest.getParts() method to retrieve multi-part form posts when using Jetty. This example is pulled from the PetStore application.

@RestPost public SeeOtherRoot uploadFile(RestRequest req) throws Exception { // Required for Jetty. MultipartConfigElement mce = new MultipartConfigElement((String)null); req.setAttribute("org.eclipse.jetty.multipartConfig", mce); String id = UUID.randomUUID().toString(); BufferedImage img = null; for (Part part : req.getParts()) { switch (part.getName()) { case "id": id = IOUtils.read(part.getInputStream()); break; case "file": img = ImageIO.read(part.getInputStream()); } } addPhoto(id, img); return new SeeOtherRoot(); // Redirect to the servlet root. }

8.7 - Guardsupdated: 9.0.0

Guards control access to REST classes and methods. When guards are associated at the class-level, it's equivalent to associating guards on all Java methods on the servlet. If multiple guards are present, ALL guards must pass. (Note that this is different in behavior to Matchers which require only one matcher to pass.)

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

Example:

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

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

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

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

A guard failure results in an HTTP 401 Unauthorized response. However, this can be configured by overriding RestGuard.guard(RestRequest,RestResponse) and processing the response yourself.

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

A simplified format is available for matching based on the user role on the request using the following:

Example:

@Rest( path="/foo", roleGuard="ROLE_ADMIN || (ROLE_READ_WRITE && ROLE_SPECIAL)" ) public class MyResource extends BasicRestServlet { ... }

8.8 - Convertersupdated: 9.0.0

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

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

Example:

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

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

  • RestConverter
    • Queryable
      Provides query parameters that can be used to transform the response (i.e. search/view/sort the POJO response before being serialized).
    • Traversable
      Allows nodes in the POJO response tree to be individually accessed through additional path info on the request.
    • Introspectable
      Allows method calls to be made on the response POJO, and for the result of that method call to be serialized as the response.

8.9 - Localized Messagesupdated: 8.2.0,9.0.0

The @Rest(messages) annotation identifies the location of the resource bundle for a @Rest-annotated class if it's different from the class name.

By default, the resource bundle name is assumed to match the class name. For example, given the class MyClass.java, the resource bundle is assumed to be MyClass.properties. This property allows you to override this setting to specify a different location such as MyMessages.properties by specifying a value of "MyMessages".

Resource bundles are searched using the following base name patterns:

  • "{package}.{name}"
  • "{package}.i18n.{name}"
  • "{package}.nls.{name}"
  • "{package}.messages.{name}"

This annotation is used to provide request-localized (based on Accept-Language) messages for the following method:

Request-localized messages are also available by passing either of the following args into your Java method:

  • ResourceBundle - Basic Java resource bundle.
  • Messages - Extended resource bundle with several convenience methods.

The value can be a relative path like "nls/Messages", indicating to look for the resource bundle "com.foo.sample.nls.Messages" if the resource class is in "com.foo.sample", or it can be an absolute path like "com.foo.sample.nls.Messages"

Examples:

#-------------------------------------------------------------------------------- # Contents of org/apache/foo/nls/MyMessages.properties #-------------------------------------------------------------------------------- HelloMessage = Hello {0}!

// Contents of org/apache/foo/MyResource.java @Rest(messages="nls/MyMessages") public class MyResource { @RestGet("/hello/{you}") public Object helloYou(RestRequest req, Messages messages, @Path("name") String you) { String msg; // Get it from the RestRequest object. msg = req.getMessage("HelloMessage", you); // Or get it from the method parameter. msg = messages.getString("HelloMessage", you); // Or get the message in a locale different from the request. msg = messages.forLocale(Locale.UK).getString("HelloMessage", you); return msg; } }

When using shared resource bundles, keys can be prefixed by class names like so and still retrieve by simple key names:

#-------------------------------------------------------------------------------- # Contents of shared org/apache/foo/nls/MyMessages.properties #-------------------------------------------------------------------------------- MyResource.HelloMessage = Hello {0}!

Messages are automatically inherited from super classes. If a string cannot be found in the bundle of the current class, it will be searched for up the class hierarchy.

8.10 - Encodersupdated: 9.0.0

The @Rest(encoders) annotation can be used to associate character encoders with a servlet class. Encoders can be used to enable various kinds of compression (e.g. "gzip") on requests and responses based on the request Accept-Encoding and Content-Encoding headers.

Example:

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

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

8.11 - Configuration Filesupdated: 9.0.0

The Server API provides methods for associating configuration files with REST servlets so that configuration properties can be defined in external files. It uses the following annotation:

Example:

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

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

Example:

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

These properties are then accessible through the Config class.

@RestGet("/") public String sample(Config config) { String path = config.get("MyProperties/path").get(); File javaHome = config.get("MyProperties/javaHome").as(File.class).orElse(null); String customMessage = config.get("MyProperties/customMessage").orElse("Hello"); ... }

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

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

Spring Boot applications typically define an application.properties file. You can reuse it as a config file like so:

@Rest( config="application.properties", ... ) public class MyResource {...}

Note that properties files are a subset of functionality of INI files (they're basically just INI files with a single default section). It's therefore possible to use INI-style syntax such as sections in your application.properties file.

Once a config file has been associated with a REST resource, it can be accessed through one of the following: RestContext.getConfig() method. It can also be access by passing in a Config bean to any of your REST OP methods.

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

@Rest( // Config file is located at ./config_dir/myconfig.cfg config="config_dir/myconfig.cfg", ... ) public class MyResource { private final String path; private final File javaHome; public MyResource(RestContext.Builder builder) { Config config = builder.getConfig(); path = config.get("MyProperties/path").orElse("mypath"); javaHome = config.get("MyProperties/javaHome").as(File.class).orElse(null); }

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

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

It's even possible to reference request-level variables in your config file if you use RestRequest.getConfig() to access the config file:

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

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @Rest( config="config_dir/myconfig.cfg", ... ) public class HelloWorldResource extends BasicRestServlet { /** * GET request handler. * Specify the GET parameter "?person=X" for a specialized message! */ @RestGet("/") public String sayHello(Config config) { return config.get("HelloWorldResource/message").get(); } }

You can even add resource bundles into the mix:

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

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

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

8.12 - SVL Variablesupdated: 9.0.0

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

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

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

Features
  • Variables can be nested arbitrarily deep (e.g. "$X{$Y{foo}}").
  • Variables can contain arguments (e.g. "$L{my.label,arg1,arg2}").
  • Variables are recursively resolved.
    i.e., if a variable results to a value with another variable in it, that variable will also be resolved (restricted for security reasons on variables that involve user input).

There are two distinct groups of variables:

  • Initialization-time variables
    These are variables that can be used in many of the annotations in @Rest.
    The RestContext.getVarResolver() method returns initialization-time variables only.
  • Request-time variables
    These are variables that are available during HTTP-requests and can be used on annotation such as HtmlDoc @HtmlDoc.
    RestRequest.getVarResolverSession() method returns initialization and request-time variables.

The following is the default list of supported variables.

Default REST SVL Variables:
ModuleClassPatternInitialization
time
Request
time
Examples
juneau-svl EnvVariablesVar $E{key[,default]} yes yes $E{PATH}
SystemPropertiesVar $S{key[,default]} yes yes $S{java.home}
ArgsVar $A{key[,default]} yes yes $A{foo,null}
ManifestFileVar $MF{key[,default]} yes yes $MF{Main-Class}
IfVar $IF{arg,then[,else]} yes yes $IF{$S{my.boolean.property},foo,bar}
SwitchVar $SW{arg,p1:then1[,p2:then2...]} yes yes $SW{$S{os.name},*win*:Windows,*:Something else}
CoalesceVar $CO{arg1[,arg2...]} yes yes $CO{$S{my.property},$E{my.property},n/a}
PatternMatchVar $PM{arg,pattern} yes yes $PM{$S{os.name},*win*}
NotEmptyVar $NE{arg} yes yes $NE{$S{foo}}
UpperCaseVar $UC{arg} yes yes $UC{$S{foo}}
LowerCaseVar $LC{arg} yes yes $LC{$S{foo}}
juneau-config ConfigVar $C{key[,default]} yes yes $C{REST/staticFiles}
juneau-rest-server FileVar $F{path[,default]}} yes yes $F{resources/MyAsideMessage.html, Oops not found!}
ServletInitParamVar $I{name[,default]} yes yes $I{my.param}
LocalizationVar $L{key[,args...]} no yes $L{MyMessage,foo,bar}
RequestAttributeVar $RA{key1[,key2...]} no yes $RA{attrName}
RequestFormDataVar $RF{key1[,key2...]} no yes $RF{paramName}
RequestHeaderVar $RH{key1[,key2...]} no yes $RH{Header-Name}
RequestPathVar $RP{key1[,key2...]} no yes $RP{pathVAr}
RequestQueryVar $RQ{key1[,key2...]} no yes $RQ{paramName}
RequestSwaggerVar $RS{key} no yes $RS{title}
RequestVar $R{key1[,key2...]} no yes $R{contextPath}
SerializedRequestAttrVar $SA{contentType,key[,default]} no yes $SA{application/json,$RA{foo}}
UrlVar $U{uri} no yes $U{servlet:/foo}
UrlEncodeVar $UE{uriPart} yes yes $U{servlet:/foo?bar=$UE{$RA{bar}}
Widget $W{name} no yes $W{MenuItemWidget}

Custom variables can be defined on resources via the following API:

Example:

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

The methods involved with variables are:

8.13 - Static filesupdated: 9.0.0

The BasicRestServlet and BasicRestObject classes come with built-in support for serving up static files through the following REST operation:

@RestGet(path="/htdocs/*") public HttpResource getHtdoc(@Path("/*") String path, Locale locale) throws NotFound { return getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new); }

The static file finder can be accessed through the following methods:

By default, the StaticFiles bean is configured as follows:

StaticFiles .create() .beanStore(beanStore) // Allow injected beans in constructor. .type(BasicStaticFiles.class) // Default implementation class. .dir("static") // Look in working /static directory. .dir("htdocs") // Look in working /htdocs directory. .cp(resourceClass, "htdocs", true) // Look in htdocs subpackage. .cp(resourceClass, "/htdocs", true) // Look in htdocs package. .caching(1_000_000) // Cache files in memory up to 1MB. .exclude("(?i).*\\.(class|properties)") // Ignore class/properties files. .headers(cacheControl("max-age=86400, public")); // Add cache control.

Static files can be configured programmatically through the following APIs:

8.14 - Client Versioningupdated: 9.0.0

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

The APIs involved with defining client version headers are:

Example:

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

8.15 - Swagger

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

As described previously, the PetStore example provides an example of auto-generated Swagger JSON:

Using SwaggerUI, we're able to render that JSON as a Swagger user interface:

8.15.1 - BasicRestServlet/BasicRestObjectupdated: 8.1.0,9.0.0

Any subclass of BasicRestServlet and BasicRestObject gets an auto-generated Swagger UI when performing an OPTIONS request with Accept:text/html due to the following method:

@RestGet( path="/api/*", summary="Swagger documentation", description="Swagger documentation for this resource." ) @HtmlDocConfig( // Should override config annotations defined on class. rank=10, // Override the nav links for the swagger page. navlinks={ "back: servlet:/", "json: servlet:/?Accept=text/json&plainText=true" }, // Never show aside contents of page inherited from class. aside="NONE" ) @BeanConfig( // POJO swaps to apply to all serializers/parsers on this method. swaps={ // Use the SwaggerUI swap when rendering Swagger beans. // This is a per-media-type swap that only applies to text/html requests. SwaggerUI.class } ) @Override /* BasicRestOperations */ public Swagger getSwagger(RestRequest req) { return req.getSwagger().orElseThrow(NotFound::new); }

The underlying mechanics are simple. The BasicRestServlet.getSwagger(RestRequest) method returns a Swagger bean consisting of information gathered from annotations and other sources. Then that bean is swapped for a SwaggerUI bean when rendered as HTML.

Note that to have your resource create Swagger UI, you must either extend from one of the basic resource classes or provide your own @RestOp-annotated method that returns a Swagger object and a SwaggerUI swap.

8.15.2 - Basic Swagger Infoupdated: 9.0.0

Let's look at the various parts of the Petstore application Swagger UI to see how they are defined.

The top part of the page shows general information about the REST interface:

The information is pulled from the @Rest(swagger) annotation.

org.apache.juneau.examples.rest.petstore.PetStoreResource

@Rest( path="/petstore", title="Petstore application", ... swagger=@Swagger("$F{PetStoreResource.json}"), ... ) public class PetStoreResource extends BasicRestServlet {...}

In this particular case, the Swagger is pulled in from a localized Swagger JSON file located in the org.apache.juneau.examples.rest.petstore package using the $F variable.

PetStoreResource.json

{ "swagger": "2.0", "info": { "version": "1.0.0", "title": "Swagger Petstore", "termsOfService": "You are on your own.", "contact": { "name": "Juneau Development Team", "email": "dev@juneau.apache.org", "url": "http://juneau.apache.org" }, "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } }, "externalDocs": { "description": "Find out more about Juneau", "url": "http://juneau.apache.org" }, ... }

Note that the $F variable allows for request-locale-sensitive name matching so that you can provide localized Swagger information.

The $F variable simply expands to a string to fill the @Swagger(value) annotation. You could equivalently embed JSON directly into your annotation like so:

@Rest( path="/petstore", title="Petstore application", ... swagger=@Swagger( // Raw Simplified JSON. // Values are concatenated. "{", "swagger: '2.0',", "version: '1.0.0',", ... "}" ), ... ) public class PetStoreResource extends BasicRestServlet {...}

However, a more typical (and less error-prone) scenario is to define all of your Swagger as annotations:

@Rest( path="/petstore", title="Petstore application", ... swagger=@Swagger( version="1.0.0", title="Swagger Petstore", termsOfService="You are on your own.", contact=@Contact( name="Juneau Development Team", email="dev@juneau.apache.org", url="http://juneau.apache.org" ), license=@License( name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html" ), externalDocs=@ExternalDocs( description="Find out more about Juneau", url="http://juneau.apache.org" ) ), ... ) public class PetStoreResource extends BasicRestServlet {...}

All annotations support SVL Variables, so you could for example pull localized strings from resource bundles using $L variables.

@Rest( path="/petstore", title="Petstore application", messages="nls/MyMessages", ... swagger=@Swagger( version="1.0.0", title="$L{myTitle}", termsOfService="$L{myTermsOfService}", contact=@Contact( name="$L{myTeam}", email="dev@juneau.apache.org", url="http://juneau.apache.org" ), license=@License( name="Apache 2.0", url="http://www.apache.org/licenses/LICENSE-2.0.html" ), externalDocs=@ExternalDocs( description="$L{myExternalDocsDescription}", url="http://juneau.apache.org" ) ), ... ) public class PetStoreResource extends BasicRestServlet {...}

A third option is to define your Swagger information in your @Rest(messages) resource bundle using predefined Swagger keywords:

PetStoreResource.version = 1.0.0 PetStoreResource.title = Swagger Petstore PetStoreResource.termsOfService = You are on your own. PetStoreResource.contact = {name:'Juneau Development Team', email:'dev@juneau.apache.org',...} PetStoreResource.license = {name:'Apache 2.0',...} PetStoreResource.externalDocs = {description:'Find out more about Juneau',...}

Information defined in multiple locations are merged into a single set of data. When the same information is provided in multiple locations, the following order-of-precedence is used:

  1. Java annotations
  2. Resource bundle
  3. Swagger JSON file
8.15.3 - Tags

Tags allow you to group operations into general categories. In the user interface, these can be expanded/collapsed by clicking on the tag sections. In the example below, the pet and store tag sections are collapsed and the user section is not:

Tags are also defined in the @Swagger annotation

PetStoreResource.json

"tags": [ { "name": "pet", "description": "Everything about your Pets", "externalDocs": { "description": "Find out more", "url": "http://juneau.apache.org" } }, { "name": "store", "description": "Access to Petstore orders" }, { "name": "user", "description": "Operations about user", "externalDocs": { "description": "Find out more about our store", "url": "http://juneau.apache.org" } } ],

The annotation-only approach is shown here:

org.apache.juneau.examples.rest.petstore.PetStoreResource

swagger=@Swagger( ... tags={ @Tag( name="pet", description="Everything about your Pets", externalDocs=@ExternalDocs( description="Find out more", url="http://juneau.apache.org" ) ), @Tag( name="store", description="Access to Petstore orders" ), @Tag( name="user", description="Operations about user", externalDocs=@ExternalDocs( description="Find out more about our store", url="http://juneau.apache.org" ) ) } ),

Tags are associated with operations using the @OpSwagger(tags) annotation:

GET /user operation

@RestGet( path="/user", summary="Petstore users", swagger=@OpSwagger( tags="user" ) ) public Collection<User> getUsers() throws NotAcceptable {...}

Operations can be mapped to multiple tags.

Tags are optional. Operations not mapped to tags are listed in the UI before tagged operations.

For example, the getTopPage() method in PetStoreResource is not tagged, as well as the getOptions() method inherited from BaseRestServlet, so these show up at the top of the page:

8.15.4 - Operations

@RestOp-annotated methods automatically get rendered as Swagger operations:

The following shows the annotations defined on the GET /pet operation:

PetStoreResource.getPets()

@RestGet( path="/pet", summary="All pets in the store", swagger=@OpSwagger( tags="pet", ... ), ... ) public Collection<Pet> getPets() throws NotAcceptable { return store.getPets(); }

Methods marked as deprecated will show up as deprecated in the Swagger UI:

PetStoreResource.findPetsByTag()

@RestGet( path="/pet/findByTags", summary="Finds Pets by tags", ... ) @Deprecated public Collection<Pet> findPetsByTags(...) { ... }

8.15.5 - Parametersupdated: 9.0.0

Expanding operations shows you a list of parameters:

Parameter information can be defined in a couple of ways. The typical way is through annotations on parameters being passed to your @RestOp-annotated method, like so:

@RestGet public Collection<Pet> getPets( @Query( name="s", schema=@Schema( description={ "Search.", "Key/value pairs representing column names and search tokens.", "'*' and '?' can be used as meta-characters in string fields.", "'>', '>=', '<', and '<=' can be used as limits on numeric and date fields.", "Date fields can be matched with partial dates (e.g. '2018' to match any date in the year 2018)." }, type="array", collectionFormat="csv" ) ) String[] s, @Query( name="v", schema=@Schema( description={ "View.", "Column names to display." }, type="array", collectionFormat="csv" ) ) String[] v, ... ) throws NotAcceptable { ... }

  • The @Schema annotation can also be attached directly to the parameter or parameter type as well.
  • The type and collectionFormat values above are optional and auto-detected based on the parameter class type if omitted. They're included here for clarity. The examples will be explained in the next section.

Another option is to specify your parameter information in the parameters annotation as free-form JSON 5. In the case of the PetStoreResource.getPets() method, we pull this information from a static field defined in the Queryable class:

PetStoreResource.getPets()

@RestGet( path="/pet", summary="All pets in the store", swagger=@OpSwagger( tags="pet", parameters={ Queryable.SWAGGER_PARAMS } ), ... converters={Queryable.class} ) public Collection<Pet> getPets() throws NotAcceptable { return store.getPets(); }

Queryable

public class Queryable implements RestConverter { public static final String SWAGGER_PARAMS="" + "{" + "in:'query'," + "name:'s'," + "description:'" + "Search.\n" + "Key/value pairs representing column names and search tokens.\n" + "\\'*\\' and \\'?\\' can be used as meta-characters in string fields.\n" + "\\'>\\', \\'>=\\', \\'<\\', and \\'<=\\' can be used as limits on numeric and date fields.\n" + "Date fields can be matched with partial dates (e.g. \\'2018\\' to match any date in the year 2018)." + "'," + "type:'array'," + "collectionFormat:'csv'," + "x-examples:{example:'?s=Bill*,birthDate>2000'}" + "}," + "{" + "in:'query'," + "name:'v'," + "description:'" + "View.\n" + "Column names to display." + "'," + "type:'array'," + "collectionFormat:'csv'," + "x-examples:{example:'?v=name,birthDate'}" + "}," ... ; }

This information could have also been defined in the Swagger JSON for the resource as well.

The parameter section contains information about the request body as well for PUT and POST methods, as shown here:

The definition of this method is shown here:

@RestPost( summary="Add a new pet to the store", swagger=@OpSwagger( tags="pet" ) ) public Ok postPet( @Content @Schema(description="Pet object to add to the store") PetCreate pet ) throws IdConflict, NotAcceptable, UnsupportedMediaType { store.create(pet); return OK; }

Note that the schema information on the body parameter is automatically detected if not provided.

8.15.6 - Responses

Under the input parameters are listed the possible responses for the resource:

The 200 response is determined by the return type on the method, in this case a collection of Pet objects:

@RestGet( path="/pet", summary="All pets in the store", ... ) public Collection<Pet> getPets() throws NotAcceptable { return store.getPets(); }

Note that additional responses can be specified by throwing exceptions annotated with the @Response annotation such as this one:

@Response @StatusCode(406) @Schema(description="Not Acceptable") public class NotAcceptable extends BasicHttpException {...}

Like input parameters, the Swagger for responses can be define in multiple locations such as:

8.15.7 - Modelsupdated: 9.0.0

The JsonSchemaGenerator.Builder.useBeanDefs() setting can be used to reduce the size of your generated Swagger JSON files by creating model definitions for beans and referencing those definitions through $ref attributes.

This setting is disabled by default but can be set on the RestContext.Builder object:

@HookEvent(INIT) public void init(RestContext.Builder builder) { builder.jsonSchemaGenerator().useBeanDefs(); }

In the Swagger UI, this causes bean definitions to show up in the Models section at the bottom of the page:

Models section
Models section with Order bean expanded

In the generated Swagger JSON, embedded schema information for beans will be replaced with references such as the one shown below for the Order bean:

{ "swagger": "2.0", "paths": { "/store/order": { "get": { "operationId": "getOrders", "summary": "Petstore orders", "responses": { "200": { "description": "OK", "schema": { "description": "java.util.Collection<org.apache.juneau.examples.rest.petstore.Order>", "type": "array", "items": { "$ref": "#/definitions/Order" } }, ... ... ... ... ... }, "definitions": { "Order": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "petId": { "type": "integer", "format": "int64" }, "shipDate": { "type": "string" }, "status": { "type": "string", "enum": [ "PLACED", "APPROVED", "DELIVERED" ] } }, "description": "org.apache.juneau.examples.rest.petstore.Order", "example": { "id": 123, "petId": 456, "shipDate": "2012-12-21", "status": "APPROVED" } }, ... }

Note that this does not affect how the information is rendered for that bean in the Swagger UI:

8.15.8 - SwaggerUI.css

The look-and-feel of the Swagger UI is controlled via a single CSS file: SwaggerUI.css.

In the microservice template, this file is located in the files/htdocs/styles directory. It's a simple straightforward file consisting of less than 350 lines. This file can be modified to change the look-and-feel of your Swagger UI.

8.16 - REST method execution statisticscreated: 8.1.3, updated: 9.0.0

Rest-annotated classes get automated timing and error statistic information for all @RestOp and lifecycle-event annotated methods on the class.

If you extend from BasicRestServlet or BasicRestObject, then the statics are made available through the REST interface via the following method:

@RestGet( path="/stats", summary="Timing statistics", description="Timing statistics for method invocations on this resource." ) @Override /* BasicRestOperations */ public RestContextStats getStats(RestRequest req) { return req.getContext().getStats(); }

Rendered in a browser, it looks like this:

The default REST configuration provides a link to the stats in the navlinks section of the page:

The exception hash shown is the same hash that is shown in the log file and provides a quick way of locating the exception in the logs.

Programmatic access to the statistics are provided via the following methods:

8.17 - @HtmlDocConfigupdated: 8.1.0,9.0.0

The @HtmlDocConfig annotation is used to customize the HTML view of POJOs serialized by HtmlDocSerializer.

It's used in the following locations:

For example, the following shows setting the title on a page:

@Rest @HtmlDocConfig( title="My Resource Page" )

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

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

The outline above is controlled by the HtmlDocTemplate interface which can be overridden via the @HtmlDocConfig(template) annotation.

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

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

SVL variables can be used in any of these annotations:

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

8.17.1 - User Interfaces (UI) vs. Developer Interfaces (DI)

An important distinction needs to be made about the HTML representations produced by the REST API. These should not be considered User Interfaces but rather Developer Interfaces.

UIs should hide the end-user from the underlying architecture. The audience generally consists of non-technical people not interested in how the UI works.

DIs, on the other hand, should NOT hide the end-user from the underlying architecture. Instead, it's a thin veneer over the REST interface with the following goals:

  • Make it easy for the developer to explore and understand the REST API.
  • Make it easy for the developer to debug the REST API using simple tools (hopefully just a browser).

As a result, the following guidelines are recommended:

  • Use titles/descriptions/asides to describe why the REST interface exists. A developer knowing little about it should be able to access it with a browser and quickly understand what it is and how to use it.
  • Don't hide the raw data! The HTML view should simply be considered an easier-to-read representation of the data normally rendered in JSON or some other format.
  • Limit your use of Javascript! You can use it sparingly if you want to implement something simple like a pull-down menu to simplify some debug task but remember that your audience cares more about interacting with your service programmatically using REST. Remember that the HTML is just icing on the cake.
  • Don't use it to implement a Web 2.0 interface! If you want a Web 2.0 UI, implement it separately ON TOP OF this REST interface. The architecture is flexible enough that you could in theory pull in and use jQuery, React, Angular, or any number of sophisticated Javascript UI frameworks. Resist the urge to do so.
8.17.2 - Widgetsupdated: 9.0.0

The Widget class allows you to add arbitrary HTML, CSS, and Javascript to HTML pages.
They are registered in the following location:

Example:

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

The Widget class is composed of the following methods:

The HTML content returned by the getHtml(RestRequest,RestResponse) method is added wherever the "$W{...}" variable is used.

The CSS returned by getScript(RestRequest,RestResponse) is added to the style section in the page header.

The Javascript returned by getScript(RestRequest,RestResponse) is added to the script section in the page header.

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

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

8.17.3 - Predefined Widgetsupdated: 9.0.0

The org.apache.juneau.rest.widget package contains predefined reusable widgets.