Juneau 9.0.1 API
Apache Juneau 9.0.1 Documentation
Table of Contents
-
Marshallingcreated: 9.0.0
End-to-End RESTcreated: 9.0.0
REST Clientcreated: 9.0.0
DTOscreated: 9.0.0
Config Filescreated: 9.0.0
Fluent Assertionscreated: 9.0.0
General Designcreated: 9.0.0
-
Serializers and Parsersupdated: 9.0.0
Bean Contextsupdated: 9.0.0
Java Beans Supportcreated: 8.2.0, updated: 9.0.0
@Bean Annotationupdated: 8.2.0,9.0.0
@Beanp Annotationupdated: 8.1.0,8.1.2,9.0.0
@Beanc Annotationupdated: 8.1.0,8.1.2
POJO Buildersupdated: 9.0.0
HTTP Part Serializers and Parsersupdated: 8.2.0,9.0.0
Context Settingsupdated: 8.1.3,9.0.0
Context Annotationscreated: 8.1.0, updated: 8.1.3,8.2.0,9.0.0
JsonMap and JsonListupdated: 8.2.0
Complex Data Typescreated: 9.0.0
SerializerSets and ParserSetsupdated: 9.0.0
Swapsupdated: 9.0.0
Auto-detected swapscreated: 8.1.0
Per-media-type Swapsupdated: 8.1.0,8.2.0
@Swap Annotationupdated: 8.0.0,9.0.0
Dynamically Applied Annotationscreated: 8.1.3, updated: 9.0.0
Bean Names and Dictionariesupdated: 9.0.0
Virtual Beansupdated: 9.0.0
Non-Tree Models and Recursion Detectionupdated: 9.0.0
Parsing into Generic Modelsupdated: 8.2.0
Reading Continuous Streamsupdated: 9.0.0
URIsupdated: 9.0.0
Comparison with Jacksonupdated: 9.0.0
-
SVL Variablesupdated: 8.0.0,8.1.0
VarResolvers and VarResolverSessionsupdated: 9.0.0
VarResolver.DEFAULTcreated: 8.1.0
Encoderscreated: 9.0.0
Object Toolscreated: 9.0.0
-
JSON Serializersupdated: 9.0.0
JSON 5updated: 9.0.0
JSON Parsersupdated: 9.0.0
-
XML Serializersupdated: 9.0.0
XML Parsersupdated: 9.0.0
-
HTML Serializersupdated: 9.0.0
HTML Parsersupdated: 9.0.0
HtmlDocSerializerupdated: 9.0.0
-
UON Serializersupdated: 9.0.0
UON Parsersupdated: 9.0.0
-
URL-Encoding Serializersupdated: 9.0.0
URL-Encoding Parsersupdated: 9.0.0
-
MessagePack Serializersupdated: 9.0.0
MessagePack Parsersupdated: 9.0.0
OpenAPI Detailsupdated: 8.2.0
OpenAPI Methodologyupdated: 8.2.0
OpenAPI Serializersupdated: 8.2.0,9.0.0
OpenAPI Parsersupdated: 8.2.0,9.0.0
-
Overviewupdated: 9.0.0
Reading Entriesupdated: 9.0.0
POJOsupdated: 9.0.0
Java Collection Framework Objectsupdated: 9.0.0
Binary Dataupdated: 9.0.0
Variablesupdated: 9.0.0
Modded/Encoded Entriesupdated: 9.0.0
Sectionsupdated: 9.0.0
Serializingupdated: 9.0.0
Importsupdated: 8.1.0
Config Storesupdated: 9.0.0
FileStoreupdated: 9.0.0
Custom ConfigStoresupdated: 9.0.0
Read-only Configsupdated: 9.0.0
System Default Configcreated: 8.0.0, updated: 8.1.0
juneau-assertionscreated: 9.0.0
Overviewcreated: 9.0.0
juneau-rest-commoncreated: 9.0.0
Helper Classescreated: 9.0.0
Annotationscreated: 9.0.0
HTTP Headerscreated: 9.0.0
HTTP Partscreated: 9.0.0
HTTP Entities and Resourcescreated: 9.0.0
HTTP Responsescreated: 9.0.0
Remote Proxy Interfacescreated: 9.0.0
juneau-rest-serverupdated: 9.0.0
Overviewcreated: 9.0.0
@Rest-Annotated Classesupdated: 8.1.2,9.0.0
Predefined Classesupdated: 9.0.0
Child Resourcesupdated: 9.0.0
Path Variablesupdated: 9.0.0
Deploymentupdated: 9.0.0
Lifecycle Hooksupdated: 9.0.0
@RestOp-Annotated Methodsupdated: 9.0.0
Inferred HTTP Methods and Pathsupdated: 9.0.0
Java Method Parametersupdated: 9.0.0
Java Method Return Typesupdated: 9.0.0
Java Method Throwable Typesupdated: 9.0.0
Path Patternsupdated: 9.0.0
Matchersupdated: 9.0.0
Additional Informationupdated: 9.0.0
HTTP Partsupdated: 9.0.0
Part Marshallersupdated: 8.1.0,9.0.0
HTTP Part Annotationsupdated: 8.1.0,9.0.0
Default Partscreated: 9.0.0
@Request Beansupdated: 8.1.0,9.0.0
@Response Beansupdated: 8.1.0,9.0.0
HTTP Part APIscreated: 9.0.0
Marshallingupdated: 9.0.0
Form Postsupdated: 9.0.0
Guardsupdated: 9.0.0
Convertersupdated: 9.0.0
Localized Messagesupdated: 8.2.0,9.0.0
Encodersupdated: 9.0.0
Configuration Filesupdated: 9.0.0
SVL Variablesupdated: 9.0.0
Static filesupdated: 9.0.0
Client Versioningupdated: 9.0.0
-
BasicRestServlet/BasicRestObjectupdated: 8.1.0,9.0.0
Basic Swagger Infoupdated: 9.0.0
Parametersupdated: 9.0.0
Modelsupdated: 9.0.0
REST method execution statisticscreated: 8.1.3, updated: 9.0.0
@HtmlDocConfigupdated: 8.1.0,9.0.0
Widgetsupdated: 9.0.0
Predefined Widgetsupdated: 9.0.0
UI Customizationupdated: 9.0.0
Stylesheetsupdated: 8.1.0,9.0.0
Logging / Debuggingcreated: 9.0.0
HTTP Status Codesupdated: 9.0.0
Built-in Parametersupdated: 9.0.0
RestContextcreated: 9.0.0
RestOpContextcreated: 9.0.0
Response Processorscreated: 9.0.0
REST/RPCupdated: 8.0.0,9.0.0
Serializing URIsupdated: 9.0.0
Utility Beanscreated: 9.0.0
Using with HTML Beanscreated: 9.0.0
juneau-rest-server-springbootcreated: 8.0.0, updated: 9.0.0
Overviewcreated: 8.0.0, updated: 9.0.0
juneau-rest-clientupdated: 9.0.0
POJO Marshallingcreated: 8.2.0, updated: 9.0.0
Request Partscreated: 8.2.0, updated: 9.0.0
Request Contentcreated: 8.2.0, updated: 9.0.0
Response Statuscreated: 8.1.0, updated: 9.0.0
Response Headerscreated: 8.2.0, updated: 9.0.0
Response Contentcreated: 8.2.0, updated: 9.0.0
Custom Call Handlerscreated: 8.2.0, updated: 9.0.0
Interceptorscreated: 8.2.0
REST Proxiescreated: 8.2.0, updated: 9.0.0
Logging and Debuggingcreated: 8.2.0, updated: 9.0.0
Customizing HttpClientcreated: 8.2.0, updated: 9.0.0
Extending RestClientcreated: 8.2.0
Authenticationupdated: 8.2.0
juneau-rest-mockcreated: 8.1.0, updated: 8.2.0
MockRestClientcreated: 8.2.0, updated: 9.0.0
juneau-microservice-corecreated: 8.1.0
Microservice Overviewcreated: 8.0.0
Lifecycle Methodscreated: 8.0.0
Argscreated: 8.0.0
Manifestcreated: 8.0.0
Configcreated: 8.0.0
System propertiescreated: 8.0.0
VarResolvercreated: 8.0.0
Console Commandscreated: 8.0.0
Listenerscreated: 8.0.0
juneau-microservice-jettycreated: 8.1.0
Overviewcreated: 8.0.0
Lifecycle Methodscreated: 8.0.0
Resource Classescreated: 8.0.0
Predefined Resource Classescreated: 8.0.0
Configcreated: 8.0.0
Jetty.xml filecreated: 8.0.0
UI Customizationcreated: 8.0.0
Extending JettyMicroservicecreated: 8.0.0
my-jetty-microservicecreated: 8.1.0
Installing in Eclipsecreated: 8.0.0
Running in Eclipsecreated: 8.0.0
Building and Running from Command-Linecreated: 8.0.0
my-springboot-microservicecreated: 8.0.0
Installing in Eclipsecreated: 8.0.0
Running in Eclipsecreated: 8.0.0
Building and Running from Command-Linecreated: 8.0.0
juneau-petstorecreated: 8.2.0, updated: 9.0.0
Running the Pet Store Appcreated: 9.0.0
juneau-petstore-apicreated: 9.0.0
juneau-petstore-clientcreated: 9.0.0
juneau-petstore-servercreated: 9.0.0
-
juneau-marshallcreated: 8.2.0
juneau-svlcreated: 8.2.0
juneau-rest-servercreated: 8.2.0
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:
Category | Maven Artifacts | Description | Prereqs |
---|---|---|---|
juneau-core | juneau-marshall |
|
|
juneau-marshall-rdf |
|
|
|
juneau-dto |
|
|
|
juneau-config |
|
|
|
juneau-assertions |
|
|
|
juneau-rest | juneau-rest-common |
|
|
juneau-rest-server |
|
|
|
juneau-rest-server-springboot |
|
|
|
juneau-rest-client |
|
|
|
juneau-rest-mock |
|
|
|
my-springboot-microservice |
|
|
|
juneau-examples | juneau-examples-core |
|
|
juneau-examples-rest |
|
||
juneau-all |
Combination of the following:
|
|
The current version of Juneau is
If you would like to work with the bleeding-edge code, you can access the
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:
Parsing back into POJOs is equally simple for any of the supported languages. Language fragments are also supported.
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
- Pojo Categories for an exhaustive list of categories supported out-of-the-box.
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.
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.
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:
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.
- Java Beans Support for more information.
Configuration Annotations
Serializers and parsers can also be configured using annotations.
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.
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:
- Context Annotations for more information.
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:
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
- JSON 5 for more information.
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=
- UON Details for more information.
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.
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.
- OpenAPI Details for more information.
JsonMap/JsonList
The JsonMap
and JsonList
collections classes allow you
to programmatically build generic JSON data structures. They are similar in concept to
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...
- JsonMap and JsonList for more information.
Serializer and Parser Sets
SerializerSet
and ParserSet
classes allow serializers and parsers
to be retrieved by W3C-compliant HTTP Accept
and Content-Type
values:
- SerializerSets and ParserSets for more information.
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
Variables can be recursively nested within the varKey (e.g.
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.
- Simple Variable Language for more information.
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:
Then define a Java interface that can be provided to consumers of your API to access your REST API:
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:
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.
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 defaultAccept header that's asking for HTML.
- In this case, it's the
- 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:
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
The advantage of using child resources is that they do not need to be declared in the JEE
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:
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
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:
Java methods on @Rest
-annotated classes have the following format:
The various parts require their own topics to fully appreciate the scope of abilities but the following is a summary:
- Annotated with
@RestOp
.- Also available:
@RestGet
/@RestPut
/@RestPost
/@RestDelete
. - Annotation optional if using standard naming conventions (e.g.
getFoo() equivalent to ).@RestGet (path="/foo" )
- Also available:
- Optionally annotated with config annotations such as
@BeanConfig
and@HtmlDocConfig
that customize the behavior of serializers and parsers at the method level. - Returned object gets serialized as the HTTP response body.
- Typically a POJO serialized based on
Accept
request header. - Support for raw values such as
Readers andInputStreams (among others). - Support for response beans annotated with
@Response
. - Support for Apache Http Core interfaces:
HttpEntity
/HttpResponse
/HttpResource
. - Standard HTTP responses such as
Ok
andTemporaryRedirect
provided inorg.apache.juneau.http.response
package. - Extensible API for defining custom return types.
- Typically a POJO serialized based on
- A wide range of possible argument types including:
- Standard
HttpServletRequest
/HttpServletResponse
objects. - Extended
RestRequest
/RestResponse
objects. - Parsed HTTP parts with either the arguments or beans annotated with
@Path
/@Header
/@Query
/@FormData
. - Parsed HTTP body with either the argument or bean annotated with
@Content
. - Raw HTTP request body with
InputStream orReader . - Raw HTTP response body with
OutputStream orWriter . - Request beans annotated with
@Request
. - Response beans annotated with
@Response
. - Standard HTTP headers such as
Accept
andContentType
provided inorg.apache.juneau.http.header
package. - Auto-generated
Swagger
. - Various other standard objects such as
Principal
,Cookie
,HttpSession
, andResourceBundle
. - Spring beans or other injectable beans.
- Extensible API for defining custom argument types.
- Standard
- Throwables can be anything.
- Typically one of the standard HTTP responses such as
BadRequest
orNotFound
provided inorg.apache.juneau.http.response
package. - Can define your own
@Response
-annotated throwables. - Anything else gets converted to an
InternalServerError
.
- Typically one of the standard HTTP responses such as
Deploying as a Servlet
The BasicRestServlet
class is the entry point for your REST resources.
It extends directly from
When the servlet 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 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 BasicSpringRestServlet
and BasicSpringRestServletGroup
that have the same capabilites as the BasicRestServlet
and BasicRestServletGroup
counterparts.
Example configuration file:
Rest.children()
annotation.
*/
Additional Information
- juneau-rest-server for more 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:
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:
Additional Information
- juneau-rest-client for more 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:
- HTML5 for more information.
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:
- Atom for more information.
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:
- Swagger for more information.
SwaggerUI
The SwaggerUI
class is a DTO class for generating Swagger user interfaces
from Swagger
beans.
The
Using SwaggerUI
, we're able to render that JSON as a Swagger user interface
when the request is asking for HTML:
- Swagger UI for more information.
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:
Config files are accessed through the Config
class which
are created through the Config.Builder
class.
Builder creator methods are provided on the
The config language may look simple but it is a very powerful feature with many capabilities.
- juneau-config for more information.
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:
The Assertions APIs are used throughout the REST client and server APIs for performing inline assertions on REST requests and responses.
Example:
- Fluent Assertions for more information.
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:
Typically developers will not deal with session objects and will just use convenience methods on the context classes themselves that handle creation of sessions:
Most context objects also have static default instances that can be used in leu of creating new contexts as well:
Most context classes also have the ability to clone and modify existing context objects:
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
Java Library
juneau-marshall-
OSGi Module
org.apache.juneau.marshall_
The
- 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:
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:
Json
String of(Object)
T to(Object,Class<T>)
Examples:
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:
In addition to the default serializers, customized serializers can be created using various built-in options:
Default serialization support is provided for Java primitives,
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:
The parsers can also be used to populating existing bean and collection objects:
ImageSerializer
- Example of a custom serializer.ImageParser
- Example of a custom parser.
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:
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.
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.
- POJO Categories - Marshalling rules for POJOs.
- Bean Dictionaries - Handling properties with subclassable types.
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:
- A way to detect and instantiate a builder using reflection.
- 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:
HttpPartSerializer
SimplePartSerializer
- Serializes directly to strings.
UonSerializer
- Serializes to UON notation.
OpenApiSerializer
- Serializes using Open-API schema rules.
HttpPartParser
SimplePartParser
- Parses directly from strings.
UonParser
- Parses from UON notation.
OpenApiParser
- Parses using Open-API schema rules.
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:
BeanConfig
CsvConfig
HtmlConfig
HtmlDocConfig
JsonConfig
JsonSchemaConfig
MsgPackConfig
OpenApiConfig
ParserConfig
PlainTextConfig
SerializerConfig
SoapXmlConfig
UonConfig
UrlEncodingConfig
XmlConfig
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:
-
Using the provided
JsonMap.writeTo(java.io.Writer)
or
JsonList.writeTo(java.io.Writer)
methods.
-
Passing them to one of the
Serializer
serialize methods.
-
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:
Enumeration
Iterator
Locale
Class
Calendar
Date
Instant
ZonedDateTime
LocalDate
LocalDateTime
LocalTime
OffsetDateTime
OffsetTime
Year
YearMonth
Temporal
TimeZone
XMLGregorianCalendar
ZoneId
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:
fromString(String) - UUID
valueOf(String) - Boolean
, Byte
,
Double
, Float
,
Integer
, Long
, Short
, Date
,
Time
, Timestamp
parse(String) - DateFormat
, MessageFormat
,
NumberFormat
, Date
, Level
parseString(String) - DatatypeConverter
forName(String) - Class
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:
Bean
Beanc
BeanIgnore
Beanp
Example
NameProperty
ParentProperty
Swap
Uri
Csv
Html
Json
Schema
MsgPack
OpenApi
PlainText
SoapXml
Uon
UrlEncoding
Xml
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:
Serializer.Builder.uriContext(UriContext)
Setting that defines the URI contextual information used to resolve relative URIs.
Serializer.Builder.uriRelativity(UriRelativity)
Setting that defines how relative URIs should be interpreted.
Possible values:
UriRelativity.RESOURCE
Relative URIs should be considered relative to the servlet URI.
(e.g. "http://host:port/context-root/servlet-path" ).
UriRelativity.PATH_INFO
Relative URIs should be considered relative to the request URI.
(e.g. "http://host:port/context-root/servlet-path/path-info" ).
Serializer.Builder.uriResolution(UriResolution)
Setting that defines the final format of serialized URIs.
Possible values:
UriResolution.ABSOLUTE
Resolve to an absolute URL.
(e.g. "http://host:port/context-root/servlet-path/path-info" ).
UriResolution.ROOT_RELATIVE
Resolve to a root-relative URL.
(e.g. "/context-root/servlet-path/path-info" ).
UriResolution.NONE
Don't do any URL resolution.
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
Jackson Juneau
@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
Group Description Examples Can
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:
TimeZone
- Uses TimeZone.getID()
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:
TimeZone
- Uses TimeZone.getTimeZone(String)
Locale
- Uses Locale.forLanguageTag(String)
after replacing '_' with '-' .
Boolean
- Blank and "null" are interpreted as null values.
- Primitives (except for
void .class ) - Uses the primitive wrapper classes for instantiating from Strings.
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:
SystemPropertiesVar
- $S{key[,default]}
EnvVariablesVar
- $E{key[,default]}
The following logic variables are also provided:
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,groupIndex}
NotEmptyVar
- $NE{arg}
UpperCaseVar
- $UC{arg}
LowerCaseVar
- $LC{arg}
LenVar
- $LN{arg[,delimiter]}
SubstringVar
- $ST{arg,start[,end]}
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:
Module Class Pattern
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:
VarResolver
resolve(String)
- Resolves variables and returns the results as a simple string.
resolveTo(String,Writer)
- Resolves variables and sends results to a writer.
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:
VarResolver.Builder
bean(Class<T>, T)
- Specify a bean for all sessions.
VarResolverSession
bean(Class<T>, T)
- Specify a bean for this session.
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:
$S{key[,default]} - SystemPropertiesVar
$E{key[,default]} - EnvVariablesVar
$A{key[,default]} - ArgsVar
$MF{key[,default]} - ManifestFileVar
$SW{stringArg,pattern:thenValue[,pattern:thenValue...]} - SwitchVar
$IF{arg,then[,else]} - IfVar
$CO{arg[,arg2...]} - CoalesceVar
$PM{arg,pattern} - PatternMatchVar
$PR{stringArg,pattern,replace} - PatternReplaceVar
$PE{arg,pattern,groupIndex} - PatternExtractVar
$UC{arg} - UpperCaseVar
$LC{arg} - LowerCaseVar
$NE{arg} - NotEmptyVar
$LN{arg[,delimiter]} - LenVar
$ST{arg,start[,end]} - SubstringVar
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:
- To override the element name.
- 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_
	
 _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_
	
 _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 <b>XML</b> 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 type JSON equivalent UON
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 type JSON equivalent UON
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:
OpenApiSerializer
- Converts POJOs to strings.
OpenApiParser
- Converts strings to POJOs.
HttpPartSchema
- Defines the schema for your POJO.
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 :
Type Format Intermediate 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:
Type Format Valid 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
- Any Serializable POJO type.
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
integer
int32
int64
number
float
double
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
- Any Serializable POJO type.
object
empty
Map<String,Object> (default)
- Beans with properties of anything on this list.
- Any POJO transformable to a map via an
ObjectSwap
uon
- Any Serializable POJO type.
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:
Type Format Valid parameter types
string or empty
byte
binary
binary-spaced
byte [] (default)
InputStream
- Returns a ByteArrayInputStream
.
Reader
- Returns a InputStreamReader
wrapped around a ByteArrayInputStream
.
String
- Constructed using String(byte[])
.
Object
- Returns the default byte [] .
- Any POJO transformable from a
byte [] (via constructors or static create methods).
date
date-time
Calendar
(default)
Date
GregorianCalendar
String
- Converted using Calendar.toString()
.
Object
- Returns the default Calendar
.
- Any POJO transformable from a
Calendar
(via constructors or static create methods).
uon
- Any Parsable POJO type.
empty
boolean
empty
integer
int32
int64
number
float
double
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
- Any Parsable POJO type.
object
empty
Map<String,Object> (default)
- Beans with properties of anything on this list.
- Maps with string keys.
uon
- Any Parsable POJO type.
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
-
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.
-
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.
-
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.
-
The RDF parsers are SLOW.
RDF simply isn't efficient with node traversal, so creating tree structures out of RDF models
is highly inefficient.
-
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.
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.
Bean properties can be excluded using the @Bean(excludeProperties|xp)
annotation.
Bean properties can be sorted alphabetically using @Bean(sort)
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.
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.
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
The @Bean(interceptor)
annotation
and BeanInterceptor
class can be used to perform interception
and inline handling of bean getter and setter calls.
The @Bean(on)
and @Bean(onClass)
annotations can be used to programmatically attach
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:
- A way to detect and instantiate a builder using reflection.
- 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.
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.
The @Name
annotation is a shortcut for specifying a bean property name:
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.
The bean property named
The following shows various ways of using dynamic bean properties.
Similar rules apply for value types and swaps. The property values optionally can be any serializable type or use swaps.
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:
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.
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.
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.
The @Beanp(format)
annotation specifies a String format for converting a bean property value to a formatted string.
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:
Beans can also be defined with a combination of read-only and read-write properties.
The @Name
annotation can also be used instead of
If neither
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:
- A way to detect and instantiate a builder using reflection.
- 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.
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.
When applied to fields and getters/setters, they will be ignored as bean properties.
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.
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:
- A way to detect and instantiate a builder using reflection.
- 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.
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.
Parsers will automatically set this field for you in the child beans.
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
The typical code for such a builder using a static inner class is shown below:
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:
- A way to detect and instantiate a builder using reflection.
- 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 Buildercreate () {...} - 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(Builderbuilder ) {...} - 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(Builderbuilder ) {...}
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.
Juneau serializers treat instances of
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:
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.
The HttpPartSchema
class also provides convenience static methods for creation of custom schemas.
The equivalent to the schema above can be structured like so:
The class hierarchy for the part marshallers are:
HttpPartSerializer
SimplePartSerializer
- Serializes directly to strings.UonSerializer
- Serializes to UON notation.OpenApiSerializer
- Serializes using Open-API schema rules.
HttpPartParser
SimplePartParser
- Parses directly from strings.UonParser
- Parses from UON notation.OpenApiParser
- Parses using Open-API schema rules.
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
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:
For performance reasons, serializers and parsers are immutable.
However, they can be 'copied' and modified using the
Default values for configurable settings can be set globally using either system properties or environment variables.
For example, the default
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
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:
Config annotations are provided for all serializers and parsers:
BeanConfig
CsvConfig
HtmlConfig
HtmlDocConfig
JsonConfig
JsonSchemaConfig
MsgPackConfig
OpenApiConfig
ParserConfig
PlainTextConfig
SerializerConfig
SoapXmlConfig
UonConfig
UrlEncodingConfig
XmlConfig
Annotations normally applied to bean classes/methods/fields/parameters
can also be programmatically attatched to beans by using the
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:
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
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
These object can be serialized in one of two ways:
-
Using the provided
JsonMap.writeTo(java.io.Writer)
orJsonList.writeTo(java.io.Writer)
methods. -
Passing them to one of the
Serializer
serialize methods. -
Simply calling the
JsonMap.asJson()
/JsonMap.toString()
orJsonList.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)
The
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
For data types consisting of nested collections an maps such as
String
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.
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
The REST servlet API builds upon the
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
In the following example, we introduce a
The swap can then be associated with serializers and parsers like so:
Another example of a
The following example shows the BASE64 swap in use:
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.
2.11.1 - Default Swaps
By default, all serializers and parsers have built in ObjectSwaps defined for the following common data types:
Enumeration
Iterator
Locale
Class
Calendar
Date
Instant
ZonedDateTime
LocalDate
LocalDateTime
LocalTime
OffsetDateTime
OffsetTime
Year
YearMonth
Temporal
TimeZone
XMLGregorianCalendar
ZoneId
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:
fromString(String) - UUID
valueOf(String) - Boolean
, Byte
,
Double
, Float
,
Integer
, Long
, Short
, Date
,
Time
, Timestamp
parse(String) - DateFormat
, MessageFormat
,
NumberFormat
, Date
, Level
parseString(String) - DatatypeConverter
forName(String) - Class
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.
By default, all serializers and parsers have built in
Enumeration
Iterator
Locale
Class
Calendar
Date
Instant
ZonedDateTime
LocalDate
LocalDateTime
LocalTime
OffsetDateTime
OffsetTime
Year
YearMonth
Temporal
TimeZone
XMLGregorianCalendar
ZoneId
Various other swaps are provided in the org.apache.juneau.swaps
package.
Various methods can be defined on a class directly to affect how it gets serialized.
This can often be simpler than using
Objects serialized as
-
method.public static T fromString(String)
Any of the following method names also work:valueOf(String) parse(String) parseString(String) forName(String) forString(String)
-
constructor.public T(String)
Note that these methods cover conversion from several built-in Java types, meaning the parsers can automatically construct these objects from strings:
fromString(String) -UUID
valueOf(String) -Boolean
,Byte
,Double
,Float
,Integer
,Long
,Short
,Date
,Time
,Timestamp
parse(String) -DateFormat
,MessageFormat
,NumberFormat
,Date
,Level
parseString(String) -DatatypeConverter
forName(String) -Class
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
Serializing to other intermediate objects can be accomplished by defining a swap method directly on the class:
method, wherepublic X swap()X is any serializable object. method, wherepublic X swap(BeanSession)X is any serializable object. method, wherepublic static MyPojo unswap(X)X is any serializable object. method, wherepublic static MyPojo swap(X,BeanSession)X is any serializable object.
Serializing to and from Maps can be accomplished by defining any of the following methods:
method.public Map toMap()
Can be any type of map with string keys and object vals. method.public JsonMap toMap() method.public Map toMap(BeanSession)
Can be any type of map with string keys and object vals. method.public JsonMap toMap(BeanSession) method.public static MyPojo fromMap(Map)
Can be any type of map with string keys and object vals. method.public static MyPojo fromMap(JsonMap) method.public static MyPojo fromMap(Map,BeanSession)
Can be any type of map with string keys and object vals. method.public static MyPojo fromMap(JsonMap,BeanSession)
The BeanSession.getMediaType()
).
The following example shows how an HTML5 form template object can be created that gets serialized as a
populated HTML5 Form
bean.
Swapped objects can be converted back into their original form by the parsers by specifying one of the following methods:
method wherepublic static T unswap(BeanSession, X)X is the swap class type. constructor wherepublic T(X)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:
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.
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.
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:
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.
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.
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.
@Swap
can be used to associate a swap class using an
annotation.
This is often cleaner than using the builder
Multiple swaps can be associated with a class using multiple @Swap
annotations:
The
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:
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:
The implementation of the FreeMarker swap would look something like this:
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.
Surrogate classes are very similar in concept to
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):
This could be solved with the following
However, the same can be accomplished by using a surrogate class that simply contains a constructor with the non-serializable class as an argument:
The surrogate class is registered in the same way as a
When the serializer encounters the non-serializable class, it will serialize an instance of the surrogate instead.
In the section Swaps, you were introduced to annotations that can be applied to bean
classes, methods, fields, and constructors such as @Bean
:
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.
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
Any Juneau annotation that has an
Bean
Beanc
BeanIgnore
Beanp
Example
NameProperty
ParentProperty
Swap
Uri
Csv
Html
Json
Schema
MsgPack
OpenApi
PlainText
SoapXml
Uon
UrlEncoding
Xml
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"
- Fully qualified:
- 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"
- Fully qualified with args:
- Fields:
- Fully qualified:
"com.foo.MyClass.myField"
- Simple:
"MyClass.myField"
- Simple inner class:
"MyClass$Inner1$Inner2.myField" "Inner1$Inner2.myField" "Inner2.myField"
- Fully qualified:
- 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()"
- Fully qualified with args:
- A comma-delimited list of anything on this list.
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
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
When serialized as JSON,
{
x: [
{_type:
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
When serialized as XML, the bean is rendered as:
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.
The
When using the annotation, you'll typically want to define it on an interface class so that it can be inherited by all subclasses.
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);
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:
When serialized, the subtype is serialized as a virtual
A1
The following shows what happens when parsing back into the original object.
A
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:
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
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 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:
Now we create a model with a loop and serialize the results.
What we end up with is the following, which does not serialize the contents of the
{
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.
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
You can parse into any 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. JsonList
is recommended.
When the map or list type is not specified, or is the abstract
For example, given the following JSON:
{
id:
We can parse this into a generic
What we end up with is the exact same output.
Even the numbers and booleans are preserved because they are parsed into
{
id:
Once parsed into a generic model, various convenience methods are provided on the
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.
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:
Note that this isn't perfect in all cases since you can't combine two JSON numbers into a single
reader (e.g.
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.
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.
URI resolution is controlled by the following settings:
Serializer.Builder.uriContext(UriContext)
Setting that defines the URI contextual information used to resolve relative URIs.Serializer.Builder.uriRelativity(UriRelativity)
Setting that defines how relative URIs should be interpreted.
Possible values:UriRelativity.RESOURCE
Relative URIs should be considered relative to the servlet URI.
(e.g."http://host:port/context-root/servlet-path" ).UriRelativity.PATH_INFO
Relative URIs should be considered relative to the request URI.
(e.g."http://host:port/context-root/servlet-path/path-info" ).
Serializer.Builder.uriResolution(UriResolution)
Setting that defines the final format of serialized URIs.
Possible values:UriResolution.ABSOLUTE
Resolve to an absolute URL.
(e.g."http://host:port/context-root/servlet-path/path-info" ).UriResolution.ROOT_RELATIVE
Resolve to a root-relative URL.
(e.g."/context-root/servlet-path/path-info" ).UriResolution.NONE
Don't do any URL resolution.
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:
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
Jackson | Juneau |
---|---|
|
@Beanp
|
|
@Beanp(name="*")
|
|
@BeanIgnore
|
@Bean(excludeProperties|xp)
|
|
No equivalent annotation but can be controlled via:
BeanContext.Builder.beanFieldVisibility(Visibility)
BeanContext.Builder.beanMethodVisibility(Visibility)
|
|
|
@Beanc
|
No equivalent. | |
|
Juneau uses swaps to convert non-serializable object to serializable forms:
@Swap
|
No equivalent annotation but can be controlled via various settings:
BeanContext
Serializer
|
|
@Bean(properties="...")
@Bean(sort=x)
|
|
|
Can be replicated using swaps with |
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
Group | Description | Examples | Can serialize? | Can parse? |
---|---|---|---|---|
1 | Java primitives and primitive objects |
|
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. |
|
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. |
|
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 .
|
|
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 .
|
|
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. |
|
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:
|
|
yes | yes |
6b |
Classes that only have a method to convert to a serializable form:
|
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 |
detectRecursions
to look for and handle these kinds of loops
(by setting these references to 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
TimeZone
- UsesTimeZone.getID()
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:
TimeZone
- UsesTimeZone.getTimeZone(String)
Locale
- UsesLocale.forLanguageTag(String)
after replacing'_' with'-' .Boolean
- Blank and"null" are interpreted as null values.- Primitives (except for
) - Uses the primitive wrapper classes for instantiating from Strings.void .class
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
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.
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
Most variables can be recursively nested within the varKey (e.g.
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:
SystemPropertiesVar
-$S{key[,default]} EnvVariablesVar
-$E{key[,default]}
The following logic variables are also provided:
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,groupIndex} NotEmptyVar
-$NE{arg} UpperCaseVar
-$UC{arg} LowerCaseVar
-$LC{arg} LenVar
-$LN{arg[,delimiter]} SubstringVar
-$ST{arg,start[,end]}
Example:
The following shows how variables can be arbitrarily nested...
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:
Module Class Pattern
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:
VarResolver
resolve(String)
- Resolves variables and returns the results as a simple string.
resolveTo(String,Writer)
- Resolves variables and sends results to a writer.
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:
VarResolver.Builder
bean(Class<T>, T)
- Specify a bean for all sessions.
VarResolverSession
bean(Class<T>, T)
- Specify a bean for this session.
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:
$S{key[,default]} - SystemPropertiesVar
$E{key[,default]} - EnvVariablesVar
$A{key[,default]} - ArgsVar
$MF{key[,default]} - ManifestFileVar
$SW{stringArg,pattern:thenValue[,pattern:thenValue...]} - SwitchVar
$IF{arg,then[,else]} - IfVar
$CO{arg[,arg2...]} - CoalesceVar
$PM{arg,pattern} - PatternMatchVar
$PR{stringArg,pattern,replace} - PatternReplaceVar
$PE{arg,pattern,groupIndex} - PatternExtractVar
$UC{arg} - UpperCaseVar
$LC{arg} - LowerCaseVar
$NE{arg} - NotEmptyVar
$LN{arg[,delimiter]} - LenVar
$ST{arg,start[,end]} - SubstringVar
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.
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.
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:
Module | Class | Pattern |
---|---|---|
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} |
The main class for performing variable resolution is VarResolver
.
Two methods are provided for resolving variables:
VarResolver
resolve(String)
- Resolves variables and returns the results as a simple string.resolveTo(String,Writer)
- Resolves variables and sends results to a writer.
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:
VarResolver.Builder
bean(Class<T>, T)
- Specify a bean for all sessions.
VarResolverSession
bean(Class<T>, T)
- Specify a bean for this session.
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:
2.21.3 - VarResolver.DEFAULTcreated: 8.1.0
VarResolver.DEFAULT
is a reusable variable resolver with default support for the following variables:
$S{key[,default]} - SystemPropertiesVar
$E{key[,default]} - EnvVariablesVar
$A{key[,default]} - ArgsVar
$MF{key[,default]} - ManifestFileVar
$SW{stringArg,pattern:thenValue[,pattern:thenValue...]} - SwitchVar
$IF{arg,then[,else]} - IfVar
$CO{arg[,arg2...]} - CoalesceVar
$PM{arg,pattern} - PatternMatchVar
$PR{stringArg,pattern,replace} - PatternReplaceVar
$PE{arg,pattern,groupIndex} - PatternExtractVar
$UC{arg} - UpperCaseVar
$LC{arg} - LowerCaseVar
$NE{arg} - NotEmptyVar
$LN{arg[,delimiter]} - LenVar
$ST{arg,start[,end]} - SubstringVar
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.
VarResolver.DEFAULT
is a reusable variable resolver with default support for the following variables:
$S{key[,default]} -SystemPropertiesVar
$E{key[,default]} -EnvVariablesVar
$A{key[,default]} -ArgsVar
$MF{key[,default]} -ManifestFileVar
$SW{stringArg,pattern:thenValue[,pattern:thenValue...]} -SwitchVar
$IF{arg,then[,else]} -IfVar
$CO{arg[,arg2...]} -CoalesceVar
$PM{arg,pattern} -PatternMatchVar
$PR{stringArg,pattern,replace} -PatternReplaceVar
$PE{arg,pattern,groupIndex} -PatternExtractVar
$UC{arg} -UpperCaseVar
$LC{arg} -LowerCaseVar
$NE{arg} -NotEmptyVar
$LN{arg[,delimiter]} -LenVar
$ST{arg,start[,end]} -SubstringVar
-
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( ) will simply be a no-op and return the same string."foobar" )
The org.apache.juneau.encoders
package defines an API for handling encoding-based matching
of
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
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.
will result in the order
Example:
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
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:
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:
- 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[]
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[]
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
- 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[]
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[]
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
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:
The getters will be called in order until the first non-null value is returned:
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
Sample Code
Person
Normal JSON
{
Simplified JSON
{
name:
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'
}
}
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 | ||
Number | Number | ||
Boolean | Boolean | ||
Null | Null | ||
Beans with properties of any type on this list | Object | ||
Maps with values of any type on this list | Object | ||
Collections and arrays of any type on this list | Array |
In addition, swaps can be used to convert non-serializable POJOs into serializable forms, such as converting
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'
}
}
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:
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
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'
}
}
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:
The following shows the JSON representation with and without the annotation present:
Without annotation | With annotation |
---|---|
{
|
{
|
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.getSchemaSerializer()
method has been
added for creating instances of schema serializers from the regular serializer instance.
Sample Beans
The code for creating our POJO model and generating JSON-Schema is shown below:
JSON Schema
{
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
Sample Code
Person
Normal XML:
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:
- To override the element name.
- 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_
	
 _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_
	
 _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 <b>XML</b> 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();
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 | ||
boolean | ||
integer | 123 | |
float | 1.23 | |
null |
Maps
Loose maps and beans use the element
Data type | JSON example | XML |
---|---|---|
Map<String,String> |
{
k1: |
|
Map<String,Number> |
{
k1: 123,
k2: 1.23,
k3: |
|
Map<String,Object> |
{
k1: |
Arrays
Loose collections and arrays use the element
Data type | JSON example | XML |
---|---|---|
String[] |
[
|
|
Number[] |
[
123,
1.23,
|
|
Object[] |
[
|
|
String[][] |
[
[ |
|
|
[ 123 ] | |
|
[
|
|
List<String> |
[
|
|
List<Number> |
[
123,
1.23,
|
|
List<Object> |
[
|
Beans
Data type | JSON example | XML |
---|---|---|
|
{
|
Beans with Map properties
Data type | JSON example | XML |
---|---|---|
|
{
|
The XmlSerializer
class is used to serialize POJOs into XML.
The XmlDocSerializer
class is the same but serializes a
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:
- To override the element name.
- 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_
	
 _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_
	
 _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 <b>XML</b> 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();
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:
The @Bean(typeName)
annotation can be used to
override the Juneau default name on bean elements.
Types names serve two distinct purposes:
- To override the element name.
- 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 |
---|---|---|---|
|
{
a: |
On bean properties, a
In the following example, a type attribute is used on property 'b' but not property 'a' since
'b' is of type
Example
Java | Without annotation | With annotation |
---|---|---|
|
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
Examples
Java | XML |
---|---|
|
|
|
|
|
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 |
---|---|
|
|
|
|
|
|
|
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
Java | XML |
---|---|
|
|
|
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 <b>XML</b> 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();
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 |
---|---|---|---|
|
{
a: [ |
||
|
{ a: [123,456] } |
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 |
---|---|---|---|
|
{
a: |
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 |
---|---|---|---|
|
{
a: |
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 |
---|---|---|---|
|
{
a: |
The XmlFormat.ATTRS
format can be applied to a single bean
property of type 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 |
---|---|---|---|
|
{
|
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 |
---|---|---|---|
|
{
a: [ |
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 |
---|---|---|
|
{
a: |
|
|
{
a: |
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 |
---|---|---|---|
|
{
a: [
|
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,
The examples below show how whitespace is handled under various circumstances:
Data type | XML |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
It should be noted that when using
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 XmlFormat.TEXT_PWS
is the same except whitespace is
preserved in the output.
Data type | JSON example | Without annotations | With annotations |
---|---|---|---|
|
{
a: |
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
Data type | JSON example | With TEXT annotation | With XMLTEXT annotation |
---|---|---|---|
|
{
a: |
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();
Let's go back to the example of our original
Sample Beans
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
Sample Code
Person
Now when we run this code, we'll see namespaces added to our output:
Enabling the XmlSerializer.XML_addNamespaceUrisToRootsetting results
in the namespace URLs being added to the root node:
We can simplify the output by setting the default namespace on the serializer so that all the elements do not need to be prefixed:
This produces the following equivalent where the elements don't need prefixes since they're already in the default document namespace:
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.
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.
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 | ||
boolean | ||
integer | 123 | |
float | 1.23 | |
null |
Maps
Maps and beans are represented as tables.
The
Data type | JSON example | HTML |
---|---|---|
Map<String,String> |
{
k1: |
|
Map<String,Number> |
{
k1: 123,
k2: 1.23,
k3: |
|
Map<String,Object> |
{
k1: |
Arrays
Collections and arrays are represented as ordered lists.
Data type | JSON example | HTML |
---|---|---|
String[] |
[
|
|
Number[] |
[
123,
1.23,
|
|
Object[] |
[
|
|
String[][] |
[
[ |
|
|
[ 123 ] | |
|
[
|
Collections
Data type | JSON example | HTML |
---|---|---|
List<String> |
[
|
|
List<Number> |
[
123,
1.23,
|
|
List<Object> |
[
|
Beans
Data type | JSON example | HTML |
---|---|---|
|
{
|
Beans with Map properties
Data type | JSON example | HTML |
---|---|---|
|
{
|
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.
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:
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:
The @Html(anchorText)
annotation is used to specify the anchor text of a hyperlink.
Example:
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
Example:
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.
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
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:
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.
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:
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.
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
The code for creating our POJO model and generating HTML-Schema is shown below:
The result is the HTML table shown below:
type | object | ||||||||||||||||||||||||||||||||||||||||||
properties |
|
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
Sample Code
Person
UON
(
2.29.1 - UON Methodology
General methodology:
Java type JSON equivalent UON
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:
General methodology:
Java type | JSON equivalent | UON |
---|---|---|
Maps/beans | OBJECT |
|
Collections/arrays | ARRAY |
|
Booleans | BOOLEAN |
|
int/float/double/... | NUMBER |
|
null | NULL |
|
String | STRING |
|
Refer to the UON specification for a complete set of syntax rules.
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:
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:
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
Sample Code
Person
URL-Encoding
2.30.1 - URL-Encoding Methodology
General methodology:
Java type JSON equivalent UON
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.
General methodology:
Java type | JSON equivalent | UON |
---|---|---|
Maps/beans | OBJECT |
|
Collections/arrays | ARRAY |
|
Booleans | BOOLEAN |
|
int/float/double/... | NUMBER |
|
null | NULL |
|
String | STRING |
|
Refer to the UON specification for a complete set of syntax rules.
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.
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:
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.
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:
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:
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:
Juneau supports converting arbitrary POJOs to and from strings using OpenAPI-based schema rules.
The relevant classes for using OpenAPI-based serialization are:
OpenApiSerializer
- Converts POJOs to strings.OpenApiParser
- Converts strings to POJOs.HttpPartSchema
- Defines the schema for your POJO.
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 :
Type Format Intermediate 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:
Type Format Valid 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
- Any Serializable POJO type.
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
integer
int32
int64
number
float
double
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
- Any Serializable POJO type.
object
empty
Map<String,Object> (default)
- Beans with properties of anything on this list.
- Any POJO transformable to a map via an
ObjectSwap
uon
- Any Serializable POJO type.
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:
Type Format Valid parameter types
string or empty
byte
binary
binary-spaced
byte [] (default)
InputStream
- Returns a ByteArrayInputStream
.
Reader
- Returns a InputStreamReader
wrapped around a ByteArrayInputStream
.
String
- Constructed using String(byte[])
.
Object
- Returns the default byte [] .
- Any POJO transformable from a
byte [] (via constructors or static create methods).
date
date-time
Calendar
(default)
Date
GregorianCalendar
String
- Converted using Calendar.toString()
.
Object
- Returns the default Calendar
.
- Any POJO transformable from a
Calendar
(via constructors or static create methods).
uon
- Any Parsable POJO type.
empty
boolean
empty
integer
int32
int64
number
float
double
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
- Any Parsable POJO type.
object
empty
Map<String,Object> (default)
- Beans with properties of anything on this list.
- Maps with string keys.
uon
- Any Parsable POJO type.
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).
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
The following table shows the "natural" intermediate type of the object based on the
Type | Format | Intermediate Java Type |
---|---|---|
binary binary-spaced |
||
date-time |
Calendar |
|
No intermediate type. (serialized directly to/from POJO) |
||
empty | String |
|
empty | Boolean |
|
Integer |
||
Long |
||
Float |
||
Double |
||
empty | Arrays of intermediate types on this list. | |
No intermediate type. (serialized directly to/from POJO) |
||
empty | ||
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.
This example shows how that POJO can be converted to a BASE64-encoded string.
In addition to defining format, the schema also allows for validations of the serialized form.
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.
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.
Under-the-covers, this gets converted to the following schema object:
HttpPartSchema
Various convenience methods exist for shortening this code.
The following code shows how the schema above can be used to create our pipe+csv list of numbers:
As a general rule, any POJO convertible to the intermediate type for the
Type | Format | Valid parameter types |
---|---|---|
binary binary-spaced |
|
|
date-time |
|
|
|
||
empty |
|
|
empty | ||
empty |
|
|
|
||
empty |
|
|
|
For arrays, an example of "Any POJO transformable to arrays of the default types" is:
In the example above, our POJO class can be used to create our pipe-delimited list of comma-delimited numbers:
The
The following shows an example of a bean with several properties of various types.
We define the following schema:
Then we serialize our bean:
String
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:
For this bean, we define the following schema:
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:
Type Format Valid parameter types
string or empty
byte
binary
binary-spaced
byte [] (default)
InputStream
- Returns a ByteArrayInputStream
.
Reader
- Returns a InputStreamReader
wrapped around a ByteArrayInputStream
.
String
- Constructed using String(byte[])
.
Object
- Returns the default byte [] .
- Any POJO transformable from a
byte [] (via constructors or static create methods).
date
date-time
Calendar
(default)
Date
GregorianCalendar
String
- Converted using Calendar.toString()
.
Object
- Returns the default Calendar
.
- Any POJO transformable from a
Calendar
(via constructors or static create methods).
uon
- Any Parsable POJO type.
empty
boolean
empty
integer
int32
int64
number
float
double
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
- Any Parsable POJO type.
object
empty
Map<String,Object> (default)
- Beans with properties of anything on this list.
- Maps with string keys.
uon
- Any Parsable POJO type.
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).
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.
The following code shows how the schema above can be used to parse our input into a POJO:
As a general rule, any POJO convertible from the intermediate type for the
Type | Format | Valid parameter types |
---|---|---|
binary binary-spaced |
|
|
date-time |
|
|
|
||
empty | ||
empty | ||
empty |
|
|
|
||
empty |
|
|
|
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:
In the example above, our POJO class can be constructed from our pipe-delimited list of comma-delimited numbers:
Just like serialization, the
The following shows an example of a bean with several properties of various types.
We define the following schema again:
Then we parse our input into our POJO:
String
Note that serializing into generic
We can also parse into Maps as well:
String
-
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).
-
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. -
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. -
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. -
The RDF parsers are SLOW.
RDF simply isn't efficient with node traversal, so creating tree structures out of RDF models is highly inefficient. -
The
Parser
methods that take inClassMeta
parameters are slightly faster than methods that take inClass
orObject
parameters, since the latter methods involve hash lookups to resolve toClassMeta
parameters.
3 - juneau-marshall-rdf
Maven Dependency
Java Library
juneau-marshall-rdf-
OSGi Module
org.apache.juneau.marshaller.rdf_
The
- RDF/XML
- RDF/XML-Abbrev
- N-Triple
- Turtle
- N3
4 - juneau-dto
Maven Dependency
Java Library
juneau-dto-
OSGi Module
org.apache.juneau.dto_
The
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 |
---|---|
|
|
|
|
|
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.
To serialize this to ATOM, use the XmlSerializer
class:
Example with no namespaces
Results
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.
{
This document can be generated by the following Java code:
Methods that take in beans and collections of beans can also take in JSON representations of those objects.
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
Swagger docs can be parsed back into Swagger beans using the following code:
Swagger
4.4 - Swagger UI
The SwaggerUI
class is a DTO class for generating Swagger user interfaces
from Swagger
beans.
The
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:
The BasicRestServlet
class (describe later) shows how this swap is used in the REST interface to
generate the Swagger UI shown above:
5 - juneau-config
Maven Dependency
Java Library
juneau-config-
OSGi Module
org.apache.juneau.config_
5.1 - Overviewupdated: 9.0.0
The
Example configuration file:
Config files are accessed through the Config
class which
are created through the Config.Builder
class.
Builder creator methods are provided on the
Once instantiated, reading values from the config are simple:
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.
-
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:
Entry
- Primary methods
- Conversion methods
- Metadata methods
The most common case for configuration values are primitives.
On integers and longs,
Numbers can also use hexadecimal and octal notation:
Strings with newlines are treated as multi-line values that get broken into separate lines:
Typically, multi-line values are started on the next line for clarity like so:
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:
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:
Beans are represented as JSON 5 by default:
The default serializer and parser is registered on the Config through the following methods:
The
String[]
String arrays can also be represented in JSON when the registered parser is a JSON parser:
String[]
Primitive arrays can also be retrieved using the
Arrays of POJOs can also be retrieved using the methods as well:
Address[]
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:
Entries can also be read as Java Collection Framework objects.
The
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:
List<Address>
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
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:
Binary lines can be split up into separate lines for readability:
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:
Config
By default, VarResolver.DEFAULT
variable resolver
which provides support for the following variables and constructs:
SystemPropertiesVar
-$S{key[,default]} EnvVariablesVar
-$E{key[,default]} ConfigVar
-$C{key[,default]}
The variable resolver is controlled via the following setting:
Additionally, the following method can be used to retrieve a
5.3.1 - Logic Variables
The default variable resolver also provides the following logic variables for performing simple logical operations:
IfVar
- $IF{arg,then[,else]}
SwitchVar
- $SW{arg,pattern1:then1[,pattern2:then2...]}
CoalesceVar
- $CO{arg1[,arg2...]}
PatternMatchVar
- $PM{arg,pattern}
NotEmptyVar
- $NE{arg}
UpperCaseVar
- $UC{arg}
LowerCaseVar
- $LC{arg}
The $IF variable can be used for simple if/else logic:
# Value set to 'foo' if myBooleanProperty is true
key1 =
$IF{
$S{myBooleanProperty},
foo
}
# Value set to 'foo' if myBooleanProperty is true, 'bar' if false.
key2 =
$IF{
$S{myBooleanProperty},
foo,
bar
}
# Value set to key1 value if myBooleanProperty is true, key2 value if false.
key3 =
$IF{
$S{myBooleanProperty},
$C{key1},
$C{key2}
}
The $SW variable can be used for switch blocks based on pattern matching:
# Shell command depends on the OS
shellCommand =
$SW{
$LC{$S{os.name}},
*win*: bat,
linux: bash,
*: sh
}
The $CO variable can be used for coalescing of values (finding the first non-null/empty match):
# Debug flag can be enabled by system property or environment variable.
debug =
$CO{
$S{debug},
$E{DEBUG},
false
}
The $PM variable can be used for calculating boolean values:
# Debug flag can be enabled by system property or environment variable.
isWindows =
$PM{
$LC{$S{os.name}},
*win*
}
The default variable resolver also provides the following logic variables for performing simple logical operations:
IfVar
-$IF{arg,then[,else]} SwitchVar
-$SW{arg,pattern1:then1[,pattern2:then2...]} CoalesceVar
-$CO{arg1[,arg2...]} PatternMatchVar
-$PM{arg,pattern} NotEmptyVar
-$NE{arg} UpperCaseVar
-$UC{arg} LowerCaseVar
-$LC{arg}
The
The
The
The
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.
The framework comes built-in with a simple xor-encode
mod tied
to the
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:
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:
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:
5.6 - Setting Values
The following methods allow you to add, remove, and modify entries and sections in a config file:
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.
The last 4 arguments in Config.set(String,Object,Serializer,String,String,List)
are optional in that if you pass
Sections can be added with optional pre-lines using the
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.
In general, external file modifications will be detected immediately in the
The
If the
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).
Setter methods that take in a
The value can then be retrieved using the equivalent parser:
Address
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.
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.
5.8 - Serializingupdated: 9.0.0
The following methods are used for serializing
Both methods are thread safe.
5.9 - Importsupdated: 8.1.0
Configurations can import values from other configurations using the following syntax:
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:
Values can be overridden by child configurations.
Example:
Config
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
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.
Dynamically removing an import has the same effect as removing keys and generates
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:
Write is slightly trickier:
The update method is called whenever the stored file gets modified externally:
Two configuration stores are provided by default:
FileStore
- File-system storage.MemoryStore
- In-memory storage.
The store is defined on the
Example:
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.
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):
The FileStore
is the typical store used for configuration files.
It provides the following configurable settings:
Example:
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.
The
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:
The
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
5.11 - Read-only Configsupdated: 9.0.0
The following settings can be used to create read-only
Example:
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:
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:
-
If the system property
"juneau.configFile" is set, we search for this file in first the home directory and then the classpath. -
In the home directory:
<jar-name>.cfg - Any file that end with
.cfg . First one matched alphabetically is used.
-
In the context classpath root package (i.e. inside the jar itself):
<jar-name>.cfg juneau.cfg default.cfg application.cfg app.cfg settings.cfg 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
By default, all properties in the system default configuration are automatically set as system properties.
This can be disabled by setting the system property
6 - juneau-assertionscreated: 9.0.0
Maven Dependency
Java Library
juneau-assertions-
OSGi Module
org.apache.juneau.assertions_
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:
- 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
assertAny(T)
assertArray(E[])
assertBean(V)
assertBeanList(List)
assertBoolean(Boolean)
assertBooleanArray(boolean[])
assertByteArray(byte[])
assertBytes(byte[])
assertBytes(InputStream)
assertCharArray(char[])
assertCollection(Collection)
assertComparable(T)
assertDate(Date)
assertDoubleArray(double[])
assertFloatArray(float[])
assertIntArray(int[])
assertInteger(Integer)
assertList(List)
assertLong(Long)
assertLongArray(long[])
assertMap(Map)
assertObject(T)
assertOptional(Optional)
assertReader(Reader)
assertShortArray(short[])
assertString(Object)
assertStringList(List)
assertThrowable(V)
assertThrown(Snippet)
assertVersion(Version)
assertZonedDateTime(ZonedDateTime)
Assertions have 3 categories of methods:
- Testing methods (
isX methods) - Transform methods (
asX methods) - Configuration methods (
setX methods)
Examples:
Testing methods (AssertionError
if
the test fails. Otherwise, the method returns the original assertion object to allow you to chain the command.
Example:
Transform methods (
Example:
Configuration methods (
Example:
The following shows the class hierarchy for the IntegerAssertion
class showing the general
design pattern for assertion classes:
Assertion
- Base class for all assertion objects containing common configuration methods.FluentAssertion<R>
- Parent class for all fluent assertions.FluentObjectAssertion<T,R>
- Tests and transforms for general POJOs.FluentComparableAssertion<T
- Tests and transforms for Comparables.extends Comparable,R>FluentIntegerAssertion<R>
- Tests and transforms for Integers.IntegerAssertion
- Assertion that returns itself.
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 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
Java Library
juneau-rest-common-
OSGi Module
org.apache.juneau.rest.common_
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.
org.apache.juneau.http
- Utility classes used for HTTP parts/headers/entities.org.apache.juneau.http.annotation
- Swagger-based annotations used on both server-side APIs and client-side proxy interfaces.org.apache.juneau.http.entity
- HTTP entity classes.org.apache.juneau.http.header
- HTTP header classes.org.apache.juneau.http.part
- HTTP query/form-data/path classes.org.apache.juneau.http.remote
- Remote proxy interface APIorg.apache.juneau.http.resource
- HTTP resource classes.org.apache.juneau.http.response
- HTTP response classes.
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
- Utility class for standard HTTP headers.HttpParts
- Utility class for standard HTTP parts.HttpEntities
- Utility class for standard HTTP entities.HttpResources
- Utility class for standard HTTP resources.HttpResponses
- Utility class for standard HTTP resources.
HttpHeaders
The HttpHeaders
class contains many convenience static methods and fields for working with standard HTTP request and response headers
and header lists.
Example:
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
:
basicHeader
booleanHeader
csvHeader
dateHeader
entityTagsHeader
entityTagHeader
integerHeader
longHeader
mediaRangesHeader
mediaTypeHeader
stringHeader
serializedHeader
stringRangesHeader
uriHeader
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:
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:
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:
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:
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:
_continue
accepted
alreadyReported
badRequest
conflict
created
earlyHints
expectationFailed
failedDependency
forbidden
found
gone
httpVersionNotSupported
imUsed
insufficientStorage
internalServerError
lengthRequired
locked
loopDetected
methodNotAllowed
misdirectedRequest
movedPermanently
multipleChoices
multiStatus
networkAuthenticationRequired
noContent
nonAuthoritiveInformation
notAcceptable
notExtended
notFound
notImplemented
notModified
ok
partialContent
payloadTooLarge
permanentRedirect
preconditionFailed
preconditionRequired
processing
rangeNotSatisfiable
requestHeaderFieldsTooLarge
resetContent
seeOther
serviceUnavailable
switchingProtocols
temporaryRedirect
tooManyRequests
unauthorized
unavailableForLegalReasons
unprocessableEntity
unsupportedMediaType
upgradeRequired
uriTooLong
useProxy
variantAlsoNegotiates
The most common location where these responses are used are in REST operation methods described later.
Example:
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.
Contact
Content
FormData
HasFormData
HasQuery
Header
License
Path
Query
Request
Response
StatusCode
Tag
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
Accept
AcceptCharset
AcceptEncoding
AcceptLanguage
AcceptRanges
Age
Allow
Authorization
CacheControl
ClientVersion
Connection
ContentDisposition
ContentEncoding
ContentLanguage
ContentLength
ContentLocation
ContentRange
ContentType
Date
Debug
ETag
Expect
Expires
Forwarded
From
Host
IfMatch
IfModifiedSince
IfNoneMatch
IfRange
IfUnmodifiedSince
LastModified
Location
MaxForwards
NoTrace
Origin
Pragma
ProxyAuthenticate
ProxyAuthorization
Range
Referer
RetryAfter
Server
TE
Thrown
Trailer
TransferEncoding
Upgrade
UserAgent
Vary
Via
Warning
WwwAuthenticate
These headers extend from the following classes that provide data-type specific functionality:
org.apache.http.NameValuePair
These subclasses provide various convenience methods to allow for easy fluent-style coding.
Examples
HeaderList
The HeaderList
class is a list of HTTP headers.
Example
Static methods are provided on HttpHeaders
to further simplify creation of header lists.
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:
Various methods are provided for iterating over the headers in this list to avoid array copies.
forEach(Consumer)
/forEach(String,Consumer)
/forEach(Predicate,Consumer)
- Use consumers to process headers.headerIterator()
/headerIterator(String)
- Use anHeaderIterator
to process headers.stream()
/stream(String)
- Use a stream.
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);
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
The HeaderList
object can be extended to defined pre-packaged lists of headers which can be used in various
annotations throughout the framework.
Example
7.4 - HTTP Partscreated: 9.0.0
The org.apache.juneau.http.part
package contains implementations of
org.apache.http.NameValuePair
PartList
The PartList
class is a list of HTTP parts (form-data, query-parameters, path-parameters).
Example
PartList
Convenience creators are provided for creating lists with minimal code:
PartList
Static methods are provided on HttpParts
to further simplify creation of part lists.
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:
Various methods are provided for iterating over the parts in this list to avoid array copies.
forEach(Consumer)
/forEach(String,Consumer)
/forEach(Predicate,Consumer)
- Use consumers to process parts.partIterator()
/partIterator(String)
- Use anPartIterator
to process parts.stream()
/stream(String)
- Use a stream.
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
A VarResolver
can be associated with this builder to create part values with embedded variables that
are resolved at runtime.
Example
The PartList
object can be extended to defined pre-packaged lists of parts which can be used in various
annotations throughout the framework.
Example
7.5 - HTTP Entities and Resourcescreated: 9.0.0
The org.apache.juneau.http.entity
package contains implementations of
The org.apache.juneau.http.resource
package contains implementations of HttpResource
which are extensions of
org.apache.http.HttpEntity
Example
HTTP entities and resources can be used by both the server and client side APIs described in later sections.
Server-side example:
Client-side example:
7.6 - HTTP Responsescreated: 9.0.0
The org.apache.juneau.http.response
package contains predefined
Accepted
AlreadyReported
BadRequest
Conflict
Continue
Created
EarlyHints
ExpectationFailed
FailedDependency
Forbidden
Found
Gone
HttpVersionNotSupported
IMUsed
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
MovedPermanently
MultipleChoices
MultiStatus
NetworkAuthenticationRequired
NoContent
NonAuthoritiveInformation
NotAcceptable
NotExtended
NotFound
NotImplemented
NotModified
Ok
PartialContent
PayloadTooLarge
PermanentRedirect
PreconditionFailed
PreconditionRequired
Processing
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ResetContent
SeeOther
ServiceUnavailable
SwitchingProtocols
TemporaryRedirect
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
UseProxy
VariantAlsoNegotiates
These are built upon existing HttpComponents APIs:
org.apache.http.HttpMessage
org.apache.http.HttpResponse
BasicHttpResponse
- 100-399 response codesBasicHttpException
- 400+ response codes
The most common location where these responses are used are in REST operation methods described later.
Example:
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
Java Library
juneau-rest-server-
OSGi Module
org.apache.juneau.rest.server_
The
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
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 handling of
-
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 pass HTTP header values as URL GET parameters (e.g.
-
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
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:
org.apache.juneau.examples.rest.jetty org.apache.juneau.examples.rest.springboot
The
The Jetty application consists of the following application class that registers our top-level servlet:
The root resources class is an example of a router page that is used to attach children to:
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:
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.
Rest
allowedHeaderParams
allowedMethodHeaders
allowedMethodParams
beanStore
callLogger
children
clientVersionHeader
config
consumes
converters
debug
debugEnablement
debugOn
defaultAccept
defaultCharset
defaultContentType
defaultRequestAttributes
defaultRequestHeaders
defaultResponseHeaders
description
disableContentParam
encoders
guards
maxInput
messages
on
onClass
parsers
partParser
partSerializer
path
produces
renderResponseStackTraces
responseProcessors
restChildrenClass
restOpArgs
restOperationsClass
roleGuard
rolesDeclared
serializers
siteName
staticFiles
swagger
swaggerProvider
title
uriAuthority
uriContext
uriRelativity
uriResolution
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:
HttpServlet
RestObject
BasicRestObject
implements BasicRestOperations
, BasicJsonConfig
BasicRestObjectGroup
implements BasicGroupOperations
The RestServlet
class itself is not configured with any serializers or parsers. However, it does
provide several convenience methods to be aware of:
RestServlet
- Logging methods:
log(Level,String,Object...)
log(Level,Throwable,String,Object...)
log(String)
log(String,Throwable)
- Other methods:
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:
- Top level resources in a servlet container:
- Top level resources in a Spring Boot environment:
- Child resources:
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:
RestInit
- Right before initialization.
RestPostInit
- Right after initialization.
RestDestroy
- Right before servlet destroy.
- 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.
The following example represents the bare-minimum needed for deploying a top-level REST endpoint with basic JSON marshalling support:
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
HttpServlet
RestObject
BasicRestObject
implements BasicRestOperations
,BasicJsonConfig
BasicRestObjectGroup
implements BasicGroupOperations
The RestServlet
class itself is not configured with any serializers or parsers. However, it does
provide several convenience methods to be aware of:
RestServlet
- Logging methods:
log(Level,String,Object...)
log(Level,Throwable,String,Object...)
log(String)
log(String,Throwable)
- Other methods:
- Logging methods:
The BasicRestOperations
interface
which defines common endpoints for swagger documentation, statistics, and serving static files:
Swagger
getSwagger
(RestRequest
HttpResource
getHtdoc
(HttpResource
getFavIcon
();
RestContextStats
getStats
(RestRequest
error
();
}
The 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:
The BasicGroupOperations
interface which provides an additional REST endpoint for listing and navigating child resources:
ChildResourceDescriptions
getChildren
(RestRequest
The
The 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:
- Top level resources in a servlet container:
- Top level resources in a Spring Boot environment:
- Child resources:
Child Resources are REST servlets or objects that are linked to parent resources through the
@Rest(children)
annotation.
Example:
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
One advantage of using child resources is that they do not need to be declared in the JEE
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:
RestInit
- Right before initialization.
RestPostInit
- Right after initialization.
RestDestroy
- Right before servlet destroy.
- 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.
The path can contain variables that get resolved to @Path
parameters
or access through the RestRequest.getPathParams()
method.
Example:
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.
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
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
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:
RestInit
- Right before initialization.
RestPostInit
- Right after initialization.
RestDestroy
- Right before servlet destroy.
- 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.
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:
Or if you want to intercept REST calls:
The following lifecycle annotations are provided.
- Resource lifecycle events:
RestInit
- Right before initialization.RestPostInit
- Right after initialization.RestDestroy
- Right before servlet destroy.
- 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.
RestOp
clientVersion
consumes
converters
debug
defaultAccept
defaultCharset
defaultContentType
defaultRequestAttributes
defaultRequestFormData
defaultRequestHeaders
defaultRequestQueryData
defaultResponseHeaders
description
encoders
guards
matchers
maxInput
method
on
parsers
path
produces
roleGuard
rolesDeclared
serializers
summary
swagger
value
Example:
The following specialized annotations are also provided for specific HTTP methods:
Example:
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:
-
Parameters based on class types:
- Request objects:
AsyncContext
CookieList
DispatcherType
HttpPartParserSession
HttpPartSerializerSession
HttpServletRequest
InputStream
InputStreamParser
Locale
Messages
Parser
Principal
Reader
ReaderParser
RequestAttributes
RequestContent
RequestFormParams
RequestHeaders
RequestPathParams
RequestQueryParams
ResourceBundle
RestRequest
ServletInputStream
Swagger
TimeZone
UriContext
UriResolver
VarResolverSession
- Response objects:
HttpServletResponse
OutputStream
RestResponse
ServletOutputStream
Writer
- Session objects:
HttpSession
RestSession
UrlPath
UrlPathMatch
BeanStore
RestOpSession
- Parsed request header values:
Accept
AcceptCharset
AcceptEncoding
AcceptLanguage
AcceptRanges
Authorization
CacheControl
ClientVersion
Connection
ContentDisposition
ContentEncoding
ContentLength
ContentType
Date
Debug
Expect
Forwarded
From
Host
IfMatch
IfModifiedSince
IfNoneMatch
IfRange
IfUnmodifiedSince
MaxForwards
NoTrace
Origin
Pragma
ProxyAuthorization
Range
Referer
TE
Thrown
Upgrade
UserAgent
Warning
- Context values:
- Annotated parameters (either on the parameter or parameter type):
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:
-
Parameters based on class types:
- Direct streams:
- Apache HttpComponent beans:
- Standard HTTP response beans:
Accepted
AlreadyReported
BadRequest
Conflict
Continue
Created
EarlyHints
ExpectationFailed
FailedDependency
Forbidden
Found
Gone
HttpVersionNotSupported
IMUsed
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
MovedPermanently
MultipleChoices
MultiStatus
NetworkAuthenticationRequired
NoContent
NonAuthoritiveInformation
NotAcceptable
NotExtended
NotFound
NotImplemented
NotModified
Ok
PartialContent
PayloadTooLarge
PermanentRedirect
PreconditionFailed
PreconditionRequired
Processing
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ResetContent
SeeOther
ServiceUnavailable
SwitchingProtocols
TemporaryRedirect
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
UseProxy
VariantAlsoNegotiates
- Other:
- Annotated parameters (either on the parameter or parameter type):
REST Java methods can also generate a response via the following:
-
By calling
RestResponse.setContent(Object)
with any of the types above.
-
By accessing the
Writer
directly by calling
RestResponse.getNegotiatedWriter()
and writing the output yourself.
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:
- Standard HTTP response beans:
BadRequest
Conflict
ExpectationFailed
FailedDependency
Forbidden
Gone
HttpVersionNotSupported
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
NetworkAuthenticationRequired
NotAcceptable
NotExtended
NotFound
NotImplemented
PayloadTooLarge
PreconditionFailed
PreconditionRequired
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ServiceUnavailable
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
VariantAlsoNegotiates
- Annotated throwables:
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
Refer to the following Javadocs for more information:
When the
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
Examples:
If
Java methods can contain any of the following parameters in any order:
-
Parameters based on class types:
- Request objects:
AsyncContext
CookieList
DispatcherType
HttpPartParserSession
HttpPartSerializerSession
HttpServletRequest
InputStream
InputStreamParser
Locale
Messages
Parser
Principal
Reader
ReaderParser
RequestAttributes
RequestContent
RequestFormParams
RequestHeaders
RequestPathParams
RequestQueryParams
ResourceBundle
RestRequest
ServletInputStream
Swagger
TimeZone
UriContext
UriResolver
VarResolverSession
- Response objects:
HttpServletResponse
OutputStream
RestResponse
ServletOutputStream
Writer
- Session objects:
HttpSession
RestSession
UrlPath
UrlPathMatch
BeanStore
RestOpSession
- Parsed request header values:
Accept
AcceptCharset
AcceptEncoding
AcceptLanguage
AcceptRanges
Authorization
CacheControl
ClientVersion
Connection
ContentDisposition
ContentEncoding
ContentLength
ContentType
Date
Debug
Expect
Forwarded
From
Host
IfMatch
IfModifiedSince
IfNoneMatch
IfRange
IfUnmodifiedSince
MaxForwards
NoTrace
Origin
Pragma
ProxyAuthorization
Range
Referer
TE
Thrown
Upgrade
UserAgent
Warning
- Context values:
- Request objects:
- Annotated parameters (either on the parameter or parameter type):
In Spring Boot environments, any available Spring Beans can also be passed in as parameters.
Example:
Additional parameter types can be defined via the annotation Rest.restOpArgs()
or by calling RestContext.Builder.restOpArgs(Class...)
.
Example:
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:
-
Parameters based on class types:
- Direct streams:
- Apache HttpComponent beans:
- Standard HTTP response beans:
Accepted
AlreadyReported
BadRequest
Conflict
Continue
Created
EarlyHints
ExpectationFailed
FailedDependency
Forbidden
Found
Gone
HttpVersionNotSupported
IMUsed
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
MovedPermanently
MultipleChoices
MultiStatus
NetworkAuthenticationRequired
NoContent
NonAuthoritiveInformation
NotAcceptable
NotExtended
NotFound
NotImplemented
NotModified
Ok
PartialContent
PayloadTooLarge
PermanentRedirect
PreconditionFailed
PreconditionRequired
Processing
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ResetContent
SeeOther
ServiceUnavailable
SwitchingProtocols
TemporaryRedirect
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
UseProxy
VariantAlsoNegotiates
- Other:
- Annotated parameters (either on the parameter or parameter type):
REST Java methods can also generate a response via the following:
-
By calling
RestResponse.setContent(Object)
with any of the types above.
-
By accessing the
Writer
directly by calling
RestResponse.getNegotiatedWriter()
and writing the output yourself.
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:
- Standard HTTP response beans:
BadRequest
Conflict
ExpectationFailed
FailedDependency
Forbidden
Gone
HttpVersionNotSupported
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
NetworkAuthenticationRequired
NotAcceptable
NotExtended
NotFound
NotImplemented
PayloadTooLarge
PreconditionFailed
PreconditionRequired
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ServiceUnavailable
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
VariantAlsoNegotiates
- Annotated throwables:
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
Refer to the following Javadocs for more information:
The return type of the Java method can be any serializable POJO as defined in POJO Categories.
It can also be RestResponse.setContent(Object)
method.
Example:
In addition to POJOs, the following return types are also supported:
-
Parameters based on class types:
- Direct streams:
- Apache HttpComponent beans:
- Standard HTTP response beans:
Accepted
AlreadyReported
BadRequest
Conflict
Continue
Created
EarlyHints
ExpectationFailed
FailedDependency
Forbidden
Found
Gone
HttpVersionNotSupported
IMUsed
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
MovedPermanently
MultipleChoices
MultiStatus
NetworkAuthenticationRequired
NoContent
NonAuthoritiveInformation
NotAcceptable
NotExtended
NotFound
NotImplemented
NotModified
Ok
PartialContent
PayloadTooLarge
PermanentRedirect
PreconditionFailed
PreconditionRequired
Processing
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ResetContent
SeeOther
ServiceUnavailable
SwitchingProtocols
TemporaryRedirect
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
UseProxy
VariantAlsoNegotiates
- Other:
- Annotated parameters (either on the parameter or parameter type):
REST Java methods can also generate a response via the following:
-
By calling
RestResponse.setContent(Object)
with any of the types above. -
By accessing the
Writer
directly by callingRestResponse.getNegotiatedWriter()
and writing the output yourself.
Example:
Additional parameter types can be defined via the annotation Rest.responseProcessors()
or by calling RestContext.Builder.responseProcessors(Class...)
.
Example:
Annotated Java methods can throw any of the following:
- Standard HTTP response beans:
BadRequest
Conflict
ExpectationFailed
FailedDependency
Forbidden
Gone
HttpVersionNotSupported
InsufficientStorage
InternalServerError
LengthRequired
Locked
LoopDetected
MethodNotAllowed
MisdirectedRequest
NetworkAuthenticationRequired
NotAcceptable
NotExtended
NotFound
NotImplemented
PayloadTooLarge
PreconditionFailed
PreconditionRequired
RangeNotSatisfiable
RequestHeaderFieldsTooLarge
ServiceUnavailable
TooManyRequests
Unauthorized
UnavailableForLegalReasons
UnprocessableEntity
UnsupportedMediaType
UpgradeRequired
UriTooLong
VariantAlsoNegotiates
- Annotated throwables:
All other throwables get processed as follows:
- Processed as 400/Bad Request:
- Processed as 401/Unauthorized:
- Any class named
"*AccessDenied*" or"*Unauthorized*"
- Any class named
- Processed as 404/Not Found:
- Any class named
"*Empty*" or"*NotFound*"
- Any class named
- Anything else processed as 500/Internal Server Error.
Example:
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
Refer to the following Javadocs for more information:
The @RestOp(path)
annotation allows
you to define URL path patterns to match against.
These patterns can contain variables of the form
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...
By default, path patterns are matched using a best-match heuristic. When overlaps occur, URLs are matched from most-specific to most-general order:
Paths that end with RequestPathParams.getRemainder()
or parameters with the
The following example shows the distinction.
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:
The interface for matchers is simple:
-
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, aServletException 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
Refer to the following Javadocs for more information:
Through the use of the built-in
For example, the URL
To support overloaded methods, the @Rest(allowedMethodParams)
setting must be enabled on your servlet.
Refer to the following Javadocs for more information:
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:
Annotations are provided for both request and response HTTP parts.
The annotations used for defining the schema for request HTTP parts are:
- HTTP request parts:
- HTTP response parts:
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:
HttpPartSerializer
SimplePartSerializer
- Serializes directly to strings.
UonSerializer
- Serializes to UON notation.
OpenApiSerializer
- Serializes using Open-API schema rules.
HttpPartParser
SimplePartParser
- Parses directly from strings.
UonParser
- Parses from UON notation.
OpenApiParser
- Parses using Open-API schema rules.
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.
- Request annotations:
- Response annotations:
- Common schema annotation:
Schema
_default
_enum
$ref
additionalProperties
aev
allOf
allowEmptyValue
cf
collectionFormat
d
description
df
discriminator
e
emax
emin
exclusiveMaximum
exclusiveMinimum
externalDocs
f
format
ignore
items
max
maxi
maximum
maxItems
maxl
maxLength
maxp
maxProperties
min
mini
minimum
minItems
minl
minLength
minp
minProperties
mo
multipleOf
on
onClass
p
pattern
properties
r
readOnly
required
ro
sie
v
t
title
type
ui
uniqueItems
xml
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):
-
Reader
@Content annotation is optional.
Content-Type is ignored.
-
InputStream
@Content annotation is optional.
Content-Type is ignored.
-
Any Parseable POJO type.
Content-Type is required to identify correct parser.
-
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.
-
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.
-
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.
-
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 void testContent@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:
RestContext.Builder
RestOpContext.Builder
defaultRequestAttributes()
defaultRequestAttributes(NamedAttribute...)
defaultRequestFormData()
defaultRequestFormData(NameValuePair...)
defaultRequestHeaders()
defaultRequestHeaders(Header...)
defaultRequestQueryData()
defaultRequestQueryData(NameValuePair...)
defaultResponseHeaders()
defaultResponseHeaders(Header...)
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();
Juneau comes with three basic marshaller types for serializing and parsing Header, Query, Form, and Path parts:
HttpPartSerializer
SimplePartSerializer
- Serializes directly to strings.UonSerializer
- Serializes to UON notation.OpenApiSerializer
- Serializes using Open-API schema rules.
HttpPartParser
SimplePartParser
- Parses directly from strings.UonParser
- Parses from UON notation.OpenApiParser
- Parses using Open-API schema rules.
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:
The following annotations allow for defining part schemas based on the OpenAPI standard.
- Request annotations:
- Response annotations:
- Common schema annotation:
Schema
_default
_enum
$ref
additionalProperties
aev
allOf
allowEmptyValue
cf
collectionFormat
d
description
df
discriminator
e
emax
emin
exclusiveMaximum
exclusiveMinimum
externalDocs
f
format
ignore
items
max
maxi
maximum
maxItems
maxl
maxLength
maxp
maxProperties
min
mini
minimum
minItems
minl
minLength
minp
minProperties
mo
multipleOf
on
onClass
p
pattern
properties
r
readOnly
required
ro
sie
v
t
title
type
ui
uniqueItems
xml
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
Example:
This is functionally equivalent to the following code:
The special name
Examples:
The @Content
annotation is used to identify POJOs to be used as the body of an HTTP request.
Examples:
This is functionally equivalent to the following code:
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):
-
Reader
@Content annotation is optional.Content-Type is ignored. -
InputStream
@Content annotation is optional.Content-Type is ignored. -
Any Parseable POJO type.
Content-Type is required to identify correct parser. -
Objects convertible from
Reader
by having one of the following non-deprecated methods:public T(Reader in) {...}public static Tcreate (Reader in) {...}public static TfromReader (Reader in) {...}
Content-Type must not be present or match an existing parser so that it's not parsed as a POJO. -
Objects convertible from
InputStream
by having one of the following non-deprecated methods:public T(InputStream in) {...}public static Tcreate (InputStream in) {...}public static TfromInputStream (InputStream in) {...}
Content-Type must not be present or match an existing parser so that it's not parsed as a POJO. -
Objects convertible from
String
by having one of the following non-deprecated methods:public T(String in) {...}public static Tcreate (String in) {...}public static TfromString (String in) {...}public static Tparse (String in) {...}public static TparseString (String in) {...}public static TforName (String in) {...}public static TforString (String in) {...}
-
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:
This is functionally equivalent to the following code:
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.
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
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:
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
Example:
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
The following shows the same for a request body:
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
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:
RestContext.Builder
RestOpContext.Builder
defaultRequestAttributes()
defaultRequestAttributes(NamedAttribute...)
defaultRequestFormData()
defaultRequestFormData(NameValuePair...)
defaultRequestHeaders()
defaultRequestHeaders(Header...)
defaultRequestQueryData()
defaultRequestQueryData(NameValuePair...)
defaultResponseHeaders()
defaultResponseHeaders(Header...)
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();
By default, HTTP parts that don't have value (such as missing query parameters) end up with null values:
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:
You can also specify default values on the annotations:
A third option is to specify default values via the Rest
and
RestOp
annotations.
Example:
Default parts can also be specified programmatically through any of the following methods:
RestContext.Builder
RestOpContext.Builder
defaultRequestAttributes()
defaultRequestAttributes(NamedAttribute...)
defaultRequestFormData()
defaultRequestFormData(NameValuePair...)
defaultRequestHeaders()
defaultRequestHeaders(Header...)
defaultRequestQueryData()
defaultRequestQueryData(NameValuePair...)
defaultResponseHeaders()
defaultResponseHeaders(Header...)
The @Request
annotation can used to define proxy interfaces against
HTTP requests in combination with the following annotations used on methods:
Example:
The example above is identical in behavior to specifying individual annotated parameters on the
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:
For clarity, the
Example:
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();
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
Custom exceptions can also extend from one of the predefined HTTP exceptions such as the Unauthorized
exception:
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:
Built in to these APIs are various convenience methods such as converting parts to different types or inline fluent assertions:
Example:
8.5 - Marshallingupdated: 9.0.0
Juneau uses Parsers
and Serializers
for marshalling
HTTP request and response bodies to POJOs using the
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:
The following classes provide common default serializers and parsers that can be used as-is or augmented by child classes:
- Classes:
- Interfaces:
Serializers and parsers can also be defined programmatically using an INIT hook method like shown below:
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:
Swaps are associated serializers and parsers registered on a REST resource via the BeanConfig
annotation
on either the class or method level:
Config annotations are defined for all serializers and parsers:
BeanConfig
CsvConfig
HtmlConfig
HtmlDocConfig
JsonConfig
JsonSchemaConfig
MsgPackConfig
OpenApiConfig
ParserConfig
PlainTextConfig
SerializerConfig
SoapXmlConfig
UonConfig
UrlEncodingConfig
XmlConfig
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:
- By parsing the entire HTTP body into a POJO using the registered
UrlEncodingParser
- By access the form post entries as HTTP parts.
The following example shows the first approach of handling an
The next example shows handling it as individual parts:
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.
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
RestRequest.getContent()
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:
The following shows using the
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:
A common use for guards is to only allow admin access to certain Java methods...
A guard failure results in an RestGuard.guard(RestRequest,RestResponse)
and processing the response
yourself.
A simplified format is available for matching based on the user role on the request using the following:
Example:
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:
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
By default, the resource bundle name is assumed to match the class name. For example, given the class
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
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
Examples:
When using shared resource bundles, keys can be prefixed by class names like so and still retrieve by simple key names:
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.
Example:
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:
In recap, the Configuration API provides support for INI-style configuration files with embedded string variables:
Example:
These properties are then accessible through the Config
class.
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
Spring Boot applications typically define an
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
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.
Another common usage is to refer to config properties through
It's even possible to reference request-level variables in your config file if you use
RestRequest.getConfig()
to access the config file:
You can even add resource bundles into the mix:
8.12 - SVL Variablesupdated: 9.0.0
In the previous examples, there were several cases where embedded variables were contained within annotation values:
Variables take the form
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
.
TheRestContext.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 asHtmlDoc @HtmlDoc.RestRequest.getVarResolverSession()
method returns initialization and request-time variables.
The following is the default list of supported variables.
Default REST SVL Variables:
Module | Class | Pattern | Initialization 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:
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:
The static file finder can be accessed through the following methods:
By default, the StaticFiles
bean is configured as follows:
StaticFiles
.
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:
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
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:
- Java annotations
- Resource bundle
- 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:
@Response
annotated classes, methods, and parameters.
@Rest(swagger)
/ @RestOp(swagger)
annotations.
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.
Any subclass of BasicRestServlet
and BasicRestObject
gets an auto-generated Swagger UI when performing an
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 Swagger
object and a SwaggerUI
swap.
Let's look at the various parts of the
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
In this particular case, the Swagger is pulled in from a localized Swagger JSON file located in the
$F
variable.
PetStoreResource.json
{
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:
However, a more typical (and less error-prone) scenario is to define all of your Swagger as annotations:
All annotations support SVL Variables, so you could for example
pull localized strings from resource bundles using $L
variables.
A third option is to define your Swagger information in your @Rest(messages)
resource
bundle using predefined Swagger keywords:
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:
- Java annotations
- Resource bundle
- 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:
@Response
annotated classes, methods, and parameters.
@Rest(swagger)
/ @RestOp(swagger)
annotations.
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.
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
Tags are also defined in the
PetStoreResource.json
The annotation-only approach is shown here:
org.apache.juneau.examples.rest.petstore.PetStoreResource
swagger=
Tags are associated with operations using the @OpSwagger(tags)
annotation:
GET /user operation
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
The following shows the annotations defined on the
PetStoreResource.getPets()
Methods marked as deprecated will show up as deprecated in the Swagger UI:
PetStoreResource.findPetsByTag()
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:
@Response
annotated classes, methods, and parameters.
@Rest(swagger)
/ @RestOp(swagger)
annotations.
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.
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
- The
@Schema
annotation can also be attached directly to the parameter or parameter type as well. - The
type
andcollectionFormat
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 Queryable
class:
PetStoreResource.getPets()
Queryable
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:
Note that the schema information on the body parameter is automatically detected if not provided.
Under the input parameters are listed the possible responses for the resource:
The
Note that additional responses can be specified by throwing exceptions annotated with the @Response
annotation such
as this one:
Like input parameters, the Swagger for responses can be define in multiple locations such as:
@Response
annotated classes, methods, and parameters.@Rest(swagger)
/@RestOp(swagger)
annotations.
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.
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
This setting is disabled by default but can be set on the RestContext.Builder
object:
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
{
Note that this does not affect how the information is rendered for that bean in the Swagger UI:
The look-and-feel of the Swagger UI is controlled via a single CSS file:
In the microservice template, this file is located in the
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
If you extend from BasicRestServlet
or BasicRestObject
, then
the statics are made available through the REST interface via the following method:
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:
The purpose of this annotation is to populate the HTML document view which by default consists of the following structure:
The outline above is controlled by the HtmlDocTemplate
interface
which can be overridden via the @HtmlDocConfig(template)
annotation.
The
SVL variables can be used in any of these annotations:
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.
MenuItemWidget
MenuItemWidget
is an abstract class for rendering menu items with drop-downs.
It defines some simple CSS and Javascript for enabling drop-down menus in the nav section of the page (although
nothing keeps you from using it in an arbitrary location in the page).
The script specifies a menuClick(element) function that toggles the visibility of the next sibling of the element.
Subclasses implement the following two methods:
For example, to render a link that brings up a simple dialog in a div tag:
@Override
public String getLabel() {
return "my-menu-item" ;
};
@Override
public Div getContent() {
return Html5Builder.div ("Surprise!" ).style("color:red" );
};
The HTML content returned by the getHtml(RestRequest,RestResponse)
method is added where the "$W{...}" is referenced in the page.
ContentTypeMenuItem
ContentTypeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in a variety of content types.
The variable it resolves is "$W{ContentTypeMenuItem}" .
An example of this widget can be found in the PetStoreResource in the examples that provides a drop-down menu item for rendering all other supported content types in plain text:
@RestGet (path="/" )
@HtmlDocConfig (
widgets={
ContentTypeMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
It renders the following popup-box:
QueryMenuItem
QueryMenuItem
is a predefined Widget that returns a menu-item drop-down form for entering search/view/sort arguments.
The variable it resolves is "$W{QueryMenuItem}" .
This widget is designed to be used in conjunction with the Queryable
converter, although implementations
can process the query parameters themselves if they wish to do so by using the RequestQueryParams.getSearchArgs()
method to retrieve the arguments and process the data themselves.
An example of this widget can be found in the PetStoreResource in the examples that provides
search/view/sort capabilities against the collection of POJOs:
@RestGet (
path="/" ,
converters=Queryable.class
)
@HtmlDocConfig (
widgets={
QueryMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
It renders the following popup-box:
Tooltips are provided by hovering over the field names.
When submitted, the form submits a GET request against the current URI with special GET search API query parameters.
(e.g. "?s=column1=Foo*&v=column1,column2&o=column1,column2-&p=100&l=100" ).
The Queryable
class knows how to perform these filters against collections of POJOs.
ThemeMenuItem
ThemeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in the various default styles.
The variable it resolves is "$W{ThemeMenuItem}" .
An example of this widget can be found in the PetStoreResource in the examples that provides
a drop-down menu item for rendering all other supported content types in plain text:
@RestGet (path="/" )
@HtmlDocConfig (
widgets={
ThemeMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
PoweredByJuneau
PoweredByJuneau
is a predefined Widget that places a powered-by-Juneau message on a page.
The variable it resolves is "$W{PoweredByJuneau}" .
It produces a simple Apache Juneau icon floating on the right.
Typically it's used in the footer of the page, as shown below in the AddressBookResource from the examples:
@Rest (path="/addressBook" )
@HtmlDocConfig (
widgets={
PoweredByJuneau.class
},
footer="$W{PoweredByJuneau}"
)
It renders the following image:
Tooltip
Tooltip
is a predefined template for adding tooltips to HTML5 bean constructs, typically in menu item widgets.
The following examples shows how tooltips can be added to a menu item widget.
public class MyFormMenuItem extends MenuItemWidget {
@Override
public String getLabel(RestRequest req ) throws Exception {
return "myform" ;
}
@Override
public Object getContent(RestRequest req ) throws Exception {
return div(
form ().id("form" ).action("servlet:/form" ).method(POST ).children(
table (
tr (
th ("Field 1:" ),
td (input ().name("field1" ).type("text" )),
td (new Tooltip("(?)" , "This is field #1!" , br(), "(e.g. '" , code("Foo" ), "')" ))
),
tr (
th ("Field 2:" ),
td (input ().name("field2" ).type("text" )),
td (new Tooltip("(?)" , "This is field #2!" , br(), "(e.g. '" , code("Bar" ), "')" ))
)
)
)
);
}
}
8.17.4 - UI Customizationupdated: 9.0.0
The HTML views of POJOs can somewhat be considered a rudimentary User Interface.
In reality, a better term for them would be a Developer Interface as they're meant to be used
primarily by developers and not end users.
Despite that distinction, it is possible to 'brand' the HTML page to whatever you desire.
The sample root page below includes some default branding for Juneau and Apache:
http://localhost:10000/helloWorld
The Juneau REST framework does not provide specific branding support (i.e. there is no concept of a brand icon).
Instead, it just uses the existing open-ended API for defining branding via annotations on your REST classes.
@Rest (
// Optional external configuration file.
config="$S{juneau.configFile}" ,
)
@HtmlDocConfig (
// 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 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.
},
// Default contents to add to the <head> section of the HTML page.
// Use it to add a favicon link to the page.
head={
"<link rel='icon' href='$U{$C{REST/favicon}}'/>"
},
// Basic page navigation links.
navlinks={
"up: request:/.."
},
// No default page footer contents.
// Can be overridden from external config file.
footer="$C{REST/footer}"
)
public interface BasicUniversalConfig {}
@Rest (...)
@HtmlDocConfig (
widgets={
ContentTypeMenuItem.class ,
ThemeMenuItem.class
},
navlinks={
"options: ?method=OPTIONS" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
},
aside={
"<div style='max-width:400px' class='text'>" ,
" <p>This is an example of a 'router' page that serves as a jumping-off point to child resources.</p>" ,
" <p>Resources can be nested arbitrarily deep through router pages.</p>" ,
" <p>Note the <span class='link'>options</span> link provided that lets you see the generated swagger doc for this page.</p>" ,
" <p>Also note the <span class='link'>sources</span> link on these pages to view the source code for the page.</p>" ,
" <p>All content on pages in the UI are serialized POJOs. In this case, it's a serialized array of beans with 2 properties, 'name' and 'description'.</p>" ,
" <p>Other features (such as this aside) are added through annotations.</p>" ,
"</div>"
},
asideFloat="RIGHT"
)
public class RootResources extends BasicRestServletGroup {...}
The default annotation values use $C
variables to pull in values from an optional
external configuration file such as the one shown below:
#=======================================================================================================================
# REST settings
#=======================================================================================================================
[REST]
staticFiles = htdocs:files/htdocs
# Stylesheet to use for HTML views.
theme = servlet:/htdocs/themes/devops.css
headerIcon = servlet:/htdocs/images/juneau.png
headerLink = http://juneau.apache.org
footerIcon = servlet:/htdocs/images/asf.png
footerLink = http://www.apache.org
favicon = $C{REST/headerIcon}
header =
<a href='$U{$C{REST/headerLink}}'>
<img src='$U{$C{REST/headerIcon}}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/>
</a>
footer =
<a href='$U{$C{REST/footerLink}}'>
<img src='$U{$C{REST/footerIcon}}' style='float:right;padding-right:20px;height:32px'/>
</a>
The take-away here is that the "User Interface" is open-ended, lets you define pretty much anything you want through arbitrary HTML,
and allows you either hardcode your interface inside annotations or pull them in via string variables from other places such as
external config files.
8.17.5 - Stylesheetsupdated: 8.1.0,9.0.0
The sample root page renders in the default "devops" look-and-feel:
http://localhost:10000
The sample root page provides a dropdown widget to try out the other default look-and-feels:
For example, the "light" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Flight.css
And the "dark" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Fdark.css
The stylesheet URL is controlled by the @HtmlDocConfig(stylesheet)
annotation.
The BasicUniversalConfig
interface defines the stylesheet served up as a static file:
@HtmlDocConfig (
stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}"
)
public interface BasicUniversalConfig {...}
The "$C{REST/theme,servlet:/htdocs/themes/devops.css}" variable says to use the URI defined
in your servlet's config file, if there is one, and to default to serving up the file
htdocs/themes/devops.css which is in the resource folder of the juneau-rest-server module.
To provide your own stylesheet, simply override the stylesheet attribute and point to a different
file:
@HtmlDocConfig (
stylesheet="servlet:/htdocs/themes/my-style.css"
)
public class MyResourceBaseClass extends BasicRestServlet {...}
You can try out different stylesheets by passing in a stylesheet attribute in the request
URL.
The example above show this in use.
In case you're curious about how the menu item works, it's defined via a widget:
@Rest (...)
@HtmlDocConfig (
widgets={
PoweredByApache.class ,
ContentTypeMenuItem.class ,
StyleMenuItem.class
},
navlinks={
"options: ?method=OPTIONS" ,
"$W{ContentTypeMenuItem}" ,
"$W{StyleMenuItem}" ,
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
}
)
public class RootResources extends BasicRestServletGroup {...}
The StyleMenuItem is a widget that extends from MenuItemWidget
, a
specialized widget for creating pop-up menus.
In the case of StyleMenuItem , it's simply returning a list of links wrapped in a div tag:
import static org.apache.juneau.dto.html5.HtmlBuilder.*;
public class StyleMenuItem extends MenuItemWidget {
private static final String[] BUILT_IN_STYLES = {"devops" , "light" , "original" , "dark" };
@Override /* Widget */
public String getLabel(RestRequest req ) {
return "styles" ;
}
@Override /* MenuItemWidget */
public Div getContent(RestRequest req ) throws Exception {
Div div = div ();
for (String style : BUILT_IN_STYLES ) {
java.net.URI uri = req .getUri(true , new AMap<String,String>().append("stylesheet" , "styles/" +s+".css" ));
div .children(a (uri , style ), br ());
}
return div ;
}
}
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.
The Widget
class allows you to add arbitrary HTML, CSS, and Javascript
to HTML pages.
They are registered in the following location:
Example:
The
The HTML content returned by the getHtml(RestRequest,RestResponse)
method is added wherever the
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 @Rest(staticFiles)
):
8.17.3 - Predefined Widgetsupdated: 9.0.0
The org.apache.juneau.rest.widget
package contains predefined reusable widgets.
MenuItemWidget
MenuItemWidget
is an abstract class for rendering menu items with drop-downs.
It defines some simple CSS and Javascript for enabling drop-down menus in the nav section of the page (although
nothing keeps you from using it in an arbitrary location in the page).
The script specifies a menuClick(element) function that toggles the visibility of the next sibling of the element.
Subclasses implement the following two methods:
For example, to render a link that brings up a simple dialog in a div tag:
@Override
public String getLabel() {
return "my-menu-item" ;
};
@Override
public Div getContent() {
return Html5Builder.div ("Surprise!" ).style("color:red" );
};
The HTML content returned by the getHtml(RestRequest,RestResponse)
method is added where the "$W{...}" is referenced in the page.
ContentTypeMenuItem
ContentTypeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in a variety of content types.
The variable it resolves is "$W{ContentTypeMenuItem}" .
An example of this widget can be found in the PetStoreResource in the examples that provides a drop-down menu item for rendering all other supported content types in plain text:
@RestGet (path="/" )
@HtmlDocConfig (
widgets={
ContentTypeMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
It renders the following popup-box:
QueryMenuItem
QueryMenuItem
is a predefined Widget that returns a menu-item drop-down form for entering search/view/sort arguments.
The variable it resolves is "$W{QueryMenuItem}" .
This widget is designed to be used in conjunction with the Queryable
converter, although implementations
can process the query parameters themselves if they wish to do so by using the RequestQueryParams.getSearchArgs()
method to retrieve the arguments and process the data themselves.
An example of this widget can be found in the PetStoreResource in the examples that provides
search/view/sort capabilities against the collection of POJOs:
@RestGet (
path="/" ,
converters=Queryable.class
)
@HtmlDocConfig (
widgets={
QueryMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
It renders the following popup-box:
Tooltips are provided by hovering over the field names.
When submitted, the form submits a GET request against the current URI with special GET search API query parameters.
(e.g. "?s=column1=Foo*&v=column1,column2&o=column1,column2-&p=100&l=100" ).
The Queryable
class knows how to perform these filters against collections of POJOs.
ThemeMenuItem
ThemeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in the various default styles.
The variable it resolves is "$W{ThemeMenuItem}" .
An example of this widget can be found in the PetStoreResource in the examples that provides
a drop-down menu item for rendering all other supported content types in plain text:
@RestGet (path="/" )
@HtmlDocConfig (
widgets={
ThemeMenuItem.class ,
},
navlinks={
"up: ..." ,
"options: ..." ,
"$W{QueryMenuItem}" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: ..."
}
)
public Collection<Pet> getPets() {
PoweredByJuneau
PoweredByJuneau
is a predefined Widget that places a powered-by-Juneau message on a page.
The variable it resolves is "$W{PoweredByJuneau}" .
It produces a simple Apache Juneau icon floating on the right.
Typically it's used in the footer of the page, as shown below in the AddressBookResource from the examples:
@Rest (path="/addressBook" )
@HtmlDocConfig (
widgets={
PoweredByJuneau.class
},
footer="$W{PoweredByJuneau}"
)
It renders the following image:
Tooltip
Tooltip
is a predefined template for adding tooltips to HTML5 bean constructs, typically in menu item widgets.
The following examples shows how tooltips can be added to a menu item widget.
public class MyFormMenuItem extends MenuItemWidget {
@Override
public String getLabel(RestRequest req ) throws Exception {
return "myform" ;
}
@Override
public Object getContent(RestRequest req ) throws Exception {
return div(
form ().id("form" ).action("servlet:/form" ).method(POST ).children(
table (
tr (
th ("Field 1:" ),
td (input ().name("field1" ).type("text" )),
td (new Tooltip("(?)" , "This is field #1!" , br(), "(e.g. '" , code("Foo" ), "')" ))
),
tr (
th ("Field 2:" ),
td (input ().name("field2" ).type("text" )),
td (new Tooltip("(?)" , "This is field #2!" , br(), "(e.g. '" , code("Bar" ), "')" ))
)
)
)
);
}
}
8.17.4 - UI Customizationupdated: 9.0.0
The HTML views of POJOs can somewhat be considered a rudimentary User Interface.
In reality, a better term for them would be a Developer Interface as they're meant to be used
primarily by developers and not end users.
Despite that distinction, it is possible to 'brand' the HTML page to whatever you desire.
The sample root page below includes some default branding for Juneau and Apache:
http://localhost:10000/helloWorld
The Juneau REST framework does not provide specific branding support (i.e. there is no concept of a brand icon).
Instead, it just uses the existing open-ended API for defining branding via annotations on your REST classes.
@Rest (
// Optional external configuration file.
config="$S{juneau.configFile}" ,
)
@HtmlDocConfig (
// 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 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.
},
// Default contents to add to the <head> section of the HTML page.
// Use it to add a favicon link to the page.
head={
"<link rel='icon' href='$U{$C{REST/favicon}}'/>"
},
// Basic page navigation links.
navlinks={
"up: request:/.."
},
// No default page footer contents.
// Can be overridden from external config file.
footer="$C{REST/footer}"
)
public interface BasicUniversalConfig {}
@Rest (...)
@HtmlDocConfig (
widgets={
ContentTypeMenuItem.class ,
ThemeMenuItem.class
},
navlinks={
"options: ?method=OPTIONS" ,
"$W{ContentTypeMenuItem}" ,
"$W{ThemeMenuItem}" ,
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
},
aside={
"<div style='max-width:400px' class='text'>" ,
" <p>This is an example of a 'router' page that serves as a jumping-off point to child resources.</p>" ,
" <p>Resources can be nested arbitrarily deep through router pages.</p>" ,
" <p>Note the <span class='link'>options</span> link provided that lets you see the generated swagger doc for this page.</p>" ,
" <p>Also note the <span class='link'>sources</span> link on these pages to view the source code for the page.</p>" ,
" <p>All content on pages in the UI are serialized POJOs. In this case, it's a serialized array of beans with 2 properties, 'name' and 'description'.</p>" ,
" <p>Other features (such as this aside) are added through annotations.</p>" ,
"</div>"
},
asideFloat="RIGHT"
)
public class RootResources extends BasicRestServletGroup {...}
The default annotation values use $C
variables to pull in values from an optional
external configuration file such as the one shown below:
#=======================================================================================================================
# REST settings
#=======================================================================================================================
[REST]
staticFiles = htdocs:files/htdocs
# Stylesheet to use for HTML views.
theme = servlet:/htdocs/themes/devops.css
headerIcon = servlet:/htdocs/images/juneau.png
headerLink = http://juneau.apache.org
footerIcon = servlet:/htdocs/images/asf.png
footerLink = http://www.apache.org
favicon = $C{REST/headerIcon}
header =
<a href='$U{$C{REST/headerLink}}'>
<img src='$U{$C{REST/headerIcon}}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/>
</a>
footer =
<a href='$U{$C{REST/footerLink}}'>
<img src='$U{$C{REST/footerIcon}}' style='float:right;padding-right:20px;height:32px'/>
</a>
The take-away here is that the "User Interface" is open-ended, lets you define pretty much anything you want through arbitrary HTML,
and allows you either hardcode your interface inside annotations or pull them in via string variables from other places such as
external config files.
8.17.5 - Stylesheetsupdated: 8.1.0,9.0.0
The sample root page renders in the default "devops" look-and-feel:
http://localhost:10000
The sample root page provides a dropdown widget to try out the other default look-and-feels:
For example, the "light" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Flight.css
And the "dark" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Fdark.css
The stylesheet URL is controlled by the @HtmlDocConfig(stylesheet)
annotation.
The BasicUniversalConfig
interface defines the stylesheet served up as a static file:
@HtmlDocConfig (
stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}"
)
public interface BasicUniversalConfig {...}
The "$C{REST/theme,servlet:/htdocs/themes/devops.css}" variable says to use the URI defined
in your servlet's config file, if there is one, and to default to serving up the file
htdocs/themes/devops.css which is in the resource folder of the juneau-rest-server module.
To provide your own stylesheet, simply override the stylesheet attribute and point to a different
file:
@HtmlDocConfig (
stylesheet="servlet:/htdocs/themes/my-style.css"
)
public class MyResourceBaseClass extends BasicRestServlet {...}
You can try out different stylesheets by passing in a stylesheet attribute in the request
URL.
The example above show this in use.
In case you're curious about how the menu item works, it's defined via a widget:
@Rest (...)
@HtmlDocConfig (
widgets={
PoweredByApache.class ,
ContentTypeMenuItem.class ,
StyleMenuItem.class
},
navlinks={
"options: ?method=OPTIONS" ,
"$W{ContentTypeMenuItem}" ,
"$W{StyleMenuItem}" ,
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
}
)
public class RootResources extends BasicRestServletGroup {...}
The StyleMenuItem is a widget that extends from MenuItemWidget
, a
specialized widget for creating pop-up menus.
In the case of StyleMenuItem , it's simply returning a list of links wrapped in a div tag:
import static org.apache.juneau.dto.html5.HtmlBuilder.*;
public class StyleMenuItem extends MenuItemWidget {
private static final String[] BUILT_IN_STYLES = {"devops" , "light" , "original" , "dark" };
@Override /* Widget */
public String getLabel(RestRequest req ) {
return "styles" ;
}
@Override /* MenuItemWidget */
public Div getContent(RestRequest req ) throws Exception {
Div div = div ();
for (String style : BUILT_IN_STYLES ) {
java.net.URI uri = req .getUri(true , new AMap<String,String>().append("stylesheet" , "styles/" +s+".css" ));
div .children(a (uri , style ), br ());
}
return div ;
}
}
The org.apache.juneau.rest.widget
package contains predefined reusable widgets.
MenuItemWidget
MenuItemWidget
is an abstract class for rendering menu items with drop-downs.
It defines some simple CSS and Javascript for enabling drop-down menus in the nav section of the page (although
nothing keeps you from using it in an arbitrary location in the page).
The script specifies a
Subclasses implement the following two methods:
For example, to render a link that brings up a simple dialog in a div tag:
The HTML content returned by the getHtml(RestRequest,RestResponse)
method is added where the
ContentTypeMenuItem
ContentTypeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in a variety of content types.
The variable it resolves is
An example of this widget can be found in the
It renders the following popup-box:
QueryMenuItem
QueryMenuItem
is a predefined Widget that returns a menu-item drop-down form for entering search/view/sort arguments.
The variable it resolves is
This widget is designed to be used in conjunction with the Queryable
converter, although implementations
can process the query parameters themselves if they wish to do so by using the RequestQueryParams.getSearchArgs()
method to retrieve the arguments and process the data themselves.
An example of this widget can be found in the
It renders the following popup-box:
Tooltips are provided by hovering over the field names.
When submitted, the form submits a GET request against the current URI with special GET search API query parameters.
(e.g. Queryable
class knows how to perform these filters against collections of POJOs.
ThemeMenuItem
ThemeMenuItem
is a predefined Widget that returns back a list of hyperlinks for rendering the contents of a page in the various default styles.
The variable it resolves is
An example of this widget can be found in the
PoweredByJuneau
PoweredByJuneau
is a predefined Widget that places a powered-by-Juneau message on a page.
The variable it resolves is
It produces a simple Apache Juneau icon floating on the right.
Typically it's used in the footer of the page, as shown below in the
It renders the following image:
Tooltip
Tooltip
is a predefined template for adding tooltips to HTML5 bean constructs, typically in menu item widgets.
The following examples shows how tooltips can be added to a menu item widget.
The HTML views of POJOs can somewhat be considered a rudimentary User Interface. In reality, a better term for them would be a Developer Interface as they're meant to be used primarily by developers and not end users. Despite that distinction, it is possible to 'brand' the HTML page to whatever you desire.
The sample root page below includes some default branding for Juneau and Apache:
http://localhost:10000/helloWorld
The Juneau REST framework does not provide specific branding support (i.e. there is no concept of a brand icon). Instead, it just uses the existing open-ended API for defining branding via annotations on your REST classes.
The default annotation values use $C
variables to pull in values from an optional
external configuration file such as the one shown below:
The take-away here is that the "User Interface" is open-ended, lets you define pretty much anything you want through arbitrary HTML, and allows you either hardcode your interface inside annotations or pull them in via string variables from other places such as external config files.
8.17.5 - Stylesheetsupdated: 8.1.0,9.0.0
The sample root page renders in the default "devops" look-and-feel:
http://localhost:10000
The sample root page provides a dropdown widget to try out the other default look-and-feels:
For example, the "light" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Flight.css
And the "dark" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Fdark.css
The stylesheet URL is controlled by the @HtmlDocConfig(stylesheet)
annotation.
The BasicUniversalConfig
interface defines the stylesheet served up as a static file:
@HtmlDocConfig (
stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}"
)
public interface BasicUniversalConfig {...}
The "$C{REST/theme,servlet:/htdocs/themes/devops.css}" variable says to use the URI defined
in your servlet's config file, if there is one, and to default to serving up the file
htdocs/themes/devops.css which is in the resource folder of the juneau-rest-server module.
To provide your own stylesheet, simply override the stylesheet attribute and point to a different
file:
@HtmlDocConfig (
stylesheet="servlet:/htdocs/themes/my-style.css"
)
public class MyResourceBaseClass extends BasicRestServlet {...}
You can try out different stylesheets by passing in a stylesheet attribute in the request
URL.
The example above show this in use.
In case you're curious about how the menu item works, it's defined via a widget:
@Rest (...)
@HtmlDocConfig (
widgets={
PoweredByApache.class ,
ContentTypeMenuItem.class ,
StyleMenuItem.class
},
navlinks={
"options: ?method=OPTIONS" ,
"$W{ContentTypeMenuItem}" ,
"$W{StyleMenuItem}" ,
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
}
)
public class RootResources extends BasicRestServletGroup {...}
The StyleMenuItem is a widget that extends from MenuItemWidget
, a
specialized widget for creating pop-up menus.
In the case of StyleMenuItem , it's simply returning a list of links wrapped in a div tag:
import static org.apache.juneau.dto.html5.HtmlBuilder.*;
public class StyleMenuItem extends MenuItemWidget {
private static final String[] BUILT_IN_STYLES = {"devops" , "light" , "original" , "dark" };
@Override /* Widget */
public String getLabel(RestRequest req ) {
return "styles" ;
}
@Override /* MenuItemWidget */
public Div getContent(RestRequest req ) throws Exception {
Div div = div ();
for (String style : BUILT_IN_STYLES ) {
java.net.URI uri = req .getUri(true , new AMap<String,String>().append("stylesheet" , "styles/" +s+".css" ));
div .children(a (uri , style ), br ());
}
return div ;
}
}
The sample root page renders in the default "devops" look-and-feel:
http://localhost:10000
The sample root page provides a dropdown widget to try out the other default look-and-feels:
For example, the "light" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Flight.css
And the "dark" look-and-feel:
http://localhost:10000/?stylesheet=styles%2Fdark.css
The stylesheet URL is controlled by the @HtmlDocConfig(stylesheet)
annotation.
The BasicUniversalConfig
interface defines the stylesheet served up as a static file:
The
To provide your own stylesheet, simply override the stylesheet attribute and point to a different file:
You can try out different stylesheets by passing in a
In case you're curious about how the menu item works, it's defined via a widget:
The MenuItemWidget
, a
specialized widget for creating pop-up menus.
In the case of
8.18 - Logging / Debuggingcreated: 9.0.0
The REST APIs provides supports enabling logging of HTTP requests and responses through the following annotations:
Debug mode enables the following:
- HTTP request/response bodies are cached in memory for logging purposes.
-
HTTP requests/responses are logged to the registered
CallLogger
.
The possible annotation values are:
"true" - Debug is enabled for all requests."false" - Debug is disabled for all requests."conditional" - Debug is enabled only for requests that have aDebug: true header.
Example
These annotations support SVL variables, so it's possible to define them as a system property for example.
The @Rest(debugOn)
annotation can also be used
to enable debugging. It takes a comma-delimited list of key-value pairs, the keys
being class or method names, and the values being one of
The primary advantage of @Rest(debugOn)
is that
you can control debugging externally such as through a system property or environment variable:
Debugging can also be enabled programmatically with the use of the following APIs:
HTTP calls can be logged with the following levels of granularity:
The following examples show the output format for each detail type:
STATUS_LINE
WARNING: [500] HTTP POST /foo?foo=bar
HEADER
WARNING: === HTTP Call (incoming) =================================================== [500] HTTP POST /foo?foo=bar Request length: 3 bytes Response code: 500 Response length: 3 bytes Exec time: 20ms ---Request Headers--- Foo: bar ---Response Headers--- Foo: bar Content-Type: text/plain === END ===================================================================
ENTITY
WARNING: === HTTP Call (incoming) =================================================== [500] HTTP POST /foo?foo=bar Request length: 3 bytes Response code: 500 Response length: 3 bytes Exec time: 20ms ---Request Headers--- Foo: bar ---Response Headers--- Foo: bar Content-Type: text/plain ---Request Content UTF-8--- Foo ---Request Content Hex--- 46 6F 6F ---Response Content UTF-8--- Foo ---Response Content Hex--- 46 6F 6F === END ===================================================================
The interface responsible for generating the log entries is CallLogger
and the default is BasicCallLogger
which produces the output above. It
can be changed through any of the following:
The call logger uses logging rules to map requests to logging detail levels. By default, these are the logging rules:
RestLogger
.
Thrown exceptions get logged with a stack trace hash and a counter like below:
WARNING: [500,9b85cc96.13] HTTP POST /foo?foo=bar
Stack trace hashes are controlled by the ThrownStore
bean which is configured
via the following:
- The
BasicTestCallLogger
class is useful for testing and allows you to suppress logging when testing error conditions by passing in anoTrace=true query parameter orNo-Trace: true header on requests. DebugEnablement
,CallLogger
, andThrownStore
can all be defined globally as Spring beans in a Spring Boot environment.
8.19 - HTTP Status Codesupdated: 9.0.0
By default, a 200 (OK) status is automatically set as the HTTP status when a Java method executes successfully.
Other status codes can be generated by throwing a BasicHttpException
with a
specific HTTP status code, or calling HttpServletResponse.setStatus(int)
.
Non-OK (200) status codes are automatically triggered by the following conditions:
Unauthorized | A guard prevented the method from being executed |
|
Not Found | No matching path patterns were found on any method | |
Method Not Implemented | A path pattern matched but no Java methods were found for the HTTP method | |
Not Acceptable |
A path pattern matched but no Java methods were found with a matching serializer for the
|
|
Precondition Failed |
A path pattern matched but no Java methods were found that were not rejected by
matchers
|
|
Unsupported Media Type |
A path pattern matched but no Java methods were found with a matching parser for the
|
|
Internal Server Error | The Java method threw an exception other than BasicHttpException |
8.20 - Built-in Parametersupdated: 9.0.0
The following URL parameters have special meaning and can be passed in through the URL of the request:
&plainText=true |
Response will always be WriterSerializer.Builder.useWhitespace() enabled).
Useful for debugging. |
&debug=true | Enable debug mode for request. |
&noTrace=true |
If an error occurs, don't log the stack trace to the log file.
Useful for automated JUnit testcases testing error states to prevent the log file from filling up with useless stack traces. |
&method=X |
Overload the HTTP method as a GET parameter (e.g Must be enabled via @Rest(allowedMethodParams) setting.
|
&Header-Name=headerValue |
Specify a header value as a GET parameter.
Must be enabled via @Rest(allowedHeaderParams) setting.
|
&body=X |
Pass in the HTTP body content on PUT and POST methods as a UON-encoded GET parameter.
Can be disabled via @Rest(disableContentParam) setting.
|
&x-response-headers=X |
Pass-through headers to the response.
Must be a UON-encoded map of key-value pairs. |
8.21 - Using with OSGi
Since REST servlets are basically just
The following code shows how to register your REST servlets in an OSGi
8.22 - RestContextcreated: 9.0.0
The RestContext
object is the workhorse class for all of the configuration
of a single REST resource class. It's by-far the most important class in the REST API.
Every class annotated with
The RestContext.Builder
class extends BeanContext.Builder
allowing you to programmatically set any properties defined on that builder class.
It also implements ServletConfig
To access this object, simply pass it in as a constructor argument or in an INIT hook:
This class is vast. Combined with RestOpContext
(which is the equivalent per-method context), these classes
define the entire configuration and workflow of the REST API.
There are multiple ways to programmatically alter how RestContext behaves. The most straightforward are the following
builder methods which are direct equivalents to values defined on the Rest
annotation:
RestContext.Builder
allowedHeaderParams
allowedMethodHeaders
allowedMethodParams
clientVersionHeader
child
children
config
consumes
debugDefault
defaultAccept
defaultCharset
defaultClasses
defaultContentType
defaultRequestAttributes
defaultRequestHeaders
defaultResponseHeaders
defaultSetting
disableContentParam
logger
maxInput
path
parserListener
produces
renderResponseStackTraces
restOpArgs
serializerListener
swaggerProvider
uriAuthority
uriContext
uriRelativity
uriResolution
For more complex configurations, access to sub-builders is provided via the following methods:
RestContext.Builder
callLogger
config
consumes
debugEnablement
defaultClasses
defaultRequestAttributes
defaultRequestHeaders
defaultResponseHeaders
defaultSettings
encoders
jsonSchemaGenerator
logger
messages
methodExecStore
parsers
partParser
partSerializer
produces
responseProcessors
restOpArgs
rootBeanStore
serializers
staticFiles
swaggerProvider
thrownStore
varResolver
- The builders or built objects above can also be defined as injected beans defined in a Spring Configuration if you wish to do all your app configuration Spring-style. This is described in detail in the juneau-rest-server-springboot documentation.
The programmatic equivalent to the annotated lifecycle methods are below:
-
It is also possible to override methods on the
RestContext
class itself by providing your own specialized subclass via theRestContext.Builder.type(Class)
method.
8.23 - RestOpContextcreated: 9.0.0
The RestOpContext
object is the workhorse class for an individual RestOp
-annotated method.
Every class annotated with RestContext
, the object is read-only and unchangeable and is
initialized with all of the various annotations pulled from the method. All functionality available through annotations
have programmatic equivalents through the builder of this class.
To access the builder for these objects, simply implement the following init method that will be called for each RestOp
-annotated method.
There are multiple ways to programmatically alter how RestOpContext behaves. The most straightforward are the following
builder methods which are direct equivalents to values defined on the RestOp
annotation:
For more complex configurations, access to sub-builders is provided via the following methods:
-
It is also possible to override methods on the
RestOpContext
class itself by providing your own specialized subclass via theRestOpContext.Builder.type(Class)
method.
8.24 - Response Processorscreated: 9.0.0
The REST Server API uses the concept of registered response processors for converting objects returned by REST
methods or set through RestResponse.setContent(Object)
into appropriate HTTP responses.
By default, REST resource classes are registered with the following response processors:
HttpEntityProcessor
HttpResourceProcessor
HttpResponseProcessor
InputStreamProcessor
PlainTextPojoProcessor
ReaderProcessor
ResponseBeanProcessor
SerializedPojoProcessor
ThrowableProcessor
Custom response processors can be associated with REST resources via the following:
Response processors can be used to process POJOs that cannot normally be handled through Juneau serializers, or because it's simply easier to define response processors for special cases.
The following example shows how to create a response processor to handle special
8.25 - REST/RPCupdated: 8.0.0,9.0.0
The REST/RPC (RPC over REST) API allows the creation of client-side remote proxy interfaces for calling methods on server-side POJOs using entirely REST.
- This is not to be confused with REST Proxies which are entirely client-side driven Java interfaces against arbitrary backend REST interfaces.
Remote Interfaces
The following example shows a remote interface:
The requirements for a remote interface method are:
- Must be public.
- Can be called anything.
- Can have any number of serializable and parsable parameters.
- Can return a serializable and parsable value.
-
Can throw any
Throwables .
Throwables with public no-arg or single-arg-string constructors are automatically recreated on the client side when thrown on the server side.
Client side
Remote Interface proxies are instantiated on the client side using one of the following methods:
Since we build upon the existing
Here's an example of the above interface being used:
Under the covers, this method call gets converted to a REST POST.
HTTP POST http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/createPerson(org.apache.juneau.examples.addressbook.Person) Accept: application/json Content-Type: application/json [ { "name":"John Smith", "birthDate":"Aug 1, 1999", "addresses":[ { "street":"My street", "city":"My city", "state":"My state", "zip":12345, "isCurrent":true } ] } ]
Note that the body of the request is an array. This array contains the serialized arguments of the method. The object returned by the method is then serialized as the body of the response.
Server side
There are two ways to expose remote interfaces on the server side:
-
Extending from
RrpcServlet
. -
Using a
annotation on a Java method.@RestOp (method=RRPC )
In either case, the proxy communications layer is pure REST. Therefore, in cases where the interface classes are not available on the client side, the same method calls can be made through pure REST calls. This can also aid significantly in debugging, since calls to the remote interface service can be made directly from a browser with no coding involved.
RrpcServlet
The RrpcServlet
class is a simple specialized servlet with an abstract
@RestOp(method=RRPC)
The
RrpcServlet in a browser
If you point your browser to the servlet above, you get a list of available interfaces:
http://localhost:10000/remote
Clicking the hyperlinks on each shows you the list of methods that can be invoked on that service.
Note that the
IAddressBook
http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook
Since
AddressBook
http://localhost:10000/remote/org.apache.juneau.examples.addressbook.AddressBook
Let's see how we can interact with this interface through nothing more than REST calls to get a better idea on how this works. We'll use the same method call as in the introduction. First, we need to create the serialized form of the arguments:
Object[]
That produces the following JSON output:
[
{
name:
Note that in this example we're using JSON. However, various other content types can also be used such as XML, URL-Encoding, UON, or HTML. In practice however, JSON will preferred since it is often the most efficient.
Next, we can use a tool such as Poster to make the REST call. Methods are invoked by POSTing the serialized object array to the URI of the interface method. In this case, we want to POST our JSON to the following URL:
http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/createPerson(org.apache.juneau.examples.addressbook.CreatePerson)
Make sure that we specify the
When we execute the POST, we should see the following successful response whose body contains the returned
From there, we could use the following code snippet to reconstruct the response object from JSON:
String response =
If we alter our servlet to allow overloaded GET requests, we can invoke methods using nothing more than a browser...
For example, to invoke the
http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/getPeople?method=POST
Here we call the
http://localhost:10000/remote/org.apache.juneau.examples.addressbook.IAddressBook/findPerson(int)?method=POST&body=@(3)
When specifying the POST body as a UonSerializer
for more information about this encoding.
Usually you can also pass in JSON if you specify
The hyperlinks on the method names above lead you to a simple form-entry page where you can test passing parameters in UON notation as URL-encoded form posts.
Sample form entry page
Sample form entry page results
8.26 - Serializing URIsupdated: 9.0.0
As mention earlier here, Juneau serializers have sophisticated support for transforming relative URIs to absolute form.
The following example shows a REST method that returns a list of URIs of various forms:
When requested as JSON, it produces the following result:
{
URI resolution is controlled by the following settings:
URIs are resolved by both regular and part serializers.
8.27 - Utility Beanscreated: 9.0.0
The org.apache.juneau.rest.beans
package contains a set of reusable utility beans meant to help with putting together
explorable REST interfaces.
The UtilityBeansResource
class shows how these beans are used.
The resource class is hosted in the example REST applications rendered below:
ResourceDescriptions
The getChildDescriptions()
method shows an example
of rendering a list of descriptive links for child endpoints.
HTML representation
JSON representation
BeanDescription
The aBeanDescription()
method shows an example
of rendering simple schema information about an arbitrary bean class.
HTML representation
JSON representation
Hyperlink
The aHyperlink()
method shows an example
of rendering a simple hyperlink.
HTML representation
JSON representation
SeeOtherRoot
The aSeeOtherRoot()
method shows an example
of sending a
Clicking on the link will just redirect to this same page.
Typically this is useful for endpoints where you want to redirect back to the servlet root, such as a DELETE.
8.28 - Using with HTML Beanscreated: 9.0.0
The HtmlBeansResource
class shows how HTML5 beans
can be
used to generate arbitrary HTML on REST endpoints.
table
The aTable()
method shows an example
of rendering an HTML table.
HTML representation
JSON representation
div
The aDiv()
method shows an example
of rendering a div tag with mixed content.
HTML representation
JSON representation
form
The aForm()
method shows an example
of rendering an HTML form.
HTML representation
JSON representation
8.29 - Other Notes
-
Subclasses can use either
HttpServlet.init(ServletConfig)
orGenericServlet.init()
for initialization just like any other servlet. -
The
X-Response-Headers header can be used to pass through header values into the response. The value should be a URL-encoded map of key-value pairs. For example, to add a"Refresh: 1" header to the response to auto-refresh a page, the following parameter can be specified:"/sample?X-Response-Headers={Refresh=1}"
9 - juneau-rest-server-springbootcreated: 8.0.0, updated: 9.0.0
Maven Dependency
Java Library
juneau-rest-server-springboot-
OSGi Module
org.apache.juneau.rest.server.springboot_
The
9.1 - Overviewcreated: 8.0.0, updated: 9.0.0
The Juneau REST servlet APIs are designed to work seemlessly with the Spring Boot framework. The only restriction is that your top-level REST resource must extend from one of the following classes:
org.apache.juneau.rest.springboot
BasicSpringRestServlet
- Basic servletBasicSpringRestServletGroup
- Basic servlet group
These classes are the equivalent to the BasicRestServlet
and BasicRestServletGroup
except they hook into the injection framework of Spring Boot to provide resolution of beans (e.g. child resources, various
configuration classes).
The
SpringRestServlet
to allow use of injection.
// All REST objects are attached to this bean using the Rest.children()
annotation.
Our root resource servlet serves as a router page. It is defined as follows:
HTML representation
JSON representation
The
Note that the message rendered is coming from our injected message provider:
HTML representation
10 - juneau-rest-clientupdated: 9.0.0
Maven Dependency
Java Library
juneau-rest-client-
OSGi Module
org.apache.juneau.rest.client_
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:
Breaking apart the fluent call, we can see the classes being used:
RestClient.Builder
It additionally provides support for creating remote proxy interfaces using REST as the transport medium.
Example:
The classes are closely tied to Apache HttpClient, yet provide lots of additional functionality:
RestClient
implements HttpClient
, createsRestRequest
objects.RestRequest
implements HttpUriRequest
, createsRestResponse
objects.RestResponse
implements HttpResponse
, createsResponseContent
andResponseHeader
objects.ResponseContent
implements HttpEntity
Instances of this class are built using the RestClient.Builder
class which can be constructed using
the RestClient.create()
method as shown above.
Clients are typically created with a root URI so that relative URIs can be used when making requests.
This is done using the RestClient.Builder.rootUrl(Object)
method.
Example:
The RestClient
class creates RestRequest
objects using the following methods:
The RestRequest
class creates RestResponse
objects using the following methods:
The distinction between the two methods is that complete()
automatically consumes the response body and
run()
does not. Note that you must consume response bodies in order for HTTP connections to be freed up
for reuse! The InputStreams
returned by the ResponseContent
object are auto-closing once
they are exhausted, so it is often not necessary to explicitly close them.
The following examples show the distinction between the two calls:
10.1 - POJO Marshallingcreated: 8.2.0, updated: 9.0.0
By default, JSON support is provided for HTTP request and response bodies. Other languages can be specified using any of the following builder methods:
Example:
Clients can also support multiple languages:
Example:
When using clients with multiple language support, the request language is selected by setting the
Languages can also be specified per-request.
The RestClient.Builder
class provides convenience methods for setting common serializer and parser
settings.
Example:
Other methods are also provided for specifying the serializers and parsers used for lower-level marshalling support:
HTTP parts (headers, query parameters, form data...) are serialized and parsed using the HttpPartSerializer
and HttpPartParser
APIs. By default, clients are configured to use OpenApiSerializer
and
OpenApiParser
. These can be overridden using the following methods:
10.2 - Request Partscreated: 8.2.0, updated: 9.0.0
Per-client or per-request HTTP parts (headers, query/form data, path parameters) can be manipulated via the following methods that return back builders for those parts:
Convenience methods are also provided for quickly adding parts:
RestClient.Builder
header(String,String)
header(String,Supplier<String>)
headers(Header...)
headersDefault(Header...)
formData(NameValuePair...)
formData(String,String)
formData(String,Supplier<String>)
formDataDefault(NameValuePair...)
queryData(NameValuePair...)
queryData(String,String)
queryData(String,Supplier<String>)
queryDataDefault(NameValuePair...)
pathData(NameValuePair...)
pathData(String,String)
pathData(String,Supplier<String>)
pathDataDefault(NameValuePair...)
RestRequest
header(Header)
header(String,Object)
headerPairs(String...)
headers(Header...)
headersBean(Object)
formData(NameValuePair...)
formData(String,Object)
formDataBean(Object)
formDataCustom(Object)
formDataPairs(String...)
queryData(NameValuePair...)
queryData(String,Object)
queryDataBean(Object)
queryDataPairs(String...)
pathData(NameValuePair...)
pathData(String,Object)
pathDataBean(Object)
pathDataPairs(String...)
Example:
The supplier methods are particularly useful for header values whose values may change over time (such as
Example:
org.apache.juneau.http.header
- PredefinedHeader
beans.
10.3 - Request Contentcreated: 8.2.0, updated: 9.0.0
The request body can either be passed in with the client creator method (e.g. post(uri,body)
),
or can be specified via the following methods:
The request body can be any of the following types:
-
Object
- POJO to be converted to text using theSerializer
defined on the client or request. -
Reader
- Raw contents ofReader
will be serialized to remote resource. -
InputStream
- Raw contents ofInputStream
will be serialized to remote resource. -
HttpEntity
- Bypass Juneau serialization and pass HttpEntity directly to HttpClient. -
PartList
- Converted to a URL-encoded FORM post. -
Supplier
- A supplier of anything on this list.
Examples:
- If the serializer on the client or request is explicitly set to
null , POJOs will be converted to strings using the registered part serializer as content type"text/plain . If the part serializer is alsonull , POJOs will be converted to strings usingClassMeta.toString(Object)
which typically just callsObject.toString()
.
10.4 - Response Statuscreated: 8.1.0, updated: 9.0.0
After execution using RestRequest.run()
or RestRequest.complete()
, the following methods can be used
to get the response status:
RestResponse
getStatusLine()
returns StatusLine
getStatusCode()
returns int getReasonPhrase()
returns StringassertStatus()
returns FluentResponseStatusLineAssertion
Example:
Equivalent methods with mutable parameters are provided to allow access to status values without breaking fluent call chains.
Example:
- If you are only interested in the response status and not the response body, be sure to use
RestRequest.complete()
instead ofRestRequest.run()
to make sure the response body gets automatically cleaned up. Otherwise you must consume the response yourself.
The assertion method is provided for quickly asserting status codes in fluent calls.
Example:
10.5 - Response Headerscreated: 8.2.0, updated: 9.0.0
Response headers are accessed through the following methods:
RestResponse
getHeader(String)
returns ResponseHeader
getHeaders(String)
returns ResponseHeader
[]getFirstHeader(String)
returns ResponseHeader
getLastHeader(String)
returns ResponseHeader
getAllHeaders()
returns ResponseHeader
[]getStringHeader(String)
returns StringcontainsHeader(String)
returns boolean
Unlike RestResponse.getFirstHeader(String)
and RestResponse.getLastHeader(String)
, the RestResponse.getHeader(String)
method returns an empty ResponseHeader
object instead of returning
Example:
RestResponse
The ResponseHeader
class extends from the HttpClient Header
class and provides several convenience
methods:
ResponseHeader
isPresent()
returns boolean asString()
returns Stringas(Type,Type...)
returns Tas(Class<T>)
returns TasMatcher(Pattern)
returns Matcher
asMatcher(String)
returns Matcher
asHeader(Class<T
extends BasicHeader> c)returns BasicHeader
asStringHeader()
returns BasicIntegerHeader
asIntegerHeader()
returns BasicStringHeader
asLongHeader()
returns BasicLongHeader
asDateHeader()
returns BasicDateHeader
asCsvHeader()
returns BasicCsvHeader
asEntityTagsHeader()
returns BasicEntityTagsHeader
asStringRangesHeader()
returns BasicStringRangesHeader
asUriHeader()
returns BasicUriHeader
The ResponseHeader.schema(HttpPartSchema)
method allows you to perform parsing of OpenAPI formats for
header parts.
Example:
Assertion methods are also provided for fluent-style calls:
ResponseHeader
assertString()
returns FluentStringAssertion
assertInteger()
returns FluentIntegerAssertion
assertLong()
returns FluentLongAssertion
assertZonedDateTime()
returns FluentZonedDateTimeAssertion
Note how in the following example, the fluent assertion returns control to the RestResponse
object after
the assertion has been completed:
Example:
10.6 - Response Contentcreated: 8.2.0, updated: 9.0.0
The response body is accessed through the following method:
RestResponse
getContent()
returns ResponseContent
The ResponseContent
class extends from the HttpClient HttpEntity
class and provides several convenience
methods:
ResponseContent
asInputStream()
returns InputStreamasReader()
returns ReaderasReader(Charset)
returns ReaderpipeTo(OutputStream)
returns RestResponse
pipeTo(Writer)
returns RestResponse
as(Type,Type...)
returns Tas(Class<T>)
returns TasFuture(Class<T>)
returns Future<T>asFuture(Type,Type...)
returns Future<T>asString()
returns StringasStringFuture()
returns Future<String>asAbbreviatedString(int)
returns StringasMatcher(Pattern)
returns Matcher
asMatcher(String)
returns Matcher
Examples:
The response body can only be consumed once unless it has been cached into memory. In many cases, the body is
automatically cached when using the assertions methods or methods such as ResponseContent.asString()
.
However, methods that involve reading directly from the input stream cannot be called twice.
In these cases, the RestResponse.cacheContent()
and ResponseContent.cache()
methods are provided
to cache the response body in memory so that you can perform several operations against it.
Assertion methods are also provided for fluent-style calls:
Example:
Object assertions allow you to parse the response body into a POJO and then perform various tests on that resulting POJO.
Example:
10.7 - Custom Call Handlerscreated: 8.2.0, updated: 9.0.0
The RestCallHandler
interface provides the ability to provide custom handling of requests.
RestClient.Builder
RestCallHandler
run(HttpHost,HttpRequest,HttpContext)
returns HttpResponse
Example:
Note that there are other ways of accomplishing this such as extending the RestClient
class and overriding
the RestClient.run(HttpHost,HttpRequest,HttpContext)
method
or by defining your own HttpRequestExecutor
. Using this interface is often simpler though.
10.8 - Interceptorscreated: 8.2.0
The RestCallInterceptor
API provides a quick way of intercepting and manipulating requests and responses beyond
the existing HttpRequestInterceptor
and HttpResponseInterceptor
APIs.
Example:
10.9 - REST Proxiescreated: 8.2.0, updated: 9.0.0
One of the more powerful features of the REST client class is the ability to produce Java interface proxies against arbitrary 3rd party REST resources.
The methods to retrieve remote interfaces are:
RestClient
getRemote(Class<T>)
returns TgetRemote(Class<T>,Object)
returns TgetRemote(Class<T>,Object,Serializer,Parser)
returns T
Annotations are used on the interface and interface methods to specify how to convert input and output to HTTP headers, query parameters, form post parameters, or request/response bodies.
Example:
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 E-Tag: 475588d4-0b27-4f56-9296-cc683251d314 { name: 'Fluffy', price: 9.99 }
The
Example:
10.9.1 - @Remoteupdated: 9.0.0
The @Remote
annotation is used on your interface class
to identify it as a REST proxy interface.
The @Remote annotation is optional but often included for code readability.
@Remote(path)
The @Remote(path)
annotation is used to define the
HTTP path of the REST service.
The path can be an absolute path to your REST service.
Example:
@Remote (path="http://localhost:10000/petstore" )
public interface PetStore {...}
PetStore store = client .getRemote(PetStore.class );
VarResolver.DEFAULT can also be used in the path.
Example:
// URL is specified via a system property.
@Remote (path="$S{PetStoreUrl}" )
public interface PetStore {...}
When a relative path is specified, it's relative to the root-url defined on the RestClient used to instantiate the interface.
Example:
@Remote (path="/petstore" )
public interface PetStore {...}
RestClient client = RestClient
.create ()
.json()
.rootUrl("http://localhost:10000" )
.build();
PetStore store = client .getRemote(PetStore.class );
When no path is specified, the root-url defined on the RestClient is used.
Example:
@Remote
public interface PetStore {...}
RestClient client = RestClient
.create ()
.json()
.rootUrl("http://localhost:10000/petstore" )
.build();
PetStore store = client .getRemote(PetStore.class );
@Remote(headers/headerList)
The @Remote(headers)
and @Remote(headerList)
annotations are used to add headers on all requests.
Example:
@Remote (
path="/petstore" ,
headers={
"Foo: bar" ,
"Baz: $S{bazProperty}"
},
headerList=MyHeaderList.class
)
public interface PetStore {...}
// Our dynamic supplier.
public class MyHeaderList extends HeaderList {
...
}
@Remote(version/versionHeader)
The @Remote(version)
and @Remote(versionHeader)
annotations are used to specify the client-side version of this interface that can be used on the server side
to perform version-specific handling.
Example:
@Remote (
path="/petstore" ,
version="1.2.3" // Adds "Client-Version: 1.2.3" header to all requests.
)
public interface PetStore {...}
This can be used in conjunction with the server-side client-versioning support.
// Call this method if Client-Version is at least 2.0.
// Note that this also matches 2.0.1.
@RestGet (clientVersion="2.0" )
public Object foo() {...}
// Call this method if Client-Version is at least 1.1 but less than 2.0.
@RestGet (clientVersion="[1.1,2.0)" )
public Object foo() {...}
// Call this method if Client-Version is less than 1.1.
@RestGet (clientVersion="[0,1.1)" )
public Object foo() {...}
10.9.2 - @RemoteOpupdated: 9.0.0
The @RemoteOp
annotation is applied to methods
of @Remote -annotated interfaces to identify REST endpoints.
Specialized sub-annotations are provided for common HTTP methods:
@RemoteOp(method/path)
The HTTP method and path are mapped to a Java method using the method and path annotations.
Example:
@Remote
public interface PetStore {
// GET /pets/{petId}
@RemoteGet ("/pets/{petId}" )
Pet getPet(@Path ("petId" ) int id );
}
The Java method name can be anything.
Inferred method/path
In such cases, method and path annotations are optional if you follow certain naming
conventions on your method that identify the method and path.
For example, the getPet method below defaults to GET /pet :
@Remote
public interface PetStore {
// GET /pet
@RemoteOp
Pet getPet(...);
}
In such cases, the @RemoteOp annotation is optional.
Method names matching the following pattern are assumed to be implying the HTTP method name:
(get|put|post|delete|options|head|connect|trace|patch).*
do(?i)(get|put|post|delete|options|head|connect|trace|patch)
Examples:
Java method name
Inferred HTTP method
Inferred HTTP path
getPet()
GET
/pet
get()
GET
/
postPet()
POST
/pet
fooPet()
[default]
/fooPet
doGet()
GET
/
doGET()
GET
/
doFoo()
[default]
/doFoo
@RemoteOp(returns)
The return type of the Java methods of can be any of the following:
-
void /Void
- Don't parse any response.
Note that the method will still throw a runtime exception if an error HTTP status is returned.
-
Any parseable POJO
- The body of the response will be converted to the POJO using the parser defined on the
RestClient based on the Content-Type of the response.
-
Any
@Response
-annotated type. (described later)
-
HttpResponse
- Returns the raw HttpResponse returned by the inner HttpClient .
-
Reader
- Returns access to the raw reader of the response.
-
InputStream
- Returns access to the raw input stream of the response.
-
A
Future
or CompletableFuture
of anything on this list.
If you're only interested in the HTTP status code of the response, you can use the returns
annotation with a value of STATUS
:
Example:
@Remote
public interface PetStore {
// POST /pets
// Returns HTTP status code.
@RemotePost (returns=STATUS )
int pets(...);
}
If your RestClient does not have a parser associated with it, then the value is converted directly from a String using
the rules defined in POJO Categories.
10.9.3 - @Contentupdated: 9.0.0
The @Content
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are the HTTP body of the request.
Examples:
// Used on parameter
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost ("/pets" )
String addPet(@Content Pet pet );
}
// Used on class
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost ("/pets" )
String addPet(Pet pet );
}
@Content
public class Pet {...}
The argument can be any of the following types:
-
Any serializable POJO - Converted to output using the
Serializer
registered with the RestClient .
Content-Type is set to that of the Serializer .
-
Reader
- Raw contents of Reader
will be serialized to remote resource.
Content-Type is set to "text/plain" .
-
InputStream
- Raw contents of InputStream
will be serialized to remote resource.
Content-Type is set to "application/octet-stream" .
-
PartList
- Converted to a URL-encoded FORM post.
Content-Type is set to "aplication/x-www-form-urlencoded" .
-
HttpEntity - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
OpenAPI schema based serialization can be used by using the OpenApiSerializer
class.
@RemotePost ("/comma-delimited-pipe-delimited-ints" )
String addCommaDelimitedPipeDelimitedInts(
@Content (
serializer=OpenApiSerializer.class ,
schema=@Schema (
type="array" ,
collectionFormat="pipes" ,
items=@Items (
type="array"
items=@SubItems (
type="int32" ,
// Auto-validates on client side!
minimum="0" ,
maximum="64"
)
)
)
)
int [][] input
);
// Same as above but using free-form schema.
// Format is simplified-JSON (outer {} brackets are optional).
@RemotePost ("/comma-delimited-pipe-delimited-ints" )
String addCommaDelimitedPipeDelimitedInts(
@Content (
serializer=OpenApiSerializer.class ,
schema=@Schema (
"type:'array',collectionFormat:'pipes',items:[type:'array',items:[type:'int32',minimum:0,maximum:64]]"
)
)
int [][] input
);
See OpenAPI Serializers for information about supported data types in OpenAPI serialization.
If your RestClient class does not have a serializer associated with it, the body will automatically be serialized to a
string using the rules defined in POJO Categories.
10.9.4 - @FormDataupdated: 9.0.0
The @FormData
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are form-data parameters on the request.
FormData
name
- Form data entry name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for form data parameters.
@RemotePost
String postParameters(
@FormData ("foo" ) String foo ,
@FormData ("bar" ) MyPojo pojo
);
// Multiple values pulled from a PartList object.
// Name "*" is inferred.
@RemotePost
String postPartList(@FormData PartList partList );
// Multiple values pulled from a Map.
@RemotePost
String postMap(@FormData Map<String,Object> map );
// Multiple values pulled from a bean.
@RemotePost
String postBean(@FormData MyBean bean );
// An entire form-data HTTP body as a String.
@RemotePost
String postString(@FormData String string );
// An entire form-data HTTP body as a Reader.
@RemotePost
String postReader(@FormData Reader reader );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @FormData(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
Reader
- Raw contents of Reader
will be serialized to remote resource.
-
InputStream
- Raw contents of InputStream
will be serialized to remote resource.
-
PartList
- Converted to a URL-encoded FORM post.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
CharSequence - Used directly as am "application/x-www-form-urlencoded" entity.
See the link below for information about supported data types in OpenAPI serialization.
10.9.5 - @Queryupdated: 9.0.0
The @Query
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are query parameters on the request.
Query
name
- Query parameter name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for query parameters.
@RemoteGet
String parameters(
@Query ("foo" ) String foo ,
@Query ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Query("*").
@RemoteGet
String partList(@Query PartList partList );
// Multiple values pulled from a Map.
// Same as @Query("*").
@RemoteGet
String map(@Query Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Query("*").
@RemoteGet
String bean(@Query MyBean myBean );
// An entire query string as a String.
// Same as @Query("*").
@RemoteGet
String string(@Query String string );
// An entire query string as a Reader.
// Same as @Query("*").
@RemoteGet
String reader(@Query Reader reader );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Query(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
Reader
- Raw contents of Reader
will be serialized directly a query string.
-
PartList
- Serialized as individual query parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
CharSequence - Serialized directly a query string.
See the link below for information about supported data types in OpenAPI serialization.
10.9.6 - @Headerupdated: 9.0.0
The @Header
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are header parameters on the request.
Header
name
- Header name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for HTTP headers.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1" )
String myProxyMethod1(@Header ("Foo" ) String foo ,
@Header ("Bar" ) MyPojo pojo );
// Multiple values pulled from a HeaderList object.
// Same as @Header("*").
@RemoteGet ("/mymethod2" )
String myProxyMethod2(@Header HeaderList headerList );
// Multiple values pulled from a Map.
// Same as @Header("*").
@RemoteGet ("/mymethod3" )
String myProxyMethod3(@Header Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Header("*").
@RemoteGet ("/mymethod4" )
String myProxyMethod4(@Header MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Header(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
HeaderList
- Serialized as individual headers.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.7 - @Pathupdated: 9.0.0
The @Path
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are path parameters on the request.
Path
name
- Path variable name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for path parameters.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1/{foo}/{bar}" )
String myProxyMethod1(@Path ("foo" ) String foo , @Path ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Path("*").
@RemoteGet ("/mymethod2/{foo}/{bar}/{baz}" )
String myProxyMethod2(@Path PartList partList );
// Multiple values pulled from a Map.
// Same as @Path("*").
@RemoteGet ("/mymethod3/{foo}/{bar}/{baz}" )
String myProxyMethod3(@Path Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Path("*").
@RemoteGet ("/mymethod4/{foo}/{bar}/{baz}" )
String myProxyMethod4(@Path MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Path(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
PartList
- Serialized as individual path parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.8 - @Requestupdated: 9.0.0
The @Request
annotation can be applied to a type of a @RemoteOp -annotated method
to identify it as a bean for setting HTTP parts through a bean-like interface.
Request
serializer
- Override the part serializer.
Example:
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost
String postPet(CreatePetRequest bean );
}
@Request
public class CreatePetRequest {
private CreatePet pet ;
public CreatePetRequest(String name , float price ) {
this .pet = new CreatePet(name , price );
}
@Content
public CreatePet getContent() {
return this .pet ;
}
@Query
public Map<String,Object> getQueryParams() {
return AMap.of ("debug" , true );
}
@Header ("E-Tag" )
public static UUID getUUID () {
return UUID.generatedUUID ();
}
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetRequest requestBean = new CreatePetRequest("Fluffy" , 9.99);
String response = store .postPet(requestBean);
The @Request annotation can be applied to either the class or argument.
The annotated methods must be no-arg and public.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly.
This means full support for OpenAPI serialization and validation.
Annotations on methods are inherited from parent classes and interfaces.
For example, the request bean above could have defined annotations in an interface to keep them clear from the implementation:
@Request
public interface CreatePetRequest {
@Content
CreatePet getContent();
@Query
Map<String,Object> getQueryParams();
@Header ("E-Tag" )
UUID getUUID();
}
public class CreatePetRequestImpl implements CreatePetRequest {
public CreatePetRequestImpl(String name , float price ) {...}
@Override
public CreatePet getContent() {
return this .pet ;
}
@Override
public Map<String,Object> getQueryParams() {
return JsonMap.of ("debug" , true );
}
@Override
public UUID getUUID() {
return UUID.generateUUID ();
}
}
10.9.9 - @Responseupdated: 9.0.0
The @Response
annotation can be applied to types returned by @RemoteOp -annotated methods.
The @Response annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
Example:
@Remote
public interface PetStore {
@RemotePost
CreatePetResponse postPet(...);
}
@Response
public interface CreatePetResponse {
@Content
Pet getContent();
@Header ("E-Tag" )
UUID getUUID();
@StatusCode
int getStatus();
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetResponse response = store .postPet(...);
Pet pet = response .getContent();
UUID uuid = response .getUUID();
int status = response .getStatus();
The annotated methods must be no-arg.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
10.9.10 - Dual-purpose (end-to-end) interfacescreated: 8.0.0
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as @RestOp and parameter-level annotations
such as @Query are inherited from parent classes.
This normally isn't possible but the framework will spider up the parent hierarchy of classes to find method and parameter level
annotations defined on overridden methods.
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
@Remote (path="/petstore" )
public interface PetStore {
@RemoteGet ("/pet" )
public Collection<Pet> getPets() throws NotAcceptable;
@RemotDelete ("/pet/{petId}" )
public Ok deletePet(
@Header (
name="api_key" ,
description="Security API key" ,
required=true ,
example="foobar"
)
String apiKey ,
@Path (
name="petId" ,
description="Pet id to delete" ,
example="123"
)
long petId
) throws IdNotFound, NotAcceptable;
...
Next you define the implementation of your interface as a normal Juneau REST resource:
@Rest (
path="/petstore" ,
title="Petstore application" ,
...
)
public class PetStoreResource extends BasicRestServlet implements PetStore {
...
@Override /* PetStore */
@RestOp (
method=GET ,
path="/pet" ,
summary="All pets in the store" ,
...
)
public Collection<Pet> getPets() throws NotAcceptable {
return store .getPets();
}
@Override /* PetStore */
@RestOp (
method=DELETE ,
path="/pet/{petId}" ,
summary="Deletes a pet" ,
...
)
public Ok deletePet(String apiKey , long petId ) throws IdNotFound, NotAcceptable {
store .removePet(petId );
return OK ;
}
Then use the interface as a remote resource like so:
RestClient client = RestClient.create ().json().rootUrl("http://localhost:10000" ).build();
PetStore store = client .getRemote(PetStore.class );
for (Pet pet : store .getPets()) {
store .deletePet("my-special-key" , pet .getId());
System.err .println("Deleted pet: id=" + pet .getId());
}
In the example above, we chose to add the @RestOp annotation to the implementation class.
However, they could have been added to the interface instead. It's personal preference where you want to place
the annotations.
Note how we didn't need to use the @Header and @Path annotations in our implementation since
the annotations were inherited from the interface.
The @Remote
annotation is used on your interface class
to identify it as a REST proxy interface.
The
@Remote(path)
The @Remote(path)
annotation is used to define the
HTTP path of the REST service.
The path can be an absolute path to your REST service.
Example:
PetStore
VarResolver.DEFAULT can also be used in the path.
Example:
When a relative path is specified, it's relative to the root-url defined on the
Example:
RestClient
When no path is specified, the root-url defined on the
Example:
RestClient
@Remote(headers/headerList)
The @Remote(headers)
and @Remote(headerList)
annotations are used to add headers on all requests.
Example:
@Remote(version/versionHeader)
The @Remote(version)
and @Remote(versionHeader)
annotations are used to specify the client-side version of this interface that can be used on the server side
to perform version-specific handling.
Example:
This can be used in conjunction with the server-side client-versioning support.
The @RemoteOp
annotation is applied to methods
of
Specialized sub-annotations are provided for common HTTP methods:
@RemoteOp(method/path)
The HTTP method and path are mapped to a Java method using the
Example:
The Java method name can be anything.
Inferred method/path
In such cases,
For example, the
In such cases, the
Method names matching the following pattern are assumed to be implying the HTTP method name:
(get|put|post|delete|options|head|connect|trace|patch).*
do(?i)(get|put|post|delete|options|head|connect|trace|patch)
Examples:
Java method name | Inferred HTTP method | Inferred HTTP path |
---|---|---|
getPet() | GET | /pet |
get() | GET | / |
postPet() | POST | /pet |
fooPet() | [default] | /fooPet |
doGet() | GET | / |
doGET() | GET | / |
doFoo() | [default] | /doFoo |
@RemoteOp(returns)
The return type of the Java methods of can be any of the following:
-
void /Void
- Don't parse any response.
Note that the method will still throw a runtime exception if an error HTTP status is returned. -
Any parseable POJO
- The body of the response will be converted to the POJO using the parser defined on the
RestClient based on theContent-Type of the response. -
Any
@Response
-annotated type. (described later) -
HttpResponse - Returns the rawHttpResponse returned by the innerHttpClient . -
Reader
- Returns access to the raw reader of the response. -
InputStream
- Returns access to the raw input stream of the response. -
A
Future
orCompletableFuture
of anything on this list.
If you're only interested in the HTTP status code of the response, you can use the returns
annotation with a value of STATUS
:
Example:
If your
10.9.3 - @Contentupdated: 9.0.0
The @Content
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are the HTTP body of the request.
Examples:
// Used on parameter
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost ("/pets" )
String addPet(@Content Pet pet );
}
// Used on class
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost ("/pets" )
String addPet(Pet pet );
}
@Content
public class Pet {...}
The argument can be any of the following types:
-
Any serializable POJO - Converted to output using the
Serializer
registered with the RestClient .
Content-Type is set to that of the Serializer .
-
Reader
- Raw contents of Reader
will be serialized to remote resource.
Content-Type is set to "text/plain" .
-
InputStream
- Raw contents of InputStream
will be serialized to remote resource.
Content-Type is set to "application/octet-stream" .
-
PartList
- Converted to a URL-encoded FORM post.
Content-Type is set to "aplication/x-www-form-urlencoded" .
-
HttpEntity - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
OpenAPI schema based serialization can be used by using the OpenApiSerializer
class.
@RemotePost ("/comma-delimited-pipe-delimited-ints" )
String addCommaDelimitedPipeDelimitedInts(
@Content (
serializer=OpenApiSerializer.class ,
schema=@Schema (
type="array" ,
collectionFormat="pipes" ,
items=@Items (
type="array"
items=@SubItems (
type="int32" ,
// Auto-validates on client side!
minimum="0" ,
maximum="64"
)
)
)
)
int [][] input
);
// Same as above but using free-form schema.
// Format is simplified-JSON (outer {} brackets are optional).
@RemotePost ("/comma-delimited-pipe-delimited-ints" )
String addCommaDelimitedPipeDelimitedInts(
@Content (
serializer=OpenApiSerializer.class ,
schema=@Schema (
"type:'array',collectionFormat:'pipes',items:[type:'array',items:[type:'int32',minimum:0,maximum:64]]"
)
)
int [][] input
);
See OpenAPI Serializers for information about supported data types in OpenAPI serialization.
If your RestClient class does not have a serializer associated with it, the body will automatically be serialized to a
string using the rules defined in POJO Categories.
10.9.4 - @FormDataupdated: 9.0.0
The @FormData
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are form-data parameters on the request.
FormData
name
- Form data entry name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for form data parameters.
@RemotePost
String postParameters(
@FormData ("foo" ) String foo ,
@FormData ("bar" ) MyPojo pojo
);
// Multiple values pulled from a PartList object.
// Name "*" is inferred.
@RemotePost
String postPartList(@FormData PartList partList );
// Multiple values pulled from a Map.
@RemotePost
String postMap(@FormData Map<String,Object> map );
// Multiple values pulled from a bean.
@RemotePost
String postBean(@FormData MyBean bean );
// An entire form-data HTTP body as a String.
@RemotePost
String postString(@FormData String string );
// An entire form-data HTTP body as a Reader.
@RemotePost
String postReader(@FormData Reader reader );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @FormData(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
Reader
- Raw contents of Reader
will be serialized to remote resource.
-
InputStream
- Raw contents of InputStream
will be serialized to remote resource.
-
PartList
- Converted to a URL-encoded FORM post.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
CharSequence - Used directly as am "application/x-www-form-urlencoded" entity.
See the link below for information about supported data types in OpenAPI serialization.
10.9.5 - @Queryupdated: 9.0.0
The @Query
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are query parameters on the request.
Query
name
- Query parameter name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for query parameters.
@RemoteGet
String parameters(
@Query ("foo" ) String foo ,
@Query ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Query("*").
@RemoteGet
String partList(@Query PartList partList );
// Multiple values pulled from a Map.
// Same as @Query("*").
@RemoteGet
String map(@Query Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Query("*").
@RemoteGet
String bean(@Query MyBean myBean );
// An entire query string as a String.
// Same as @Query("*").
@RemoteGet
String string(@Query String string );
// An entire query string as a Reader.
// Same as @Query("*").
@RemoteGet
String reader(@Query Reader reader );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Query(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
Reader
- Raw contents of Reader
will be serialized directly a query string.
-
PartList
- Serialized as individual query parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
CharSequence - Serialized directly a query string.
See the link below for information about supported data types in OpenAPI serialization.
10.9.6 - @Headerupdated: 9.0.0
The @Header
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are header parameters on the request.
Header
name
- Header name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for HTTP headers.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1" )
String myProxyMethod1(@Header ("Foo" ) String foo ,
@Header ("Bar" ) MyPojo pojo );
// Multiple values pulled from a HeaderList object.
// Same as @Header("*").
@RemoteGet ("/mymethod2" )
String myProxyMethod2(@Header HeaderList headerList );
// Multiple values pulled from a Map.
// Same as @Header("*").
@RemoteGet ("/mymethod3" )
String myProxyMethod3(@Header Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Header("*").
@RemoteGet ("/mymethod4" )
String myProxyMethod4(@Header MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Header(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
HeaderList
- Serialized as individual headers.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.7 - @Pathupdated: 9.0.0
The @Path
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are path parameters on the request.
Path
name
- Path variable name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for path parameters.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1/{foo}/{bar}" )
String myProxyMethod1(@Path ("foo" ) String foo , @Path ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Path("*").
@RemoteGet ("/mymethod2/{foo}/{bar}/{baz}" )
String myProxyMethod2(@Path PartList partList );
// Multiple values pulled from a Map.
// Same as @Path("*").
@RemoteGet ("/mymethod3/{foo}/{bar}/{baz}" )
String myProxyMethod3(@Path Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Path("*").
@RemoteGet ("/mymethod4/{foo}/{bar}/{baz}" )
String myProxyMethod4(@Path MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Path(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
PartList
- Serialized as individual path parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.8 - @Requestupdated: 9.0.0
The @Request
annotation can be applied to a type of a @RemoteOp -annotated method
to identify it as a bean for setting HTTP parts through a bean-like interface.
Request
serializer
- Override the part serializer.
Example:
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost
String postPet(CreatePetRequest bean );
}
@Request
public class CreatePetRequest {
private CreatePet pet ;
public CreatePetRequest(String name , float price ) {
this .pet = new CreatePet(name , price );
}
@Content
public CreatePet getContent() {
return this .pet ;
}
@Query
public Map<String,Object> getQueryParams() {
return AMap.of ("debug" , true );
}
@Header ("E-Tag" )
public static UUID getUUID () {
return UUID.generatedUUID ();
}
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetRequest requestBean = new CreatePetRequest("Fluffy" , 9.99);
String response = store .postPet(requestBean);
The @Request annotation can be applied to either the class or argument.
The annotated methods must be no-arg and public.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly.
This means full support for OpenAPI serialization and validation.
Annotations on methods are inherited from parent classes and interfaces.
For example, the request bean above could have defined annotations in an interface to keep them clear from the implementation:
@Request
public interface CreatePetRequest {
@Content
CreatePet getContent();
@Query
Map<String,Object> getQueryParams();
@Header ("E-Tag" )
UUID getUUID();
}
public class CreatePetRequestImpl implements CreatePetRequest {
public CreatePetRequestImpl(String name , float price ) {...}
@Override
public CreatePet getContent() {
return this .pet ;
}
@Override
public Map<String,Object> getQueryParams() {
return JsonMap.of ("debug" , true );
}
@Override
public UUID getUUID() {
return UUID.generateUUID ();
}
}
10.9.9 - @Responseupdated: 9.0.0
The @Response
annotation can be applied to types returned by @RemoteOp -annotated methods.
The @Response annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
Example:
@Remote
public interface PetStore {
@RemotePost
CreatePetResponse postPet(...);
}
@Response
public interface CreatePetResponse {
@Content
Pet getContent();
@Header ("E-Tag" )
UUID getUUID();
@StatusCode
int getStatus();
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetResponse response = store .postPet(...);
Pet pet = response .getContent();
UUID uuid = response .getUUID();
int status = response .getStatus();
The annotated methods must be no-arg.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
10.9.10 - Dual-purpose (end-to-end) interfacescreated: 8.0.0
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as @RestOp and parameter-level annotations
such as @Query are inherited from parent classes.
This normally isn't possible but the framework will spider up the parent hierarchy of classes to find method and parameter level
annotations defined on overridden methods.
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
@Remote (path="/petstore" )
public interface PetStore {
@RemoteGet ("/pet" )
public Collection<Pet> getPets() throws NotAcceptable;
@RemotDelete ("/pet/{petId}" )
public Ok deletePet(
@Header (
name="api_key" ,
description="Security API key" ,
required=true ,
example="foobar"
)
String apiKey ,
@Path (
name="petId" ,
description="Pet id to delete" ,
example="123"
)
long petId
) throws IdNotFound, NotAcceptable;
...
Next you define the implementation of your interface as a normal Juneau REST resource:
@Rest (
path="/petstore" ,
title="Petstore application" ,
...
)
public class PetStoreResource extends BasicRestServlet implements PetStore {
...
@Override /* PetStore */
@RestOp (
method=GET ,
path="/pet" ,
summary="All pets in the store" ,
...
)
public Collection<Pet> getPets() throws NotAcceptable {
return store .getPets();
}
@Override /* PetStore */
@RestOp (
method=DELETE ,
path="/pet/{petId}" ,
summary="Deletes a pet" ,
...
)
public Ok deletePet(String apiKey , long petId ) throws IdNotFound, NotAcceptable {
store .removePet(petId );
return OK ;
}
Then use the interface as a remote resource like so:
RestClient client = RestClient.create ().json().rootUrl("http://localhost:10000" ).build();
PetStore store = client .getRemote(PetStore.class );
for (Pet pet : store .getPets()) {
store .deletePet("my-special-key" , pet .getId());
System.err .println("Deleted pet: id=" + pet .getId());
}
In the example above, we chose to add the @RestOp annotation to the implementation class.
However, they could have been added to the interface instead. It's personal preference where you want to place
the annotations.
Note how we didn't need to use the @Header and @Path annotations in our implementation since
the annotations were inherited from the interface.
The @Content
annotation can be applied to arguments of
Examples:
The argument can be any of the following types:
-
Any serializable POJO - Converted to output using the
Serializer
registered with theRestClient .Content-Type is set to that of theSerializer . -
Reader
- Raw contents ofReader
will be serialized to remote resource.Content-Type is set to"text/plain" . -
InputStream
- Raw contents ofInputStream
will be serialized to remote resource.Content-Type is set to"application/octet-stream" . -
PartList
- Converted to a URL-encoded FORM post.Content-Type is set to"aplication/x-www-form-urlencoded" . -
HttpEntity - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
OpenAPI schema based serialization can be used by using the OpenApiSerializer
class.
See OpenAPI Serializers for information about supported data types in OpenAPI serialization.
If your
The @FormData
annotation can be applied to arguments of
FormData
name
- Form data entry name.serializer
- Override the part serializer.
Example:
Single-part arguments (i.e. those with name !=
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with theRestClient (OpenApiSerializer
by default) or associated via the@FormData(serializer)
annotation.
Multi-part arguments (i.e. those with name ==
-
Reader
- Raw contents ofReader
will be serialized to remote resource. -
InputStream
- Raw contents ofInputStream
will be serialized to remote resource. -
PartList
- Converted to a URL-encoded FORM post. -
Map - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
Bean - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
CharSequence - Used directly as am"application/x-www-form-urlencoded" entity.
See the link below for information about supported data types in OpenAPI serialization.
10.9.5 - @Queryupdated: 9.0.0
The @Query
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are query parameters on the request.
Query
name
- Query parameter name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for query parameters.
@RemoteGet
String parameters(
@Query ("foo" ) String foo ,
@Query ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Query("*").
@RemoteGet
String partList(@Query PartList partList );
// Multiple values pulled from a Map.
// Same as @Query("*").
@RemoteGet
String map(@Query Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Query("*").
@RemoteGet
String bean(@Query MyBean myBean );
// An entire query string as a String.
// Same as @Query("*").
@RemoteGet
String string(@Query String string );
// An entire query string as a Reader.
// Same as @Query("*").
@RemoteGet
String reader(@Query Reader reader );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Query(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
Reader
- Raw contents of Reader
will be serialized directly a query string.
-
PartList
- Serialized as individual query parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
CharSequence - Serialized directly a query string.
See the link below for information about supported data types in OpenAPI serialization.
10.9.6 - @Headerupdated: 9.0.0
The @Header
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are header parameters on the request.
Header
name
- Header name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for HTTP headers.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1" )
String myProxyMethod1(@Header ("Foo" ) String foo ,
@Header ("Bar" ) MyPojo pojo );
// Multiple values pulled from a HeaderList object.
// Same as @Header("*").
@RemoteGet ("/mymethod2" )
String myProxyMethod2(@Header HeaderList headerList );
// Multiple values pulled from a Map.
// Same as @Header("*").
@RemoteGet ("/mymethod3" )
String myProxyMethod3(@Header Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Header("*").
@RemoteGet ("/mymethod4" )
String myProxyMethod4(@Header MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Header(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
HeaderList
- Serialized as individual headers.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.7 - @Pathupdated: 9.0.0
The @Path
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are path parameters on the request.
Path
name
- Path variable name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for path parameters.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1/{foo}/{bar}" )
String myProxyMethod1(@Path ("foo" ) String foo , @Path ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Path("*").
@RemoteGet ("/mymethod2/{foo}/{bar}/{baz}" )
String myProxyMethod2(@Path PartList partList );
// Multiple values pulled from a Map.
// Same as @Path("*").
@RemoteGet ("/mymethod3/{foo}/{bar}/{baz}" )
String myProxyMethod3(@Path Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Path("*").
@RemoteGet ("/mymethod4/{foo}/{bar}/{baz}" )
String myProxyMethod4(@Path MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Path(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
PartList
- Serialized as individual path parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.8 - @Requestupdated: 9.0.0
The @Request
annotation can be applied to a type of a @RemoteOp -annotated method
to identify it as a bean for setting HTTP parts through a bean-like interface.
Request
serializer
- Override the part serializer.
Example:
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost
String postPet(CreatePetRequest bean );
}
@Request
public class CreatePetRequest {
private CreatePet pet ;
public CreatePetRequest(String name , float price ) {
this .pet = new CreatePet(name , price );
}
@Content
public CreatePet getContent() {
return this .pet ;
}
@Query
public Map<String,Object> getQueryParams() {
return AMap.of ("debug" , true );
}
@Header ("E-Tag" )
public static UUID getUUID () {
return UUID.generatedUUID ();
}
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetRequest requestBean = new CreatePetRequest("Fluffy" , 9.99);
String response = store .postPet(requestBean);
The @Request annotation can be applied to either the class or argument.
The annotated methods must be no-arg and public.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly.
This means full support for OpenAPI serialization and validation.
Annotations on methods are inherited from parent classes and interfaces.
For example, the request bean above could have defined annotations in an interface to keep them clear from the implementation:
@Request
public interface CreatePetRequest {
@Content
CreatePet getContent();
@Query
Map<String,Object> getQueryParams();
@Header ("E-Tag" )
UUID getUUID();
}
public class CreatePetRequestImpl implements CreatePetRequest {
public CreatePetRequestImpl(String name , float price ) {...}
@Override
public CreatePet getContent() {
return this .pet ;
}
@Override
public Map<String,Object> getQueryParams() {
return JsonMap.of ("debug" , true );
}
@Override
public UUID getUUID() {
return UUID.generateUUID ();
}
}
10.9.9 - @Responseupdated: 9.0.0
The @Response
annotation can be applied to types returned by @RemoteOp -annotated methods.
The @Response annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
Example:
@Remote
public interface PetStore {
@RemotePost
CreatePetResponse postPet(...);
}
@Response
public interface CreatePetResponse {
@Content
Pet getContent();
@Header ("E-Tag" )
UUID getUUID();
@StatusCode
int getStatus();
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetResponse response = store .postPet(...);
Pet pet = response .getContent();
UUID uuid = response .getUUID();
int status = response .getStatus();
The annotated methods must be no-arg.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
10.9.10 - Dual-purpose (end-to-end) interfacescreated: 8.0.0
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as @RestOp and parameter-level annotations
such as @Query are inherited from parent classes.
This normally isn't possible but the framework will spider up the parent hierarchy of classes to find method and parameter level
annotations defined on overridden methods.
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
@Remote (path="/petstore" )
public interface PetStore {
@RemoteGet ("/pet" )
public Collection<Pet> getPets() throws NotAcceptable;
@RemotDelete ("/pet/{petId}" )
public Ok deletePet(
@Header (
name="api_key" ,
description="Security API key" ,
required=true ,
example="foobar"
)
String apiKey ,
@Path (
name="petId" ,
description="Pet id to delete" ,
example="123"
)
long petId
) throws IdNotFound, NotAcceptable;
...
Next you define the implementation of your interface as a normal Juneau REST resource:
@Rest (
path="/petstore" ,
title="Petstore application" ,
...
)
public class PetStoreResource extends BasicRestServlet implements PetStore {
...
@Override /* PetStore */
@RestOp (
method=GET ,
path="/pet" ,
summary="All pets in the store" ,
...
)
public Collection<Pet> getPets() throws NotAcceptable {
return store .getPets();
}
@Override /* PetStore */
@RestOp (
method=DELETE ,
path="/pet/{petId}" ,
summary="Deletes a pet" ,
...
)
public Ok deletePet(String apiKey , long petId ) throws IdNotFound, NotAcceptable {
store .removePet(petId );
return OK ;
}
Then use the interface as a remote resource like so:
RestClient client = RestClient.create ().json().rootUrl("http://localhost:10000" ).build();
PetStore store = client .getRemote(PetStore.class );
for (Pet pet : store .getPets()) {
store .deletePet("my-special-key" , pet .getId());
System.err .println("Deleted pet: id=" + pet .getId());
}
In the example above, we chose to add the @RestOp annotation to the implementation class.
However, they could have been added to the interface instead. It's personal preference where you want to place
the annotations.
Note how we didn't need to use the @Header and @Path annotations in our implementation since
the annotations were inherited from the interface.
The @Query
annotation can be applied to arguments of
Query
name
- Query parameter name.serializer
- Override the part serializer.
Example:
Single-part arguments (i.e. those with name !=
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with theRestClient (OpenApiSerializer
by default) or associated via the@Query(serializer)
annotation.
Multi-part arguments (i.e. those with name ==
-
Reader
- Raw contents ofReader
will be serialized directly a query string. -
PartList
- Serialized as individual query parameters. -
Map - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
Bean - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
CharSequence - Serialized directly a query string.
See the link below for information about supported data types in OpenAPI serialization.
The @Header
annotation can be applied to arguments of
Header
name
- Header name.serializer
- Override the part serializer.
Example:
Single-part arguments (i.e. those with name !=
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with theRestClient (OpenApiSerializer
by default) or associated via the@Header(serializer)
annotation.
Multi-part arguments (i.e. those with name ==
-
HeaderList
- Serialized as individual headers. -
Map - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
Bean - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.7 - @Pathupdated: 9.0.0
The @Path
annotation can be applied to arguments of @RemoteOp -annotated methods
to denote that they are path parameters on the request.
Path
name
- Path variable name.
serializer
- Override the part serializer.
Example:
@Remote (path="/myproxy" )
public interface MyProxy {
// Explicit names specified for path parameters.
// pojo will be converted to UON notation (unless plain-text parts enabled).
@RemoteGet ("/mymethod1/{foo}/{bar}" )
String myProxyMethod1(@Path ("foo" ) String foo , @Path ("bar" ) MyPojo pojo );
// Multiple values pulled from a PartList object.
// Same as @Path("*").
@RemoteGet ("/mymethod2/{foo}/{bar}/{baz}" )
String myProxyMethod2(@Path PartList partList );
// Multiple values pulled from a Map.
// Same as @Path("*").
@RemoteGet ("/mymethod3/{foo}/{bar}/{baz}" )
String myProxyMethod3(@Path Map<String,Object> map );
// Multiple values pulled from a bean.
// Same as @Path("*").
@RemoteGet ("/mymethod4/{foo}/{bar}/{baz}" )
String myProxyMethod4(@Path MyBean myBean );
}
Single-part arguments (i.e. those with name != "*" ) can be any of the following types:
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with the
RestClient (OpenApiSerializer
by default) or associated via the @Path(serializer)
annotation.
Multi-part arguments (i.e. those with name == "*" or empty) can be any of the following types:
-
PartList
- Serialized as individual path parameters.
-
Map - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
-
Bean - Converted to key-value pairs.
Values serialized using the registered HttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
10.9.8 - @Requestupdated: 9.0.0
The @Request
annotation can be applied to a type of a @RemoteOp -annotated method
to identify it as a bean for setting HTTP parts through a bean-like interface.
Request
serializer
- Override the part serializer.
Example:
@Remote (path="/petstore" )
public interface PetStore {
@RemotePost
String postPet(CreatePetRequest bean );
}
@Request
public class CreatePetRequest {
private CreatePet pet ;
public CreatePetRequest(String name , float price ) {
this .pet = new CreatePet(name , price );
}
@Content
public CreatePet getContent() {
return this .pet ;
}
@Query
public Map<String,Object> getQueryParams() {
return AMap.of ("debug" , true );
}
@Header ("E-Tag" )
public static UUID getUUID () {
return UUID.generatedUUID ();
}
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetRequest requestBean = new CreatePetRequest("Fluffy" , 9.99);
String response = store .postPet(requestBean);
The @Request annotation can be applied to either the class or argument.
The annotated methods must be no-arg and public.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly.
This means full support for OpenAPI serialization and validation.
Annotations on methods are inherited from parent classes and interfaces.
For example, the request bean above could have defined annotations in an interface to keep them clear from the implementation:
@Request
public interface CreatePetRequest {
@Content
CreatePet getContent();
@Query
Map<String,Object> getQueryParams();
@Header ("E-Tag" )
UUID getUUID();
}
public class CreatePetRequestImpl implements CreatePetRequest {
public CreatePetRequestImpl(String name , float price ) {...}
@Override
public CreatePet getContent() {
return this .pet ;
}
@Override
public Map<String,Object> getQueryParams() {
return JsonMap.of ("debug" , true );
}
@Override
public UUID getUUID() {
return UUID.generateUUID ();
}
}
10.9.9 - @Responseupdated: 9.0.0
The @Response
annotation can be applied to types returned by @RemoteOp -annotated methods.
The @Response annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
Example:
@Remote
public interface PetStore {
@RemotePost
CreatePetResponse postPet(...);
}
@Response
public interface CreatePetResponse {
@Content
Pet getContent();
@Header ("E-Tag" )
UUID getUUID();
@StatusCode
int getStatus();
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetResponse response = store .postPet(...);
Pet pet = response .getContent();
UUID uuid = response .getUUID();
int status = response .getStatus();
The annotated methods must be no-arg.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
10.9.10 - Dual-purpose (end-to-end) interfacescreated: 8.0.0
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as @RestOp and parameter-level annotations
such as @Query are inherited from parent classes.
This normally isn't possible but the framework will spider up the parent hierarchy of classes to find method and parameter level
annotations defined on overridden methods.
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
@Remote (path="/petstore" )
public interface PetStore {
@RemoteGet ("/pet" )
public Collection<Pet> getPets() throws NotAcceptable;
@RemotDelete ("/pet/{petId}" )
public Ok deletePet(
@Header (
name="api_key" ,
description="Security API key" ,
required=true ,
example="foobar"
)
String apiKey ,
@Path (
name="petId" ,
description="Pet id to delete" ,
example="123"
)
long petId
) throws IdNotFound, NotAcceptable;
...
Next you define the implementation of your interface as a normal Juneau REST resource:
@Rest (
path="/petstore" ,
title="Petstore application" ,
...
)
public class PetStoreResource extends BasicRestServlet implements PetStore {
...
@Override /* PetStore */
@RestOp (
method=GET ,
path="/pet" ,
summary="All pets in the store" ,
...
)
public Collection<Pet> getPets() throws NotAcceptable {
return store .getPets();
}
@Override /* PetStore */
@RestOp (
method=DELETE ,
path="/pet/{petId}" ,
summary="Deletes a pet" ,
...
)
public Ok deletePet(String apiKey , long petId ) throws IdNotFound, NotAcceptable {
store .removePet(petId );
return OK ;
}
Then use the interface as a remote resource like so:
RestClient client = RestClient.create ().json().rootUrl("http://localhost:10000" ).build();
PetStore store = client .getRemote(PetStore.class );
for (Pet pet : store .getPets()) {
store .deletePet("my-special-key" , pet .getId());
System.err .println("Deleted pet: id=" + pet .getId());
}
In the example above, we chose to add the @RestOp annotation to the implementation class.
However, they could have been added to the interface instead. It's personal preference where you want to place
the annotations.
Note how we didn't need to use the @Header and @Path annotations in our implementation since
the annotations were inherited from the interface.
The @Path
annotation can be applied to arguments of
Path
name
- Path variable name.serializer
- Override the part serializer.
Example:
Single-part arguments (i.e. those with name !=
-
Any serializable POJO - Converted to a string using the
HttpPartSerializer
registered with theRestClient (OpenApiSerializer
by default) or associated via the@Path(serializer)
annotation.
Multi-part arguments (i.e. those with name ==
-
PartList
- Serialized as individual path parameters. -
Map - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default). -
Bean - Converted to key-value pairs.
Values serialized using the registeredHttpPartSerializer
(OpenApiSerializer
by default).
See the link below for information about supported data types in OpenAPI serialization.
The @Request
annotation can be applied to a type of a
Request
serializer
- Override the part serializer.
Example:
PetStore
The
The annotated methods must be no-arg and public. They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
Annotations on methods are inherited from parent classes and interfaces. For example, the request bean above could have defined annotations in an interface to keep them clear from the implementation:
10.9.9 - @Responseupdated: 9.0.0
The @Response
annotation can be applied to types returned by @RemoteOp -annotated methods.
The @Response annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
Example:
@Remote
public interface PetStore {
@RemotePost
CreatePetResponse postPet(...);
}
@Response
public interface CreatePetResponse {
@Content
Pet getContent();
@Header ("E-Tag" )
UUID getUUID();
@StatusCode
int getStatus();
}
PetStore store = client .getRemote(PetStore.class , "http://localhost:10000" );
CreatePetResponse response = store .postPet(...);
Pet pet = response .getContent();
UUID uuid = response .getUUID();
int status = response .getStatus();
The annotated methods must be no-arg.
They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
10.9.10 - Dual-purpose (end-to-end) interfacescreated: 8.0.0
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as @RestOp and parameter-level annotations
such as @Query are inherited from parent classes.
This normally isn't possible but the framework will spider up the parent hierarchy of classes to find method and parameter level
annotations defined on overridden methods.
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
@Remote (path="/petstore" )
public interface PetStore {
@RemoteGet ("/pet" )
public Collection<Pet> getPets() throws NotAcceptable;
@RemotDelete ("/pet/{petId}" )
public Ok deletePet(
@Header (
name="api_key" ,
description="Security API key" ,
required=true ,
example="foobar"
)
String apiKey ,
@Path (
name="petId" ,
description="Pet id to delete" ,
example="123"
)
long petId
) throws IdNotFound, NotAcceptable;
...
Next you define the implementation of your interface as a normal Juneau REST resource:
@Rest (
path="/petstore" ,
title="Petstore application" ,
...
)
public class PetStoreResource extends BasicRestServlet implements PetStore {
...
@Override /* PetStore */
@RestOp (
method=GET ,
path="/pet" ,
summary="All pets in the store" ,
...
)
public Collection<Pet> getPets() throws NotAcceptable {
return store .getPets();
}
@Override /* PetStore */
@RestOp (
method=DELETE ,
path="/pet/{petId}" ,
summary="Deletes a pet" ,
...
)
public Ok deletePet(String apiKey , long petId ) throws IdNotFound, NotAcceptable {
store .removePet(petId );
return OK ;
}
Then use the interface as a remote resource like so:
RestClient client = RestClient.create ().json().rootUrl("http://localhost:10000" ).build();
PetStore store = client .getRemote(PetStore.class );
for (Pet pet : store .getPets()) {
store .deletePet("my-special-key" , pet .getId());
System.err .println("Deleted pet: id=" + pet .getId());
}
In the example above, we chose to add the @RestOp annotation to the implementation class.
However, they could have been added to the interface instead. It's personal preference where you want to place
the annotations.
Note how we didn't need to use the @Header and @Path annotations in our implementation since
the annotations were inherited from the interface.
The @Response
annotation can be applied to types returned by
The
Example:
PetStore
The annotated methods must be no-arg. They can be named anything.
Any of the following annotations can be used on the methods:
The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
A common coding practice is to use the same Java interface to define both your server and client side REST interfaces. The advantage to this approach is that changes that you make to your REST interface can be reflected in both places at the same time, reducing the chances for compatibility mistakes.
What makes this possible is that method-level annotations such as
The general approach is to define your @Remote
-annotated interface first.
The following example is pulled from the PetStore app:
Next you define the implementation of your interface as a normal Juneau REST resource:
Then use the interface as a remote resource like so:
RestClient client = RestClient.
In the example above, we chose to add the
Note how we didn't need to use the
10.10 - Logging and Debuggingcreated: 8.2.0, updated: 9.0.0
The following methods provide logging of requests and responses:
The following example shows the results of logging all requests that end with
Examples:
This produces the following console output:
=== HTTP Call (outgoing) ====================================================== === REQUEST === POST http://localhost/bean ---request headers--- Accept: application/json5 ---request entity--- Content-Type: application/json5 ---request content--- {f:1} === RESPONSE === HTTP/1.1 200 ---response headers--- Content-Type: application/json ---response content--- {f:1} === END =======================================================================",
It should be noted that if you enable request logging detail level DetailLevel.FULL
, response bodies will be cached by default which may introduce
a performance penalty.
Additionally, the following method is also provided for enabling debug mode:
Enabling debug mode has the following effects:
RestClient.Builder.logToConsole()
is called.
10.11 - Customizing HttpClientcreated: 8.2.0, updated: 9.0.0
Several methods are provided for customizing the underlying HTTP client and client builder classes:
RestClient.Builder
httpClientBuilder(HttpClientBuilder)
- Set the client builder yourself.createHttpClientBuilder()
- Override to create the client builder.createHttpClient()
- Override to create the client.createConnectionManager()
- Override to create the connection management.
Additionally, all methods on the
Example:
Refer to the org.apache.http.client.impl.HttpClientBuilder
docs for more information.
10.12 - Extending RestClientcreated: 8.2.0
The
Example:
The RestRequest
and RestResponse
objects can also be extended and integrated by overriding the
RestClient.createRequest(URI, String, boolean)
and RestClient.createResponse(RestRequest, HttpResponse, Parser)
methods.
10.13 - Authenticationupdated: 8.2.0
The Juneau REST client itself does not implement any support for authentication. Instead, it delegates it to the underlying Apache HTTP Client interface.
The following sections show how some common authentication mechanisms can be set up using HTTP Client APIs.
10.13.1 - BASIC Authentication
The RestClient.Builder.basicAuth(String,int,String,String)
method
can be used to quickly enable BASIC authentication support.
Example:
// Create a client that performs BASIC authentication using the specified user/pw.
RestClient client = RestClient.create ()
.basicAuth(HOST , PORT , USER , PW )
.build();
This is functionally equivalent to the following:
RestClient.Builder builder = RestClient.create ();
AuthScope scope = new AuthScope(HOST , PORT );
Credentials up = new UsernamePasswordCredentials(USER , PW );
CredentialsProvider provider = new BasicCredentialsProvider();
provider .setCredentials(scope , up );
builder .setDefaultCredentialsProvider(provider );
10.13.2 - FORM-based Authentication
The RestClient.Builder
class does not itself provide FORM-based
authentication since there is no standard way of providing such support.
Typically, to perform FORM-based or other types of authentication, you'll want to create your own
subclass of RestClient.Builder
and override the
RestClient.Builder.createHttpClient()
method to provide an
authenticated client.
The following example shows an implementation of a client that performs FORM-based authentication against
the IBM Jazz platform.
/**
* Constructor.
*/
public JazzRestClient.Builder(URI jazzUri , String user , String pw ) throws IOException {
...
}
/**
* Override the createHttpClient() method to return an authenticated client.
*/
@Override /* RestClient.Builder */
protected CloseableHttpClient createHttpClient() throws Exception {
CloseableHttpClient client = super .createHttpClient();
formBasedAuthenticate(client );
visitAuthenticatedURL(client );
return client ;
}
/*
* Performs form-based authentication against the Jazz server.
*/
private void formBasedAuthenticate(HttpClient client ) throws IOException {
URI uri2 = jazzUri .resolve("j_security_check" );
HttpPost request = new HttpPost(uri2);
request .setConfig(RequestConfig.custom ().setRedirectsEnabled(false ).build());
// Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters.
request .addHeader("Content-Type" , "application/x-www-form-urlencoded; charset=utf-8" );
List<NameValuePair> params = AList.of (
BasicNameValuePair.of ("j_username"" , user ),
BasicNameValuePair.of ("j_password" , pw )
);
request .setEntity(new UrlEncodedFormEntity(params ));
HttpResponse response = client .execute(request );
try {
int rc = response .getStatusLine().getStatusCode();
Header authMsg = response .getFirstHeader("X-com-ibm-team-repository-web-auth-msg" );
if (authMsg != null )
throw new IOException(authMsg .getValue());
// The form auth request should always respond with a 200 ok or 302 redirect code
if (rc == SC_MOVED_TEMPORARILY ) {
if (response .getFirstHeader("Location" ).getValue().isPattern("^.*/auth/authfailed.*$" ))
throw new IOException("Invalid credentials." );
} else if (rc != SC_OK ) {
throw new IOException("Unexpected HTTP status: " + rc );
}
} finally {
EntityUtils.consume (response .getEntity());
}
}
/*
* This is needed for Tomcat because it responds with SC_BAD_REQUEST when the j_security_check URL is visited before an
* authenticated URL has been visited. This same URL must also be visited after authenticating with j_security_check
* otherwise tomcat will not consider the session authenticated
*/
private int visitAuthenticatedURL(HttpClient httpClient ) throws IOException {
HttpGet authenticatedURL = new HttpGet(jazzUri .resolve("authenticated/identity" ));
HttpResponse response = httpClient .execute(authenticatedURL );
try {
return response .getStatusLine().getStatusCode();
} finally {
EntityUtils.consume (response .getEntity());
}
}
10.13.3 - OIDC Authentication
The following example shows an implementation of a client that performs OIDC authentication against
the IBM Jazz platform.
/**
* Constructor.
*/
public JazzRestClient.Builder(URI jazzUri , String user , String pw ) throws IOException {
...
}
/**
* Override the createHttpClient() method to return an authenticated client.
*/
@Override /* RestClient.Builder */
protected CloseableHttpClient createHttpClient() throws Exception {
CloseableHttpClient client = super .createHttpClient();
oidcAuthenticate(client );
return client ;
}
private void oidcAuthenticate(HttpClient client ) throws IOException {
HttpGet request = new HttpGet(jazzUri );
request .setConfig(RequestConfig.custom ().setRedirectsEnabled(false ).build());
// Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters.
request .addHeader("Content-Type" , "application/x-www-form-urlencoded; charset=utf-8" );
HttpResponse response = client .execute(request );
try {
int code = response .getStatusLine().getStatusCode();
// Already authenticated
if (code == SC_OK )
return ;
if (code != SC_UNAUTHORIZED )
throw new RestCallException("Unexpected response during OIDC authentication: "
+ response .getStatusLine());
// x-jsa-authorization-redirect
String redirectUri = getHeader(response , "X-JSA-AUTHORIZATION-REDIRECT" );
if (redirectUri == null )
throw new RestCallException("Expected a redirect URI during OIDC authentication: "
+ response .getStatusLine());
// Handle Bearer Challenge
HttpGet method = new HttpGet(redirectUri + "&prompt=none" );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 2: "
+ response .getStatusLine());
String loginRequired = getHeader(response , "X-JSA-LOGIN-REQUIRED" );
if (! "true" .equals(loginRequired ))
throw new RestCallException("X-JSA-LOGIN-REQUIRED header not found on response during OIDC authentication phase 2: "
+ response .getStatusLine());
method = new HttpGet(redirectUri + "&prompt=none" );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 3: "
+ response .getStatusLine());
// Handle JAS Challenge
method = new HttpGet(redirectUri );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 4: "
+ response .getStatusLine());
cookie = getHeader(response , "Set-Cookie" );
Header[] defaultHeaders = new Header[] {
BasicStringHeader.of ("User-Agent" , "Jazz Native Client" ),
BasicStringHeader.of ("X-com-ibm-team-configuration-versions" ,
"com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0" ),
BasicStringHeader.of ("Accept" , "text/json" ),
BasicStringHeader.of ("Authorization" , "Basic "
+ StringUtils.base64EncodeToString (user + ":" + pw )),
BasicStringHeader.of ("Cookie" , cookie )
};
setDefaultHeaders(AList.of (defaultHeaders ));
} finally {
EntityUtils.consume (response .getEntity());
}
}
private void addDefaultOidcHeaders(HttpRequestBase method ) {
method .addHeader("User-Agent" , "Jazz Native Client" );
method .addHeader("X-com-ibm-team-configuration-versions" ,
"com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0" );
method .addHeader("Accept" , "text/json" );
if (cookie != null ) {
method .addHeader("Authorization" , "Basic "
+ StringUtils.base64EncodeToString (user + ":" + pw ));
method .addHeader("Cookie" , cookie );
}
}
The RestClient.Builder.basicAuth(String,int,String,String)
method
can be used to quickly enable BASIC authentication support.
Example:
This is functionally equivalent to the following:
RestClient.Builder
The RestClient.Builder
class does not itself provide FORM-based
authentication since there is no standard way of providing such support.
Typically, to perform FORM-based or other types of authentication, you'll want to create your own
subclass of RestClient.Builder
and override the
RestClient.Builder.createHttpClient()
method to provide an
authenticated client.
The following example shows an implementation of a client that performs FORM-based authentication against the IBM Jazz platform.
10.13.3 - OIDC Authentication
The following example shows an implementation of a client that performs OIDC authentication against
the IBM Jazz platform.
/**
* Constructor.
*/
public JazzRestClient.Builder(URI jazzUri , String user , String pw ) throws IOException {
...
}
/**
* Override the createHttpClient() method to return an authenticated client.
*/
@Override /* RestClient.Builder */
protected CloseableHttpClient createHttpClient() throws Exception {
CloseableHttpClient client = super .createHttpClient();
oidcAuthenticate(client );
return client ;
}
private void oidcAuthenticate(HttpClient client ) throws IOException {
HttpGet request = new HttpGet(jazzUri );
request .setConfig(RequestConfig.custom ().setRedirectsEnabled(false ).build());
// Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters.
request .addHeader("Content-Type" , "application/x-www-form-urlencoded; charset=utf-8" );
HttpResponse response = client .execute(request );
try {
int code = response .getStatusLine().getStatusCode();
// Already authenticated
if (code == SC_OK )
return ;
if (code != SC_UNAUTHORIZED )
throw new RestCallException("Unexpected response during OIDC authentication: "
+ response .getStatusLine());
// x-jsa-authorization-redirect
String redirectUri = getHeader(response , "X-JSA-AUTHORIZATION-REDIRECT" );
if (redirectUri == null )
throw new RestCallException("Expected a redirect URI during OIDC authentication: "
+ response .getStatusLine());
// Handle Bearer Challenge
HttpGet method = new HttpGet(redirectUri + "&prompt=none" );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 2: "
+ response .getStatusLine());
String loginRequired = getHeader(response , "X-JSA-LOGIN-REQUIRED" );
if (! "true" .equals(loginRequired ))
throw new RestCallException("X-JSA-LOGIN-REQUIRED header not found on response during OIDC authentication phase 2: "
+ response .getStatusLine());
method = new HttpGet(redirectUri + "&prompt=none" );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 3: "
+ response .getStatusLine());
// Handle JAS Challenge
method = new HttpGet(redirectUri );
addDefaultOidcHeaders(method );
response = client .execute(method );
code = response .getStatusLine().getStatusCode();
if (code != SC_OK )
throw new RestCallException("Unexpected response during OIDC authentication phase 4: "
+ response .getStatusLine());
cookie = getHeader(response , "Set-Cookie" );
Header[] defaultHeaders = new Header[] {
BasicStringHeader.of ("User-Agent" , "Jazz Native Client" ),
BasicStringHeader.of ("X-com-ibm-team-configuration-versions" ,
"com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0" ),
BasicStringHeader.of ("Accept" , "text/json" ),
BasicStringHeader.of ("Authorization" , "Basic "
+ StringUtils.base64EncodeToString (user + ":" + pw )),
BasicStringHeader.of ("Cookie" , cookie )
};
setDefaultHeaders(AList.of (defaultHeaders ));
} finally {
EntityUtils.consume (response .getEntity());
}
}
private void addDefaultOidcHeaders(HttpRequestBase method ) {
method .addHeader("User-Agent" , "Jazz Native Client" );
method .addHeader("X-com-ibm-team-configuration-versions" ,
"com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0" );
method .addHeader("Accept" , "text/json" );
if (cookie != null ) {
method .addHeader("Authorization" , "Basic "
+ StringUtils.base64EncodeToString (user + ":" + pw ));
method .addHeader("Cookie" , cookie );
}
}
The following example shows an implementation of a client that performs OIDC authentication against the IBM Jazz platform.
11 - juneau-rest-mockcreated: 8.1.0, updated: 8.2.0
Maven Dependency
Java Library
juneau-rest-mock-
OSGi Module
org.apache.juneau.rest.mock_
The
11.1 - MockRestClientcreated: 8.2.0, updated: 9.0.0
The MockRestClient
class is used for performing serverless unit testing of @Rest
-annotated
and @Remote
-annotated classes.
The MockRestClient
itself extends from RestClient
providing it with the rich
feature set of that API.
The following shows a simple example of invoking a PUT method on a simple REST interface and asserting
the correct status code and response body:
Breaking apart the fluent method call above will help you understand how this works.
The concept of the design is simple. The MockRestClient
class is used to create instances of MockServletRequest
and MockServletResponse
which are passed directly to the call handler on the resource class RestOpInvoker.invoke(RestOpSession)
.
In effect, you're fully testing your REST API as if it were running in a live servlet container, yet not
actually having to run in a servlet container.
All aspects of the client and server side code are tested, yet no servlet container is required. The actual
over-the-wire transmission is the only aspect being bypassed.
The
The MockRestRequest
object has convenience methods provided to allow you to set properties
directly on the underlying HttpServletRequest
object. The following example shows how
this can be used to directly set roles on the request object to perform security testing.
Example:
The MockRestClient
class has a debug mode that will cause your HTTP requests and responses to
be sent to the console:
MockRestClient
The MockRestClient
class can also be used for testing of Remote
-annotated
interfaces against @Rest
-annotated resources.
Example:
12 - juneau-microservice-corecreated: 8.1.0
Maven Dependency
Java Library
juneau-microservice-core-
OSGi Module
org.apache.juneau.microservice.core_
Juneau Microservice is an API for creating stand-alone executable jars with automatic support for Juneau configurations and console commands.
Features include:
- A builder-based API for defining and starting microservices.
- An extensible API that allows you to hook into various lifecycle events.
- Simple-to-use APIs for accessing manifest file entries, command-line arguments, and external configuration file properties.
12.1 - Microservice Overviewcreated: 8.0.0
The Microservice API consists of a base class for defining executable microservices.
Features include:
- A builder-based API for defining and starting microservices.
- An extensible API that allows you to hook into various lifecycle events.
- Simple-to-use APIs for accessing manifest file entries, command-line arguments, and external configuration file properties.
The Microservice API consists of the following packages and classes:
org.apache.juneau.microservice
Microservice
- The base microservice class.Microservice.Builder
- Builder for the microservice class.MicroserviceListener
- Interface for hooking into lifecyle events of the microservice.BasicMicroserviceListener
- Adapter for MicroserviceListener class.
org.apache.juneau.microservice.console
ConsoleCommand
- Abstract class for defining console commands.
By itself the Microservice API doesn't provided much functionality but it does provide the basis for the Jetty Microservice described later.
The most-basic creation of a microservice from an entry-point method is shown below:
12.2 - Lifecycle Methodscreated: 8.0.0
The lifecycle methods of the Microservice
class consists of the following:
A typical implementation of an app with lifecycle methods might look like the following:
If your application consists of a single microservice, you can use the Microservice.getInstance()
method
from anywhere in your code:
The Microservice.startConsole()
and Microservice.stopConsole()
control
the lifecycle of the console commands.
Typically you'll want to control these separately from the app so that you can easily restart your application
from the console without affecting the console itself.
The lifecycle methods on the Microservice
class are purposely left non-final so that
subclasses can override them to provide customized behavior.
12.3 - Argscreated: 8.0.0
Command-line arguments can be associated with a microservice using the Microservice.Builder.args(String...)
method.
Example:
When specified, the arguments can be retrieved using the Microservice.getArgs()
method which provides
an API for easily accessing command-line arguments using common notation:
Args
Specifying the command-line arguments also makes them available through $A
SVL variables.
These can be used in the configuration file and throughout various Juneau annotations.
Example:
12.4 - Manifestcreated: 8.0.0
The Microservice.Builder.manifest(Object)
method can be used to specify the contents or location of of the main
manifest file of the executable jar.
If you do not specify the location/contents of the manifest file, the microservice will attempt to resolve it through the following methods:
-
Looking on the file system for a file at
"META-INF/MANIFEST.MF" . This is primarily to allow for running microservices from within eclipse workspaces where the manifest file is located in the project root. -
Using the class loader for this class to find the file at the URL
"META-INF/MANIFEST.MF" .
If you do manually specify the manifest file, you can pass in any of the following types:
ManifestFile
- A pre-parsed manifest file.Manifest
- A pre-parsed manifest file.Reader
- Containing the raw contents of the manifest.InputStream
- Containing the raw contents of the manifest.File
- File containing the raw contents of the manifest.String
- Path to file containing the raw contents of the manifest.Class
- Finds and loads the manifest file of the jar file that the specified class is contained within.
The manifest file can be retrieved using the the Microservice.getManifest()
method which
provides an API for accessing manifest file entries.
This method returns an instance of ManifestFile
which extends from JsonMap
allowing
you to retrieve entries as any data types supported by that class.
Example:
ManifestFile
The manifest is also used for the $MF
SVL variable.
Examples:
12.5 - Configcreated: 8.0.0
The following methods can be used to define the configuration for your microservice using the powerful Config API:
If you do not specify any of this information, we attempt to resolve it through the following methods:
-
Resolve file first in working directory, then in classpath, using the following names:
-
The
"configFile" argument in the command line arguments passed in through the constructor. -
The value of the
Main-Config entry in the manifest file. -
A config file in the same location and with the same name as the executable jar file.
(e.g.
"java -jar myjar.jar" will look for"myjar.cfg" ).
-
The
-
Resolve any
"*.cfg" file that can be found in the working directory. -
Resolve any of the following files in the classpath:
juneau.cfg default.cfg application.cfg app.cfg settings.cfg
If no configuration file is found, and empty in-memory configuration is used.
The configName(String)
method allows you to explicitly specify the name
of the external configuration file location for your microservice.
Microservice
.
By default, we try to find the file on the file system and then the classpath. If located on the file system, then the configuration is writeable and the microservice can automatically listen for and react to changes in the configuration file on the file system. If located on the classpath, then the configuration can still react to modifications made to it through the Config API but the changes cannot be persisted since the location prevents the file from being modified.
The configStore(ConfigStore)
method can be used to explicitly
specify a configuration store.
This can include your own custom configuration store, such as one that's implemented in a relational database.
Microservice
.
The config(Config)
method can be used to explicitly specify a Config
file as the microservice configuration. When this method is used, the above two methods are bypassed entirely.
Config
Once the configuration is resolved, it is made as the system default configuration available through the Config.getSystemDefault()
.
This in turn allows it to be used by REST resources that reference the system default configuration via the BasicRestConfig interface.
BasicRestConfig.java
The Microservice.getConfig()
method can be used to get access to the configuration.
Config
Changes to the configuration file can trigger notifications that can be used to restart your microservice or make various other
on-the-fly changes.
This can be accomplished by either overriding the Microservice.onConfigChange(ConfigEvents)
or implementing
a listener and using the MicroserviceListener.onConfigChange(Microservice,ConfigEvents)
methods.
These will be described in detail later.
12.6 - System propertiescreated: 8.0.0
As a convenience, the
12.7 - VarResolvercreated: 8.0.0
The Microservice API incorporates the Simple Variable Language API.
The variable resolver can be augmented through the following methods:
A typical usage pattern is shown below:
The variable resolver becomes much more powerful when used in REST resource annotations which will be described latter in juneau-microservice-jetty
By default, support for the following variables are provided:
$S{key[,default]} -SystemPropertiesVar
$E{key[,default]} -EnvVariablesVar
$A{key[,default]} -ArgsVar
$C{key[,default]} -ConfigVar
$MF{key[,default]} -ManifestFileVar
$IF{arg,then[,else]} -IfVar
$SW{arg,pattern1:then1[,pattern2:then2...]} -SwitchVar
$CO{arg[,arg2...]} -CoalesceVar
$PM{arg,pattern} -PatternMatchVar
$UC{arg} -UpperCaseVar
$LC{arg} -LowerCaseVar
$NE{arg} -NotEmptyVar
12.8 - Console Commandscreated: 8.0.0
The Microservice API provides support for simple console commands.
When started, the console renders the following output:
Running class 'Microservice' using config file 'my-microservice.cfg'. List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help >
The builder methods for controlling the console are as follows:
By default, the supported commands are pulled from the configuration file:
New commands can be added by adding them to the configuration file, or programmatically using the consoleCommands(ConsoleCommand...)
builder method.
The API for defining console commands is shown below:
By default, the console input and output are taken from System.in
and System.out
.
These can be overridden using the console(Scanner,PrintWriter)
method.
12.9 - Listenerscreated: 8.0.0
As mentioned previously, the lifecycle methods for the Microservice
class are explicitly
defined as non-final so that they can be overridden by subclasses.
In addition to this support, an interface for defining event listeners for your microservice:
This listener API can be used for listening for and reacting to configuration changes on the file system.
Note that the Microservice.onConfigChange(ConfigEvents)
method can also be overridden
to react to configuration changes as well:
13 - juneau-microservice-jettycreated: 8.1.0
Maven Dependency
Java Library
juneau-microservice-jetty-
OSGi Module
org.apache.juneau.microservice.jetty_
Juneau Microservice Jetty is an API for creating stand-alone executable jars that can be used to start lightweight configurable REST interfaces with all the power of the Juneau REST server and client APIs.
13.1 - Overviewcreated: 8.0.0
The Jetty Microservice API consists of a combination of the Juneau Core, Server, and Client APIs and an embedded Eclipse Jetty Servlet Container.
The API builds upon the Core Microservices classes to produce easy-to-create and easy-to-use microservices in a standard Java 1.8+ environment.
The
-
invalid @link
org.apache.juneau.microservice.jetty
JettyMicroservice
- The Jetty microservice class.JettyMicroservice.Builder
- Builder for the microservice class.JettyMicroserviceListener
- Interface for hooking into lifecyle events of the microservice.BasicJettyMicroserviceListener
- Adapter for JettyMicroserviceListener class.
JettyServerFactory
- Interface for defining custom Jetty servers.BasicJettyServerFactory
- Adapter for JettyServerFactory class.
The most-basic creation of a Jetty microservice from an entry-point method is shown below:
13.2 - Lifecycle Methodscreated: 8.0.0
To review, the Microservice
class contains the following lifecycle methods:
The JettyMicroservice
class which extends from Microservice
provides the following additional lifecycle methods:
JettyMicroservice
createServer()
startServer()
destroyServer()
The additional lifecycle methods are typically not called directly but are exposed to allow subclasses to provide customized behavior for these events. For this reason, these methods are left as non-final so that they can be overridden.
A typical implementation of an app with lifecycle methods might look like the following:
Similar to Microservice.getInstance()
, the JettyMicroservice.getInstance()
also allows easy access to the microservice:
13.3 - Resource Classescreated: 8.0.0
This section describes how to define a top-level REST resource page and deploy it in our microservice. The example is a router page that serves as a jumping off page to child resources.
When deployed, it looks like this in a browser:
http://localhost:10000
-
The title and
description annotations define the titles on the page.
These can be globalized using$L{...} variables, or by defining specially-named properties in the properties file for the resource. -
In this case, the
path annotation defines the context root of your application since it was not specified in the manifest or config file.
Therefore, this resource is mapped tohttp://localhost:10000 . -
The
children annotation make up the list of child resources.
These child resources can be anything that extends fromServlet , although usually they will be subclasses ofBasicRestServlet
or other resource groups.
If you click the
http://localhost:10000/helloWorld
...which is generated by this class...
The most-common case for deploying the top-level resource is to use the JettyMicroservice.Builder.servlet(Class)
method:
However, there are multiple ways of deploying top-level resources:
-
JettyMicroservice.Builder.servlet(Class)
- Using the builder. Several methods provided. -
JettyMicroservice.addServlet(Servlet,String)
- After the Jetty container has been started. -
As a configuration variable
"Jetty/servlets" .#======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Subclasses of RestServlet servlets =org.apache.juneau.examples.rest.RootResources -
As a configuration variable
"Jetty/servletMap" .#======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Any servlets and their path specs servletMap ={ '/*': 'org.apache.juneau.examples.rest.RootResources' } -
Directly in the
jetty.xml file.<Configure id ="ExampleServer" class ="org.eclipse.jetty.server.Server" > ...<New id ="context" class ="org.eclipse.jetty.servlet.ServletContextHandler" > <Set name ="contextPath" >/</Set> <Call name ="addServlet" > <Arg> org.apache.juneau.rest.test.Root</Arg> <Arg> /*</Arg> </Call> <Set name ="sessionHandler" > <New class ="org.eclipse.jetty.server.session.SessionHandler" /> </Set> </New> ...
13.4 - Predefined Resource Classescreated: 8.0.0
The following predefined resource classes are also provided for easy inclusion into your microservice:
ConfigResource
- View and modify the external INI config file.DirectoryResource
- View and modify file system directories.SampleRootResource
- A sample root resource class to get started from.ShutdownResource
- Shutdown and/or restart the JVM.
13.5 - Configcreated: 8.0.0
In Config, we described how to associate a configuration file with your microservice. In this section we describe how that configuration can be used to customize the behavior or your REST resource classes.
The most common usage for the configuration file is to reference values using the $C
variable in annotations.
For example, the DefaultConfig
interface that defines the annotations that control the look-and-feel of
classes that extend from BasicRestServlet
use several
These values in turn are pulled from the external configuration file shown below.
Note that the configuration file can also contain
Configuration files can also be accessed programmatically. There are 3 primary ways of getting access to the config file:
Microservice.getConfig()
Any initialization-time variables can be used.
RestContext.getConfig()
Any initialization-time variables can be used.
Example usage:
#---------------------------------- # Configuration for MyHelloResource #---------------------------------- [MyHelloResource] greeting =Hello world! #--------------------------------- # Contents of MyHelloResource.java #--------------------------------- @Rest (...)public class MyHelloResourceextends BasicRestServlet {private Stringgreeting ;// Or access config file in servlet init method. @Override /* Servlet */ public void init() { Configconfig = getContext().getConfig();this .greeting =config .getString("MyHelloResource/greeting" ); } }Additional user-defined variables at the servlet level can be defined by adding a
RestInit
hook method and using theorg.apache.juneau.rest.RestContext.Builder.vars(Class...)method.-
RestRequest.getConfig()
- An instance method to access it from inside a REST method.Any initialization-time or request-time variables can be used.
Example usage:
#---------------------------------- # Configuration for MyHelloResource #---------------------------------- [MyHelloResource] greeting =Hello $RP{person}! // $RP is RequestPathVar localizedGreeting =$L{HelloMessage,$RP{person}} // $L is LocalizationVar with args #--------------------------------- # Contents of MyHelloResource.java #--------------------------------- @Rest ( path="/hello" , messages="nls/Messages" , ... )public class MyHelloResourceextends BasicRestServlet {/** Standard hello message. */ @RestGet ("/{person}" )public String sayHello(RestRequestreq ) {return req .getConfig().getString("MyHelloResource/greeting" ); }/** Hello message in users language. */ @RestGet ("/localized/{person}" )public String sayLocalizedHello(RestRequestreq ) {return req .getConfig().getString("MyHelloResource/localizedGreeting" ); } }#--------------------------------------- # Contents of nls/Messages_en.properties #--------------------------------------- MyHelloResource.HelloMessage =Hello {0}! Additional user-defined variables can be defined at this level by overriding the
org.apache.juneau.rest.RestContext.Builder.vars(Class...)method.
That
-
The HTTP call matches the
/hello path on theMyHelloResource class. -
The HTTP call matches the
/localized/{person} path on thesayLocalizedHello() method. -
The request attribute
person gets assigned the value"Bob" . -
The call to
req.getConfig().getString("MyHelloResource/localizedGreeting") finds the value"$L{HelloMessage,$RP{person}}" . -
The arguments in the
$L{} variable get resolved, resulting in"$L{HelloMessage,Bob}" . -
The
$L{} variable gets resolved to the message"Hello {0}!" in the localized properties file of the servlet based on theAccept-Language header on the request. -
The arguments get replaced in the message resulting in
"Hello Bob!" . -
The resulting message
"Hello Bob!" is returned as a POJO to be serialized to whatever content type was specified on theAccept header on the request.
This particular example is needlessly complex but it gives an idea of how variables can be used recursively to produce sophisticated results
13.6 - Jetty.xml filecreated: 8.0.0
The Jetty microservice comes with a bare-bones
The
-
Using the
JettyMicroservice.Builder.jettyXml(Object,boolean)
method to specify the location or contents of the file. -
Specifying the location using a
Jetty-Config value in theMANIFEST.MF file.Jetty-Config: files/jetty.xml -
Specifying the location using the
"Jetty/jettyXml" configuration value.#======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Path of the jetty.xml file used to configure the Jetty server. config =files/jetty.xml
SVL variables in the
The HTTP port used is controlled via the following:
-
The
JettyMicroservice.Builder.ports(int...)
method.JettyMicroservice .
create () .args(args ) .servlets(RootResource.class ) .port(1000,2000,0,0,0)// Try port 1000, then 2000, then 3 random ports. .build() .start() -
The
"Jetty/ports" configuration property.#======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Port to use for the jetty server. # You can specify multiple ports. The first available will be used. '0' indicates to try a random port. port =1000,2000,0,0,0
The first available port is then made available through the system property
The JettyMicroservice.Builder.jettyServerFactory(JettyServerFactory)
method is also provided
to use your own customized Jetty server.
13.7 - UI Customizationcreated: 8.0.0
The Microservice project contains a
These files can be used to tailor the look-and-feel of your microservice.
http://localhost:10000/helloWorld
The REST configuration section of your microservice configuration file can be used to tailor the header and footer on the pages:
The DefaultConfig
interface (which defines the default settings for BasicRestServlet
pulls in this information using $C
and $U
variables:
Note that the
The theme files are externally accessible and can be modified to produce any look-and-feel you desire.
The microservice still works without the files directory. An embedded
If you're testing out changes in the theme stylesheets, you may want to set the following system property that prevents caching of those files so that you don't need to restart the microservice each time a change is made:
13.8 - Extending JettyMicroservicecreated: 8.0.0
This example shows how the JettyMicroservice
class
can be extended to implement lifecycle listener methods or override existing methods.
We'll create a new class
Optionally, you can extend the JettyMicroservice.Builder
class as well:
14 - my-jetty-microservicecreated: 8.1.0
Starter Project Zip
my-jetty-microservice-
The
It includes a combination of the Juneau Core, Server, and Client APIs and all libraries needed to execute in a Java 1.8+ environment.
14.1 - Installing in Eclipsecreated: 8.0.0
Follow these instructions to create a new template project in Eclipse.
-
Download the
my-jetty-microservice- file from the downloads page (located in the binaries) and import it into your workspace as an existing project:9.0.1 .zip
-
Select the archive file and import the project:
-
In your workspace, you should now see the following project:
The important elements in this project are:
-
App.java - The entry point.
This class creates and starts our microservice:
public class App {public static void main(String[]args )throws Exception { JettyMicroservice .create () .args(args ) .servlet(RootResources.class ) .build() .start() .startConsole() .join(); } } -
RootResources.java - The top-level REST resource.
This class routes HTTP requests to child resources:
@Rest ( path="/" , title="My Microservice" , description="Top-level resources page" , children={ HelloWorldResource.class , ConfigResource.class , LogsResource.class } )@HtmlDocConfig ( widgets={ ContentTypeMenuItem.class , StyleMenuItem.class }, navlinks={"options: servlet:/?method=OPTIONS" } )public class RootResourcesextends BasicRestServletGroup {// No code } -
mjm.cfg - The external configuration file.
Contains various useful settings.
Can be used for your own resource configurations.
#======================================================================================================================= # Basic configuration file for REST microservices # Subprojects can use this as a starting point. #======================================================================================================================= #======================================================================================================================= # Jetty settings #======================================================================================================================= [Jetty] # Path of the jetty.xml file used to configure the Jetty server. config =jetty.xml # Resolve Juneau variables in the jetty.xml file. resolveVars =true # Port to use for the jetty server. # You can specify multiple ports. The first available will be used. '0' indicates to try a random port. # The resulting available port gets set as the system property "availablePort" which can be referenced in the # jetty.xml file as "$S{availablePort}" (assuming resolveVars is enabled). port =10000,0,0,0 # Optionally specify your servlets here: #servlets = org.apache.juneau.microservice.sample.RootResources #======================================================================================================================= # REST settings #======================================================================================================================= [REST] # Comma-delimited list of key-value pairs that represent locations of static files that can be served up by your @Rest-annotated # classes. These are static files that are served up by the servlet under the specified sub-paths. # For example, given the following setting... # staticFiles = htdocs:my-docs,styles/my-styles # ...the URI "/servletPath/htdocs/javadoc.css" resolves to the path "/my-docs/javadoc.css". # This path can be relative to the working directory, classpath root, or package of your resource class. # Used by the BasicRestConfig interface that defines the following value: # staticFiles="$C{REST/staticFiles}" staticFiles = htdocs:htdocs# Stylesheet to use for HTML views. # Used by the BasicRestConfig interface that defines the following value: # stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}" theme =servlet:/htdocs/themes/devops.css # Various look-and-feel settings used in the BasicRestConfig interface. headerIcon =servlet:/htdocs/images/juneau.png headerLink =http://juneau.apache.org footerIcon =servlet:/htdocs/images/asf.png footerLink =http://www.apache.org favicon =$C{REST/headerIcon} header =<a href='$U{$C{REST/headerLink}}'> <img src='$U{$C{REST/headerIcon}}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/> </a> footer =<a href='$U{$C{REST/footerLink}}'> <img src='$U{$C{REST/footerIcon}}' style='float:right;padding-right:20px;height:32px'/> </a> #======================================================================================================================= # Console settings #======================================================================================================================= [Console] enabled =true # List of available console commands. # These are classes that implements ConsoleCommand that allow you to submit commands to the microservice via # the console. # When listed here, the implementations must provide a no-arg constructor. # They can also be provided dynamically by overriding the Microservice.createConsoleCommands() method. commands =org.apache.juneau.microservice.console.ExitCommand, org.apache.juneau.microservice.console.RestartCommand, org.apache.juneau.microservice.console.HelpCommand, org.apache.juneau.microservice.console.ConfigCommand #======================================================================================================================= # Logger settings #----------------------------------------------------------------------------------------------------------------------- # See FileHandler Java class for details. #======================================================================================================================= [Logging] ...#======================================================================================================================= # System properties #----------------------------------------------------------------------------------------------------------------------- # These are arbitrary system properties that are set during startup. #======================================================================================================================= [SystemProperties] # Configure Jetty for StdErrLog Logging # org.eclipse.jetty.util.log.class = org.eclipse.jetty.util.log.StrErrLog # Configure Jetty to log using java-util logging org.eclipse.jetty.util.log.class =org.apache.juneau.microservice.jetty.JettyLogger # Jetty logging level # Possible values: ALL, DEBUG, INFO, WARN, OFF org.eclipse.jetty.LEVEL =WARN derby.stream.error.file =$C{Logging/logDir}/derby-errors.log -
jetty.xml - The Jetty configuration file.
A bare-bones config file that can be extended to use any Jetty features.
<Configure id ="ExampleServer" class ="org.eclipse.jetty.server.Server" ><Set name ="connectors" > <Array type ="org.eclipse.jetty.server.Connector" > <Item> <New class ="org.eclipse.jetty.server.ServerConnector" > <Arg> <Ref refid ="ExampleServer" /> </Arg> <Set name ="port" > $S{availablePort,8080}</Set> </New> </Item> </Array> </Set> <New id ="context" class ="org.eclipse.jetty.servlet.ServletContextHandler" > <Set name ="contextPath" >/</Set> <!-- Optionally specify your servlets here --> <!--Call name="addServlet"> <Arg>org.apache.juneau.microservice.sample.RootResources</Arg> <Arg>/*</Arg> </Call--> <Set name ="sessionHandler" > <New class ="org.eclipse.jetty.server.session.SessionHandler" /> </Set> </New> <Set name ="handler" > <New class ="org.eclipse.jetty.server.handler.HandlerCollection" > <Set name ="handlers" > <Array type ="org.eclipse.jetty.server.Handler" > <Item> <Ref refid ="context" /> </Item> <Item> <New class ="org.eclipse.jetty.server.handler.DefaultHandler" /> </Item> </Array> </Set> </New> </Set> <New id ="RequestLogImpl" class ="org.eclipse.jetty.server.CustomRequestLog" > <!-- Param 0: org.eclipse.jetty.server.RequestLogWriter --> <Arg> <New class ="org.eclipse.jetty.server.RequestLogWriter" > <Set name ="append" > false</Set> ;<Set name ="filename" ><Property name ="jetty.logs" default ="$C{Logging/logDir,logs}" /> /jetty-requests.log</Set> ;<Set name ="filenameDateFormat" > yyyy_MM_dd</Set> <Set name ="retainDays" > 90</Set> <Set name ="timeZone" > GMT</Set> </New> </Arg> <!-- Param 1: String --> <Arg> <Get class ="org.eclipse.jetty.server.CustomRequestLog" name ="EXTENDED_NCSA_FORMAT" /> </Arg> </New> <Get name ="ThreadPool" > <Set name ="minThreads" type ="int" > 10</Set> <Set name ="maxThreads" type ="int" > 100</Set> <Set name ="idleTimeout" type ="int" > 60000</Set> <Set name ="detailedDump" > true</Set> </Get> </Configure>
At this point, you're ready to start the microservice from your workspace.
14.2 - Running in Eclipsecreated: 8.0.0
The
Go to Run -> Run Configurations -> Java Application -> my-jetty-microservice and click Run. In your console view, you should see the following output:
Running class 'JettyMicroservice' using config file 'mjm.cfg'. Server started on port 10000 List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help >
Now open your browser and point to
http://localhost:10000
You can enter the command
14.3 - Building and Running from Command-Linecreated: 8.0.0
The
The easiest way to build your microservice is to run the following from the project root.
mvn clean install
Your
my-jetty-microservice-1.0.jar mjm.cfg
To start from a command line, run the following command from inside your
java -jar my-jetty-microservice-1.0.jar
You should see the following console output:
Running class 'JettyMicroservice' using config file 'mjm.cfg'. Server started on port 10000 List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help >
If you get this error message: java.net.BindException: Address already in use
,
then this microservice is already running elsewhere and so it cannot bind to port 10000.
15 - my-springboot-microservicecreated: 8.0.0
Starter Project Zip
my-springboot-microservice-
The
It includes a combination of the Juneau Core, Server, and Client APIs and all libraries needed to execute in a Java 1.8+ environment.
One significant difference is that we are not using the Juneau Microservice
API for our
application but instead using the existing Spring Boot API.
15.1 - Installing in Eclipsecreated: 8.0.0
Follow these instructions to create a new template project in Eclipse.
-
Download the
my-springboot-microservice- file from the downloads page (located in the binaries) and import it into your workspace as an existing project:9.0.1 .zip
-
Select the archive file and import the project:
-
In your workspace, you should now see the following project:
The important elements in this project are:
-
App.java - The entry point.
This class creates and starts our microservice.
Note that we're using the existing Spring Boot application logic for the microservice and we're retrieving our root resource as a spring bean.
Only the top-level resource needs to be annotated withJuneauRestRoot @JuneauRestRoot
@SpringBootApplication @Controller public class App {public static void main(String[] args) {new SpringApplicationBuilder(App.class ) .initializers(new JuneauRestInitializer(App.class )) .run(args); }@Bean @JuneauRestRoot public RootResources getRootResources() {return new RootResources(); } } -
RootResources.java - The top-level REST resource.
This class routes HTTP requests to child resources.
This is identical to the Jetty example.
@Rest ( path="/" , title="My Microservice" , description="Top-level resources page" , htmldoc=@HtmlDoc ( widgets={ ContentTypeMenuItem.class , StyleMenuItem.class }, navlinks={"options: servlet:/?method=OPTIONS" } ), children={ HelloWorldResource.class , ConfigResource.class , LogsResource.class } )public class RootResourcesextends BasicRestServletGroup {// No code } -
juneau.cfg - The configuration file.
Contains various useful settings.
Can be used for your own resource configurations.
Note that the Jetty configuration is not present.
Also it's located in the classpath so that our microservice can be built as a single executable jar.
#======================================================================================================================= # Basic configuration file for REST microservices # Subprojects can use this as a starting point. #======================================================================================================================= #======================================================================================================================= # REST settings #======================================================================================================================= [REST] # Comma-delimited list of key-value pairs that represent locations of static files that can be served up by your @Rest-annotated # classes. These are static files that are served up by the servlet under the specified sub-paths. # For example, given the following setting... # staticFiles = htdocs:my-docs,styles/my-styles # ...the URI "/servletPath/htdocs/javadoc.css" resolves to the path "/my-docs/javadoc.css". # This path can be relative to the working directory, classpath root, or package of your resource class. # Used by the BasicRestConfig interface that defines the following value: # staticFiles="$C{REST/staticFiles}" staticFiles = htdocs:htdocs# Stylesheet to use for HTML views. # Used by the BasicRestConfig interface that defines the following value: # stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}" theme =servlet:/htdocs/themes/devops.css # Various look-and-feel settings used in the BasicRestConfig interface. headerIcon =servlet:/htdocs/images/juneau.png headerLink =http://juneau.apache.org footerIcon =servlet:/htdocs/images/asf.png footerLink =http://www.apache.org favicon =$C{REST/headerIcon} header =<a href='$U{$C{REST/headerLink}}'> <img src='$U{$C{REST/headerIcon}}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/> </a> footer =<a href='$U{$C{REST/footerLink}}'> <img src='$U{$C{REST/footerIcon}}' style='float:right;padding-right:20px;height:32px'/> </a>
At this point, you're ready to start the microservice from your workspace.
15.2 - Running in Eclipsecreated: 8.0.0
The
Go to Run -> Run Configurations -> Java Application -> my-springboot-microservice and click Run. In your console view, you should see the following output:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.1.RELEASE) ... INFO: Tomcat started on port(s): 8080 (http) with context path '' Dec 21, 2012 12:30:00 AM org.springframework.boot.StartupInfoLogger logStarted INFO: Started App in 1.999 seconds (JVM running for 2.999)
Now open your browser and point to
http://localhost:5000
15.3 - Building and Running from Command-Linecreated: 8.0.0
The
The easiest way to build your microservice is to run the following from the project root.
mvn clean install
Your
my-springboot-microservice-1.0.jar
To start from a command line, run the following command from inside your
java -jar my-springboot-microservice-1.0.jar
You should see the following console output:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.1.RELEASE) ... INFO: Tomcat started on port(s): 8080 (http) with context path '' Dec 21, 2012 12:30:00 AM org.springframework.boot.StartupInfoLogger logStarted INFO: Started App in 1.999 seconds (JVM running for 2.999)
16 - juneau-petstorecreated: 8.2.0, updated: 9.0.0
The
What makes Juneau unique is the ability to create Java interfaces that behave just like RPC but using REST as the underlying protocol. And the technology it not tied to any platform so it can be used in any environment by simply pulling in Maven dependencies. The server-side need only provide the ability to host a servlet.
Visit the GitHub project hosting the application.
The project is broken down into the following subprojects:
juneau-petstore-api - Contains the Java interface and DTOs for the petstore application.juneau-petstore-server - Contains the server-side Java implementation of the petstore Java interface as a REST resource.juneau-petstore-client - Contains the client-side Java proxy of the petstore Java interface.
16.1 - Running the Pet Store Appcreated: 9.0.0
The Pet Store app is a Spring Boot application that can be started up by running the
- Install docker on your machine.
-
Clone the Petstore project on your machine.
git clone https://github.com/apache/juneau-petstore.git
-
Open terminal inside the project directory and run the below command to start the app.
docker build . -t petstore && docker run -p 5000:5000 petstore
16.2 - juneau-petstore-apicreated: 9.0.0
The
The
PetStore.java
Both sets of annotations are provided by pulling in the Juneau dependency below:
Maven Dependency
The
Pet.java
The annotations here are a combination of Juneau annotations for controlling marshalling (
16.3 - juneau-petstore-clientcreated: 9.0.0
The
Main.java
Notice how little code is necessary to construct a remote proxy.
16.4 - juneau-petstore-servercreated: 9.0.0
The
App.java
Notice how cleanly Juneau servlets fit into Spring Boot. No special initializers are required to integrate Juneau with Spring Boot.
The
RootResources.java
By extending from
This page renders as follows:
http://localhost:5000
The
PetStoreResource.java
Clicking the
http://localhost:5000/petstore
The methods defined in our
PetStoreResource.java
After running the
http://localhost:5000/petstore/pet
The
http://localhost:10000/petstore/pet?method=OPTIONS
Since we've defined tags on our annotations, the pet-related operations are all grouped under the
Information for all HTTP parts is automatically generated:
The schema models for POJO models is available in the
Auto-generated examples are available for all supported languages:
For example,
Examples can be derived in a number of ways. In our case, we've defined a static method on our
Pet.java
Similar functionality exists for request bodies as well:
At the bottom of the page is a listing of the POJO models in the app:
17 - Security Best-Practices
Security is always an ongoing concern in any library. If you discover any security vulnerabilities in this code, please refer to the instructions found here:
17.1 - juneau-marshallcreated: 8.2.0
Demarshalling vulnerabilities
One common security vulnerability is the ability to create arbitrary Java object instances through crafted
user input. For example, support for constructing POJOs based on an input attribute defining a
fully-qualified class name like
Fortunately, Juneau does not support an open-ended
The following example shows a potential vector that circumvents the restriction above:
Juneau does support something similar to a
POJO types of generalized input are also inferred through swaps. Again, since the POJO types are hardcoded at compile time, these should not be subject to demarshalling vulnerabilities. However, it is possible to circumvent this through your swap implementation as shown below:
All other parsers (JSON, URL-Encoding, MessagePack, etc...) work the same way in determining POJO types, so should be safe from demarshalling vulnerabilities.
Dependent libraries
When accessing security vulnerabilities of any library, dependent libraries must also be taken into account:
- The JSON, HTML, MsgPack, URL-Encoding, and UON parsers are written from scratch and do not rely on any other parsing technologies.
- The XML and HTML parsers uses the built-in Java StAX parser. This *should* be free from vulnerabilities.
- The RDF parsers rely on Apache Jena 2.7.1.
As of
7.0.1 , no known security vulnerabilities exist that affect Juneau at this time.
17.2 - juneau-svlcreated: 8.2.0
Care must be used when defining new Vars
using the SVL API since mistakes
could potentially expose system properties, environment variables, or even file system files.
For recap, the SVL support allows you to embed variables of the form
An example of a potential security hole is shown below that could potentially expose any file on a file system through a REST request:
This code is simply echoing the value of the
In reality, the above security hole does not exist because of the following restrictions:
-
Vars have two methodsVar.allowNested()
andVar.allowRecurse()
that can be overridden to prevent recursive processing of string variables. These are bothfalse for the$R variable, so the$F variable in the result will never get processed and instead be treated as plain text. -
The
$F variable only allows you to retrieve files within the JVM starting directory.
Even though the built-in Juneau variables are safe, special care is needed when defining your own custom
variables. If your variable resolves user input in any way, it's HIGHLY recommended that you override the
Var.allowNested()
and Var.allowRecurse()
methods to prevent recursive handling of variables.
17.3 - juneau-rest-servercreated: 8.2.0
Denial of service attacks can be alleviated through the maxInput()
setting. Arbitrarily-large input will trigger an exception before causing out-of-memory errors.
The default value for this setting is 100MB.
Since the parsers do not use intermediate DOMs and instead parse directly into Java objects, deeply nested data structures will almost always trigger stack overflow errors long before memory consumption becomes an issue. However, this is NOT true of the RDF parsers that use an intermediate DOM. If parsing RDF, you may want to consider lowering the max-input value above.
18 - v9.0 Migration Guide
The following guide can be used to help migrate your code to v9.0. Note that you can also refer to the Release Notes for changes as well.
Old | New |
---|---|
|
Has been replaced with RestOp , RestGet , RestPut , RestPost , RestDelete , RestOptions
|
|
Has been replaced with RemoteOp , RemoteGet , RemotePut , RemotePost , RemoteDelete .
|
|
These have been removed in 9.0 due to the removal of the Jena packages (due to security issues). Replace
with BasicRestServlet and BasicRestServletGroup .
|
|
Has been renamed to |
|
Has been renamed to Query.def() / FormData.def() . Note however that |
|
Has been renamed to Rest.defaultRequestHeaders() and added Rest.defaultResponseHeaders() .
|
|
Changed from a string array to a BasicRestServlet /BasicRestObject , the BasicRestOperations.getHtdoc(String,Locale)
is already implemented for you to provide static files under the sub-URI BasicStaticFiles which provides basic out-of-the-box functionality, so you can usually
just remove the previous |
Release Notes
Release Notes
5.0.0.0 (Jun 11, 2012)
Version 5.0 marks a major release milestone for the Juno/JJSON library. It is now available for download from iRAM under the name "Juno (previously JJSON)". The Juno Starters Guide has been updated to reflect new functionality in this release.
-
New name.
Unfortunately, "JJSON" was already trademarked by another similar library. Therefore, it's been renamed "Juno" (after the Roman goddess and wife of Jupiter) which does not appear to have any similar trademark issues (crosses fingers). The name is also a play on the word "Uno", indicating that this is a single simple unifying interface of several kinds of technology. -
Simplified APIs for working with beans.
Significant improvements have been made to the parsers to make it easier to convert serialized POJOs back into their original forms. -
Serializer/Parser classes now directly subclass from
BeanContext
.
In previous releases, if you wanted to change the way beans were handled by the serializers and parsers, you had to construct a separate bean map factory and pass it to the serializer or parser. Now, you can call the bean map factory methods directly on the serializer or parser class. -
Simplified Filter API for handling non-standard POJOs.
The API for handling non-standard POJOs has been simplified by introducing the concept of aTransformclass, which is associated with theBeanContext class (and thus the Serializer and Parser classes too) through theBeanContext.addTransforms(Class[]) method.
Two new subclasses ofTransform:BeanFilter- Filter POJO beans.PojoSwap- Filter POJOs that aren't beans.
Cast andBeanFilter APIs which were considerably more complicated and puts them under a common API. -
Elimination of
_class attributes in parsable output.
One of the complaints about the previous version of JJSON was that if you wanted to have the resulting JSON or XML be parsable back into beans, you had to enable the"addClassAttrs" property on the bean map factory class so that"_class" attributes could be added to the output.
This requirement is virtually eliminated in v5. In many cases, the parsers are able to determine through reflection what the correct target type is based on the top-level class passed in on the parse method. -
Performance improvements.
Several significant performance improvements have been made in this release.-
New Reader-based JSON parser.
Previously, the JSON parser required that the entire JSON text be loaded into memory as a String before being parsed. The new JSON parser is Reader-based which significantly reduces memory consumption. -
New StAX-based XML parser.
The old XML parser was based on DOM. The new XML parser uses a StAX parser which significantly reduces memory consumption. -
Caching of reflection data in the
BeanMap API.
The number of reflection calls have been significantly reduced in theBeanMap API code. Reflection is used to determine the class types of property values on beans. This information is now cached and persisted so that the reflection API calls to determine class types are only performed the first time a bean type is encountered. -
Automatic support for GZIP compression/decompression in
RestServlets .
This is completely transparent to the developer. The output writer is negotiated by the framework to automatically handle compression and charset requests without the developer needing to know anything about it.
-
New Reader-based JSON parser.
- Cognos/XML support.
- JSON-schema support.
-
New
PojoIntrospectorclass. -
Significant REST servlet API improvements.
- Defining child resources is considerably simpler now. In addition to the standard doX() methods for handling the requests for the current resource, you can also define getX() methods for returning child resources which automatically become available under the child URL specified by the getter name.
- Initialization of the child resources occurs automatically when the parent resource initialization occurs.
-
Other improvments have been made in the area of automatic negotiation of input and output type streams.
For example, automatic support is provided for GZIP (
Accept-Encoding: gzip ) and charsets (e.gAccept-Charset: SJIS ) on both incoming and outgoing data. It's all transparent from a developers perspective. The developer simply working with POJOs, and all details about content types, encoding, charsets, and so forth are handled by the framework. -
Support for generating complex
OPTIONS pages for resources.
-
Automatic support for SOAP XML output on
"text/soap+xml" requests againstRestServlet . - Support for XML namespaces.
-
Support for setting the XML root element name by either passing in a parameter on the serializer, or by specifying it via a
@Bean annotation. - Support for loading beans directly from Readers and Strings.
-
Parsing support for POJOs of type
Enum . -
Significant improved support for various flavors of parameterized types, such as subclasses of parameterized types (e.g.
MyBeanList ).extends LinkedList<MyBean> - Improved ordering of bean properties (should now be ordered as they are defined in the class).
-
Various default filters provided:
- byte[]<-->Base64 encoded strings
- Date/Calendar<-->ISO8601/RFC822/Long
-
New
HtmlParser
andUrlEncodingParser
classes. - HtmlSerializer now produces XHTML.
5.0.0.1 (Jun 14, 2012)
Juno 5.0.0.1 is a moderate update.
- New support for generating XML-Schema documents from POJO models.
- New support for serializing to RDF/XML.
5.0.0.2 (Sept 28, 2012)
Juno 5.0.0.2 is a minor update.
-
Improvements to Javadocs. Most of the information in the Juno Starters Guide wiki has been moved into the overview and package-level javadocs.
Since the information is now written in HTML, you can now copy and paste the code examples directly from the Javadocs.
The code examples are also syntax-highlighted using CSS. - Support for defining default XML namespaces on packages and classes for the XML and RDF serializers.
-
Restructured the packages along content type support (e.g. all JSON support moved to
org.apache.juneau.json ). -
Automatic support for parsing maps with
Enum keys, and parsingEnum strings.
This was previously possible using filters but now it's built-in for all the parsers. -
Replaced the
ObjectList.toXArray() methods with a newelements(Class<T> type) method that's more efficient and avoids creating an unnecessary array. -
Support for parsing into beans with read-only properties.
New@BeanConstructorannotation allows you to specify bean property values to be passed in through a constructor. -
Separated the rest library into separate independent client and server libraries.
Use one, use both, it's up to you.
5.0.0.3 (Oct 3, 2012)
Juno 5.0.0.3 is a minor update.
-
Support for parsing into read-only beans (i.e. beans with only getters, property values set through constructor args).
To support this, the@BeanConstructorannotation has been added. - Merged separate settings classes back into their base classes (simplifies the API).
-
SerializerGroup SerializerGroupsandParserGroup ParserGroupsnow shareBeanContexts
to reduce memory consumption of class type metadata.
5.0.0.4 (Oct 7, 2012)
Juno 5.0.0.4 is a minor update.
-
New
RestMethod @RestMethodannotation onRestServletmethods.
Allows the usage of URL pattern matching and automatic conversion of URL variables to arguments passed to method handlers.
SeeRestServletfor more information. -
Enhancements to
BeanContext.convertToType(Object,Class)to be able to convertStrings to classes withfromString(String) /valueOf(String) static methods orT(String) constructors.
5.0.0.5 (Oct 29, 2012)
Juno 5.0.0.5 is a major update.
- New
annotation for identifying child resources.@RestChild -
New
traversable andfilterable attributes added toRestMethod @RestMethodannotation.
Eliminates the need forPojoResourceandFilteredRestResourceclasses. - Simplified client API. Easier to use when making multiple connections to the same server.
- Support for pluggable authentication in the client API.
- Support for authenticating against Jazz Team Servers.
- Support for rendering package-level Javadocs in REST resources.
- Support for parsing of header values into specific object types.
- Changed default XML representation to not include JSON-type attributes. Produces cleaner XML.
-
New
resourceUri attributed added to@Bean annotation to associate beans with resource URIs.- Used for automatically creating hyperlinks in
HtmlSerializer
. - Used for automatically creating
uri attributes inXmlSerializer
. - Used for automatically creating
rdf:about attributes inRdfXmlSerializer .
- Used for automatically creating hyperlinks in
5.0.0.6 (Oct 30, 2012)
Juno 5.0.0.6 is a minor update that fixes a small bug in 5.0.0.5.
5.0.0.7 (Jan 20, 2013)
Juno 5.0.0.7 is a major update.
Core API updates
- Combined previous 3 libraries into a single library.
-
New
ParserListener
class.
Adds ability to find and process unknown bean properties during parsing. -
Enhancements to
XmlParser
:- Coalescing support
- Validations support
- Support for replacing entity references
- Resolver support
- Event allocator support
- Trim-whitespace support
-
Enhanced XML support:
-
New
@Xml.format
annotation.
Controls how POJOs get serialized to XML.
Also allows you to collapse collections and arrays. -
New
@Xml.namespaces annotation.
Namespaces can be defined at package, class, method, or field levels. -
New
@Xml.nsUri annotation.
Shortcut for specifying namespace URIs. -
New
@Xml.valAttr annotation.
Serializes a bean property value as an attribute. - Ability to override XS and XSI namespaces on XML and RDF/XML serializers.
- Ability to override RDF namespace on RDF/XML serializer.
- New more-efficient namespace resolution.
-
New
-
New configurable property classes for everything are now structured better and easier to locate and identify through the following new classes:
BeanContext
SerializerContextParserContext
-
Enhancements to
BeanContext
:-
Ability to mark bean properties as hidden using
@BeanProperty(hidden)so that they don't get serialized. -
Simplified
ClassType ClassMeta
API.
Combined 4 classes into a single class. -
New
@Bean.filterand@BeanProperty.filterannotations.
Used for defining filters on bean classes and bean properties instead of just globally throughBeanContext.addTransforms(Class[]) . -
New
PropertyNamer
API /@Bean.propertyNamer
annotation.
Used for customizing bean property names. -
New
@BeanProperty.beanUriand@BeanProperty.idannotations.
Used for associating beans with URLs and IDs.
Used by XML serializer to add a url attribute on a bean element.
Used by RDF/XML serializer to constructrdf:resource attributes. -
New
BeanProperty.properties()annotation. Used for limiting properties on child elements.
-
Ability to mark bean properties as hidden using
-
Automatic support for
URL
andURI
objects.- Converted to hrefs in HTML.
- Converted to url attributes in XML.
- Converted to resource:about attributes in RDF/XML.
- Improvements to Javadocs.
-
Improved
PojoQuerysupport.
REST client updates
- GZIP compression support.
- Bug fixes.
REST server updates
-
Support for overriding bean context and serializer properties in a REST method call through new
RestResponse.setProperty(String,Object)method.
For example, allows you to control whitespace options on a per-request basis. -
Several new annotations on REST servlets:
@RestResource.filters- Associate post-formatting filters on a resource level.@RestResource.guards- Associate resource-level guards.@RestResource.messages- Associate a resource bundle with a REST servlet. Comes with several convenience methods for looking up messages for the client locale.@RestResource.properties- Override default bean context, serializer, and parser properties though an annotation.
-
Several new annotations on REST methods:
@RestMethod(filters) - Associate post-formatting filters on a method level.RestMethod.guards() @RestMethod(guards)- Associate method-level guards.
-
New annotations on REST method parameters with automatic conversion:
@Attr- A parameter or URL variable value as a parsed POJO.@Param- A query parameter value as a parsed POJO.@PathRemainder- The remainder after a URL pattern match as a String.@Header- An HTTP header value as a parsed POJO.@Content- The HTTP content as a parsed POJO.@Method
- The HTTP method name as a String.
-
HTTP response content POJOs can now simply be returned from methods instead of calling
RestResponse.setOutput(Object).
5.0.0.8 (Jan 30, 2013)
Juno 5.0.0.8 is a minor update.
-
New
INI file
support.- Makes reading, updating, and manipulating INI configuration files a snap.
- Supports automatic conversion of data types in line with the functionality of the rest of the product.
- Comments and layout of INI files are persisted during saves.
5.0.0.9 (Feb 26, 2013)
Juno 5.0.0.9 is a moderate update.
Core API changes
-
INI config file support
:- A convenient API for reading, writing, and manipulating INI files.
- Ability to convert INI files to batch and shell environment variables.
- Command-line interface for updating INI files.
- Support for encoded INI file values.
- Support for fluent-style bean setters (setters that return the bean itself).
- Ability to use
@Bean
annotation to override bean identification settings. - New
ObjectMap.cast(Class)method to convertObjectMaps directly to beans.
REST server API changes
- Build-in default
OPTIONS pages. - New
@RestResource.defaultRequestHeadersand@RestResource.defaultResponseHeadersannotations. - New
RestMethod.serializers() @RestMethod(serializers)andRestMethod.parsers() @RestMethod(parsers)annotations. - New
RestMethod.properties() @RestMethod(properties)annotation. - New
@RestMethod(defaultRequestHeaders)annotation. - New
RestMethod.matchers() @RestMethod(matchers)annotation andRestMatcherclass. Readers andInputStreams can be specified on@Contentannotated parameters.- New
@HasParamannotation. - Full RFC2616 support for matching
Accept headers to serializers.
Other notes
- Smaller library size (460kB).
5.0.0.10 (Mar 7, 2013)
Juno 5.0.0.10 is a minor update.
Core API changes
- New
ObjectMap.findKeyIgnoreCase(String)method. - HtmlSerializer will now create 2-dimensional tables for collections of mixed beans/maps if all object have the same set of property names/keys.
REST server API changes
- New
RestServletProperties class that defines all the class-level properties that can be set on the servlet. - Properties can be set through
@RestResource.propertiesannotation, or newRestServlet.setProperty(String,Object)method. - New
"?noTrace" URL parameter to prevent stack traces from being logged (for JUnit testing of error conditions). - New
RestServletProperties.REST_useStackTraceHashes property to prevent the same stack trace from being logged multiple times. - New
RestServletProperties.REST_renderResponseStackTraces property for preventing stack traces in responses for security reasons. - New overridable
RestServlet.onError(HttpServletRequest,HttpServletResponse,RestException,boolean) andRestServlet.onSuccess(RestRequest,RestResponse,long)methods for plugging in your own logging and peformance monitoring. - Eliminated
RestServlet.getInitParams() method, since it's now redundant withRestServlet.getProperties(). - Header parameters passed as URL parameters are now case-insensitive.
5.0.0.11 (Mar 8, 2013)
Juno 5.0.0.11 is a moderate update.
REST server API changes
-
New
UrlEncodingRestSerializer andUrlEncodingRestParser classes.
Allows parsing form posts directly to POJOs. -
Support for
Accept andContent-Type "application/x-www-form-urlencoded" added by default onBasicRestServlet. -
New
RestServlet.renderError(HttpServletRequest,HttpServletResponse,RestException)method to allow customized handling of response errors.
5.0.0.12 (Mar 10, 2013)
Juno 5.0.0.12 is a minor update.
Core API changes
-
Relaxed method naming conventions when using
@BeanPropertyannotation.
Methods with zero parameters are interpreted as getters, and methods with one parameter are interpreted as setters.
Eliminated theBeanProperty.method annotation, since it's now unnecessary.
REST server API changes
-
Significantly improved response error messages.
Older messages were rather cryptic. Error conditions should be much easier to debug now. -
New
PlainTextRestSerializer class for serializing"plain/text" requests.
Useful for debugging purposes. -
Readers andInputStreams can now be passed in as@Contentparameters if you need direct access to the HTTP body content without involving the parsers.
Equivalent to previously callingRestRequest.getInputStream()
andRestRequest.getReader()
. -
Improved support for the
?debug parameter.
Dumps better information to the log file, such as all header parameters.
5.0.0.13 (Mar 14, 2013)
Juno 5.0.0.13 is a minor update.
Core API changes
-
New support for relative URIs.
- URIs of the form
"foo/bar" are interpreted as relative to the context root of the web application. - URIs of the form
"/foo/bar" are interpreted as relative to the HTTP authority (e.g."http://myhost:9080" ).
- URIs of the form
-
New
SerializerContext.SERIALIZER_uriContext andSerializerContext.SERIALIZER_uriAuthority serializer properties for specifying values for relative URIs. -
New
URI @URIannotation that allows you to specify classes and bean properties as URLs that aren'tjava.net.URI orjava.net.URL . -
New
HtmlSerializer.HTML_uriAnchorTextHTML serializer property for tailoring how anchor text is rendered. -
Renamed
BeanProperty#uri annotation toBeanProperty#beanUri to make it clear that this property represents the URI of the bean itself instead of an arbitrary property containing a URI. -
Removed
BeanProperty#id annotation.
REST server API changes
-
Improvements to
RestServletto automatically handle relative URIs in POJOs.SerializerContext.SERIALIZER_uriContext property set by default to web app context root.SerializerContext.SERIALIZER_uriAuthority property set by default to the request scheme+hostname+port.
-
Fixed bug involving
Accept-Charset header in Chrome that prevented HTML output from rendering correctly in that browser.
Accept-Charset handling should now be fully W3C compliant.
5.0.0.14 (Mar 23, 2013)
Juno 5.0.0.14 is a major update.
The biggest change is that the
Instead, the existing Serializer
, Parser
, SerializerGroup, and ParserGroup classes of the core API have been augmented to replace them.
Adoptions will be required if you have previously used these classes.
Core API changes
-
New
org.apache.juneau.serializer package.- Entirely reworked class hierarchy to make it easier to define new serializers.
- New
WriterSerializer
base class for defining character-based serializers. - New
OutputStreamSerializer
base class for defining byte-based serializers. - Updated
SerializerGroupclass with full support for RFC2616Accept-Content headers. - Improved cloning support on serializers and serializer groups.
-
New
org.apache.juneau.parser package.- Entirely reworked class hierarchy to make it easier to define new parsers.
- New
ReaderParser
base class for defining character-based parsers. - New
InputStreamParser
base class for defining byte-based parsers. - Improved cloning support on parsers and parser groups.
-
New
org.apache.juneau.swap package.- Cleaner class structure.
- Improved
BeanFilterclass for defining property filters on beans. - Improved
PojoQueryclass for defining filters on objects (previously calledObjectFilter ).
-
New
org.apache.juneau.encoders package.- Defines API for
Encoders
for enabling compression in REST servlets and clients. - Previously, gzip compression was enabled by default. This new API allows you to plug in your own compression algorithms.
- New
GzipEncoder
class for enabling gzip compression. - New
EncoderGroupclass for managing multiple encoders and finding them based on RFC2616Accept-Encoding header values.
- Defines API for
-
New
org.apache.juneau.plaintext package.- New
PlainTextSerializer
andPlainTextParser
classes for serializing/parsing text/plain content.
- New
-
New
org.apache.juneau.jso package.- New
JsoSerializerclass for serializingapplication/x-java-serialized-object content.
- New
-
New
org.apache.juneau.soap package.- New
SoapXmlSerializer
class for serializingtext/xml+soap content.
- New
-
Improved cloning support on the
BeanContext
class.- Better caching. Improved caching performance.
-
JsonMap andJsonList changed toObjectMapandObjectListto better reflect that they're not limited to just JSON support. -
Renamed
PojoSwap toPojoQueryto not confuse it with the new Filter API.
REST server API changes
-
Eliminated
org.apache.juneau.rest.serializers andorg.apache.juneau.rest.parsers packages.- All existing REST serializers and parsers merged into the core API.
REST client API changes
-
Simplified
RestClient
API.- You can now only specify a single serializer or parser per client. This significantly simplifies the code.
- Support for
Encoders
.
-
Eliminated
RestCmdLine (since it's essentially redundant with CURL).
5.0.0.15 (Mar 24, 2013)
Juno 5.0.0.15 is a moderate update.
-
Juno-Wink integration components that have been requested by many for a long time!
Refer tojaxrsfor information. -
New
@Producesannotation in place ofISerializer.getMediaTypes() for specifying what media types a serializer produces.
Available when subclassing fromSerializer
. -
New
@Consumesannotation in place ofIParser.getMediaTypes() for specifying what media types a parser consumes.
Available when subclassing fromParser
.
5.0.0.16 (Mar 25, 2013)
Juno 5.0.0.16 is a minor update.
-
New
@PropertiesREST method parameter annotation that can be used to get the runtime properties map through a parameter instead of throughRestResponse
.
5.0.0.17 (Mar 25, 2013)
Juno 5.0.0.17 is a minor update.
-
Charset now passed as a parameter to
IOutputStreamSerializer.serialize() andIInputStreamParser.parse() .
5.0.0.18 (Mar 27, 2013)
Juno 5.0.0.18 is a moderate update.
The biggest change is the introduction of the RdfSerializer class that uses Jena to generate RDF/XML, RDF/XML-ABBREV, N-Tuple, N3, and Turtle output.
This code should be considered prototype-quality, and subject to change in the future.
There are plans of adding an equivalent
The
However, I'm keeping it around, since it's considerably faster and uses far less memory than the Jena-based serializer since it serializes directly from POJOs to RDF/XML.
It may or may not be removed in the future depending on demand.
Other changes
-
New
JsoParserclass.
5.0.0.19 (Apr 1, 2013)
Juno 5.0.0.19 is a minor update.
-
New methods on
RestServlet:RestServlet.onPreCall(RestRequest)RestServlet.onPostCall(RestRequest,RestResponse)
-
TRIM_NULLS setting changed toSerializerContext.SERIALIZER_trimNullProperties.
New property default istrue . Only applies to bean properties, not map or collection entries.
5.0.0.20 (Apr 7, 2013)
Juno 5.0.0.20 is a major update.
Core API changes
-
New Jena-based
RdfSerializerfor serializing POJOs to RDF/XML, RDF/XML-ABBREV, N-Triple, Turtle, and N3.
Serializes ANY POJOs to RDF, even simple objects and primitives. -
New Jena-based
RdfParserfor parsing RDF/XML, RDF/XML-ABBREV, N3, Turtle, and N-Triple back into POJOs. -
XmlSerializerContext.XML_autoDetectNamespacesdefault changed totrue .
The old default value would cause XML with unmapped namespaces if you didn't manually specify them via theXmlSerializerContext.XML_namespacesannotation.
While setting the default totrue is somewhat slower (since the serializer must crawl the POJO tree to find namespaces), the benefits of having it work out-of-the-box outweighs the performance concerns.
For developers concerned about performance, they can always change it back to false and specify the namespaces themselves.
REST server API changes
-
Allow inheritance of
@RestResourceannotation.
Serializers, parsers, filters, properties , guards, and converters definitions are automatically inherited from parent classes and interfaces. -
Enhancements to
RestMethod @RestMethodannotation:-
New
RestMethod.filters() annotation for defining POJO filters at the method level. -
New
RestMethod.serializersInherit()andRestMethod.parsersInherit()annotations for controlling how serializers and parsers (and associated filters and properties) are inherited from the class.
This replaces the previousaddSerializers andaddParsers annotations.
-
New
-
New
RestServletJenaDefaultservlet that includes serialization/parsing support for all Jena-based serializers and parsers. -
New
DefaultJenaProviderJAX-RS provider that includes serialization/parsing support for all Jena-based serializers and parsers. -
Eliminated
RestServletChild class.
It's redundant with the introduction of inheritable annotations. -
New methods on
RestServlet:RestServlet.createConfigFactory() RestServlet.createSerializers() RestServlet.createParsers()
getBeanContext() /getSerializers() /getParsers() methods.
REST client API changes
-
New
RestCall.setDateHeader(String,Object) method for setting ISO8601 datetime headers.
5.0.0.21 (Apr 9, 2013)
Juno 5.0.0.21 is a minor update.
Core API changes
-
New
HtmlDocSerializerContext.HTMLDOC_navlinksannotation for addint links to HTML page views. -
Renamed the properties in
HtmlDocSerializerContextfor clarity.
Servlet API changes
-
Added new
RestServlet.addDefaultProperties(ObjectMap,RestRequest) method for programatically adding properties to the property map per request. -
Added the following new properties in the properties map to make them easily available to serializers and parsers (since they don't have access to the HTTP request object).
Note that theSerializerContext.SERIALIZER_uriAuthority andSerializerContext.SERIALIZER_uriContext properties were previously available.RestServletProperties.REST_servletPath RestServletProperties.REST_pathInfo RestServletProperties.REST_method
-
Path variables annotated with
@Attrare now automatically URL-decoded.
5.0.0.22 (Apr 12, 2013)
Juno 5.0.0.22 is a minor update.
Core API changes
-
New
annotation for specifying localized property values.@Property .nls()
For example, allows you to set theHTMLDOC_title andHTMLDOC_description properties to localized values pulled from a resource bundle.
See theAddressBookResource class for an example.
REST Servlet API changes
- Fix a bug where the
&Content query parameter was not always parsed correctly.
5.0.0.23 (Apr 14, 2013)
Juno 5.0.0.23 is a minor update.
-
Simplified
cognos Cognossupport. -
Fixed bug where
annotation was not being inherited by inner classes.@Xml - Javadoc stylesheet improvements.
5.0.0.24 (May 9, 2013)
Juno 5.0.0.24 is a major update.
Core API updates
-
New support for
ATOM
.- New
AtomFeedResource class added to sample war.
- New
-
New
XmlFormat.CONTENTenum value.
Allows bean properties to be persisted as XML element text. -
New
XmlContentHandlerclass and@Xml.contentHandlerannotation.
Allows customized serialization and parsing of beans to XML element text.
Added for support of ATOM text content that must support both plain text and embedded XHTML. -
New
@XmlSchema
and updated@XmlNs
annotations to better mimic JAXB. -
Removed
annotation since it's now redundant with@Xml .valAttr .@Xml (format=CONTENT ) -
Fixed timezone bug in
CalendarSwap. -
Simplified
Serializer.serialize(Object,Object,SerializerContext) method. -
Fixed bug where lists returned by
ObjectMap.getObjectList(String)were not updatable. - Eliminated old RDF/XML serializer.
Documentation updates
- New
JSON Support Overview
document. - New
XML Support Overview
document. - New
RDF Languages Support Overviewdocument. - New
ATOM Support Overview
document.
5.0.0.25 (May 11, 2013)
Juno 5.0.0.25 is a minor update.
Core API updates
-
New
ResultSetList
DTO for serializing SQL result sets to JSON/XML/HTML and so forth. -
New
SqlQueryResource class in the sample war for demonstrating theResultSetList DTO.
Server API updates
-
Fixed issue with media type for CSS files being reported as
"text/plain" instead of"text/css" . -
Moved initialization of class properties to before the call to
Servlet.init() so thatgetProperties() can be called during servlet initialization. -
New
annotation with support for using system properties as resource properties.@Property .type
5.0.0.26 (Jun 5, 2013)
Juno 5.0.0.26 is a minor update.
- FindBug fixes.
-
Changed the way child REST resources are defined.
Eliminated the@RestChild annotation on getter methods and replaced it with@RestResource(children)defined on the resource class itself.
Child resource paths are specified through@RestResource(path). -
New
ChildResourceDescriptionsbean for automatically generating the contents of router resource pages. -
Changed
to@RestMethod .pattern()RestMethod.path() @RestMethod(path)for naming consistency.
5.0.0.27 (July 7, 2013)
Juno 5.0.0.27 is a moderate update.
-
Fixed some HTML formatting issues in
HtmlSerializer
. -
BasicRestServletnow includesPlainTextSerializer
andPlainTextParser
for plain-text support. -
Child resources now render on default
OPTIONS pages through new methodResourceOptions.getChildren(). -
Changes to
UrlEncodingSerializer
/UrlEncodingParser
to reduce the need for quoted string values.
More changes are likely in this area of the code to support multipart form posts. - FindBugs fixes.
5.0.0.28 (July 9, 2013)
Juno 5.0.0.28 is a moderate update.
-
Fixes an
OutOfMemoryError and performance issue caused by incorrect caching of class metadata. -
Added
WriterSerializer.serialize(Object,Writer) convenience method for serializing directly to a writer.
Applies to all serializers.
5.0.0.29 (Aug 2, 2013)
Juno 5.0.0.29 is a moderate update.
-
Revamped the API for filter support:
- Updated
BeanFilterclass to mirror the@Bean
annotation. - Introduced support for bean
Bean.subTypeProperty() subtypes. - Replaced
with new@Bean (filter=xxx)@Transformannotation.
- Updated
-
Revamped URL-Encoding support.
The old URL-Encoding serializer and parser simply used the JSON serializer/parser with a thin URL-encoding top layer.
The new URL-Encoding serialize and parser was written from scratch and is considerably more consistent in design and output. -
Improved number parsing.
The new number parser should handle any valid numeric syntax for decimals and floats that Java itself supports. -
JsonSerializer
LAX mode now quotes reserved word attributes. -
New predefined DateFilters with millisecond precision:
org.apache.juneau.swaps.DateSwap.ISO8601DTP org.apache.juneau.swaps.DateSwap.ISO8601DTZP
5.0.0.30 (Aug 8, 2013)
Juno 5.0.0.30 is a minor update.
-
Fixed bug involving beans using
Bean.subTypes()annotation in addition tosubTypes property. - Modified the JSON parser to handle non-existent JSON values to get around an issue where Cognos was generating invalid JSON.
5.0.0.31 (Aug 9, 2013)
Juno 5.0.0.31 is a moderate update.
-
Simplified the
Serializer
andParser
class hierarchies.
This reverses a previous change that added a bunch of interfaces in these APIs (and subsequently required compiling with Java 7 to get around a compiler bug).
The new class hierarchy is much simpler to understand. -
Added
XMLGregorianCalendarSwapto convert these to ISO8601 strings during serialization, and vice versa during parsing. -
Added a strict mode to
JsonParser
. -
Added default
JsonParser.DEFAULT_STRICT
parser.
5.0.0.32 (Oct 5, 2013)
Juno 5.0.0.32 is a moderate update.
-
New support for generating and consuming fully-compliant JSON-Schema documents.
Seejsonschemafor information. -
New methods added to
Parser
:org.apache.juneau.parser.Parser.parseMap(Object,int,Class,Class,Class) org.apache.juneau.parser.Parser.parseCollection(Object,int,Class,Class)
-
@Bean
annotation can now be defined on interfaces and inherited by subclasses. -
Support for customizing serialized values for
Enums through overridingtoString() andfromString() on the enum class.
Previously usedEnum.valueOf() to convert strings back intoEnums .
Used for JSON-Schema support to allowJsonType
enum to be serialized to lowercase per the specification (e.g."string" instead of"STRING" ). -
cognos CognosDTOs now have fluent-style bean setters. -
Support for generic bean objects whose type was erased at compile time.
Previous behavior gave you an error message that the type could not be determined.
New behavior assumes a type ofObject when the type is erased. -
Bug fixes:
-
When duplicate fluent-style setters were defined with different parameter types (e.g.
setFoo(Foo f) ,setFoo(Bar b) ), theBeanMap
API would sometime choose the wrong setter as the bean property setter.
Now validates that the setter being chosen is the one whose return type matches the property getter. -
Passing in
Accept GET parameters with'+' (e.g.&Accept=text/json+simple ) wasn't working anymore.
TheAccept parameter is supposed to interpret spaces as'+' to allow you to not have to write&Accept=text/json%2Bsimple . -
Parsers would not set bean properties of abstract type
Number
.
Now it detects the numeric type based on input and sets the value accordingly.
-
When duplicate fluent-style setters were defined with different parameter types (e.g.
5.0.0.33 (Oct 20, 2013)
Juno 5.0.0.33 is a moderate update.
-
Removed generic parameter from
WriterSerializer
class.-
Many of the examples in the documentation were written as follows, which resulted in "unchecked" compile warnings:
WriterSerializer s = new JsonSerializer();
These compile warnings will now go away.
-
Many of the examples in the documentation were written as follows, which resulted in "unchecked" compile warnings:
-
New settings in BeanContext. These can be applied to all serializers/parsers.
BEAN_ignoreInvocationExceptionsOnGettersBEAN_ignoreInvocationExceptionsOnSettersBEAN_notBeanPackages_addBEAN_notBeanPackages_remove
-
Eliminated
addNotBeanClassPatterns(String...) methods throughout API since these are now controlled byBEAN_notBeanPackages_add/BEAN_notBeanPackages_removeproperties. -
New settings in
RestServletProperties .RestServletProperties.REST_trimTrailingUriSlashes
Also removedRestRequest.getRequestURI(boolean trimTrailingSlashes) method which is now redundant with this property.RestServletProperties.REST_pathInfoBlankForNull
Also removedRestRequest.getPathInfo(boolean returnBlankForNull) method which is now redundant with this property.
-
New JSON-Schema
JsonSchemaMap
class for supporting linked schemas. -
Serializers will no longer throw an exception when
maxDepth setting is reached, and will instead simply ignore content below the specified depth.
While the old behavior was as-designed, the new behavior is more in-line with expected behavior. -
Added support for HTTP header
"X-Response-Headers" toRestServlet.
Allows you to specify one or more headers that should be returned on the response from the servlet.
For example, to get a page to automatically refresh every 1 second, you can append the following to a URL:?x-response-headers={Refresh=1} -
Removed
HtmlDocSerializerContext. setting that added a Refresh meta tag to HTML documents, since this can now be controlled throughHTML_REFRESH X-Response-Headers . -
Small improvements to samples.
PhotosResource now includes a default entry.
5.0.0.34 (Nov 10, 2013)
Juno 5.0.0.34 is a moderate update.
-
New support for runtime-replaced variables in REST resource properties:
@RestResource ( messages="nls/Messages" , properties={@Property (name="label" ,value="$L{servletTitle}" ),// Localized variable in Messages.properties @Property (name="javaVendor" ,value="$S{java.vendor}" ),// System property @Property (name="foo" ,value="bar" ),@Property (name="bar" ,value="baz" ),@Property (name="v1" ,value="$R{foo}" ),// Request variable. value="bar" @Property (name="v2" ,value="$R{$R{foo}}" )// Nested request variable. value="baz" } )RestServlet.createRequestVarResolver(RestRequest) for more information. -
Eliminated
@Property.type annotation which was the old way of specifying NLS variables that got resolved at runtime. -
New methods on
RestRequest
:RestRequest.getVarResolver()RestRequest.getServletURI()RestRequest.getRequestParentURI()
-
New methods on
RestResponse
:RestResponse.sendRedirect(CharSequence)
-
New methods on
RestServletthat allow easier customization by subclasses:RestServlet.createConfigFactory()RestServlet.createConverters()RestServlet.createDefaultRequestHeaders()RestServlet.createDefaultResponseHeaders()RestServlet.createEncoders()RestServlet.createFilters()RestServlet.createGuards()RestServlet.createMimetypesFileTypeMap()RestServlet.createParsers()RestServlet.createProperties()RestServlet.createRequestProperties(ObjectMap,RestRequest)RestServlet.createRequestVarResolver(RestRequest)RestServlet.createSerializers()RestServlet.createUrlEncodingParser()
-
Changed
RestServletNls to useResourceDescription/MethodDescription instead ofRestResource/RestMethod -
New property
RestServletProperties.REST_htDocsFolder .
New support for serving up static documents from classpath through REST interface. -
Exception APIs now use
MessageFormat
(e.g."{0}" ) for message variables instead of"%s" . -
New
@Bean.stopClass
annotation for specifying stop classes for bean properties. -
New
BeanFilter.setStopClass(Class)which is the program equivalent to the annotation above. -
New methods on
ResultSetList
:ResultSetList.handleBlob(Blob) ResultSetList.handleClob(Clob)
5.0.0.35 (Nov 26, 2013)
Juno 5.0.0.35 is a minor update.
RestGuard.guard(RestRequest,RestResponse)now returns a boolean to allow redirects to login pages.- Fixed bug in RestServlet where occasional false positive "duplicate method with same name and path" errors were occurring.
5.0.0.36 (Dec 18, 2013)
Juno 5.0.0.36 is a minor update.
- Implemented
org.apache.juneau.urlencoding.UrlEncodingParser.parseArgs(Reader,int,ClassMeta[]) . name parameter ofResourceDescription.ResourceDescription(String,String,String). is now automatically URL-encoded so that the name can contain special characters (e.g."foo/bar(baz)" ).- Support for URL-matching and path info containing encoded characters (e.g.
'/' ) now supported. - Removed some lazy-initialization of bean information in
ClassMeta
that allowed the removal of some synchronized blocks. - Improved support of
BeanContext.getClassMetaFromString(String). Now supports primitive arrays such as"long[]" in addition to the previous support for the equivalent"[J" . - Various new convenience methods added to
StringUtilsandClassUtils.
5.1.0.0 (Jan 18, 2014)
Juno 5.1.0.0 is a major update.
Major changes
- Brand new REST client API that uses Apache HttpClient for HTTP communication.
The new client API is simply a thin layer on top ofHttpClient that performs serialization and parsing using Juno parsers but leaves all the details of the HTTP connection to the Apache code.
See theclientpackage for details. - New
org.apache.juneau.rest.client.jazz package andorg.apache.juneau.rest.client.jazz.JazzRestClient class for performing REST operations against Jazz servers.
Includes improved support for FORM authentication, and better SSL certificate validation. - Completely redesigned URL-Encoding support.
Seeurlencodingpackage for details. - Changes to Parser API.
- Removal of
ExtendedReaderParser abstract class and moved methods intoReaderParser
class. - Removal of
DataFormat class from API since it was no longer necessary due to API change above. - Removal of
ParserStringReader class.
This was a reader optimized to work withString input.
However, it could interfere with garbage collection of the original string object.
Instead, the existingParserReader
was enhanced to work well withString input, and tests show no significant performance differences. - New
org.apache.juneau.parser.Parser.parse(Object,int,ClassMeta) convenience method added.
- Removal of
Other changes
- Various new methods added to
StringUtilsandClassUtils. - Improved support on
BeanContext.getClassMetaFromString(String).
Now supports resolving"long[]" , and so forth. ResourceDescriptionname parameter is now automatically URL-encoded in links.RestRequest
now correctly handles cases involving URL-encoded characters in the path info portion of URLs (e.g.http://host/contextRoot/foo%2Fbar ).- Removed lazy-initialization that required locking in
ClassMeta
. - New
BeanContext.setDefaultParser(ReaderParser) method added for specifying a default parser to use in a bean context (used when converting beans toStrings usingBeanContext.convertToType(Object,Class). Old behavior simply used the default JSON serializer in these cases. - More consistent handling of exceptions across all parsers.
- Minor changes to
RestRequest
class.- Changed the order of parameters on
RestRequest.getParameter(String,Class). - Added
RestRequest.getMapParameter(String,Class,Class,Class) andRestRequest.getCollectionParameter(String,Class,Class)} methods.
- Changed the order of parameters on
5.1.0.1 (Jan 25, 2014)
Juno 5.1.0.1 is a minor update.
- Addressed some behavioral differences between Tomcat and WAS.
- Query parameter lookup is now always case-insensitive (per WAS behavior).
- Consistent handling of redirect requests (Tomcat and WAS handle relative redirect paths differently).
- Fixed bug involving incorrect resolution of overlapping URL match patterns.
- Overall improvements in HTTP request parameter and header value resolution.
- Made workspace changes so as not to be dependent on the WAS test environment being loaded.
- Renamed
@Remainder annotation to@PathRemainder. - Fixed bug involving incorrect calculation of
pathInfo on child resources.
5.1.0.2 (Apr 27, 2014)
Juno 5.1.0.2 is a minor update.
- Fixed issue preventing
&Accept-Language from being used as a GET parameter. - Minor XSS vulnerability fix.
- Empty results on HTML pages now shows
"no results" instead of a blank page. - Fixed issues preventing REST pages from rendering HTML in newer versions of Internet Explorer.
- Changed
RestServletProperties.REST_allowMethodParam to be disabled by default.
5.1.0.3 (Jun 28, 2014)
Juno 5.1.0.3 is a moderate update.
Core API updates
- Ability to detect and use non-public bean classes, getters/setters, and fields using the following new properties:
BeanContext.BEAN_beanConstructorVisibility- Control which bean constructors are visible to Juno.BeanContext.BEAN_beanClassVisibility- Control which bean classes are interpreted as beans to Juno.BeanContext.BEAN_beanFieldVisibility- Control which fields are visible to Juno as bean properties.BeanContext.BEAN_beanMethodVisibility- Control which getters/setters are visible to Juno as bean properties.
BeanContext. andINCLUDE_BEAN_FIELD_PROPERTIES BeanContext. properties, since ignoring fields and methods can be accomplished by setting the appropriate properties above toINCLUDE_BEAN_METHOD_PROPERTIES NONE
. Also, the@BeanPropertyannotation can now be used on non-public fields/getters/setters to override the default behavior defined by theVISIBILITY properties identified above. This is a convenient way of identifying protected or private fields or methods as bean properties. Previously, you could only identify public fields/getters/setters using this annotation. - New
BeanContext.BEAN_useJavaBeanIntrospectorproperty that lets Juno use the Java beanIntrospector class to determine bean properties. In the previous release, the method for determining bean properties was a mixture of Juno-based and Introspector-based. Now it's either pure Juno-based or pure Introspector-based. The result is considerably cleaner code and consistent behavior. - New
@BeanIgnore
annotation. Replaces the previous@BeanProperty(hidden=true)annotation for ignoring bean properties. Can also be used on classes that look like beans so that they're not treated as beans. - Support for parsing into non-static member classes. This applies to all parsers.
- New
@Json(wrapperAttr)
annotation that automatically wraps beans and objects in a wrapper attribute when serializing to or parsing from JSON. - Changed the default ordering of bean properties to be in parent-to-child class order.
- New
BeanFilter.readProperty(Object,String,Object) readProperty()andBeanFilter.writeProperty(Object,String,Object) writeProperty()methods added toBeanFilterclass to allow individualized serialization and parsing behavior on a class-by-class basis. - Eliminated previous restriction where bean subtype attributes had to be listed first in JSON objects when using the
Bean.subTypeProperty()annotation. The previous behavior was not strictly JSON-compliant since JSON objects are supposed to consist of unordered lists of key/value pairs. While targeted for JSON, the restriction is also lifted for all other parsers. - New fluent-style
BeanMap.load()
methods for initializing bean maps. HtmlDocSerializer
will now embed the data portion of the output in a element to make it easier to extract the data portion of the page in Javascript in browsers.<div id ='data' >
REST Server API updates
- New
RestRequest.getJavaMethod()method for getting access to the method used to handle a request. Useful for accessing the method name or annotations during requests, such as in calls toRestGuard.guard(RestRequest,RestResponse). - Fixed bug when using Jetty where you tried to read text input after a header was written.
- Added new string variables
$A{...} (request attributes) and$P{...} (request parameters) toRestServlet.createRequestVarResolver(RestRequest) .
5.1.0.4 (Aug 25, 2014)
Juno 5.1.0.4 is a minor update.
- New
RestServlet.getPath()method. - New
SerializerContext.getJavaMethod() andParserContext.getJavaMethod() to allow access to REST methods that invoked the serializers or parsers. For example, can be used to access additional annotations on REST methods to perform special handing during serialization or parsing. - Better behavior on overriding of filters in
BeanContext.addTransforms(Class[]) . Previously, adding multiple conflicting filters resulted in random behavior. Now filters are overridden when multiple matching filters are applied. - Allow
HtmlDocSerializerContextproperties to be set viaSerializer.setProperty(String,Object). Previously, these could only be defined through override properties (e.g. through REST class and method annotations). - Fixed memory leak in XML parser.
5.1.0.5 (Sept 1, 2014)
Juno 5.1.0.5 is a moderate update.
- New
Redirectclass that simplifies performing redirections in REST methods. - New pluggable
ResponseHandlerclass and@RestResource(responseHandlers)annotation for defining customer response handlers for special kinds of POJOs. - New method
UrlEncodingSerializer.serializeUrlPart(Object)method. - New method
RestRequest.getServletURIBuilder()for construcing servlet-based URLs more efficiently. - New method
RestResponse.getNegotiatedOutputStream()
that uses encoders if a match is found, andRestResponse.getOutputStream()
that just return the underlying output stream without any modifications. - Fixed bug where some properties were not being propagated correctly when using
CoreObject.setProperties(ObjectMap)on serializer and parser subclasses. - Fixed bug in
HtmlSerializer
where URL keys in Maps were not being serialized as hyperlinks. - Fixed bug in
JsonSerializer
where"_class" and"items" attributes were not quoted in strict mode when using SERIALIZER_addClassAttrs feature. - Fixed bug where
Content-Encoding andCharacter-Encoding headers were being set when callingRestResponse.getOutputStream()
. These should not be set if interacting with the output streams at a low level. - Eliminated various convenience
RestResponse.sendRedirect(...) methods due to the introduction of theRedirectclass.
5.1.0.6 (Sept 21, 2014)
Juno 5.1.0.6 is a moderate update.
- Simplified API for
PojoSwap. Since it's rarely used, thebeanContext parameter was replaced with aPojoSwap.getBeanContext()method on the class. - New simplified way of defining POJO filters without needing to extend
PojoSwap. SeeSurrogateSwapfor details. - New
@Html
annotation. Will allow the definition of standard XHTML DTOs in future releases. For now,Imgis an example of defining an XHTML element using Juno DTOs. JsonParser
now ignores trailing';' characters in input so that it can parse strings of the form"var x = {'foo':'bar'};" .- New
TumblrParserResource in the samples war file showing how to combine the REST client and server APIs into a single resource in order to download Tumblr blogs and convert the response into any supported response content type.
5.1.0.7 (Oct 5, 2014)
Juno 5.1.0.7 is a moderate update.
- Improved error handling.
- New
ParserContext.PARSER_debugandSerializerContext.SERIALIZER_debug. settings for logging additional information for debugging problems. - New
SERIALIZER_ignoreRecursionssetting for explicitly ignoring recursions when serializing models. Previously, theSERIALIZER_detectRecursions setting did this but now it simply looks for recursions and throws exceptions when they occur. - Improved handling of
StackOverflowErrors . WhenSERIALIZER_detectRecursions is enabled, a useful error message is displayed showing the exact chain of objects that resulted in the stack overflow. - Bug fixes in
ResultSetList
for Oracle and SQL Server. - Serializers and parsers can now access HTTP request attributes, parameters, and headers through
SerializerContext.getProperties() andParserContext.getProperties() . - Removed media-type and encoding attributes from
SerializerContextandParserContextsince these are now available through context properties, and are typically not used. XmlParser
now acceptsapplication/xml .- Improved handling of bean property serialization when multiple matching pojo filters for the bean property class exist.
- Improved concurrency on BeanContext class.
- Fixed bug in
Traversablethat was causing it to be executed even if the servlet extra path info was empty. - Fixed bug in
Traversablewhere it was not picking up filters and properties defined on REST Java methods.
5.1.0.8 (Oct 25, 2014)
Juno 5.1.0.8 is a moderate update, focused primarily on performance improvements.
- Improved performance on JSON and URL-Encoding parsers by approximately 50% on large data sets.
- Rewrote
ParserReader
class to handle it's own buffering. The change allowed several optimizations to be made when dealing with JSON and URL-Encoding text by avoiding char array copies. - Added a
estimatedSize parameter to theParser
parse methods to optimize buffering when the input size is known beforehand.
- Rewrote
- Revamped the
BeanContext
API to perform better in multi-threaded environments.- Introduced a new
BeanPropertyStore class that handles creation ofBeanContext
objects. This allowsBeanContext objects to be considered immutable, and therefore cacheable/reusable by the framework. While this was technically possible to cache these objects beforehand, it relied on a locking mechanism to prevent bean contexts from being modified after being created. The new mechanism is much more straightforward.
- Introduced a new
- Modifications to the
clientAPIs to make it easier to work with custom Apache HTTP clients.- Added overridable
RestClient.createHttpClient()to allow customized subclasses to construct customized HTTP clients. - Removed the
DefaultRestClient class since it's now fully redundant withRestClient . - Added
RestClient.shutdown() method for cleaning up the internal HTTP client when you're done using a REST client.
- Added overridable
5.1.0.9 (Dec 1, 2014)
Juno 5.1.0.9 is a major update. There weren't very many code changes but the source has been made a part of Jazz Foundation. This required some restructuring of the project. The project on Jazz Hub will eventually be discontinued. However, the libraries on IBM Community Source will continue to be updated regularly.
- Project split up into 3 separate bundles:
org.apache.juneau - Core serializers and parsers.org.apache.juneau.rest - REST server component.org.apache.juneau.rest.client - REST client component.
- Code changes to facilitate breaking up bundles:
org.apache.juneau.rest.labels.Link class moved toLink.- References to
org.apache.juneau.rest.RestException inEncoder
class changed toIOException .
- Changed configuration names for consistency with Jazz Framework.
RestClient.execute(HttpUriRequest)method that allows subclasses to handle their own HTTP request execution.- Changes in
JazzRestClient to handle introduction of SSO support in v6. &plainText debug feature was broken.- Removed double-buffering in
RestRequest . - Metadata cleanup, Find Bug fixes.
5.1.0.10 (Dec 23, 2014)
Juno 5.1.0.10 is a moderate update.
Core
- Major changes to URL-Encoded serializer and parser.
- Logic for serializing and parsing URL-Encoded key-value pairs moved to
UrlEncodingSerializer
andUrlEncodingParser
classes. - Logic for serializing and parsing URL-Encoded values moved to new
UonSerializer
andUonParser
classes.
- Logic for serializing and parsing URL-Encoded key-value pairs moved to
- Fix bug where
BeanRuntimeExceptions weren't being thrown on subsequent calls toBeanContext.getClassMeta(Class)
. - Moved logic for
BeanContext.getPrimitiveDefault(Class) to newClassMeta.getPrimitiveDefault()
method for performance reasons. - Fixed bug in
BeanContext.addTransforms(Class[])that would cause filter order to get messed up. ClassMeta.newInstance()
can now create array instances.- Fixed indentation bugs in
HtmlSerializer
. - Fixed issue in
HtmlSerializer
where newlines were not being converted into line breaks. - New
WriterSerializer.toString(Object)
method that's identical to the serialize method but throwsRuntimeExceptions to make the serializer easier to use for debugging.
Server
- Fixed major issue that prevented parsing URL-Encoded form posts into POJOs.
Calling
HttpServlet.getParameter(String)was forcing the underlying servlet code to process the HTTP body itself, preventing theUrlEncodingSerializer class from being able to parse the content. Updated code no longer inadvertantly calls this method. - New
RestRequest.getQueryParameter(String),RestRequest.hasQueryParameter(String), andRestRequest.hasAnyQueryParameters(String[])methods that only look for parameters in the URL query string to prevent loading and parsing of URL-Encoded form posts. - New
@QParamand@HasQParamannotations for accessing query parameters from the URL query string. &plainText parameter can now specify a false value.- Removed properties parameters from
RestServlet.onPreCall(RestRequest)andRestServlet.onPostCall(RestRequest,RestResponse)methods since the properties are already accessible throughRestRequest.getProperties() . - Added
UonSerializer
andUonParser
to serializer and parser lists onBasicRestServletandRestServletJenaDefault.
Client
- Moved to Apache HttpClient 4.3 to match Jazz 6.0.
- Renamed
RestResponseEntity toRestRequestEntity. - Improved performance on URL-Encoded form posts by serializing directly to output stream instead of serialized to string first.
- New methods on
RestClient
class that makes it easier to associate serializer and parser attributes with registered serializer and parser:RestClient.setProperty(String,Object)RestClient.setProperties(ObjectMap)RestClient.addNotBeanClasses(Class[])RestClient.addTransforms(Class[])RestClient.addImplClass(Class,Class)
- Renamed
RestClient.shutdown() toRestClient.close()
to mirror change in Apache API.
Samples
- New
CodeFormatterResource for quickly formatting Java and XML code samples in Javadocs. - New
UrlEncodedFormResource for showing how to work with URL-Encoded form posts.
5.1.0.11 (Feb 14, 2015)
Juno 5.1.0.11 is a moderate update.
Core
- Additions to
@Html
bean annotation.- New
@Html(noTables)
annotation that prevents arrays/Collections from being serialized as tables. - New
@Html(noTableHeaders)
annotation that prevents HTML tables from having header rows.
- New
- Several improvements to URL-Encoding support.
- Improved whitespace handling in
UonParser
. - New
UonParserContext.UON_whitespaceAwareproperty for controlling whether whitespace is ignored. - New
UrlEncodingContext.URLENC_expandedParamsproperty for controlling whether arrays/Collections should be serialized/parsed as multi-part parameters. - New
@UrlEncoding(expandedParams)
annotation that specifies that bean properties of type array/Collection be serialized as multi-part parameters (e.g.&key=val1&key=val2 ).
- Improved whitespace handling in
- New
JsonSerializerContext.JSON_escapeSolidusproperty for controlling whether slash characters should be escaped. - New
TeeOutputStreamandTeeWriterclasses. - New
ClassMeta.isInstance(Object)
method. - Performance improvements when using the
BeanMap.add(String,Object)
method. Array properties are stored in a temporary list cache untilBeanMap.getBean()
is called. - New
BeanPropertyMeta.add(BeanMap,Object)method for adding values to Collection and array properties. - Config INI files now support keys with name
"*" .
Server
- REST method parameters can now be generic types (e.g.
). This applies to headers, attributes, and parameters.@Param ("foo" ) Map<String,Integer> foo - New
@Param(multipart)and@Query(multipart)annotations for handling multi-part GET and POST parameters. - GET parameters are now CASE-SENSITIVE per W3C standards.
&Content must now be specified as&content .&Method must now be specified as&method .&debug must now be specified as&debug=true .&plainText must now be specified as&plainText=true .¬race must now be specified as&noTrace=true .
- Performance improvements around query parameters.
- New methods on
RestRequest
for handling multi-part parameters:RestRequest.getParameters(String,Class)RestRequest.getQueryParameters(String,Class)
- Fixed Jetty issue in
RestResponse.setHeader(String,String)
where setting theContent-Type through this method was inconsistent with the behavior in WAS/Tomcat. &noTrace=true now prevents any errors from being logged in log file.- Workaround for Jetty issue where
ServletContext.getContextPath() always ends with"null" . RestServletProperties.REST_allowMethodParam is nowtrue by default on all subclasses ofBasicRestServletandRestServletJenaDefault.
Client
- New method
RestCall.allowRedirectsOnPosts(boolean). - New method
RestCall.peekInputStream() allows you to read response bodies without interrupting execution flow. - New method
RestCall.toString()now useful for debugging purposes. Shows all request/response headers and bodies. RestCallExceptionnow includesHttpResponse object for easier debugging.- New method
RestClient.addListener(RestClientListener) for registering request/response listeners. - New
RestClient.setClassLoader(ClassLoader)method. - TLS support in
JazzRestClient .
Other changes
samples.ear andsamples.war projects have been replaced with an OSGi bundle with activated servlets injuno.samples .
5.1.0.12 (Mar 28, 2015)
Juno 5.1.0.12 is a minor update.
Core
- Fixed
ConfigFile.isEmpty()method. - Changed behavior on
UonParser
to not treat'~' characters as escapes unless followed by one of the following characters:( ) , $ = ~ .
Client
- New class
RestCallInterceptor. Allows responses to be inspected and modified before being processed. ReplacesRestClientListener class. - Minor connection cleanup fixes.
5.1.0.13 (Apr 24, 2015)
Juno 5.1.0.13 is a minor update.
Core
ClassMeta.newInstance()
method can now create new instances of arrays.- Arguments passed to
Linkare now serialized usingUrlEncodingSerializer
, so arbitrary POJOs can now be passed as arguments. - New date filters:
org.apache.juneau.swaps.Datefilter.ISO8601DTZP andorg.apache.juneau.swaps.Datefilter.SimpleP . - New
HtmlDocSerializerContext.HTMLDOC_nowrapsetting forHtmlDocSerializer
class. Adds"* {white-space:nowrap}" to the style header to prevent word wrapping. - Fixed bug in
UonParser
where passing in a blank value on an array or collection type in a form post would cause aClassCastException . New behavior creates an empty array orCollection . - Improved implementation of
UrlEncodingSerializer.serializeUrlPart(Object)method.
Server
RestConverterAPI fixed to handle the existence of POJO filters.Introspectable/Queryable/Traversableclasses can now work with filtered POJOs.@RestResource(messages)annotation can now be defined on super and subclasses so that NLS messages can be defined in multiple resource bundles.- Performance improvements in
RestServletNls class. - Fixed bug where two REST java methods mapped to the same path pattern wasn't triggering an exception when it was supposed to.
Client
- New
RestCall.setRedirectMaxAttempts(int)method to prevent endless redirection loops. - New
RestCall.setRetryable(int,long,RetryOn)method to automatically retry on failed connection attempts. - New
RestCallInterceptor.onRetry(RestCall,int,HttpRequest,HttpResponse) method for listening in on retry attempts.
5.1.0.14 (May 10, 2015)
Juno 5.1.0.14 is a moderate update.
The major addition is support for
Core
- Simplified
PojoIntrospectorclass. - New
ClassUtils.getMethodSignature(Method)method.
Client
- New methods in
RestClient
for working with remoteable services:RestClient.setRemoteableServletUri(String)RestClient.getRemoteableProxy(Class)
Server
- Added a default OPTIONS page to
BasicRestServletandRestServletJenaDefault. RestServletProperties.REST_allowMethodParam has been enhanced to allow you to explicitly specify which HTTP methods can be used in the&method parameter.- New methods added to
RestRequest
:RestRequest.getParser()RestRequest.getReaderParser()
5.1.0.15 (May 24, 2015)
Juno 5.1.0.15 is a minor update.
Core
- New properties in
SerializerContext:SerializerContext.SERIALIZER_relativeUriBaseSerializerContext.SERIALIZER_absolutePathUriBase
SERIALIZER_uriAuthority andSERIALIZER_uriContext properties. - Improvements in
CsvSerializer
.
Server
- New properties in
RestServletProperties :REST_defaultCharset REST_servletURI REST_relativeServletURI
- Improvements involving path calculations when servlets deployed outside of a war file with a context root.
Client
- New methods in
RestCall:RestRequest.getHeader(String,Class)RestRequest.getHeader(String,Object,Class)RestRequest.getHeader(String,Type,Type...)RestRequest.getQueryParameter(String,Class)RestRequest.getQueryParameter(String,Object,Class)RestRequest.getQueryParameter(String,Type,Type...)RestRequest.getQueryParameter(String,Object,Type,Type...)RestRequest.getQueryParameters(String,Class)RestRequest.getQueryParameters(String,Type,Type...)RestRequest.getFormDataParameter(String,Class)RestRequest.getFormDataParameter(String,Object,Class)RestRequest.getFormDataParameters(String,Class)RestRequest.getFormDataParameter(String,Type,Type...)RestRequest.getFormDataParameters(String,Type,Type...)RestRequest.getPathParameter(String,Class)RestRequest.getPathParameter(String,Type,Type...)RestRequest.getBody(Class)RestRequest.getBody(Type,Type...)
5.1.0.16 (June 28, 2015)
Juno 5.1.0.16 is a moderate update.
Core
- New methods on
ClassMeta
that eliminates language-specific code in the general class metadata.ClassMeta.getXmlMeta()ClassMeta.getJsonMeta()ClassMeta.getHtmlMeta()ClassMeta.getUrlEncodingMeta()ClassMeta.getRdfMeta()
- New
JsonType.ANY
enum. - New
@Html(asPlainText)annotation. - New
HtmlDocSerializerContext.HTMLDOC_cssImportsproperty. - Significant changes to RDF support.
- New
@Rdfand@RdfSchemaannotations. These replace the use of defining namespaced through the XML annotations, and allows XML and RDF to be serialized using different namespaces. - Support for serializing arrays/collections as RDF bags, RDF lists, and multi-valued properties.
- Fixed warning message about
"tab" setting when using the N3/Turtle serializers.
- New
- New
SerializerContext.SERIALIZER_sortCollectionsandSerializerContext.SERIALIZER_sortMapsproperties. - FindBug fixes.
Server
- New
RestRequest.getServletParentURI()method. - New
$R{servletParentURI} variable. - Removed final modifier from
ChildResourceDescriptions.
Samples
- Added source code links to examples.
5.1.0.17 (Aug 3, 2015)
Juno 5.1.0.17 is a major update.
Core
BeanMap.get(Object)
andBeanMap.put(String,Object)
now automatically performs filtering if filters are defined on the bean property or bean property class.- Deleted the following methods which are now unnecessary:
BeanMap.getFiltered(String) BeanMap.putFiltered(String,Object) BeanMapEntry.getFiltered(String) BeanMapEntry.putFiltered(String,Object) BeanMapEntry.putFiltered(String,Object) BeanPropertyMeta.getFiltered() BeanPropertyMeta.setFiltered(Object) BeanPropertyMeta.getTransformedClassMeta()
BeanPropertyMeta.getClassMeta()
now returns the filtered type of the property.
- Deleted the following methods which are now unnecessary:
StringVarResolvernow has support for chained resolvers.StringVarResolvernow resolves variables inside resolved values. i.e. if a resolved variable value itself contains a variable, it now resolves that variable too.- Fixed bug where inner interface classes being used in
RestResource.filters()were being interpreted as surrogate classes because they have hidden 1-arg constructors due to being inner classes. - Fixed bug in
MultiSet
where exception was being thrown if last set was empty. - New
ZipFileListclass for providing efficiently zipped directories through the REST interface. - New
RdfProperties.RDF_useXmlNamespaces property. - New
XmlParserContext.XML_preserveRootElementproperty. - Worked around bug in Sun VM on OS/X where XML parser was throwing an exception when trying to set a reporter.
Server
- New
ZipFileListResponseHandlerclass. - Simplified labels in servlet resource bundles:
"[ClassName].ResourceDescription" is now"[ClassName].label" ."[ClassName].MethodDescription.[methodName]" is now"[ClassName].[methodName]" .
- Several changes to
RestRequest
:- Added new methods:
RestRequest.getQueryParameterMap()RestRequest.getQueryParameterNames()RestRequest.getPathInfoUndecoded()RestRequest.getPathRemainderUndecoded()RestRequest.getTrimmedRequestURI()RestRequest.getTrimmedRequestURL()RestRequest.getServletTitle()RestRequest.getServletDescription()RestRequest.getMethodDescription()
- Behavior changes to
HttpServletRequestWrapper.getPathInfo()
to follow Servlet specs. Returnsnull instead of blank for no path info. RestRequest.getPathRemainder()now automatically decodes the path remainder. UseRestRequest.getPathRemainderUndecoded()to get the unencoded path remainder.- Bug fixes in
RestRequest.getRequestParentURI()when servlet is mapped to"/*" . - Bug fixes in
RestRequest.getServletURI()when servlet is mapped to"/*" .
- Added new methods:
- New string replacement variables:
$R{contextPath} - Returns value fromRestRequest.getContextPath()
$R{methodDescription} - Returns value fromRestRequest.getMethodDescription()$R{resourceTitle} - Returns value fromRestRequest.getServletTitle()$R{resourceDescription} - Returns value fromRestRequest.getServletDescription()$R{trimmedRequestURI} - Returns value fromRestRequest.getTrimmedRequestURI()$E{var} - Environment variables.
- Added methods
RestServlet.getDescription(RestRequest)andRestServlet.getLabel(RestRequest). BasicRestServletandRestServletJenaDefaultnow provide default HTML titles and descriptions:@Property (name=HTMLDOC_title , value="$R{resourceTitle}" ),@Property (name=HTMLDOC_description , value="$R{resourceDescription}" )- Options pages on
BasicRestServletandRestServletJenaDefaultnow provide default descriptions and back links: and descriptions:@Property (name=HTMLDOC_navlinks , value="{back:'$R{servletURI}" ),@Property (name=HTMLDOC_description , value="Resource options" ) - New
BasicRestServletGroupclass. - Removed
RestServletProperties.REST_trimTrailingUriSlashes andRestServletProperties.REST_pathInfoBlankForNull . - New annotations for providing labels and descriptions. Useful if you don't plan on having to support other languages, so you don't
want to provide labels in resource bundles.
RestResource.label()@RestResource(description)RestMethod.description() @RestMethod(description)RestMethod.responses()Attr.description()Content.description()HasParam.description()HasQParam.description()Header.description()Param.description()QParam.description()
- Support for sorting resources by name in
ChildResourceDescriptions.
Samples
- Added
/tempDir/upload showing how to useServletFileUpload with multipart form posts.
5.1.0.18 (Aug 5, 2015)
Juno 5.1.0.18 is a minor update affecting the server component only.
Server
- Fixed bug where localized strings weren't resolving when using chained resource bundles.
- Servlet and method labels and descriptions can now contain embedded string variables.
- New
RestMethod.input()andRestMethod.responses()annotations. These replace the variousdescription annotations added 2 days ago with a simpler design. - New methods on
RestServlet:RestServlet.getMethodDescription(String,RestRequest)so that subclasses can override the method description in the OPTIONS page.RestServlet.createRequestVarResolver(RestRequest)so that subclasses can override and augment the variable resolver.RestServlet.resolveChild(Class)andRestServlet.replaceChild(RestServlet)classes that allows customized resolution of servlet instances (e.g. if services are defined in OSGi).
- Reverted the
MethodDescriptionback to 5.1.0.16 since it was being used by someone.
5.1.0.19 (Aug 15, 2015)
Juno 5.1.0.19 is a minor update in terms of core functionality.
But it introduces a Microservices project for building REST microservices and docker containers.
Core
- Beans can now be serialized to and parsed from
ObjectMap ObjectMaps. SeeSerializing to ObjectMaps for details. - New
ObjectMap.include(String[])andObjectMap.exclude(String[])methods. @Html
annotations can now be applied to bean properties.- New
IOPipeutility class. - Behavior change on
StringVarResolver.null input now results in blank strings instead ofnull .
Client
- New
RestClient.doCallback(String)method.
Server
- New
RestRequest.getHeaders()
method. - New
RestResponse.getUnbufferedWriter() method. - Fixed bug that was preventing
x-response-headers parameter from working correctly. - Added
@Bean.properties
annotations to the various classes inorg.apache.juneau.rest.labels so that the order of the bean properties are consistent on all JVMs. On IBM JVMs this is unnecessary because the order of the properties as defined in the class are stored in the bytecode. Other JVMs such as OpenJRE do not implement this feature causing the bean properties to be in random order. - New
ResourceDescription.ResourceDescription(RestRequest,String,String)constructor.
5.1.0.20 (Sept 5, 2015)
Juno 5.1.0.20 is a moderate update.
The biggest improvement is the ability to associate external INI config files with REST servlets using the ConfigFile functionality.
Core
- Significant API changes to
org.apache.juneau.config API.ConfigFileis now thread safe and can be shared across multiple threads.- New
ConfigMgrclass for managing configuration files. - Serializers and parsers can be associated with config files for storing and retrieving POJOs. Default support provided for JSON.
- New
SimpleHtmlWriter
class. Can be used for simple HTML DOM construction. - New
ProcBuilderclass for calling external processes. - New
ObjectMap.remove(Class,String,Object)method. "class='link'" added to links generated byHtmlDocSerializer
.- New
EncoderGroup.append(EncoderGroup)method. - New
HtmlDocSerializerContext.HTMLDOC_addLinks configuration property. - Modified the
Parser.createContext(ObjectMap,Method,Object) method. Outer context objects can be passed in to create instances of non-static inner classes. - Fixed bug in
HtmlStrippedDocSerializer
where exception was thrown when trying to serialize primitive arrays. JsonParser
now handles parsing JSON boolean/numeric values as strings to bean properties of type boolean or number.UrlEncodingSerializer
andUrlEncodingParser
now represent arrays and collections as key-value pairs where the keys are numbers (e.g."?0=foo&1=bar" ).- Various internal improvements to
IOPipe. - New
ReflectionUtils.getResource(Class,String)method. StringUtils.parseNumber(String,Class)now returns zero for empty strings. This affects the way most parsers handle blank values.
Server
- You can now parse into non-static inner classes of a servlet for parameters/attributes/content. Useful if you like to define your marshaller beans inside your servlet.
- Changes to
RestServlet:- New methods for accessing external INI config files:
RestServlet.getConfig()
RestServlet.createConfigFile() - New
"$C{...}" variable that resolve to INI config file values. - New
"$UE{...}" variable that URL-encodes the value inside the variable. - New convenience methods for retrieving classpath resource files:
RestServlet.getResource(String)
RestServlet.getResourceAsString(String)
RestServlet.getResource(Class,String,String). Useful if you want to load predefined POJOs from JSON files in your classpath. - New
RestServlet.handleNotFound(int,RestRequest,RestResponse)method for customized handling of when a resource or method was not found.
- New methods for accessing external INI config files:
BasicRestServletnow automatically processes"/favicon.ico" requests by overriding the newRestServlet.handleNotFound(int,RestRequest,RestResponse)method.- New
RestRequest
methods:RestRequest.resolveVars(String)RestRequest.getVarResource(String) RestRequest.getConfig()
- New
RestResponse
methods:RestResponse.getDirectWriter(String)
.RestResponse.getNegotiatedWriter()
.getWriter() now returns an unnegotiated writer.getUnbufferedWriter() has been removed.
- New
RestMethod.encoders() @RestMethod(encoders)andRestMethod.inheritEncoders()annotations. Allows encoders to be fine-tuned at the method level. - New
@RestResource(config)annotation for associating externalConfigFileconfig files with servlets. ResourceLink.- New
org.apache.juneau.rest.matcher package for commonly-usedRestMatchers:MultipartFormDataMatcherUrlEncodedFormMatcher
Microservice
- New juneau-microservice.jar file that encapsulates all 3 juneau jars with code necessary for creating fast and efficent jetty-powered REST microservices.
Contains the following:- Jetty 8.0
- Apache HttpClient 4.3.5
- Apache Commons FileUpload 1.3.1
- Microservice now supports Java 6 (previously required Java 7)
5.2.0.0 (Dec 30, 2015)
Juno 5.2.0.0 is a major update. Major changes have been made to the microservice architecture and config INI file APIs.
Core
- Significant changes and enhancements to the
org.apache.juneau.config API.- More consistent handling of comma-delimited lists of objects.
- New methods in
ConfigFile:ConfigFile.getStringArray(String),ConfigFile.getStringArray(String,String[])ConfigFile.getSectionAsBean(String,Class)- Instantiate a new bean with property values in the specified section..ConfigFile.writeProperties(String,Object,boolean,Class[])- Copy the properties in a config file section into properties on an existing bean or POJO.ConfigFile.getSectionMap(String)- Get all the resolved values in a section.ConfigFile.containsNonEmptyValue(String)ConfigFile.isEncoded(String)ConfigFile.addListener(ConfigFileListener)- Listen for modification events on the config file.ConfigFile.merge(ConfigFile)- Merge the contents of another config file into this config file.ConfigFile.getResolving(),ConfigFile.getResolving(StringVarResolver)- Return an instance of the config file that resolves string variables. Much more efficient than the previous design since the same underlying config file object is shared.ConfigFile.toWritable()- Wraps the config file in aWritableinterface so that it can be serialized by the REST interface as a plain-text INI file instead of as a serialized POJO.ConfigFile.getInt(String)- Now supports"M" and"K" to identify millions and thousands.
- New methods in
ConfigMgr:ConfigMgr.create(),ConfigMgr.create(Reader),ConfigMgr.create(File)ConfigMgr.deleteAll()
- New methods in
Section:Section.setParent(ConfigFileImpl)- Used by parsers to set the config file for this section.Section.setName(String)- Used by parsers to set the name for this section.
- New interfaces:
ConfigFileListenerSectionListenerEntryListener
Encodermethods have access to field names to use them as salt values.- The name of the default section is now
"default" . Before it was justnull . XorEncoderXOR key can be overridden through the"org.apache.juneau.config.XorEncoder.key" system property.
- Support for converting Strings to POJOs if the POJO class has any of the following static methods:
fromString(String) valueOf(String) (e.g. enums)parse(String) (e.g. loggingLevel class)parseString(String) forName(String) (e.g.Class andCharset classes)
- Support for parsing into objects with unbound type variables.
For example, if you have a class
Pair<S,T> and you try to parse into this class (e.g.parser.parse(in, Pair. ), the unbound type variables is interpreted asclass )Object instead of throwing an exception. - Support for serializing/parsing the following new types:
AtomicInteger AtomicLong BigInteger BigDecimal
- Parsers have been enhanced to allow parent POJOs and field names to be passed into child POJOs.
New
@NameProperty
and@ParentProperty
annotations are provided for identifying methods for setting names and parent POJOs on child POJOs. For example, the config fileSectionclass represents a section in a config file. It needs to know it's own name and have a link to theConfigFilethat it belongs to. With these new annotations, config files can be reconstructed using any of the parsers. - New classes and interfaces:
Streamableinterface for identifying objects that can be serialized directly to an output stream.Writableinterface for identifying objects that can be serialized directly to a writer.StringObjectclass that can be used for delayed object serialization.ByteArrayCacheByteArrayInOutStreamFileUtilsThrowableUtilsStringVarMultipartStringVarWithDefault
- New fields on
ObjectList:ObjectList.EMPTY_LIST
- New fields and methods on
ObjectMap:ObjectMap.EMPTY_MAPObjectMap.getStringArray(String)ObjectMap.getStringArray(String,String[])ObjectMap.putIfNull(String,Object)ObjectMap.putIfEmpty(String,Object)
- New methods in
ArrayUtils
:ArrayUtils.contains(Object,Object[])ArrayUtils.indexOf(Object,Object[])ArrayUtils.toPrimitiveArray(Object)
- New methods in
IOUtils:IOUtils.pipe(Reader,Writer)IOUtils.read(File)IOUtils.readFile(String)IOUtils.write(File,Reader)
- New methods on
PojoRest:PojoRest.get(Class,String,Object)PojoRest.getString(String)PojoRest.getString(String,String)PojoRest.getInt(String)PojoRest.getInt(String,Integer)PojoRest.getLong(String)PojoRest.getLong(String,Long)PojoRest.getBoolean(String)PojoRest.getBoolean(String,Boolean)PojoRest.getMap(String)PojoRest.getMap(String,Map)PojoRest.getList(String)PojoRest.getList(String,List)getObjectMap(String)getObjectMap(String,ObjectMap)getObjectList(String)getObjectList(String,ObjectList)
- New methods on
ProcBuilder:ProcBuilder.pipeTo(Writer,boolean)ProcBuilder.pipeTo(Writer)ProcBuilder.logTo(Writer,boolean)ProcBuilder.logTo(Writer)ProcBuilder.logTo(Level,Logger)ProcBuilder.maxExitStatus(int)
- New methods on
StringUtils:StringUtils.isEmpty(Object)StringUtils.nullIfEmpty(String)StringUtils.base64EncodeToString(String)StringUtils.base64Encode(byte[])StringUtils.base64DecodeToString(String)StringUtils.base64Decode(String)StringUtils.generateUUID(int)StringUtils.trim(String)StringUtils.parseISO8601Date(String)StringUtils.replaceVars(String,Map)StringUtils.pathStartsWith(String,String)StringUtils.pathStartsWith(String,String[])
- New
StringVar.doResolve(String)method. - New
StringVarResolver.DEFAULTfield. - Eliminated dependency on
javax.mail.internet.MimeUtility by implementing our ownStringUtils.base64Encode(byte[])method. CalendarSwapandDateSwapclasses now handle blank input better. Returnsnull instead of throwing an exception.HtmlDocSerializer
specifies the default CSS location as/servletPath/style.css instead of/servletPath/htdocs/juneau.css . This coincides with enhancements made in the server code for specifying styles.HtmlDocSerializer
wraps output in two div tags instead of one (e.g.<div class='outerdata'><div class='data' id='data'>...</div></div> ). Needed for supporting the new devops look-and-feel.- Fixed indentation inconsistencies in
HtmlDocSerializer
. - Renamed
HtmlSchemaSerializertoHtmlSchemaDocSerializer. - RDF serializers and parsers now support
RdfProperties.RDF_looseCollection loose collections. - RDF parser handles case where resources point to themselves (an unfortunate behavior in JFS RDF documents).
- JSON parser with throw an exception in strict mode if it encounters numbers that are valid in Java but invalid in JSON (e.g. octal, hexadecimal numbers).
Parser
methods now check fornull input.SerializerGroupandParserGroupignores serializers and parsers if they throwNoClassDefFoundErrors .UrlEncodingParser
creates lists if the same attribute name is encountered more than once. Before it would just replace the previous value with the new value.- New
UrlEncodingSerializer.DEFAULT_SIMPLE_EXPANDEDserializer. - Changes to
Args:getMainArg(int) changed toArgs.getArg(int). Non-existent arguments are returned asnull instead of blank strings. This is more inline with the behavior of the rest of the library.- New
Args.hasArg(int)method.
- Removed
org.apache.juneau.utils.CharsetUtils class. - Removed
org.apache.juneau.utils.ConcurrentIdentityList class. - Fixed bug in
MultiIterableclass. PojoIntrospectormust now be instantiated with aReaderParser . Simplifies the API on the class.PojoRestmust now be instantiated with aReaderParser . Simplifies the API on the class.MessageBundleandSafeResourceMultiBundle moved from server component.- Several bug fixes and performance improvements in
StringVarResolver. - Various enhancements to
TeeWriterandTeeOutputStream. - Renamed
CharSettoAsciiSet. SerializerGroupandParserGroupnow ignoresNoClassDefFoundErrors so that resources that include Jena support can continue to operate even if the Jena libraries are not present.- New
FileUtils.createTempFile(String)
method. - New
PojoQuerymodified to handle bean getters that throw exceptions.
Client
- Upgraded to use Apache HttpClient 4.5.
- New classes:
AllowAllRedirectsHttpMethodResponsePatternSimpleX509TrustManagerSSLOpts
- Removed
org.apache.juneau.rest.client.LaxRedirectStrategy . Use HTTP Client equivalent. - New methods on
RestCall:RestCall.addInterceptor(RestCallInterceptor)RestCall.pipeTo(Writer)RestCall.pipeTo(Writer,boolean)RestCall.pipeTo(String,Writer,boolean)RestCall.getWriter(String)RestCall.pipeTo(OutputStream)RestCall.pipeTo(OutputStream,boolean)RestCall.pipeTo(String,OutputStream,boolean)RestCall.getOutputStream(String)RestCall.byLines()RestCall.captureResponse()RestCall.successPattern(String)RestCall.failurePattern(String)RestCall.addResponsePattern(ResponsePattern)RestCall.run()- Renamed fromexecute() .RestCall.getCapturedResponse()RestCall.getResponsePojoRest(Class)RestCall.getResponsePojoRest()RestCall.logTo(Level,Logger)RestCall.setConfig(RequestConfig)
- New lifecycle listener methods on
RestCallInterceptor:RestCallInterceptor.onInit(RestCall)RestCallInterceptor.onClose(RestCall)
- New methods on
RestClient
:RestClient.setBasicAuth(String,int,String,String)RestClient.logTo(Level,Logger)RestClient.setRootUrl(String)RestClient.enableSSL(SSLOpts)RestClient.enableLaxSSL()RestClient.doCall(HttpMethod,Object,Object)RestClient.createHttpClientBuilder()
- New passthrough methods on
RestClient
defined onHttpClientBuilder :RestClient.setRedirectStrategy(RedirectStrategy)RestClient.setDefaultCookieSpecRegistry(Lookup)RestClient.setRequestExecutor(HttpRequestExecutor)RestClient.setSSLHostnameVerifier(HostnameVerifier)RestClient.setPublicSuffixMatcher(PublicSuffixMatcher)RestClient.setSSLContext(SSLContext)RestClient.setSSLSocketFactory(LayeredConnectionSocketFactory)RestClient.setMaxConnTotal(int)RestClient.setMaxConnPerRoute(int)RestClient.setDefaultSocketConfig(SocketConfig)RestClient.setDefaultConnectionConfig(ConnectionConfig)RestClient.setConnectionTimeToLive(long,TimeUnit)RestClient.setConnectionManager(HttpClientConnectionManager)RestClient.setConnectionManagerShared(boolean)RestClient.setConnectionReuseStrategy(ConnectionReuseStrategy)RestClient.setKeepAliveStrategy(ConnectionKeepAliveStrategy)RestClient.setTargetAuthenticationStrategy(AuthenticationStrategy)RestClient.setProxyAuthenticationStrategy(AuthenticationStrategy)RestClient.setUserTokenHandler(UserTokenHandler)RestClient.disableConnectionState()RestClient.setSchemePortResolver(SchemePortResolver)setUserAgent(String)RestClient.setDefaultHeaders(Collection)RestClient.addInterceptorFirst(HttpResponseInterceptor)RestClient.addInterceptorLast(HttpResponseInterceptor)RestClient.addInterceptorFirst(HttpRequestInterceptor)RestClient.addInterceptorLast(HttpRequestInterceptor)RestClient.disableCookieManagement()RestClient.disableContentCompression()RestClient.disableAuthCaching()RestClient.setHttpProcessor(HttpProcessor)RestClient.setRetryHandler(HttpRequestRetryHandler)RestClient.disableAutomaticRetries()RestClient.setProxy(HttpHost)RestClient.setRoutePlanner(HttpRoutePlanner)RestClient.disableRedirectHandling()RestClient.setConnectionBackoffStrategy(ConnectionBackoffStrategy)RestClient.setBackoffManager(BackoffManager)RestClient.setServiceUnavailableRetryStrategy(ServiceUnavailableRetryStrategy)RestClient.setDefaultCookieStore(CookieStore)RestClient.setDefaultCredentialsProvider(CredentialsProvider)RestClient.setDefaultAuthSchemeRegistry(Lookup)RestClient.setContentDecoderRegistry(Map)RestClient.setDefaultRequestConfig(RequestConfig)RestClient.useSystemProperties()RestClient.evictExpiredConnections()RestClient.evictIdleConnections(long,TimeUnit)
JazzRestClient now supports OIDC authentication.- These classes are now deprecated and will be removed in a future release:
org.apache.juneau.rest.client.jazz.CertificateStore org.apache.juneau.rest.client.jazz.ICertificateValidator org.apache.juneau.rest.client.jazz.ITrustStoreProvider org.apache.juneau.rest.client.jazz.LenientCertificateValidator org.apache.juneau.rest.client.jazz.SharedTrustStoreProvider org.apache.juneau.rest.client.jazz.ValidatingX509TrustManager
Server
- New
ReaderResourceclass. Represents the contents of a text file with convenience methods for resolvingStringVarvariables and adding HTTP response headers. REST Java methods can return instances of these to serializeReaders containing text withStringVarResolvervariables in them. - New
StreamResourceclass. REST Java methods can return instances of these to serializeOutputStreams . - Fixed a bug in the stack trace hash algorithm in
RestException. - New methods in
RestRequest
:RestRequest.getReaderResource(String)- ReplacesgetVarResource(String) .RestRequest.getReaderResource(String,boolean)RestRequest.getReaderResource(String,boolean,String)
- Changes in
RestResponse
:- Don't set
Content-Encoding: identity when no encoding is used. Some clients don't interpret it correctly.
- Don't set
- New methods in
RestServlet:RestServlet.getChildClasses()- Programmatic equivalent to@RestResource(children)annotation.RestServlet.shouldLog(HttpServletRequest,HttpServletResponse,RestException)RestServlet.shouldLogStackTrace(HttpServletRequest,HttpServletResponse,RestException)RestServlet.logObjects(Level,String,Object[])RestServlet.resolveStaticFile(String)RestServlet.createStyleSheet()RestServlet.createFavIcon()RestServlet.createStaticFilesMap()RestServlet.getConfigMgr()
- Removed
JsoParserfromBasicRestServletandRestServletJenaDefault. These may represent a security risk if not handled correctly, so removed them as a precaution. - Removed
RestServletProperties.REST_htDocsFolder . Replaced with@RestResource(staticFiles)}. - New annotations on
@RestResource.RestResource.stylesheet()RestResource.favicon()@RestResource(staticFiles)
- Eliminated
org.apache.juneau.rest.jaxrs.JsonProvider class. Some JAX-RS implementations use code scanning to find providers, so if you were usingDefaultJenaProvider , it would pick upJsonProvider as well. It's easy enough to create your own implementation if needed. - OPTIONS pages now specify
consumes andproduces fields instead ofaccept andcontentType which was confusing. - Eliminated
properties from OPTIONS pages. - New
ResourceLink.ResourceLink(String,RestRequest,String,Object[])constructor. - New response handlers:
StreamableHandler- Allows REST Java methods to return instances ofStreamable.WritableHandler- Allows REST Java methods to return instances ofWritable.
- New DevOps stylesheet.
- Servlet initialization and HTTP requests are now logged at
FINE level. - Added
abstract modifier on variousRestServlet subclasses to indicate that they're meant to be subclassed. - New
RestUtils.trimPathInfo(StringBuffer,String,String)method.
Microservice
- Completely revamped API.
- New
Microservice
class that serves as a generic interface for microservices and their lifecycles. - New
RestMicroserviceclass that implements a microservice consisting of a REST interface.- REST resources and configuration settings can be defined through either manifest files or config files.
- Enhanced logging support.
- Easy-to-configure SSL support.
- BASIC auth support.
- Automatic restartability if the config file changes.
- Eliminated
org.apache.juneau.microservice.Main class. This is replaced by the microservice classes defined above. ResourceandResourceGroupclasses now support the following new string variables:"$A{key,default}"" - Command line arguments."$MF{key,default}"" - Manifest file entries.
- CSS stylesheet now configurable through config file entry
"REST/stylesheet" . - New
BasicRestServletJenaclass if you want your REST interface to support RDF. - Eliminated the following classes:
org.apache.juneau.microservice.RootResource org.apache.juneau.microservice.SampleResource
- New predefined reusable resources:
ConfigResource
- REST resource for viewing and editing microservice config file.LogsResource
- REST resource for viewing log files.SampleRootResource
- Sample REST resource that contains the config and logs resource as children.ShutdownResource
- REST resource for stopping the microservice JVM. Useful for testing purposes.
Samples
- Converted to a REST microservice.
- Look-and-feel changed to IBM DevOps.
Documentation Updates
microservice- New package-level javadoc.config- New package-level javadoc.StringVarResolver- New documentation.client- New package-level javadoc.Overview / Samples - New section.org.apache.juneau.swap / Stop Classes - New section.rest- Extensive updates.
5.2.0.1 (Mar 23, 2016)
Juno 5.2.0.1 is a moderate update.
com.ibm.team.juno
- Improved support for multi-line values in config files. Any line that begins with whitespace is interpreted as a continuation of the previous line.
- Support for
'\uXXXX' character sequences in config files. - Fixed issue in
XmlSerializer
where'\r' and'\n' characters were not being handled per XML specs. - New methods on
ObjectList:ObjectList.getAt(Class,String)ObjectList.putAt(String,Object)ObjectList.postAt(String,Object)ObjectList.deleteAt(String)
- New methods on
ObjectMap:ObjectMap.getAt(Class,String)ObjectMap.putAt(String,Object)ObjectMap.postAt(String,Object)ObjectMap.deleteAt(String)
@ThreadSafeannotation.- New
ClassFilter class. ConfigFile.getResolving(StringVarResolver,boolean)method.ConfigFile.getStringVar()method.- New
ParserContext.PARSER_trimStringsproperty. - New
SerializerContext.SERIALIZER_trimStringsproperty. Args.getStringVar()method.- New
ManifestFile
class - New
MessageBundleclass. ReplacesSafeResourceBundle /SafeResourceMultiBundle /RestNls . - New
StringMapVarclass. - New
StringVarsclass with reusable commonStringVarinstances. - New
JuneauLoggerclass. - Default value for
XmlParserContext.XML_trimWhitespacechanged totrue .
Server
- New methods on
RestContext
:
Client
- Fixed potential issue in
RestClient
where the HTTP connection pool could end up exhausted if an error occurred. - Improved thread safety on
RestClient
. - New warning message is logged if a
RestClient
is garbage collected without being closed:"WARNING: RestClient garbage collected before it was finalized."
6.0.0 (Oct 3, 2016)
Juneau 6.0.0 is a major update.
The major change is rebranding from "Juno" to "Juneau" in preparation for donation to the Apache Foundation.
org.apache.juneau
- Major changes around how serializer and parser class properties are defined to improve performance
and concurrency.
- New
PropertyStoreclass - Used for creating context objects. - New
Context
class - Read-only configurations for serializers and parsers. - New
Sessionclass - One-time use objects used by serializers and parsers. - All context context properties can now also be specified via system properties.
- New
- Refactored serializer and parser APIs for more consistency between stream-based and character-based serializers
and parsers.
- More consistent handling of exceptions.
- More consistent method declarations.
- Refactored var resolver API and added them to a new package -
org.apache.juneau.svl .- Support for stream-based variables -
StreamedVar
. - Added support for context and session objects.
- Support for stream-based variables -
- Eliminated
"_class" properties and replaced them with"_type" properties. The class properties were a little-used feature where we would serialize fully-qualified class names when the class type could not be inferred through reflection. It's been replaced with bean type names and bean dictionaries. Instead of class names, we serialize"_type" properties whose name is the type name defined on the bean being serialized. The parsers use a 'dictionary' of bean classes to resolve those names to actual bean classes. The following features were added to enable this support:@Bean(typeName)
- Annotation that defines an identifying name for a bean class.BeanFilterBuilder.typeName(String)- Programmatic equivalent to annotation above.BeanContext.BEAN_beanDictionary- List of bean classes that make up the bean dictionary for lookup during parsing.BEAN_beanTypePropertyName- The overridable type property name. Default is"_type" .@BeanProperty(beanDictionary)- Define a type dictionary for a particular bean property value. This overrides the value specified usingBeanContext.BEAN_beanDictionary.SerializerContext.SERIALIZER_addBeanTypeProperties- Controls whether type properties are serialized.
@Bean(typeName)
value replaces the@Xml(name) annotation, and the"type" and"_class" attributes in the XML and HTML serializers have been standardized on a single"_type" attribute. - Refactor bean filter support to use
BeanFilterBuilderclass. Allows theBeanFilter class to use final fields. MessagePack
support.- Serializers can now serialize directly to
Files
. SeeSerializer.serialize(Object,Object)
- Parsers can now parse directly from
Files
and other types. SeeParser.parse(Object,ClassMeta)
- Parsers will automatically covert numeric input to POJOs that have numeric constructors (e.g.
java.util.Date ). - Renamed 'Filters' to 'BeanFilters' and 'PojoSwaps'. Filters is just too overloaded a term.
- Internal utility classes moved to a new
org.apache.juneau.internal package. These internal utility classes are not meant for consumption outside the Juneau codebase. - New methods on
Parser
:org.apache.juneau.parser.Parser.createSession(ObjectMap,Method,Object) Parser.getMediaRanges()
- New methods on
Serializer
:org.apache.juneau.serializer.Serializer.createSession(ObjectMap,Method) Serializer.getMediaRanges()
- New
@Bean(sort)
annotation. - Added
@Bean.properties annotations on various DTO beans to make the ordering consistent between IBM and Oracle JVMs.
IBM JVMs maintain the order of methods in a class, whereas Oracle JVMs do not. - Serializers and parsers now automatically convert
Class
objects to readable names viaClassUtils.getReadableClassName(Class). - Eliminated the
ClassFilter class since it's no longer needed. - Code and concurrency improvements to
SerializerGroupandParserGroup. - Various enhancements to
BeanContext.convertToType(Object,Class). - New properties on
HtmlSerializer
:HtmlSerializerContext.HTML_detectLinksInStrings- Automatically detect hyperlinks in strings.HtmlSerializerContext.HTML_lookForLabelParameters- Specify anchor text by appending&label=MyLabel to URL.HtmlSerializerContext.HTML_labelParameter- Specify what URL parameter to use as the anchor text label.HtmlSerializerContext.URI_ANCHORoption forHtmlSerializerContext.HTML_uriAnchorText.
- Removed generics from
BeanPropertyMeta
. - Introduced new classes to eliminate the references to language-specific metadata in the core metadata classes:
ClassMetaExtended/ClassMeta.getExtendedMeta(Class)BeanMetaExtended/BeanMeta.getExtendedMeta(Class)BeanPropertyMetaExtended/BeanPropertyMeta.getExtendedMeta(Class)
- Renamed
@Transform annotation to@Pojoso that it can be used for various POJO-related behavior, not just associating transforms. - Introduced
Swagger DTOs
.
org.apache.juneau.rest
- OPTIONS pages replaced with Swagger documents.
Lots of changes related to supporting Swagger.
- Annotation name changes to conform to Swagger specs:
@Attr ->@Path ,@QParam ->@Query ,@Param ->@FormData ,@Content ->@Body - Eliminated
ResourceOptions and related code. - New annotations and related methods:
@RestResource(title)/RestInfoProvider.getTitle(RestRequest)@RestResource(description)/RestInfoProvider.getDescription(RestRequest)@RestResource(termsOfService)/RestInfoProvider.getTermsOfService(RestRequest)@RestResource(contact)/RestInfoProvider.getContact(RestRequest)@RestResource(license)/RestInfoProvider.getLicense(RestRequest)@RestResource(version)/RestInfoProvider.getVersion(RestRequest)@RestResource(tags)/RestInfoProvider.getTags(RestRequest)@RestResource(externalDocs)/RestInfoProvidergetExternalDocs(RestRequest)RestMethod.summary() @RestMethod(summary)/RestInfoProvider.getMethodSummary(String,RestRequest)RestMethod.description() @RestMethod(description)/RestInfoProvider.getMethodDescription(String,RestRequest)@RestMethod(externalDocs)@RestMethod(tags)@RestMethod(deprecated)@RestMethod(parameters)@RestMethod(responses)
- Annotation name changes to conform to Swagger specs:
- New
RestServletContext.paramFormatcontext property. - New/updated methods on
RestServlet:RestServlet.createProperties()RestServlet.createBeanContext(ObjectMap,Class[],Class[])RestServlet.createBeanFilters()RestServlet.createPojoSwaps()RestServlet.createParsers(ObjectMap,Class[],Class[])RestServlet.createUrlEncodingSerializer(ObjectMap,Class[],Class[])RestServlet.createUrlEncodingParser(ObjectMap,Class[],Class[])RestServlet.createConverters(ObjectMap)RestServlet.createDefaultRequestHeaders(ObjectMap)RestServlet.createDefaultResponseHeaders(ObjectMap)RestServlet.createEncoders(ObjectMap)RestServlet.createGuards(ObjectMap)RestServlet.createMimetypesFileTypeMap(ObjectMap)RestServlet.createResponseHandlers(ObjectMap)
- New client-version annotations:
RestResource.clientVersionHeader- The name of the header used to identify the client version.RestMethod.clientVersion- The client version range applied to a Java method.
org.apache.juneau.rest.client
- Removed the
JazzRestClient class. - New method
RestClient.setClientVersion(String).
6.0.1 (Jan 3, 2017)
Juneau 6.0.1 is a minor update.
org.apache.juneau
- General improvements to JSON parser.
- Several fixes to handle obscure edge cases.
- New properties in
ParserContext.ParserContext.PARSER_strictParserContext.PARSER_inputStreamCharsetParserContext.PARSER_fileCharset
- Removed
JsonParserContext.JSON_strictMode . Replaced byPARSER_strict . arrays can now be passed tobyte[] Parser.parse(Object,Class)
for reader-based parsers.
6.1.0 (Feb 25, 2017)
Juneau 6.1.0 is a major update.
In particular, this release cleans up the BeanContext
API to match
the PropertyStore/Context
/Session paradigm
previously used in the serializer and parser APIs.
It also makes several improvements to the HTML and XML serialization support and introduces HTML5 DTO beans.
org.apache.juneau
- Improvements to XML serialization support.
- New supported XML formats:
XmlFormat.ATTRS
format can now be applied to bean classes to have all bean properties serialized as attributes instead of elements by default.XmlFormat.ELEMENT
format can now be applied to bean properties to override theXmlFormat.ATTRS
setting above on specific bean properties.- New
XmlFormat.ELEMENTS
format can be applied to a bean property of type array/Collection to represent the child elements. - New
XmlFormat.MIXED
format can be applied to a bean property of type array/Collection to represent mixed content (text + child elements). - New
XmlFormat.MIXED_PWS
format. Identical toMIXED except preserves whitespace. - New
XmlFormat.TEXT
format can be applied to a bean property of a single object to represent a text node as a child. - New
XmlFormat.TEXT_PWS
format. Identical toTEXT except preserves whitespace. - New
XmlFormat.XMLTEXT
format that's identical toXmlFormat.TEXT
except XML content is not escaped and serialized directly as the child content. The parser will reconvert this to the original XML text during parsing.
- New support methodology and other improvements to
xmldocumentation. - Eliminated unnecessary
<string> elements. - Eliminated
XmlContentHandlerclass. - Parser efficiency improvements through reuse of string builders.
- Reworked and simplified the default XML serializers. The
XmlSerializer.DEFAULT
serializer now has namespaces disabled, andXmlSerializer.DEFAULT_NS
has namespaces enabled. The 'XML-JSON' serializers have been eliminated. - Eliminated the
addJsonTypeAttrs andaddJsonStringTypeAttrs settings. - Namespace support is now disabled by default.
- New supported XML formats:
- Significant modifications and improvements to HTML serialization support.
- Parser converted from
XMLEventReader -based toXMLStreamReader . - Eliminated many unnecessary type tags and
<string> elements and improved the readable layout. The new format is much leaner. - New exhaustive support methodology section added to
htmldocumentation.
- Parser converted from
- New HTML5 DTO support:
html5. BeanContext
class split into separateBeanContext
andBeanSession
classes.- Session object meant to be single-use that can be discarded after use and contains session-level object cache and overridable Locale and TimeZone.
SerializerContextandParserContextnow extend directly fromBeanContext
.SerializerSession
andParserSession
now extend directly fromBeanSession
.- New settings in
BeanContext
:BEAN_debug- Debug setting. Replaces individual debug properties in the serializer and parser contexts.BEAN_locale- Specifies a default locale at the context level.BEAN_timeZone- Specifies a default timezone at the context level.BEAN_mediaType- Specifies a default media type at the context level.
- Improvements to Parser class:
- Simplified the parse methods (e.g.
parseMap() ,parseCollection() ) by replacing them with two simple methods:Parser.parse(Object,Class)
- Normal method.Parser.parse(Object,Type,Type...)
- Method for parsing into parameterized maps and collections.
ClassMeta object.
For example:// Old way: ClassMeta<?> cm = parser.getMapClassMeta( HashMap.class , String.class , parser.getCollectionClassMeta( LinkedList.class , MyBean.class ) ); Map<String,List<MyBean>> map = (Map<String,List<MyBean>>)parser.parse(input, cm);// New way: Map<String,List<MyBean>> map = parser.parse(input, HashMap.class , String.class , LinkedList.class , MyBean.class ); - Arbitrarily-complex parameterized maps and collections can now be parsed without the need for creating intermediate
ClassMeta objects. - No need for casting anymore if you were using the old
parseMap() andparseCollection() methods! - Changes allow me to eliminate
BeanContext.normalizeClassMeta() method. - Convenience methods added for setting parser properties:
// Old way: new JsonParser().setProperty(PARSER_strict ,true ).setProperty(BEAN_locale , mylocale);// New way: new JsonParser().setStrict(true ).setLocale(mylocale);
- Simplified the parse methods (e.g.
- Improvements to Serializer class:
- Convenience methods added for setting serializer properties:
// Old way: new JsonSerializer().setProperty(JSON_simpleMode ,true ).setProperty(SERIALIZER_quoteChar ,'"' );// New way: new JsonSerializer().setSimpleMode(true ).setQuoteChar('"' );
- Convenience methods added for setting serializer properties:
- Simplified
PojoSwapclass. Now just two methods:PojoSwap.swap(BeanSession,Object)PojoSwap.unswap(BeanSession,Object,ClassMeta)
- General code improvements made to
ClassMeta
class.- All fields are now final which should improve overall performance.
- Replaced support for
toObjectMap() andfromObjectMap()/T(ObjectMap) methods with generalizedswap(BeanSession) /unswap(BeanSession,X) /T(BeanSession,X) methods.
See new sectionSwap methods for information.
- Session-level media type now available through
BeanSession.getMediaType()
method. Allows for swaps and serializer/parser behavior to be tailored to individual media types. - Several new
Calendar
andDate
swaps:ToString,ToString- ToStrings
using theDate.toString()
method.ISO8601DT,ISO8601DT- To ISO8601 date-time strings.ISO8601DTZ,ISO8601DTZ- Same asISO8601DT , except always serializes in GMT.ISO8601DTP,ISO8601DTP- Same asISO8601DT except with millisecond precision.ISO8601DTPZ,ISO8601DTPZ- Same asISO8601DTZ except with millisecond precision.RFC2822DT,RFC2822DT- To RFC2822 date-time strings.RFC2822DTZ,RFC2822DTZ- Same asRFC2822DT , except always serializes in GMT.RFC2822D,RFC2822D- To RFC2822 date strings.DateTimeSimple,DateTimeSimple- To simple"yyyy/MM/dd HH:mm:ss" date-time strings.DateSimple,DateSimple- To simple"yyyy/MM/dd" date strings.TimeSimple,TimeSimple- To simple"HH:mm:ss" time strings.DateFull,DateFull- ToDateFormat.FULL
date strings.DateLong,DateLong- ToDateFormat.LONG
date strings.DateMedium,DateMedium- ToDateFormat.MEDIUM
date strings.DateShort,DateShort- ToDateFormat.SHORT
date strings.TimeFull,TimeFull- ToDateFormat.FULL
time strings.TimeLong,TimeLong- ToDateFormat.LONG
time strings.TimeMedium,TimeMedium- ToDateFormat.MEDIUM
time strings.TimeShort,TimeShort- ToDateFormat.SHORT
time strings.DateTimeFull,DateTimeFull- ToDateFormat.FULL
date-time strings.DateTimeLong,DateTimeLong- ToDateFormat.LONG
date-time strings.DateTimeMedium,DateTimeMedium- ToDateFormat.MEDIUM
date-time strings.DateTimeShort,DateTimeShort- ToDateFormat.SHORT
date-time strings.
- New method
SerializerGroup.getSerializerMatch(String)that returns the matched serializer and media type. - New method
ParserGroup.getParserMatch(String)that returns the matched parser and media type. - New method
EncoderGroup.getEncoderMatch(String)that returns the matched encoder and encoding. - General improvements to Bean Dictionary support.
- New
BeanDictionaryList
class can be used for defining reusable sets of bean dictionaries consisting of classes annotated with@Bean(typeName)
. - New
BeanDictionaryMap
class can be used for defining reusable sets of bean dictionaries consisting of classes not annotated with@Bean(typeName)
. - New
@Bean(beanDictionary)annotation.
- New
- Removed restriction on getters and setters to be prefixed with "getX/setX/isX" if a
@BeanProperty(name)annotation is used. - Improvements to ATOM DTO:
- New
AtomBuilder
class. - New setter method names for a better fluent design.
- Updated
atomdocumentation.
- New
- New
MapSwapandStringSwapclasses. - New
WriterSerializer.println(Object)
method. Useful for debugging purposes. - New
BeanContext.getClassMeta(Type,Type...)
andBeanSession.getClassMeta(Type,Type...)
methods for retrieving Map and Collection class metas. Replaces the variousgetMapClassMeta() /getCollectionClassMeta() methods. - New section added to this document:
Juneau Data Transfer Objects (org.apache.juneau.dto) - Modified the UON specification to work with mixed content.
- The new specification is considerably cleaner and eliminates the need for separate normal/simple modes.
It also allows for arbitrary whitespace to be added to the output without any confusion. - Eliminated the
UonParser. andDEFAULT_WS_AWARE UrlEncodingParser. parsers.DEFAULT_WS_AWARE
The normalUonParser.DEFAULT
andUrlEncodingParser.DEFAULT
parsers will now handle whitespace. - Eliminated the
UonParserContext. configuration setting.UON_whitespaceAware - Eliminated the
UonSerializer. ,DEFAULT_SIMPLE UonSerializer. andDEFAULT_SIMPLE_ENCODING UrlEncodingSerializer. serializers since there is no separate simple mode anymore.DEFAULT_SIMPLE - Eliminated the
UonParserContext. configuration setting.UON_simpleMode
- The new specification is considerably cleaner and eliminates the need for separate normal/simple modes.
- Added new
OutputStreamSerializer.serializeToHex(Object)method.
Useful mostly for testing purposes.
Equivalently, theParser.parse(Object,Class)
method can now read the output from this method. - Eliminated the
and@Bean (subTypeProperty) annotations and replaced them with the ability to define subtypes using the existing@Bean (subTypes)@Bean(beanDictionary)annotation on parent classes and interfaces.
This has the added benefit of simplifying the overall code. - The
SerializerContext.SERIALIZER_addBeanTypePropertiessetting is now enabled by default. - Combined the
SERIALIZER_addIndentation /JSON_addWhitespace /UON_addWhitespace properties into a singleSerializerContext.SERIALIZER_useWhitespacesetting.
org.apache.juneau.rest
RestRequest
now passes locale and timezone to serializers/parsers/transforms.RestRequest.getTimeZone()method.- Standardized the following methods in
RestRequest
to remove dependency onClassMeta objects and eliminate the need for casts:RestRequest.getHeader(String,Class)RestRequest.getHeader(String,Object,Class)RestRequest.getHeader(String,Type,Type...)RestRequest.getQueryParameter(String,Class)RestRequest.getQueryParameter(String,Object,Class)RestRequest.getQueryParameter(String,Type,Type...)RestRequest.getQueryParameter(String,Object,Type,Type...)RestRequest.getQueryParameters(String,Class)RestRequest.getQueryParameters(String,Type,Type...)RestRequest.getFormDataParameter(String,Class)RestRequest.getFormDataParameter(String,Object,Class)RestRequest.getFormDataParameters(String,Class)RestRequest.getFormDataParameter(String,Type,Type...)RestRequest.getFormDataParameters(String,Type,Type...)RestRequest.getPathParameter(String,Class)RestRequest.getPathParameter(String,Type,Type...)RestRequest.getBody(Class)RestRequest.getBody(Type,Type...)
- New methods on
NameValuePairs - Fixed issue where whitespace was not added to UON/URL-Encoding output when
&plainText=true specified.
6.2.0 (Apr 28, 2017)
Juneau 6.2.0 is a major update.
org.apache.juneau
-
Revamped the serializer, parser classes to use builders for creation.
Serializers and parsers are now unmodifiable objects once they are created.
This is a breaking code change that will require adoption.
/* Creating a new serializer or parser */ // Old way WriterSerializer s =new JsonSerializer().setUseWhitespace(true ).pojoSwaps(BSwap.class ).lock();// New way WriterSerializer s = JsonSerializer.create ().ws().pojoSwaps(BSwap.class ).build();/* Cloning an existing serializer or parser */ // Old way WriterSerializer s = SimpleJsonSerializer.DEFAULT .clone().setUseWhitespace(true ).pojoSwaps(BSwap.class ).lock();// New way WriterSerializer s = SimpleJsonSerializer.DEFAULT .builder().ws().pojoSwaps(BSwap.class ).build(); - Also introduced the following builder classes and related architecture changes to make the built objects unmodifiable:
BuilderBuilderBuilder
- Revamped the config file API to use a build:
ConfigFileBuilder. - Removed the
Lockableinterface. - New
addBeanTypeProperties setting added to serializers to override theSerializerContext.SERIALIZER_addBeanTypePropertiessetting for individual serializers in a serializer group:HtmlSerializerContext.HTML_addBeanTypePropertiesJsonSerializerContext.JSON_addBeanTypePropertiesMsgPackSerializerContext.MSGPACK_addBeanTypePropertiesUonSerializerContext.UON_addBeanTypePropertiesXmlSerializerContext.XML_addBeanTypePropertiesRdfSerializerContext.RDF_addBeanTypeProperties
- UON notation serializers and parsers moved into the new
org.apache.juneau.uon package. - New
XmlFormat.VOID
format to identify HTML void elements. - Tweaks to HTML5 support.
- Fixed handling of empty non-void elements in HTML serializer.
- Added
style() override methods to all elements.
- Improvements to Swagger support.
- New
SwaggerBuilder
class. - Fluent-style setters added to the Swagger beans.
- Added Swagger examples
hereand in theswaggerjavadocs.
- New
- Improvements to
VarResolver
.- New
$IF
variable for if-else block logic. $SWITCH variable for switch block logic.- Whitespace wasn't being ignored in some cases.
- New
HtmlParser
can now parse full body contents generated byHtmlDocSerializer
.- Parse-args supported added to
MsgPackParser
to allow it to be used in remoteable proxies. - Added some convenience classes for constructing collections using a fluent interface:
AListASetAMap
- New
@Bean(typePropertyName)
annotation allows you to specify the name of the"_type" property at the class level. - New methods added to HTML5 container beans:
- New common serializer setting:
SerializerContext.SERIALIZER_abridged. - Support for defining interface proxies against 3rd-party REST interfaces.
New packageremoteablefor all remoteable proxy interface annotations.@Remoteable annotation has been moved to this package. - Updated doc:
6 - Remoteable Services - New doc:
6.1 - Interface proxies against 3rd-party REST interfaces - New URL-encoding serializer setting:
UrlEncodingSerializerContext.URLENC_paramFormat. - New methods on
UrlEncodingSerializer.Builder
:Builder.paramFormat(String)Builder.plainTextParams()
org.apache.juneau.rest
@RestResourceannotation can now be applied to any class! You're no longer restricted to subclassing your resources fromRestServlet.
This is a major enhancement in the API. Anything you could do by subclassing fromRestServlet should have an equivalent for non-RestServlet classes.
The only restriction is that the top-level resource must subclass fromRestServlet . Child resources do not.
The majority of code has been split up into two separate classes:RestConfig- A modifiable configuration of a resource. Subclasses fromjavax.servlet.ServletConfig.RestContext
- A read-only configuration that's the result of a snapshot of the config.
TheRestServletclass now has the following initialization method that allows you to override the config settings define via annotations:RestServlet.init(RestConfig)- A modifiable configuration of a resource.
RestServlet classes must have one of the following to allow it to be instantiated:- A
constructor.public T(RestConfig) - A
constructor.public T() - The parent resource must have a customized
RestResourceResolverfor instantiating it.
Non-RestServlet classes can optionally include the following init methods to gain access to the config and context:public init(RestConfig)public init(RestContext)
- New annotations added to
@RestResourceto allow non-RestServlet resources to do the same as subclassing directly fromRestServlet :RestResource.resourceResolver()- Specify aRestResourceResolverclass for resolving child resources.RestResource.callHandler()- Specify aRestCallHandlerclass for handling the lifecycle of a REST call.RestResource.infoProvider()- Specify aRestInfoProviderclass for customizing title/description/Swagger information on a REST resource.RestResource.logger()- Specify aRestLoggerclass for handling logging.
- New annotations added to
@RestResourceandRestMethod @RestMethodto simplify defining page title, text, and links on HTML views:@RestResource(pageTitle)@RestMethod(pageTitle)@RestResource(pageText)@RestMethod(pageText)@RestResource(pageLinks)@RestMethod(pageLinks)
// Old method @RestResource ( properties={@Property (name=HTMLDOC_title , value="System properties resource" ),@Property (name=HTMLDOC_description , value="REST interface for performing CRUD operations on system properties." ),@Property (name=HTMLDOC_navlinks , value="{up:'$R{requestParentURI}',options:'?method=OPTIONS'}" ) } )// New method @RestResource ( pageTitle="System properties resource" , pageDescription="REST interface for performing CRUD operations on system properties." , pageLinks="{up:'$R{requestParentURI}',options:'?method=OPTIONS'}" )Typically you're going to simply want to use the
title anddescription annotations which apply to both the page title/text and the swagger doc:@RestResource ( title="System properties resource" , description="REST interface for performing CRUD operations on system properties." , pageLinks="{up:'$R{requestParentURI}',options:'?method=OPTIONS'}" ) RestResource.stylesheet()can now take in a comma-delimited list of stylesheet paths.StreamResourcecan now contain multiple sources from a variety of source types (e.g. arrays,byte []InputStreams ,Files , etc...) and is now immutable. It also includes a newStreamResourceBuilderclass.- Simplified remoteable proxies using the
annotation on REST methods. Used to expose interface proxies without the need for@RestMethod (name="PROXY" )RemoteableServlet.// Server side @RestMethod (name="PROXY" , path="/myproxy/*" )public IAddressBook getProxy() {return addressBook ; }// Client side RestClient client = RestClient.create ().rootUrl(samplesUrl ).build(); IAddressBook ab = client.getRemoteableProxy(IAddressBook.class ,"/addressBook/myproxy" );RestMethod.name() @RestMethod(name)for more information. RestRequest.toString()
can be called at any time to view the headers and content of the request without affecting functionality. Very useful for debugging.RestMethod.name() @RestMethod(name)annotation is now optional. Defaults to"GET" .
org.apache.juneau.rest.client
- Revamped the client API to use builders.
- New doc:
1.5 - Debugging - The
RestClient classdoX(Object url) methods now handle HttpClientURIBuilder instances. - New methods added/updated to
RestClient
:RestClient.getRemoteableProxy(Class,Object)- For interface proxies defined using .@RestMethod (name="PROXY" )RestClient.getRemoteableProxy(Class,Object,Serializer,Parser)- Same as above but overrides the serializer and parser defined on the client.RestClient.doPost(Object)RestClient.doCall(HttpMethod,Object,Object)- Can now pass in instances ofNameValuePairsfor easy form posts.
This extends to all methods that take in the input.
- New methods on
RestCall:RestCall.uri(Object) uri(Object)query(String,Object,boolean,PartSerializer)RestCall.query(String,Object) query(String,Object)RestCall.queryIfNE(String,Object) queryIfNE(String,Object)RestCall.query(Map) query(Map)RestCall.queryIfNE(Map) queryIfNE(Map)RestCall.query(String) query(String)formData(String,Object,boolean,PartSerializer)RestCall.formData(String,Object) formData(String,Object)RestCall.formDataIfNE(String,Object) formDataIfNE(String,Object)RestCall.formData(Map) formData(Map)RestCall.formDataIfNE(Map) formDataIfNE(Map)header(String,Object,boolean,PartSerializer)RestCall.header(String,Object) header(String,Object)RestCall.headerIfNE(String,Object) headerIfNE(String,Object)RestCall.headers(Map) headers(Map)RestCall.headersIfNE(Map) headersIfNE(Map)RestCall.host(String) host(String)RestCall.port(int) port(int)RestCall.userInfo(String,String) userInfo(String,String)RestCall.userInfo(String) userInfo(String)RestCall.scheme(String) scheme(String)
- New methods added to
RestClient.Builder
:executorService(ExecutorService,boolean)
Builder.paramFormat(String)Builder.plainTextParams()noTrace()
- Adds aNo-Trace: true header on all requests to prevent the servlet from logging errors.
Useful for testing scenarios when you don't want the console to end up showing errors done on purpose.debug()
now adds aDebug: true header on all requests.
- New methods added/updated to
RestCall:RestCall.runFuture() runFuture()RestCall.getResponseFuture(Class) getResponseFuture(Class)org.apache.juneau.rest.client.RestCall.getResponseFuture(Type,Type...) getResponseFuture(Type,Type...)RestCall.getResponseAsStringFuture() getResponseAsStringFuture()RestCall.serializer(Serializer) serializer(Serializer)- Override the serializer defined on the client for a single call.RestCall.parser(Parser) parser(Parser)- Override the parser defined on the client for a single call.input(Object)- Now accepts instances ofNameValuePairs.RestCall.getResponse(Class) getResponse(Class)- Can now pass in any of the following:HttpResponse
- Returns the rawHttpResponse returned by the innerHttpClient .Reader
- Returns access to the raw reader of the response.InputStream
- Returns access to the raw input stream of the response.
- New methods added to
NameValuePairs:NameValuePairs.append(String,Object) append(String,Object)append(String,Object,PartSerializer)
RetryOnis now an abstract class with an additional method:RetryOn.onResponse(HttpResponse) onResponse(HttpResponse)
org.apache.juneau.microservice
"REST/port" configuration setting can now be a comma-limited list of port numbers to try.
You can also specify one or more0 s to try a random port.- Methods added to
RestMicroserviceclass:getPort()getURI()- Override methods added from
Microservice
class for easier method chaining.
6.3.0 (Jun 30, 2017)
Juneau 6.3.0 is a major update with significant new functionality for defining proxy interfaces against arbitrary 3rd-party REST interfaces.
org.apache.juneau
-
New package:
org.apache.juneau.http
. -
Support for dynamic beans. See
@BeanProperty(name). -
New doc:
2.8 - Virtual Beans -
New doc:
2.13 - Comparison with Jackson -
All parsers now allow for numeric types with
'K' /'M' /'G' suffixes to represent kilobytes, megabytes, and gigabytes.// Example int i = JsonParser.DEFAULT .parse("123M" );// 123MB - New/modified methods on
ConfigFile:ConfigFile.put(String,String,String,boolean)ConfigFile.put(String,String,Object,Serializer,boolean,boolean)ConfigFile.getObject(String,Type,Type...)ConfigFile.getObject(String,Parser,Type,Type...)ConfigFile.getObject(String,Class)ConfigFile.getObject(String,Parser,Class)ConfigFile.getObject(String,String,Type,Type...)ConfigFile.getObject(String,String,Parser,Type,Type...)ConfigFile.getObject(String,String,Class)ConfigFile.getObject(String,String,Parser,Class)ConfigFile.getObjectWithDefault(String,Object,Type,Type...)ConfigFile.getObjectWithDefault(String,Parser,Object,Type,Type...)ConfigFile.getObjectWithDefault(String,Object,Class)ConfigFile.getObjectWithDefault(String,Parser,Object,Class)
- New ability to interact with config file sections with proxy interfaces with new method
ConfigFile.getSectionAsInterface(String,Class). @BeanPropertyannotation can now be applied to getters and setters defined on interfaces.- New methods on
SerializerSession
andParserSession
for retrieving context and runtime-override properties:Session.getProperty(String)Session.getProperty(String,String)Session.getProperty(Class,String)Session.getProperty(Class,String,Object)
- New
PartSerializerinterface particularly tailored to HTTP headers, query parameters, form-data parameters, and path variables.
Allows easy user-defined serialization of these objects.
The interface can be used in the following locations:Builder.partSerializer(Class)Path.serializerQuery.serializerQueryIfNE.serializerFormData.serializerFormDataIfNE.serializerHeader.serializerHeaderIfNE.serializer
- Across-the-board improvements to the URI-resolution support (i.e. how URIs get serialized).
- New support for resolving URIs with the following newly-recognized protocols:
"context:/..." - Relative to context-root of the application."servlet:/..." - Relative to the servlet URI."request:/..." - Relative to the request URI.
pages=
With these new protocols, we can define them like so:"{up:'$R{requestParentURI}', options:'?method=OPTIONS', upload:'upload'}" links=
The old method of using variables and servlet-relative URIs will still be supported but using these new protocols should (hopefully) be easier to understand."{top:'context:/', up:'request:/..' ,options:'servlet:/?method=OPTIONS', upload:'servlet:/upload'}"
These protocols work on all serialized URL and URI objects, as well as classes and properties annotated withURI @URI. - New classes:
- New configuration properties:
SerializerContext.SERIALIZER_uriContextSerializerContext.SERIALIZER_uriRelativitySerializerContext.SERIALIZER_uriResolutionSerializerContext.SERIALIZER_maxIndent
- New support for resolving URIs with the following newly-recognized protocols:
- New annotation property:
@BeanProperty(value).
The following two annotations are considered equivalent:@BeanProperty (name="foo" )@BeanProperty ("foo" ) - Fixed a race condition in ClassMeta.
URLENC_paramFormat has been moved toUonSerializer.UON_paramFormat, and the UON/URL-Encoding serializers will now always serialize all values as plain text.
This means that arrays and maps are converted to simple comma-delimited lists.- Listener APIs added to serializers and parsers:
SerializerListener
Serializer.Builder.listener(Class)
@RestResource(serializerListener)RestConfig.serializerListener(Class)ParserListener
Parser.Builder.listener(Class)
@RestResource(parserListener)RestConfig.parserListener(Class)Builder.listeners(Class,Class)
- The
BEAN_debugflag will now capture parser input and make it available through theParserSession.getInputAsString()method so that it can be used in the listeners. - Significant new functionality introduced to the HTML serializer.
Lots of new options for customizing the HTML output.- New
@Html(render)
annotation andHtmlRender
class that allows you to customize the HTML output and CSS style on bean properties:
Annotation can be applied to POJO classes and bean properties. - Several new properties for customizing parts of the HTML page:
HtmlDocSerializerContext.HTMLDOC_titleHtmlDocSerializerContext.HTMLDOC_descriptionHtmlDocSerializerContext.HTMLDOC_brandingHtmlDocSerializerContext.HTMLDOC_headerHtmlDocSerializerContext.HTMLDOC_navHtmlDocSerializerContext.HTMLDOC_asideHtmlDocSerializerContext.HTMLDOC_footerHtmlDocSerializerContext.HTMLDOC_noResultsMessageHtmlDocSerializerContext.HTMLDOC_cssUrlHtmlDocSerializerContext.HTMLDOC_cssHtmlDocSerializerContext.HTMLDOC_template
- New interface
HtmlDocTemplate
that allows full control over rendering of HTML produced byHtmlDocSerializer
.
- New
@NameProperty
and@ParentProperty
can now be applied to fields.- New properties on
BeanContext
:BEAN_includePropertiesBEAN_excludeProperties
- New annotation property:
@BeanProperty(format).
org.apache.juneau.rest
- MAJOR enhancements made to the REST API.
-
The
RestRequest
class functionality has been broken up into the following functional pieces to reduce its complexity:RestRequest.getBody()- The request body.RestRequest.getHeaders()
- The request headers.RestRequest.getQuery()- The request query parameters.RestRequest.getFormData()- The request form data parameters.RestRequest.getPathMatch()- The path variables and remainder.
RequestBodyRequestHeadersRequestQueryRequestFormDataRequestPath
-
The un-annotated parameter types that can be passed in through REST Java methods has been significantly expanded.
For reference, the previous supported types were:RestRequest
- The request object.javax.servlet.http.HttpServletRequest- The superclass ofRestRequest .RestResponse
- The response object.javax.servlet.http.HttpServletResponse- The superclass ofRestResponse .
AcceptAcceptCharsetAcceptEncodingAcceptLanguageAuthorizationCacheControlConnectionContentLengthContentTypeDateExpectFromHostIfMatchIfModifiedSinceIfNoneMatchIfRangeIfUnmodifiedSinceMaxForwardsPragmaProxyAuthorizationRangeRefererTEUserAgentUpgradeViaWarningTimeZone
InputStream
javax.servlet.ServletInputStreamReader
OutputStream
javax.servlet.ServletOutputStreamWriter
ResourceBundle
- Client-localized resource bundle.MessageBundle- A resource bundle with additional features.Locale
- Client locale.RequestHeaders- API for accessing request headers.RequestQuery- API for accessing request query parameters.RequestFormData- API for accessing request form data.RequestPath- API for accessing path variables.RequestBody- API for accessing request body.HttpMethod
- The method name matched (when using )@RestMethod (name="*" )Logger
- The logger to use for logging.JuneauLogger- Logger with additional features.RestContext
- The resource read-only context.Parser
- The parser matching the request content type.Swagger
- The auto-generated Swagger doc.ConfigFile- The external config file for the resource.
/** Old way */ @RestMethod (name="*" , path="/example1/{a1}/{a2}/{a3}/*" )public String example1(@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,@Header ("Accept-Language" ) String lang,@Header ("Accept" ) String accept )/** New way */ @RestMethod (name="*" , path="/example2/{a1}/{a2}/{a3}/*" )public String example2( HttpMethod httpMethod, RequestPathParams pathParams, RequestQuery query, AcceptLanguage acceptLanguage, Accept accept ) -
A new annotation
@RestResource(paramResolvers)} that allows you to define your own custom Java method parameter resolvers. -
Fixed bug where Writer returned by
RestResponse.getWriter()
was not being flushed automatically at the end of the HTTP call. -
New annotations added to
RestMethod @RestMethod:RestMethod.defaultQuery() defaultQuery()RestMethod.defaultFormData() defaultFormData()bpIncludes()bpExcludes()
-
Default values on header, query, and form-data annotations:
@Header(def)- Default header value.@Query(def)- Default query parameter value.@FormData(def)- Default form data parameter value.
-
New attributes on
@RestResource:serializerListener()parserListener()widgets()swagger()htmldoc()
-
New attributes on
RestMethod @RestMethod:widgets()RestMethod.swagger() swagger()RestMethod.htmldoc() htmldoc()
-
New string vars:
UrlVar
- Resolve"$U{...}" variables to URLs.WidgetVar- Resolve"$W{...}" variables to widget contents.
-
New methods on
RestConfig:setHtmlTitle(String)setHtmlDescription(String)setHtmlBranding(String)setHtmlHeader(String)setHtmlLinks(String)setHtmlNav(String)setHtmlAside(String)setHtmlFooter(String)setHtmlCss(String)setHtmlCssUrl(String)setHtmlNoWrap(boolean)setHtmlNoResultsMessage(String)setHtmlTemplate(Class)setHtmlTemplate(HtmlDocTemplate)addWidget(Class)
-
New methods on
RestResponse
:setHtmlTitle(Object)setHtmlDescription(Object)setHtmlBranding(Object)setHtmlHeader(Object)setHtmlLinks(Object)setHtmlNav(Object)setHtmlAside(Object)setHtmlFooter(Object)setHtmlCss(Object)setHtmlCssUrl(Object)setHtmlNoWrap(boolean)setHtmlNoResultsMessage(Object)setHtmlTemplate(Class)setHtmlTemplate(HtmlDocTemplate)
-
&plainText=true parameter now works on byte-based serializers by converting the output to hex. -
New classes for widget support:
Widget
PoweredByJuneauWidgetContentTypeLinksColumnWidgetContentTypeLinksRowWidgetQueryWidget
-
devops.css cleaned up. -
Removed a bunch of URL-related methods from
RestRequest
. These all have equivalents inRestRequest.getUriContext()
. - New annotation attributes:
org.apache.juneau.rest.client
-
New
Pathannotation for specifying path variables on remoteable interfaces. -
New
@RequestBeanannotation for specifying beans with remoteable annotations defined on properties. -
The following annotations (and related methods on RestCall) can now take
NameValuePairs and beans as input when using"*" as the name.FormData,FormDataIfNE,Query,QueryIfNE,Header,HeaderIfNE
org.apache.juneau.microservice
org.apache.juneau.examples.rest
- Many code enhancements make to examples to reflect new functionality.
- All pages now render aside comments to help explain what feature they're trying to explain using the
new features that allow you to customize various elements of the page.
6.3.1 (Aug 1, 2017)
Juneau 6.3.1 is a minor release.
org.apache.juneau
-
PojoQueryimprovements. -
New
RemoteMethod.returns()annotation.
Allows you to specify whether the remote method returns the HTTP body or status code. -
Fixed bugs with
BEAN_includePropertiesandBEAN_excludePropertiessettings. -
New/modified settings in
HtmlDocSerializerContext:HTMLDOC_scriptHTMLDOC_style- WasHTMLDOC_css .HTMLDOC_stylesheet- WasHTMLDOC_cssUrl . Now an array.
-
New
ResourceFinderutility class. Allows you to search for resources up the parent hierarchy chain. Also allows you to search for localized resources. -
Eliminated the following properties from
HtmlDocSerializerContext:HTMLDOC_title ,HTMLDOC_description ,HTMLDOC_description
See below on changes to simplify HTML headers. -
Var
implementations can now throw exceptions and will be converted to""{exceptionMessage}" values.
org.apache.juneau.rest
-
New 'light' stylesheet:
Compared with previous 'devops':
For those nolstalgic for old times, there's also 'original':
-
Simplified the stylesheets and HTML code.
For example, the nav links are now an ordered list of elements which makes rendering as as side-bar (for example) easier to do in CSS. -
Modifications to the following
@HtmlDoc annotations:HtmlDoc.navlinks() navlinks()- Now an array of strings instead of a JSON object. Simplified syntax.
For example:// Old syntax htmldoc=@HtmlDoc ( links="{" +"up:'request:/..'," +"options:'servlet:/?method=OPTIONS'," +"contentTypes:'$W{ContentTypeMenuItem}'," +"styles:'$W{StyleMenuItem}'," +"source:'$C{Source/gitHub}/org/apache/juneau/examples/rest/PetStoreResource.java'" +"}" )// New syntax htmldoc=@HtmlDoc ( navlinks={"up: request:/.." ,"options: servlet:/?method=OPTIONS" ,"$W{ContentTypeMenuItem}" ,"$W{StyleMenuItem}" ,"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/PetStoreResource.java" } )- Several annotations are now arrays of strings instead of simple strings.
Values are simply concatenated with newlines which makes multi-line values cleaner.
HtmlDoc.header() header()HtmlDoc.nav() nav()HtmlDoc.aside() aside()HtmlDoc.footer() footer()HtmlDoc.script() script()HtmlDoc.style() style()
"INHERIT" string literal can be used to combine the value with the value defined on the servlet or parent class. Links can also be inserted at specific index positions.
-
Improvements made to the
Widget
API.-
You can now add arbitrary CSS and Javascript along with your widgets through new methods:
Widget.getHtml(RestRequest)Widget.getScript(RestRequest)Widget.getStyle(RestRequest)
-
Declaration of widgets moved to
HtmlDoc.widgets() @HtmlDoc(widgets)instead of separately on@RestResourceand@RestMethod annotations. -
Widget.getName()
now defaults to the simple class name.
So now you can just refer to the class name:"$W{ContentTypeMenuItem}" . -
Renamed widgets:
PoweredByApacheWidget ->PoweredByApache PoweredByJuneauWidget ->PoweredByJuneau
-
New
MenuItemWidget
can be used as a starting point for creatint pull-down menu items. -
New
ContentTypeMenuItem
widget that provides a pull-down menu with hyperlinks for all supported languages for that page:
-
Improved
QueryMenuItem
widget that provides a pull-down menu of a search/view/order-by/page form:
Fields are now pre-filled with current query parameters. -
New
ThemeMenuItem
widget that provides a pull-down menu with hyperlinks to show the content in the default stylesheets:
-
You can now add arbitrary CSS and Javascript along with your widgets through new methods:
-
New/modified annotations on
HtmlDoc @HtmlDoc:HtmlDoc.style() style()- Renamed fromcss() .HtmlDoc.stylesheet() stylesheet()- Renamed fromcssUrl() .
Can now be a comma-delimited list of URLs.HtmlDoc.script() script()- Add arbitrary Javascript to page header.
-
Bug fix with
HtmlDoc.nowrap() @HtmlDoc(nowrap)so that the setting only applies to the data contents, not the whole page. -
Two convenience methods added to
RestRequest
:RestRequest.attr(String,Object)RestRequest.prop(String,Object)
-
Annotations added:
@RestResource(siteName)@RestResource(flags)RestMethod.flags() @RestMethod(flags)
-
Eliminated the
@RestResource(stylesheet)annotation. It's no longer needed now that you can easily specify styles via@Htmldoc . -
Eliminated the following annotations since they are now redundant with
HtmlDoc.header() @HtmlDoc(header):title() description() branding()
BasicRestServletclass defines the following default header that can be easily overridden:htmldoc=
Note that the subtitle first tries using the method summary and then the servlet description.@HtmlDoc ( header={"<h1>$R{resourceTitle}</h1>" ,"<h2>$R{methodSummary,resourceDescription}</h2>" ,"<a href='http://juneau.apache.org'><img src='$U{servlet:/htdocs/juneau.png}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/></a>" } ) -
New
$F
variable resolver for resolving the contents of files in the classpath.
TheDockerRegistryResource examples shows how it can be used to pull in a localized file from the classpath to populate the aside section of a page.htmldoc=
@HtmlDoc (// Pull in aside contents from file. aside="$F{resources/DockerRegistryResourceAside.html}" ) -
New
ReaderResource.toCommentStrippedString()method. -
The
bpIncludes() andbpExcludes() annotations on@RestMethod has been replaced with the following:RestMethod.bpi() bpi()- Now an array of simplified values instead of LAX JSON.RestMethod.bpx() bpx()- Now an array of simplified values instead of LAX JSON.
-
Two new variables added to
$R variable:"$R{servletClass}" ,"$R{servletClassSimple}"
org.apache.juneau.rest.examples
- Added CONTENT-TYPE and STYLES menu items to most pages.
- Added improved QUERY menu item to PetStore page.
6.4.0 (Oct 5, 2017)
The major change in this release is the project structure.
The library now consists of the following artifacts found in the Maven group
Category | Maven Artifacts | Description | Prereqs |
---|---|---|---|
Juneau Core | juneau-marshall | Serializers and parsers for:
|
|
juneau-marshall-rdf |
Serializers and parsers for:
|
|
|
juneau-dto |
Data Transfer Objects for:
|
|
|
juneau-svl | Simple Variable Language API |
|
|
juneau-config | Configuration file API |
|
|
Juneau REST | juneau-rest-server | REST Servlet API |
|
juneau-rest-server-jaxrs | Optional JAX-RS support |
|
|
juneau-rest-client | REST Client API |
|
|
Juneau Microservice | juneau-microservice-server | REST Microservice Server API |
|
juneau-microservice-template | Developer template project |
|
|
Examples | Core code examples | ||
REST code examples | |||
Juneau All |
Combination of the following:
|
|
juneau-marshall
-
Improvements to swap support.
- New
@Swap
annotation.
Replaces the@Pojo and@BeanProperty(swap)annotations. - Support for per-media-type swaps.
Programmatic example:@Swap (MyJsonOnlySwap.class )public class MyPojo {}public class MyJsonOnlySwapextends PojoSwap<MyPojo,String> {public MediaType[] forMediaTypes() {return MediaType.forStrings ("*/json" ); }public String swap(BeanSession session, MyPojo o)throws Exception {return "It's JSON!" ; }
Annotated example:@Swap (impl=ToStringSwap.class , mediaTypes="*/json" )public class MyBean { ... }public class ToStringSwapextends PojoSwap<Object,String> {public String swap(BeanSession session, Object o)throws Exception {return o.toString(); } } - Support for templated swaps which provide additional context information for a swap.
The following is an example of a templated swap class used to serialize POJOs to HTML using FreeMarker:// Our abstracted templated swap class. public abstract class FreeMarkerSwapextends PojoSwap<Object,Reader> {public MediaType[] forMediaTypes() {return MediaType.forStrings ("*/html" ); }public Reader swap(BeanSession session, Object o, String template)throws Exception {return getFreeMarkerReader(template, o);// Some method that creates raw HTML. } }@Swap (impl=FreeMarkerSwap.class , template="MyPojo.div.ftl" )public class MyPojo {} - New
Swaps @Swapsannotation for defining multiple swaps against the same POJO when they're differentiated by media types:@Swaps ( {@Swap (MyJsonSwap.class ),@Swap (MyXmlSwap.class ),@Swap (MyOtherSwap.class ) } )public class MyPojo {}
- New
-
New
Surrogateinterface for identifying surrogate classes. -
Serializers can now serialize to
StringBuilders
. -
Serializers now serialize the contents of
Readers
andInputStreams
directly to the output stream or writer.
When used with conjunction withPojoSwaps , this can be used to provide customized output for specific content types.@Pojo (swap=MyBeanSwap.class )public class MyBean {...}public class MyBeanSwapextends PojoSwap<MyBean,Object> {public Object swap(BeanSession session, MyPojo o)throws Exception { MediaType mt = session.getMediaType();if (mt.hasSubType("json" ))return new StringReader("{foo:'bar'}" );// Custom JSON output return o;// Otherwise treat as normal bean } }// Produces "{foo:'bar'}" String json = SimpleJsonSerializer.DEFAULT .toString(new MyBean());
This feature helps with the implementation of language-agnostic template support such as for using FreeMaker to serialize POJOs to HTML. -
SerializerSession
andParserSession
objects are now reusable if used within the same thread.// Old way (still works) JsonSerializer.DEFAULT .serialize(writer1, pojo1); JsonSerializer.DEFAULT .serialize(writer2, pojo2);// Same but using a session object SerializerSession session = JsonSerializer.DEFAULT .createSession();try { session.serialize(writer1, pojo1); session.serialize(writer2, pojo2); }finally { session.close(); } -
PojoSwap.swap(BeanSession,Object)andPojoSwap.unswap(BeanSession,Object,ClassMeta)can now throw arbitrary exceptions instead of having to wrap them inSerializeException /ParseException . -
New
CalendarUtilsclass that encapsulates serialization/parsing logic fromCalendarSwapandDateSwap. -
New annotation
Html.anchorText()
. -
New methods on
ObjectList:ObjectList.get(int,Class) get(int,Class)org.apache.juneau.ObjectList.get(int,Type,Type...) get(int,Type,Type...)ObjectList.getMap(int,Class,Class) getMap(int,Class,Class)ObjectList.getList(int,Class) getList(int,Class)
-
New methods on
ObjectMap:ObjectMap.get(String,Class) get(String,Class)org.apache.juneau.ObjectMap.get(String,Type,Type...) get(String,Type,Type...)ObjectMap.getWithDefault(String,Object) getWithDefault(String,Object)ObjectMap.getWithDefault(String,Object,Class) getWithDefault(String,Object,Class)org.apache.juneau.ObjectMap.getWithDefault(String,Object,Type,Type...) getWithDefault(String,Object,Type,Type...)ObjectMap.getSwapped(String,PojoSwap) getSwapped(String,PojoSwap)ObjectMap.getAt(String,Class) getAt(String,Class)org.apache.juneau.ObjectMap.getAt(String,Type,Type...) getAt(String,Type,Type...)ObjectMap.getMap(String,Class,Class,Map) getMap(String,Class,Class,Map)ObjectMap.getList(String,Class,List) getList(String,Class,List)
-
New methods on
PojoRest:PojoRest.get(String,Class) get(String,Class)org.apache.juneau.utils.PojoRest.get(String,Type,Type...) get(String,Type,Type...)PojoRest.getWithDefault(String,Object) getWithDefault(String,Object)PojoRest.getWithDefault(String,Object,Class) getWithDefault(String,Object,Class)org.apache.juneau.utils.PojoRest.getWithDefault(String,Object,Type,Type...) getWithDefault(String,Object,Type,Type...)
-
Fixed bug where
BeanSession.getMediaType()
wasn't returning a value. -
Eliminated the
@Consumes and@Produces annotations.
The supported media types are now passed in through the constructors.
This was changed to eliminate a performance issue where a field could not be set as final because the call togetClass() to retrieve the annotation value could not be called before calling the method.super () -
New class:
PojoMerge -
New doc:
2.6.2 - @Pojo annotation -
New doc:
2.6.5 - Serializing Readers and InputStreams
juneau-dto
-
HtmlElementMixed.children(Object...)
can now take in collections of objects. -
The DTO beans can now be serialized to strings of their typical language by calling the
toString() method.
For example,Swagger.toString() produces JSON and the HTML5Form.toString() produces HTML.
juneau-rest-server
-
Revamped and simplified servlet and REST-call lifecycle handling through new
@RestHookannotation.- The
RestServlet.init(ServletConfig)method is now final and can no longer be extended.
Instead, useHookEvent.INITorHookEvent.POST_INITfor initialization. - The following methods on
RestServlethave been removed:init(RestConfig) - UseHookEvent.INITinstead.onSuccess(RestRequest, RestResponse, long) - UseHookEvent.END_CALLinstead.onPreCall(RestRequest) - UseHookEvent.PRE_CALLinstead.onPostCall(RestRequest, RestResponse) - UseHookEvent.POST_CALLinstead.
- The
-
Simplified
MenuItemWidget
.
Exposes an abstract methodgetContent(RestRequest)that can return raw HTML via readers or char-sequences, or any other object (such as HTML5 beans) that will get converted to HTML usingHtmlSerializer.DEFAULT
. -
RestResourceResolverinstances are now inherited from parent resources to child resources unless explicitly overridden at the child level.
It's also been changed to an interface. - New annotations on
@RestResource:resourceResolver()
Allows you to specify a resource resolver on the servlet context to make it easier to work with dependency injection frameworks.contextPath()-
Allows you to override the context path value inherited from the servlet container.allowHeaderParams()-
Replaces theRestContext.REST_allowHeaderParams setting.allowMethodParam()-
Replaces theRestContext.REST_allowMethodParam setting.allowBodyParam()-
Replaces theRestContext.REST_allowBodyParam setting.renderResponseStackTraces()-
Replaces theRestContext.REST_xxx setting.useStackTraceHashes()-
Replaces theRestContext.REST_useStackTraceHashes setting.defaultCharset()-
Replaces theRestContext.REST_defaultCharset setting.paramFormat()-
Replaces theRestContext.REST_paramFormat setting.
- New annotations on
RestMethod @RestMethod:RestMethod.defaultCharset() defaultCharset()-
Replaces theRestContext.REST_defaultCharset setting.RestMethod.paramFormat()-
Replaces theRestContext.REST_paramFormat setting.
-
The following implementation classes can now be defined as non-static inner classes of servlets/resources:
Widget
RestConverterRestGuardResponseHandlerRestCallHandlerRestInfoProviderRestResourceResolverRestLoggerHtmlDocTemplate
-
New tooltip template:
Tooltip
-
New dark theme:
-
Stylesheet selection now stored in HTTP session when passed in via
?stylesheet query parameter. -
New doc:
Lifecycle Hooks -
Eliminated the
RestServletJenaDefault class to remove the Jena dependency class on thejuneau-rest-server artifact.
It's simple enough to simply extendBasicRestServlet and add the RDF serializers and parsers.
juneau-microservice
-
The microservice has been significantly modified to be configured via a
jetty.xml file for maximum flexibility instead of the hodge-podge of support in the config file.
Top-level servlets should now be defined in the providedjetty.xml file. -
New methods on
RestMicroservice:addServlet(Servlet,String)addServletAttribute(String,Object)getServer()getInstance()getPort()getContextPath()getProtocol()getHostName()
-
New methods on
Microservice
: -
New class
JettyLoggerfor directing Jetty logging to the java.util.logging framework. -
New class
DebugResourcefor viewing and generating Jetty thread dumps through REST calls.
org.apache.juneau.rest.examples
-
New example of adding a menu-item widget to the Pet Store resource (including tooltips):
7.0.0 (Oct 25, 2017)
This release ups the Java prerequisite to Java 7.
juneau-marshall
-
New class
HttpMethodNamewith valid static string HTTP method names.
juneau-dto
-
Class
org.apache.juneau.dto.Link renamed toLinkString
. Helps avoid confusion since there are other Link classes in the library.
juneau-rest-server
-
Annotation
renamed to@HtmlDoc (links)HtmlDoc.navlinks() navlinks. -
New annotation
HtmlDoc.head() @HtmlDoc(head).
Allows you to specify arbitrary HTML content in the<head> section of the page. -
Removed annotation
.@HtmlDoc (favIcon)
This was a discouraged way of defining fav-icons anyway, and with the addition of , you can define them using:@HtmlDoc (head)head={
"<link rel='icon' href='$U{servlet:/htdocs/juneau.png}'/>" } -
Removed several of the HTMLDOC-related methods from the
RestResponse/RestConfig/RestContext classes and moved it into the newHtmlDocBuilderclass.
7.0.1 (Dec 24, 2017)
This release is a minor update. It includes the following prereq updates:
- Apache HttpClient: 4.5.3 to 4.5.4
- Eclipse Jetty: 9.4.6.v20170531 to 9.4.8.v20171121
juneau-marshall
- New static
create() methods for builders on serializers and parsers.
This simplifies the syntax of creation of serializers and parsers by scratch.// Old way JsonSerializer s1 =new JsonSerializer.Builder().ws().build();// New way JsonSerializer s2 = JsonSerializer.create ().ws().build();
The same static create methods have also been added to the following classes:SerializerGroup.create()ParserGroup.create()EncoderGroup.create()RestClient.create()
ConfigFile.create()
-
The order of the parameters in
SerializerSession.serialize(Object,Object)
has been change to matchSerializer.serialize(Object,Object)
. - Fixed some bugs in the XML parser related to whitespace and comments.
juneau-svl
- New methods on
Var
class to restrict when nested and embedded variables are resolved.
juneau-rest-server
-
New
@RestResource(maxInput)andRestMethod.maxInput() @RestMethod(maxInput)for alleviating potential DoS attacks.
juneau-microservice-server
-
New pluggable console commands.
When you start up the microservice, you'll now see the following:Running class 'RestMicroservice' using config file 'examples.cfg'. Server started on port 10000 List of available commands: exit -- Shut down service restart -- Restarts service help -- Commands help echo -- Echo command > help help NAME help -- Commands help SYNOPSIS help [command] DESCRIPTION When called without arguments, prints the descriptions of all available commands. Can also be called with one or more arguments to get detailed information on a command. EXAMPLES List all commands: > help List help on the help command: > help help >
Commands are pluggable and extensible through the config file.
#======================================================================================================================= # Console settings #======================================================================================================================= [Console] enabled =true # List of available console commands. # These are classes that implements ConsoleCommand that allow you to submit commands to the microservice via # the console. # When listed here, the implementations must provide a no-arg constructor. # They can also be provided dynamically by overriding the Microservice.createConsoleCommands() method. commands =org.apache.juneau.microservice.console.ExitCommand, org.apache.juneau.microservice.console.RestartCommand, org.apache.juneau.microservice.console.HelpCommand - New classes:
- New methods on
Microservice
startConsole()
createConsoleCommands()getConsoleReader()
getConsoleWriter()
- Console input reader and output writer can now be overridden.
- Console strings are now internationalized.
7.1.0 (Mar 08, 2018)
Version 7.1.0 is a major update with major implementation refactoring across all aspects of the product.
juneau-marshall
-
Significant improvements made to the internals of the Serializer and Parser classes.
-
Caching improvements on serializers and parsers have reduced execution time of the core
JUnits by approximately 1/3.
The 17000+ JUnit tests now execute in less than 10 seconds and have a cache-reuse hit rate of 98% (164104 serializers/parsers/bean-contexts retrieved but only 1801 created from scratch). -
All the various separate
Context classes (e.g.JsonSerializerContext ) have been folded into their respective serializer or parser classes (e.g.JsonSerializer ).
Additionally, these classes are their own bean contexts.
For example, the class hierarchy ofJsonSerializer is now: AllContext objects are thread-safe and read-only. -
Session objects also now have a consistent class hierarchy.
For example, the class hierarchy ofJsonSerializerSession is now: Session objects are transient objects that live for the duration of a single parse. -
Builder objects also now have a consistent class hierarchy.
For example, the class hierarchy ofJsonSerializer.Builder is now: Builder objects are used for building up and creatingContext objects. -
The
PropertyStoreclass has been completely rewritten. It is now a read-only configuration store build using thePropertyStoreBuilderclass.
The previousPropertyStore class was overly-complicated with many read/write locks to ensure thread-safety.
The new design shifts to a builder-based model with read-onlyPropertyStore objects that can be used as hash keys.
-
Caching improvements on serializers and parsers have reduced execution time of the core
JUnits by approximately 1/3.
-
Improvements to the HTTP-Part APIs.
The existingPartSerializer /PartParser classes have been replaced with the following all located in the neworg.apache.juneau.httppart package:org.apache.juneau.httppart
HttpPartType
HttpPartSerializer
UonPartSerializerSimpleUonPartSerializerSimplePartSerializer
HttpPartParser
UonPartParserSimplePartParser
-
Context.Builder.property(String,Object) renamed toBuilder.set(String,Object). -
ResourceFinder class has been replaced with the following:ClasspathResourceFinderClasspathResourceFinderSimpleClasspathResourceFinderBasicClasspathResourceFinderRecursiveClasspathResourceManager
-
New methods on
SerializerSession
: -
New methods on
ParserSession
: -
New
Parser.PARSER_unbufferedsetting allows you to disable internal buffering on the JSON and UON parsers so that they can be used to read continous streams of objects. -
New
JsonParser.JSON_validateEndandUonParser.UON_validateEndsettings allow you to control whether we validate that there is no garbage at the end of the parsed input. -
New
Parser.PARSER_autoCloseStreamssetting allows input streams and readers passed into parsers to be automatically closed after parsing. -
Syntax changed on unswap method on
Surrogateclasses.
It's now a regular method instead of a static method. -
@Swap
annotation can now be used withSurrogateclasses. -
New support for
PojoBuilders POJO Builders.
juneau-svl
- New variables:
-
Variables moved from
juneau-microservice :
juneau-config
-
The Config API has been completely revamped.
New features include:- Support for pluggable storage.
- File-system watcher integration support.
Changes made to file system files now automatically reflected in configurations and interface proxies. - New builder-based design.
juneau-dto
-
Enhancements to Swagger DTO:
- New methods for setting and retrieving properties via name:
-
Support for setting non-standard fields such as
"$ref" via getter and setter above. - Setter methods that take in beans and collections of beans can now take in JSON strings.
juneau-rest-server
-
RestServletDefault renamed toBasicRestServlet. -
RestServletGroupDefault renamed toBasicRestServletGroup. -
The
"$R{...}" variable has been split into the following:"$RA{key1[,key2...]}" -RequestAttributeVar
, first non-null value returned byHttpServletRequest.getAttribute(String) ."$RF{key1[,key2...]}" -RequestFormDataVar
, first non-null value returned byRestRequest.getFormData(String)."$RH{key1[,key2...]}" -RequestHeaderVar
, first non-null value returned byRestRequest.getHeader(String)
."$RI{key1[,key2...]}" -RestInfoVar, first non-null value returned byRestRequest.getInfoProvider().
The possible values are:"contact" - Value returned byInfo.getContact()
"description" - Value returned byRestInfoProvider.getDescription(RestRequest)"externalDocs" - Value returned bySwagger.getExternalDocs()
"license" - Value returned byInfo.getLicense()
"methodDescription" - Value returned byRestInfoProvider.getMethodDescription(Method,RestRequest)"methodSummary" - Value returned byRestInfoProvider.getMethodSummary(Method,RestRequest)"siteName" - Value returned byRestInfoProvider.getSiteName(RestRequest)"tags" - Value returned bySwagger.getTags()
"termsOfService" - Value returned byInfo.getTermsOfService()
"title" - SeeRestInfoProvider.getTitle(RestRequest)"version" - SeeInfo.getVersion()
"$RP{key1[,key2...]}" -RequestPathVar
, first non-null value returned byRestRequest.getPath(String)."$RQ{key1[,key2...]}" -RequestQueryVar
, first non-null value returned byRestRequest.getQuery(String)."$R{key1[,key2...]}" -RequestVar
, first non-null other request variable.
The possible values are:"contextPath" - Value returned byRestRequest.getContextPath()
"method" - Value returned byRestRequest.getMethod()
"methodDescription" - Value returned byRestRequest.getMethodDescription()"methodSummary" - Value returned byRestRequest.getMethodSummary()"pathInfo" - Value returned byHttpServletRequestWrapper.getPathInfo()
"requestParentURI" - Value returned byUriContext.getRootRelativePathInfoParent()
"requestURI" - Value returned byHttpServletRequestWrapper.getRequestURI()
"resourceDescription" - Value returned byRestRequest.getResourceDescription()"resourceTitle" - SeeRestRequest.getResourceTitle()"servletParentURI" - Value returned byUriContext.getRootRelativeServletPathParent()
"servletPath" - SeeRestRequest.getServletPath()
"servletURI" - SeeUriContext.getRootRelativeServletPath()
"siteName" - SeeRestRequest.getSiteName()
-
Refactored the
RestConfig class intoRestContext.Builder
.
Settings onRestContext
objects can now be set declaratively through the following new properties:RestContext.REST_allowHeaderParamsRestContext.REST_allowBodyParamRestContext.REST_allowedMethodParams REST_allowedMethodParamsRestContext.REST_renderResponseStackTraces REST_renderResponseStackTracesRestContext.REST_useStackTraceHashesRestContext.REST_defaultCharset REST_defaultCharsetRestContext.REST_maxInput REST_maxInputRestContext.REST_paramResolversRestContext.REST_converters REST_convertersRestContext.REST_guards REST_guardsRestContext.REST_responseHandlersRestContext.REST_defaultRequestHeadersRestContext.REST_defaultResponseHeadersRestContext.REST_produces REST_producesRestContext.REST_consumes REST_consumesRestContext.REST_clientVersionHeader REST_clientVersionHeaderRestContext.REST_resourceResolverRestContext.REST_loggerRestContext.REST_callHandlerRestContext.REST_infoProviderRestContext.REST_path REST_pathRestContext.REST_contextPathRestContext.REST_staticFiles REST_staticFilesRestContext.REST_staticFileResponseHeadersRestContext.REST_classpathResourceFinderRestContext.REST_useClasspathResourceCachingRestContext.REST_widgetsRestContext.REST_mimeTypes
-
Support for static files has been simplified and improved.
- Syntax on
@RestResource(staticFiles)has been simplified, and now allows you to specify response headers in the strings. - Response headers for static files can also be configured through
RestContext.REST_staticFileResponseHeaders - Static file in-memory caching now configurable through
RestContext.REST_useClasspathResourceCaching - Static file retrieval can be customized through
RestContext.REST_classpathResourceFinder
- Syntax on
-
Eliminated the
RestMatcherReflecting class.
You can now simply create aRestMatcherthat has a public constructor that takes in the server and method arguments. -
@RestResource.allowMethodParamrenamed toRestResource.allowedMethodParams. -
@RestMethod.serializersInherit and@RestMethod.parsersInherit replaced with simplified@RestMethod(inherit). -
Changes to
RequestFormData:RequestFormData.addDefault(Map) addDefault(Map)takes in aMap<String,Object> instead ofMap<String,String> .
-
Changes to
RequestHeaders:RequestHeaders.addDefault(Map) addDefault(Map)takes in aMap<String,Object> instead ofMap<String,String> .
-
Changes to
RequestQuery:RequestQuery.addDefault(Map) addDefault(Map)takes in aMap<String,Object> instead ofMap<String,String> .
-
Changes to
RestContext
:getResource(String,Locale) renamed togetClasspathResource(String,Locale)getResourceAsString(String,Locale) renamed togetClasspathResourceAsString(String,Locale)getResource(Class,MediaType,String,Locale) renamed togetClasspathResourceAsString(Class,MediaType,String,Locale)- New method
getClasspathResource(Class,String,Locale). - New method
getClasspathResourceAsString(Class,String,Locale). - New method
getClasspathResource(Class,Class,MediaType,String,Locale). RestContext.getDefaultRequestHeaders()returns aMap<String,Object> instead ofMap<String,String> .RestContext.getDefaultResponseHeaders()returns aMap<String,Object> instead ofMap<String,String> .
-
Changes to
RestRequest
:getSupportedMediaTypes() replaced withRestRequest.getConsumes() getConsumes()andRestRequest.getProduces() getProduces().getReaderResource(String,boolean,MediaType) renamed togetClasspathReaderResource(String,boolean,MediaType)getReaderResource(String,boolean) renamed togetClasspathHttpResource(String,boolean)getReaderResource(String) renamed togetClasspathHttpResource(String)
-
Changes to
@RestResource- New
mimeTypes()annotation.
- New
-
Changes to
RestMethod @RestMethod:- New
RestMethod.consumes() consumes()andRestMethod.produces() produces()for overriding the supported media types inferred from the serializers and parsers.
- New
-
RestCallHandler split up intoRestCallHandlerandBasicRestCallHandler -
RestInfoProvider split up intoRestInfoProviderandBasicRestInfoProvider -
RestLogger split up intoRestLogger,BasicRestLoggerandNoOpRestLogger -
RestResourceResolverSimple renamed toBasicRestResourceResolver -
Introduced the following classes that helps make the code more understandable:
RestContextPropertiesRestMethodPropertiesRequestProperties
-
Eliminated the
@Messages and@Properties REST java method parameter annotations.
These aren't needed anymore since you can just pass inMessageBundle andRestRequestProperties as unannotated parameters. -
Revamped the
RestInfoProviderclass. -
New builder classes:
ReaderResourceBuilderStreamResourceBuilder
-
RestResponse.getNegotiatedOutputStream()
now returns aFinishableServletOutputStreamandRestResponse.getNegotiatedWriter()
now returns aFinishablePrintWriterthat allows you to finish the output without closing the stream.
TheDefaultHandlerclass now callsfinish() instead ofclose() on the stream. -
Added the following annotations to the
BasicRestServletclass (which were previously defined on theResourceclass):@RestResource ( htmldoc=@HtmlDoc ( navlinks={"up: request:/.." ,"options: servlet:/?method=OPTIONS" }, stylesheet="$C{REST/stylesheet,servlet:/styles/devops.css}" ),// Optional external configuration file. config="$S{juneau.configFile}" )
juneau-rest-client
-
New configuration property
RestClient.RESTCLIENT_queryand builder methodBuilder.query(String,Object). -
API changes to replace
PartSerializer withHttpPartSerializer
.
The default value is nowSimpleUonPartSerializerwhich will serialize strings as plain-text and collections/arrays as comma-delimited lists.
We decided to change the default behavior in favor of practicality over purity. -
New methods on
RestCallclass:RestCall.getResponseHeader(String) getResponseHeader(String)RestCall.getResponseCode() getResponseCode()
-
RestCallandRestClient
now implement theCloseable interface.
juneau-microservice
-
Resource andResourceGroup classes removed.BasicRestServletandBasicRestServletGroupcan be used instead. -
ResourceJena andResourceJenaGroup classes renamed toBasicRestServletJenaandBasicRestServletJenaGroup.
7.2.0 (Sept 25, 2018)
7.2.0 is a major release that introduces several significant new features:
- OpenAPI part serializing and parsing with full support for OpenAPI validation of input and output in the REST servlet and client APIs.
- Swagger UI.
- New HTTP-Part annotations that are applicable to both the servlet and client APIs.
- Serverless servlet and client unit testing.
- Simplified UI customization.
- Marshalls that combines serializers and parsers into a single API.
juneau-marshall
-
The REST client
@Remoteable annotations and REST server@RemoteMethod annotations which used to be in separate packages in the client and server projects have been combined into a single set of annotations in theorg.apache.juneau.http.annotation
package.
This fixes a long-standing problem where it was easy to mix up using client-side annotations in server-side code, and vis-versa.
Additionally, much work has been done on these annotations to add support for Swagger-style validations and documentation.
These are used with new Swagger schema/documentation annotations to produce schema-based serialization/parsing/validation and auto-generated Swagger documentation:
Additionally, the@Remoteable annotation has been split into the following two annotations:RemoteInterface- Used for remote proxy interfaces served up throughRemoteInterfaceServletor REST"PROXY" methods.
Defaults to"POST" with method signatures as paths.RemoteResource- Used for 3rd-party REST interfaces.
Defaults to"GET" with standardized naming conventions for paths.
-
Support for multi-valued parameters as maps or beans on server-side annotations (it was previously supported on client-side):
,@Query ("*" ) ,@FormData ("*" ) ,@Header ("*" )@Path ("*" ) -
Support for server-side use of
@Request annotation on@RestMethod annotations and newRestRequest.getRequest(RequestBeanMeta)
method. -
Fixed bug where
was not being detected on non-bean POJO classes.@Bean (typeName) - Fixed bug where HTML-Schema was not being rendered correctly.
-
Support for POJO examples:
BeanContext.BEAN_examplesExample
- Fixed bug where parsers could report the wrong line number when an error occurred.
-
A runtime exception is now thrown if you define a
@BeanProperty(name)but forget to add it to your annotation.@Bean (properties) -
and@Html (asXml) replaced with@Html (asPlainText)@Html(format)
. -
HTML serializer will now serializers beans and maps as HTML even when those objects are embedded
within an object with
.@Html (format=XML )
The previous behavior was to serialize it as XML. -
New settings for binary-based serializers and parsers:
OutputStreamSerializer.OSSERIALIZER_binaryFormatInputStreamParser.ISPARSER_binaryFormat
-
Added support for auto-detection of fluent-style setters:
BeanContext.BEAN_fluentSettersBean.fluentSetters()
-
The
SERIALIZER_abridged setting has been replaced withSerializer.SERIALIZER_addRootType SERIALIZER_addRootType -
The
SERIALIZER_addBeanTypeProperties setting has been replaced withSerializer.SERIALIZER_addBeanTypes SERIALIZER_addBeanTypesand is disabled by default. -
Parse exception messages are now clearer and include code snippets of where a parse exception occurred:
org.apache.juneau.parser.ParseException: Expected '[' at beginning of JSON array. At line 80, column 20. While parsing into: currentClass: List<String> currentProperty: required: java.util.List, field=[null], getter=[public java.util.List org.apache.juneau.dto.swagger.SchemaInfo.getRequired()], setter=[public org.apache.juneau.dto.swagger.SchemaInfo org.apache.juneau.dto.swagger.SchemaInfo.setRequired(java.util.Collection)] ---start-- 0075: "name": "body", 0076: "description": "Pet object that needs to be added to the store", 0077: "required": true, 0078: "schema": { 0079: "required": true, 0080: } 0081: } 0082: ], 0083: "responses": { 0084: "405": { 0085: "description": "Invalid input" ---end---
-
New property
Parser.PARSER_debugOutputLinesfor controlling how many input lines are added to the exception message above. -
New property
BeanContext.BEAN_useEnumNamesfor controlling whether enums are serialized using their name or thetoString() method. -
New property
BeanContext.BEAN_examplesfor defining examples of POJOs. -
New
@Example
annotation for defining examples of POJOs.
Used heavily in JSON-Schema support. -
If a bean has both a
getX() andisX() method, thegetX() method takes precedence.
The previous behavior was arbitrary. -
Significant improvements to JSON-Schema serialization support.
- New
@JsonSchemaannotation.
- New
-
Fixed
NullPointerException when serializing beans with a dyna-property (i.e. ) which returns a@Bean ("*" )null value. -
New option for dyna-property (i.e.
) using a method that returns a collection of extra keys.@Bean ("*" )
See new options #4 onBeanProperty.name() -
New formats for the
@Html(format)
annotation:HtmlFormat.HTML_CDC- Format collections as comma-delimited lists.HtmlFormat.HTML_SDC- Format collections as space-delimited lists.
-
Serializers now allow for q-values on the media types they handle.
For example, the accept media type onJsonSerializer.Simple is"application/json+simple,application/json;q=0.9" .
This means the serializer CAN handle requests for"application/json" if no other serializers provide a better match. -
New methods for creating unmodifiable
ObjectMap ObjectMapsandObjectList ObjectLists.ObjectMapObjectMap.isUnmodifiable() isUnmodifable()ObjectMap.unmodifiable() unmodifiable()ObjectMap.modifiable() modifiable()
ObjectListObjectList.isUnmodifiable() isUnmodifable()ObjectList.unmodifiable() unmodifiable()ObjectList.modifiable() modifiable()
-
The
JsonSerializer.Simple class has been moved into the top-levelSimpleJsonSerializerclass. -
RDF serializer subclasses have been moved into top-level classes:
RdfSerializer.Xml ->RdfXmlSerializerRdfSerializer.XmlAbbrev ->RdfXmlAbbrevSerializerRdfSerializer.N3 ->N3SerializerRdfSerializer.NTriple ->NTripleSerializerRdfSerializer.Turtle ->TurtleSerializerRdfParser.Xml ->RdfXmlParserRdfParser.N3 ->N3ParserRdfParser.NTriple ->NTripleParserRdfParser.Turtle ->TurtleParser
-
New API for pairing serializers and parsers for simplified syntax:
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);MarshallCharMarshallHtmlJsonPlainTextSimpleJsonUonUrlEncodingXmlN3NTripleRdfXmlRdfXmlAbbrevTurtle
StreamMarshallJsoMsgPack
- New/updated documentation:
juneau-dto
-
Fixed bug where Swagger
org.apache.juneau.dto.swagger.SchemaInfo.required(Object...)was defined as a boolean instead of a list of strings. -
Boolean attributes are now handled correctly for HTML5.
For example, calling will producenew Select().disabled(true )<select disabled='disabled'>
juneau-rest-server
-
Auto-generated
RestSwagger Swagger UI. -
Simplified
@RestResource(swagger)andRestMethod.swagger() @RestMethod(swagger)annotations. -
Fixed bug in
UriResolver when request path info had special characters. -
Fixed bug where incorrect media type was being set on responses (e.g.
text/html+schema instead oftext/html for schema documents). -
The
RemoteableServlet class has been moved and renamed toRemoteInterfaceServlet. -
RemoteInterfaceServletnow provides a form page for invoking remote interface methods in a browser. -
Newlines were being stripped from
when serialized which could cause script lines to become commented out.@HtmlDoc (script) -
New
@Response
annotation that can be applied to throwables thrown from REST methods and POJOs returned by REST methods to specify non-200 status return codes and descriptions in Swagger documentation. - Swagger fields added to the following annotations:
-
The
@PathRemainder annotation has been removed entirely.
Use to access the path remainder which includes all the new OpenAPI parsing support.@Path ("/*" ) -
"Helper" classes (i.e. reusable beans that can be returned by REST methods) have been moved to the following package with some new additions:
helperBeanDescriptionChildResourceDescriptionsReaderResourceReaderResourceBuilderSeeOtherRootResourceDescriptionStreamResourceStreamResourceBuilder
-
Predefined HTTP responses.
responseAcceptedAlreadyReportedContinueCreatedEarlyHintsFoundIMUsedMovedPermanentlyMultipleChoicesMultiStatusNoContentNonAuthoritiveInformationNotModifiedOkPartialContentPermanentRedirectProcessingResetContentSeeOtherSwitchingProtocolsTemporaryRedirectUseProxy
-
Predefined HTTP error throwables.
When added to REST Java methods, reflected in generated Swagger documentation.exceptionBadRequestConflictExpectationFailedFailedDependencyForbiddenGoneHttpVersionNotSupportedInsufficientStorageInternalServerErrorLengthRequiredLockedLoopDetectedMethodNotAllowedMisdirectedRequestNetworkAuthenticationRequiredNotAcceptableNotExtendedNotFoundNotImplementedPayloadTooLargePreconditionFailedPreconditionRequiredRangeNotSatisfiableRequestHeaderFieldsTooLargeServiceUnavailableTooManyRequestsUnauthorizedUnavailableForLegalReasonsUnprocessableEntityUnsupportedMediaTypeUpgradeRequiredUriTooLongVariantAlsoNegotiates
-
The
HtmlDoc.nav() @HtmlDoc(nav)andHtmlDoc.navlinks() @HtmlDoc(navlinks)can now both be used on the same annotation.
The contents ofnav() are free-form HTML that gets rendered immediately after the navigation links. -
The following new parameter types can be used on REST methods:
ReaderParser
- The reader parser matching the request content type.InputStreamParser
- The input stream parser matching the request content type.
-
The
$F variable can now be used as a initialization time variable.
For example, theAtomFeedResource example pulls a bean example from a file on the classpath:@RestResource ( path="/atom" , title="Sample ATOM feed resource" , properties={@Property (name=BEAN_examples , value="{'org.apache.juneau.dto.atom.Feed': $F{AtomFeedResource_example.json}}" ) }, ... )
It should be noted that you cannot use the$F variable to retrieve localized versions of files (since you're not within the scope of a client request. -
The
RestResource.nowrap()annotation has been changed to a string with a default value of"true" .
Having it as a string allows us to differentiate between a set and unset value so that it can be overridden in subclasses. -
The
Path.name()
annotation parameter is now required. -
New class for mock unit testing of REST resources:
MockRest
-
annotation has been removed and replaced with the following classes:@RestMethod (inherit)InheritNone
These can be used in the following locations:RestResource.serializers()RestResource.parsers()RestResource.beanFilters()RestResource.pojoSwaps()RestMethod.serializers()RestMethod.parsers()RestMethod.beanFilters()RestMethod.pojoSwaps()
One advantage is that you now have control over the precedence of serializers and parsers by where you insert theInherit class. -
RequestPathMatch class has been renamed toRequestPath. -
@Request
objects can now be used as parameters in@RestMethod methods.
Includes new methods onRestRequest
: -
New methods added to
MenuItemWidget
to allow population of menu item content using Javascript and Ajax calls:MenuItemWidget
getBeforeShowScript(RestRequest)getAfterShowScript(RestRequest)
-
New methods added to
Widget
to allow retrieving classpath resources with embedded SVL variables:Widget
loadHtmlWithVars(RestRequest,String)loadScriptWithVars(RestRequest,String)loadStyleWithVars(RestRequest,String)
-
New/updated documentation:
juneau-rest-server.UnitTesting -
The behavior of the default values for
RestMethod.name()andRestMethod.path()have changed.
If not specified, the values are inferred from the Java method name.
See Also:RestMethod -
RedirectToServletRoot class has been renamed toSeeOtherRoot. -
New REST context settings:
RestContext
RestContext.REST_uriAuthority REST_uriAuthorityRestContext.REST_uriContext REST_uriContextRestContext.REST_uriRelativity REST_uriRelativityRestContext.REST_uriResolution REST_uriResolution
-
New convenience annotations for specifying default
Accept andContent-Type headers:RestResourcedefaultAcceptdefaultContentType
RestMethodRestMethod.defaultAccept defaultAcceptRestMethod.defaultContentType defaultContentType
juneau-rest-client
- Remote Resource interfaces support OpenAPI annotations.
-
Made improvements to the builder API for defining SSL support.
New methods added:RestClient.Builder
org.apache.juneau.rest.client.RestClient.Builder.sslProtocols(String...) sslProtocols(String...)org.apache.juneau.rest.client.RestClient.Builder.cipherSuites(String...) cipherSuites(String...)Builder.hostnameVerifier(HostnameVerifier) hostnameVerifier(HostnameVerifier)org.apache.juneau.rest.client.RestClient.Builder.keyManagers(KeyManager...) keyManagers(KeyManager...)org.apache.juneau.rest.client.RestClient.Builder.trustManagers(TrustManager...)Builder.secureRandom(SecureRandom)Builder.httpClientConnectionManager(HttpClientConnectionManager)
-
Clients no longer have JSON defined as the default serializer and parser.
Instead, the clients can now be used with no serializer/parser if you're working with InputStreams/Readers or POJOs that can be converted to Strings and converted from Strings/InputStreams/Readers. - Methods added to client builder to make it easy to define the transport language:
-
New method added for allowing serverless client testing against REST interfaces.
RestClient.Builder
mockHttpConnection(MockHttpConnection)
-
Removed the deprecated
RestCall.execute() method.
UseRestCall.run(). -
RestCall.input(Object) method renamed toRestCall.body(Object)to match OpenAPI terminology. -
Made constructors on
RestClient andRestClient.Builder protected so that they can be subclassed. -
The
RestClient.getRemoteableProxy() methods have been split into separate methods for Remote Interfaces and Remote Resources:RestClient
RestClient.getRemoteInterface(Class)RestClient.getRemoteInterface(Class,Object)RestClient.getRemoteInterface(Class,Object,Serializer,Parser)RestClient.getRemoteResource(Class)RestClient.getRemoteResource(Class,Object)RestClient.getRemoteResource(Class,Object,Serializer,Parser)
juneau-rest-microservice
-
The look-and-feel of an application is now controlled through the external configuration file and access to
CSS stylesheets in the working directory in a new folder called
files :
The default configuration is this:#======================================================================================================================= # REST settings #======================================================================================================================= [REST] staticFiles =htdocs:files/htdocs # Stylesheet to use for HTML views. theme =servlet:/htdocs/themes/devops.css headerIcon =servlet:/htdocs/images/juneau.png headerLink =http://juneau.apache.org footerIcon =servlet:/htdocs/images/asf.png footerLink =http://www.apache.org icon =$C{REST/headerIcon} header =<a href='$U{$C{REST/headerLink}}'><img src='$U{$C{REST/headerIcon}}' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/></a> footer =<a href='$U{$C{REST/footerLink}}'><img style='float:right;padding-right:20px;height:32px' src='$U{$C{REST/footerIcon}}'>
Note that static files can now be served up from thefiles directory in the working directory, and you have access to modify the CSS theme files.
TheSwaggerUI.css file controls the look-and-feel of the Swagger UI, so you can make modification there as well.
TheBasicRestConfig interface (which defines the default settings for theBasicRestServlet class) now looks like this...@RestResource ( ... htmldoc=@HtmlDoc ( header={"<h1>$R{resourceTitle}</h1>" ,"<h2>$R{methodSummary,resourceDescription}</h2>" ,"$C{REST/header}" }, navlinks={"up: request:/.." }, stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}" , head={"<link rel='icon' href='$U{$C{REST/icon}}'/>" }, footer="$C{REST/footer}" ),// These are static files that are served up by the servlet under the specified sub-paths. // For example, "/servletPath/htdocs/javadoc.css" resolves to the file "[servlet-package]/htdocs/javadoc.css" staticFiles={"$C{REST/staticFiles}" } )public interface BasicRestConfig {}
ThePoweredByApache widget which used to serve as a page footer has been eliminated.
If you're testing out changes in the theme stylesheets, you may want to set the following system property that prevents caching of those files so that you don't need to restart the microservice each time a change is made:#======================================================================================================================= # System properties #======================================================================================================================= [SystemProperties] # Disable classpath resource caching. # Useful if you're attached using a debugger and you're modifying classpath resources while running. RestContext.useClasspathResourceCaching.b =false - Upgraded to Jetty 9.4.12.
7.2.1 (Oct 23, 2018)
This release contains mostly bug fixes. Code changes have been made to preserve binary backwards compatibility with 7.1.0.
juneau-marshall
-
The
@JsonSchemaannotation has been merged with the@Schemaannotation. -
Annotations typically used on bean properties (getters/setters/public fields) can now be used on private fields.
This is inline with behavior on JPA-annotated beans.
These include:
@Swap ,@Html ,@Xml ,@BeanProperty .
juneau-rest-server
-
Method-level annotations (e.g.
@RestMethod ) and parameter-level annotations (e.g.@Query ) are now inheritable from parent classes and interfaces.
This allows you to defineRestcDualPurposeInterfaces. -
The
ReaderResource andStreamResource classes have been moved to theorg.apache.juneau.http package injuneau-marshall . This allows them to be used as return types in remote REST interfaces.
A newResolvingReaderResourceclass has been added that includes the variable-resolving support since this relies on thejuneau-svl package. -
The
RemoteInterfaceServlet class has been renamed toRrpcServlet
. -
has been changed to@RestMethod (name="PROXY" ) .@RestMethod (name="RRPC" )
juneau-rest-client
-
The
RestClient.getRemoteInterface() method has been renamed toRestClient.getRrpcInterface(Class)
. -
Fixed a bug where
values containing '/' characters were erroneously being encoded.@RemoteMethod (path)
7.2.2 (Nov 7, 2018)
This release contains minor bug fixes and general improvements to the PetStore sample application.
8.0.0 (Jan 01, 2019)
This release cleans up deprecated APIs from the 7.2.0 release and makes significant modifications to the Microservice APIs.
The project structures of the REST, Microservice, and Examples have been modified to fit new Spring Boot integration support. The structure is now as follows:
juneau-rest juneau-rest-client juneau-rest-server juneau-rest-server-jaxrs juneau-rest-server-rdf juneau-rest-server-springboot - New Spring Boot integration support classes.
juneau-microservice juneau-microservice-core - New. Contains baseMicroservice
class.juneau-microservice-jetty - New. Contains newJettyMicroservice
class.juneau-my-jetty-microservice - New. Template starter project for Jetty-based microservices.juneau-my-springboot-microservice - New. Template starter project for Spring-Boot-based microservices.
juneau-examples juneau-core juneau-microservice-rest - Now contains only servlet example classes. No Jetty configuration.juneau-microservice-rest-jetty - Servlet examples deployed using Jetty.juneau-microservice-rest-springboot - Servlet examples deployed using Spring Boot.
juneau-svl
- New SVL variables:
juneau-config
- New methods for setting a system default configuration:
-
New classpath store.
ConfigClasspathStore
- New API method:
juneau-rest-server
-
New methods on
RestServlet:RestServlet.setRestResourceResolver(RestResourceResolver) setRestResourceResolver(RestResourceResolver)RestServlet.getPath() getPath()
-
The registered resource resolver is now used to instantiate objects of classes defined via
@RestResource.
This allows for any of those instance to be injectable beans.
juneau-rest-server-springboot
- New project containing classes for making it easy to use Juneau with Spring Boot.
juneau-microservice-core
-
New project that consist of just the core
Microservice
class and console support. -
The Microservice API has been revamped to use a builder-based approach to creating microservices.
The new API is considerably more flexible in design and allows for the configuration and external files to be located in either the home directory or inside the jar classpath. -
If the microservice cannot find a config file in the home directory with the same name as the jar and
the
"juneau.configFile" system property is not found, it will try to find any file in the home directory that ends with".cfg" . -
New
MicroserviceListener
API for defining lifecycle event listeners. -
New
ConfigCommand
class for performing config queries and updates through the microservice console.
juneau-microservice-jetty
-
New
JettyMicroservice
class replaces the previousRestMicroservice .
juneau-my-jetty-microservice
- New template starter project for Jetty-based microservices.
juneau-my-springboot-microservice
- Template starter project for Spring-Boot-based microservices.
-
app.json andProcfile files for deploying examples into Heroku.
Includes instructions.
8.1.0 (Aug 21, 2019)
8.1.0 introduces some significant new features including:
-
ConfigurableAnnotations Configurable Annotations -
DefaultPojoSwaps Default PojoSwaps -
ConfigImports Config Imports -
BasicRest BasicRest,BasicRestGroup BasicRestGroupclasses -
RestPathAnnotation Path variables on resource paths -
RestmRequestAttributes Request Attributes API -
RestRoleGuards Role Guards -
RestLoggingAndDebugging Improved REST logging/debugging - New MockRest API
juneau-marshall
- New utility class for diffing beans:
- New annotation for defining bean property names:
-
New serializer properties:
WriterSerializer
WriterSerializer.WSERIALIZER_fileCharset WSERIALIZER_fileCharsetWriterSerializer.WSERIALIZER_streamCharset WSERIALIZER_streamCharset
-
The following POJO methods can be used to convert a POJO to/from a Map before serialization and after parsing.
It's a convenient way of defining a POJO transform. - Can be any type of map with string keys and object vals.public Map toMap()public ObjectMap toMap() - Can be any type of map with string keys and object vals.public Map toMap(BeanSession bs)public ObjectMap toMap(BeanSession bs) - Can be any type of map with string keys and object vals.public static T fromMap(Map m)public static T fromMap(ObjectMap m) - Can be any type of map with string keys and object vals.public static T fromMap(BeanSession bs, Map m)public static T fromMap(BeanSession bs, ObjectMap m)
-
New convenience debugging methods on Marshall API:
Marshallorg.apache.juneau.marshall.Marshall.format(String,Object...) format(String,Object...)-MessageFormat -style formatter.org.apache.juneau.marshall.Marshall.out(String,Object...) out(String,Object...)- PrintsMessageFormat -style messages to STDOUT.org.apache.juneau.marshall.Marshall.err(String,Object...) err(String,Object...)- PrintsMessageFormat -style messages to STDERR.
-
Serializer and parser APIs now throw
IOExceptions in addition toSerializeException andParseException to make it easier to determine if problems are stream based or syntax based. -
New Java 8 date-time transforms:
TemporalSwap- For all Java 8 temporal types (e.g.ZonedDateTime
)TemporalDateSwap- ForDate
TemporalCalendarSwap- ForCalendar
-
All serializers and parsers now have built-in default swaps for common class types:
Enumeration
Iterator
Locale
Calendar
- ISO offset date-time.Date
- Local date-timeInstant
- ISO instant.ZonedDateTime
- ISO offset date-time.LocalDate
- ISO local date.LocalDateTime
- ISO local date-time.LocalTime
- ISO local time.OffsetDateTime
- ISO offset date-time.OffsetTime
- ISO offset time.Year
- ISO year.YearMonth
- ISO year-month.Temporal
- ISO instant.TimeZone
XMLGregorianCalendar
ZoneId
juneau-config
-
Support for import statements:
. Import values from another configuration: <ParentConfig1> [Foo] bar =baz -
The
ConfigFileStorenow automatically resolves file extensions.
New configuration property for specifying search paths for file extensions:ConfigFileStore.FILESTORE_extensions
-
Fixed a bug where instances of
ConfigMemoryStoreended up resolving to the same object. -
Uses
"application.properties" file as a system default if present.
Useful when being used in a Spring Boot application. -
New
Config.setSystemProperties()
method for quickly moving configuration settings into the system properties. -
Entries in the system config are automatically set as system properties.
This mean you can set any of the various serializer and parser settings (e.g."JsonSerializer.simpleMode.b" ) in the default configuration area orapplication.properties .
juneau-rest-server
-
New annotations that can be applied to REST classes and methods to configure serializers and parsers.
// Old way using generic properties. @RestResource ( path="/atom" , title="Sample ATOM feed resource" , properties={@Property (name=WSERIALIZER_quoteChar , value="'" ),@Property (name=RDF_rdfxml_tab , value="5" ),@Property (nameRDF_addRootProperty , value="true" ),@Property (name=BEAN_examples , value="{'org.apache.juneau.dto.atom.Feed': $F{AtomFeedResource_example.json}}" ) } ... )public class AtomFeedResourceextends BasicRestServletJena { ... }// New way using specific annotations. @RestResource ( path="/atom" , title="Sample ATOM feed resource" ... )@SerializerConfig (quoteChar="'" )@RdfConfig (rdfxml_tab="5" , addRootProperty="true" )@BeanConfig (examples="Feed: $F{AtomFeedResource_example.json}" )public class AtomFeedResourceextends BasicRestServletJena { ... }Config annotations are provided for all serializers and parsers:
-
New support for using Servlet request attributes.
RequestAttributesRestContext
REST_attrs
RestContext.Builder
attrs(String...)attr(String,Object)
RestMethodContextRestMethodContext.RESTMETHOD_attrs RESTMETHOD_attrs
RestRequest
RestResponse
getAttributes()
RestResponse.attr(String,Object) attr(String,Object)
Attr
RestMethod.attrs()RestResource.attrs()
This deprecates the following APIs:RequestPropertiesRestMethodPropertiesRestRequest
RestRequest.getProperties() getProperties()RestRequest.prop(String,Object) prop(String,Object)
-
Added the following classes that provide the same support as the servlet classes but doesn't extend from
HttpServlet .
This fixes an issue where instances ofBasicRestServletare registered as top-level servlets even though you don't want them to be.BasicRest- Non-servlet equivalent toBasicRestServletBasicRestGroup- Non-servlet equivalent toBasicRestServletGroupBasicRestJena- Non-servlet equivalent toBasicRestServletJenaBasicRestJenaGroup- Non-servlet equivalent toBasicRestServletJenaGroup
-
HTML widgets now have access to the
RestResponse object if they need access to the output bean. -
New annotations for simplified role-based guards on classes and methods:
RestResourceroleGuard()rolesDeclared()
RestMethodRestMethod.roleGuard roleGuard()RestMethod.rolesDeclared rolesDeclared()
-
New annotations for fine-tuned handling of http-methods/http-headers as query parameters and others:
RestResourceallowedHeaderParams()allowedMethodHeaders()allowedMethodParams()
-
The
@RestResource(path)annotation can now use variables:@RestResource ( path="/myResource/{foo}/{bar}" )public class MyResourceextends BasicRestServlet {...} - New methods:
-
New interface method for catching arbitrary thrown objects and converting them to other throwables.
RestCallHandlerRestCallHandler.convertThrowable(Throwable) convertThrowable(Throwable)
BasicRestCallHandlerconvertThrowable(Throwable)
-
Support for fine-tuned logging of HTTP requests and responses.
@RestResource ( debug="per-request" , logging=@Logging ( level="info" , rules={@LoggingRule (codes"400-499" , level="warning" , req="short" , res="short" )@LoggingRule (codes"500-" , level="severe" , req="long" , res="long" ) } ) )public class MyRest {@RestMethod ( method="POST" , path="foo" logging=@Logging ( level="info" , rules={@LoggingRule (exceptions"NotFound*" , level="info" )@LoggingRule (codes"200" , disabled="true" ) } ) )public String myMethod()throws Exception {...}RestLoggingAndDebuggingfor details. - Fixed a bug where the HTTP response was returning 405 (method not found) but should really be 404 (not found) when no path patterns match on any of the Java methods.
juneau-rest-client
- PATCH support added.
8.1.1 (Sept 20, 2019)
Juneau 8.1.1 is a minor release but introduces some new features/modifications.
juneau-marshall
-
Support for serializing/parsing
Optional
objects and bean properties. -
Fixed a bug in the parsers where the generic subtype of a complex bean property type involving both collections and arrays
was not being found. (e.g.
List<Long>[] ) -
New packages of HTTP response/exceptions beans from
juneau-rest-server bundle and REST proxy annotations fromjuneau-rest-client so that they can be used when building REST proxy interfaces while only pulling in the marshall bundle. These replace theorg.apache.juneau.rest.response ,org.apache.juneau.rest.exception , andorg.apache.juneau.rest.client.remote packages. -
Defaulting SVL variables now won't ignore additional parameters.
"$S{Foo,bar,baz}" used to default to"bar" but now will default to"bar,baz" . -
Ranking support has been added to all
@XConfig annotations. This allows you to override the normal precedence of application of config annotations on class and method hierarchies.
juneau-rest-server
-
Support for
Optional
on method parameters annotated withHeader
,FormData
,Query
,Path
. -
Fixed issue where
org.apache.juneau.rest.annotation.RestMethod.debug() RestMethod.debug()annotation wasn't resulting in the HTTP request being logged. -
RestExceptionhas been deprecated and replaced byHttpException. -
Resolved issue where parameterized types on
@RestMethod -annotated method parameters of Spring beans. This is due to Spring relying on CGLIB for runtime-recompilation of beans that results in loss of parameterized-type information.
In the following example, thebeans parameter would resolve as an unparameterizedList which would typically result in getting aList<ObjectMap> and subsequent ClassCastExceptions .@RestMethod public void doFoo(@Body List<MyBean> beans) {...}- New methods on
BasicRestto provide feature-parity withRestServlet:BasicRestBasicRest.getContext() getContext()BasicRest.getRequest() getRequest()BasicRest.getResponse() getResponse()BasicRest.log(String) log(String)BasicRest.log(String,Throwable) log(String,Throwable)BasicRest.log(Level,String,Object[]) log(Level,String,Object[])BasicRest.logObjects(Level,String,Object[])BasicRest.log(Level,Throwable,String,Object[]) log(Level,Throwable,String,Object[])
- The
@RestResource(staticFiles)annotation now supports absolute path locations and multiple mappings:// Resolves static files in root package "htdocs" or working directory "htdocs", and then relative package "htdocs". @RestResource (staticFiles="htdocs:/htdocsfolder,htdocs:htdocs.package" )- Fixed a bug in
@RestResource(staticFiles)where the order of lookup between parent and child resources was wrong. - New methods on
juneau-rest-client
-
Removed the dependency on the
juneau-rest-server module. Allows the client API to be used without pulling in all the javax.servlet and server dependencies.
juneau-examples
- The PetStore application has been moved to a separate Git repository.
8.1.2 (Dec 01, 2019)
Juneau 8.1.2 is a moderate release.
juneau-marshall
-
Support for read-only and write-only properties.
Bean.bpro()Bean.bpwo()Beanp.ro()
Beanp.wo()
BEAN_bproBEAN_bpwoBuilder.bpro(Map)Builder.bpwo(Map)
-
New convenience methods:
ObjectMap.parse(CharSequence)ObjectList.parse(CharSequence)
-
CharMarshallandStreamMarshallnow have public constructors. -
@Beanp
replacesBeanProperty @BeanProperty. -
@Beanc
replacesBeanConstructor @BeanConstructor. -
@Remote
replacesRemoteResource @RemoteResource. -
Shortened names for
@Bean(dictionary)
andBeanContext.Builder.beanDictionary(Class...)
.
juneau-rest-server
-
@Rest
replacesRestResource @RestResourcewith shorter syntax. -
New method
RestResponse.setHeaderSafe(String,String)to strip invalid characters from header values. - Fixed issues related to invalid characters being set on HTTP header values.
juneau-rest-client
-
RestClient
is now extendible. The constructor has been made public and simplified to:org.apache.juneau.rest.client.RestClient.RestClient(RestClient.Builder). -
Duplicate methods between
RestClient.Builder
andHttpClientBuilder
have been made deprecated on the former. This eliminates the need to try to keep the two builder classes in sync.
8.1.3 (Jan 20, 2020)
Juneau 8.1.3 is a moderate release.
juneau-marshall
-
Dynamically applied annotations:
DynamicallyAppliedAnnotations -
Better representation of nulls for XML and HTML content properties.
Old:"<myBean><null></myBean>"
New:"<myBean nil='true'></myBean>" -
Configurable properties such as
Context.CONTEXT_debugcan now be set globally by either system properties or environment variables.
ForCONTEXT_debug you can use either the system property"Context.debug" or environment variables"CONTEXT_DEBUG" . -
Fixed an initialization time race condition that can cause initial parse errors when concurrently parsing into
array types like so:
JsonParser.
DEFAULT .parse(input, MyBean[].class );
juneau-rest-server
-
Fixed bug in
BasicRestCallHandlerwhere if you have the following REST methods...@RestMethod (name="GET" , path="/foo" )@RestMethod (name="*" , path="/bar" ) -
Fixed an issue involving using Juneau REST with Spring Security. When Spring Security cannot authenticate a
request, it sets the URL on the request to
"/error" with a 401 status. When Juneau then processes this request, it cannot find that mapping and changes the status to 404 which messes with HTTP clients.
Solution was to add a default no-op error method to theBasicRestConfig(and implementers):@RestMethod (name="*" , path="/error" )public void error() {} -
Fixed a bug where
wouldn't log requests if a@RestResource (debug="true" )@RestMethod -annotated method was not matched. -
Renamed the following annotations:
@Rest(attrs) -->Rest.reqAttrs() @Rest(reqAttrs)@Rest(defaultRequestHeaders) -->Rest.reqHeaders() @Rest(reqHeaders)@Rest(defaultResponseHeaders) -->Rest.resHeaders() @Rest(resHeaders)@RestMethod(attrs) -->RestMethod.reqAttrs() @RestMethod(reqAttrs)@RestMethod(defaultRequestHeaders) -->RestMethod.reqHeaders() @RestMethod(reqHeaders)
-
New auto-generated REST method execution statistics:
RestExecutionStatistics
juneau-rest-client
-
Several convenience methods defined in
RestClient.Builder
that were deprecated in 8.1.2 have been undeprecated in this release due to user feedback.
juneau-doc
-
New auto-generated glossary of all configurable properties:
GlossaryConfigurableProperties
8.2.0 (Oct 14, 2020)
Juneau 8.2.0 is a major release.
The most significant change is the addition of an entirely new RestClient API build from scratch
with near 100% unit test coverage.
The new API is located in the
juneau-marshall
-
@Bean
and@BeanIgnore
annotations can alternately occur in parent class hierarchy. The first one found dictates whether a class is ignored as a bean or not. -
Applying the
@Bean
annotation on a class will now force non-public classes to be interpreted as beans. For example, applying@Bean
to aprivate class will force it to be treated as a bean.
Also, if a public bean constructor cannot be found, the default constructor will be used regardless of it's visibility if the@Bean
annotation is on the class. -
The
@Beanc annotation can now be recognized and used on non-public constructors. -
Annotations are now aggregated across the entire class hierarchy instead of simply being overridden.
The following is an example.// Parent class with properties a,b,c @Bean (bpi="a,b,c" )public class MyClass {public int a, b, c, d; }// New behavior: Child class with properties a,c because @Beans are aggregated. // Old behavior: Child class with properties a,c,d because @Bean is overridden. @Bean (bpx="b" )public class MyClass {public int a, b, c, d; } -
Include/exclude/read-only/write-only properties defined on the bean context now override those defined on
annotations of the class itself. For example, the following methods override the
@Bean
annotations on classes:BeanContext.Builder
Builder.bpi(Class,String) bpi(Class,String)Builder.bpi(String,String) bpi(String,String)Builder.bpx(Class,String) bpx(Class,String)Builder.bpx(String,String) bpx(String,String)Builder.bpro(Class,String) bpro(Class,String)Builder.bpro(String,String) bpro(String,String)Builder.bpwo(Class,String) bpwo(Class,StringBuilder.bpwo(String,String) bpwo(String,String
-
Config annotations now override class-level annotations.
For example, only the 'a' and 'b' properties get serialized on the bean below:// Parent class with properties a,b,c @Bean (bpi="a,b,c" )public class MyClass {public int a, b, c, d; }@RestMethod @BeanConfig (beanApply={@Bean (on="MyClass" ,bpi="a,b" )}public MyClass getMyClass() {...} -
The following concrete annotation implementation classes are now provided that can be used with the
BeanContext.Builder.annotations(Annotation...)
method:BeanAnnotation
implements Bean
BeancAnnotation
implements Beanc
BeanIgnoreAnnotation
implements BeanIgnore
BeanpAnnotation
implements Beanp
ExampleAnnotation
implements Example
NamePropertyAnnotation
implements NameProperty
ParentPropertyAnnotation
implements ParentProperty
SwapAnnotation
implements Swap
UriAnnotation
implements URICsvAnnotation
implements Csv
HtmlAnnotation
implements Html
JsoAnnotationimplements JsoJsonAnnotation
implements Json
SchemaAnnotationimplements SchemaMsgPackAnnotation
implements MsgPack
OpenApiAnnotation
implements OpenApi
PlainTextAnnotation
implements PlainText
SoapXmlAnnotation
implements SoapXml
UonAnnotation
implements Uon
UrlEncodingAnnotation
implements UrlEncoding
XmlAnnotation
implements Xml
Example:@Bean (bpi="street,city" )// Will be overridden public class AddressBean {...}Bean ba =new BeanAnnotation("AddressBean" ).bpi("street,city,state" ); WriterSerializer ws = JsonSerializer.create ().annotations(ba).build(); String json = ws.toString(addressBean);// Will print street,city,state -
Bean maps now have the concept of "hidden" properties (properties that aren't serialized but otherwise accessible).
For example, theHtml.link()
can now reference hidden properties:@Bean (bpi="a" )// Will be overridden public class MyBean {@Html (link="servlet:/{b}" )public Stringa ;public Stringb ;// Not serialized but referenced in link on a. }
The general rule for theBeanMap
class is thatget() ,put() , andcontainsKey() will work against hidden properties butkeySet() andentrySet() will skip them. -
Several bug fixes in the
HtmlSerializer
andHtmlParser
classes around the handling of collections and arrays of beans with annotations.@Bean (typeName) -
New swaps auto-added to all serializers/parsers:
MatchResultSwapStackTraceElementSwap
-
Html.noTableHeaders()
now can be applied to collections of beans. -
New
HtmlDocConfig.asideFloat()
setting so that you can position the contents of the aside section on the page. -
Various minor fixes surrounding HTML serialization.
- Collections of beans that were supposed to be serialized as tables were being serialized as lists.
- Collections of beans with
were not being serialized in the correct column order.@Bean (bpi)
-
Fixed a bug where a copy constructor can erroneously be recognized as a builder constructor if the class also
has a static
create method. Net effect was that the copy constructor would needlessly be called during parsing. -
New
org.apache.juneau.collections
package containing various convenience fluent-style collection classes:AListASetASortedSetAMapASortedMapOMapOList
-
ObjectMapis being deprecated and replaced withOMap. -
ObjectListis being deprecated and replaced withOList. -
All classes in the
org.apache.juneau.http.response
andexceptionnow haveheader(String,Object) methods for adding response headers.// Method that performs a BASIC Auth handshake. @RestMethod public Ok checkBasicAuth(@Header ("Authorization" ) String auth)throws Unauthorized {if (auth ==null )throw new Unauthorized().header("WWW-Authenticate" ,"BASIC realm=\"foo\"" );return Ok.OK ; } -
New annotations for multi-part support:
Header.multi()Query.multi()FormData.multi()
-
BeanTraverseContext.BEANTRAVERSE_ignoreRecursionssetting no longer requiresBeanTraverseContext.BEANTRAVERSE_detectRecursionsto be enabled. -
Fixed bug in JSON/UON/URL-Encoding serializers where indentation was not correct of first line when
BeanTraverseContext.BEANTRAVERSE_initialDepthused. - Fixed bug in JSON/UON/URL-Encoding serializers where properties past the max depth were being serialized as null instead of being treated as null and not being serialized at all.
-
Fixed bug in HTML serializer where tables of maps were not sorted if
SERIALIZER_sortMaps was specified. -
SERIALIZER_trimNullProperties has been replaced withSerializer.SERIALIZER_keepNullProperties SERIALIZER_keepNullProperties. -
Improvements to OpenAPI serializer and parser:
- Collection format can now be specified on OBJECTs to allow key/value pairs to be delimited with the same support as ARRAYs.
- New
OpenApiCommon.OAPI_format OAPI_formatandOpenApiCommon.OAPI_collectionFormat OAPI_collectionFormatproperties.
-
Convenience methods added to
HttpPartSchema
and related classes to simplify schema definitions:import static org.apache.juneau.httppart.HttpPartSchema.*;// Old HttpPartSchema s =schema ("object" ) .property("f01" ,schema ("array" ).collectionFormat("pipes" ).items(schema ("string" ))) .property("f02" ,schema ("array" ).collectionFormat("pipes" ).items(schema ("string" ,"byte" ))) .property("f03" ,schema ("array" ).collectionFormat("pipes" ).items(schema ("string" ,"date-time" ))) .build();// New HttpPartSchema s =tObject () .p("f01" ,tArray (tString() )) .p("f02" ,tArray (tByte() )) .p("f03" ,tArray (tDateTime() )) .build(); - Fixes where the bean method/constructor visibility wasn't being used when finding swap methods and constructors.
- HTML-Schema support is being deprecated due to low-use and difficulty in maintaining. It will be removed in 9.0.
-
JuneauLogger class is being deprecated. Improvements in logging in Java 8 make it obsolete. -
Bean filters can now be specified programmatically through a builder API.
// Create a JSON serializer that only includes specified fields on a specific class. WriterSerializer s = JsonSerializer .create () .beanFilters(BeanFilter.create (MyBean.class ).bpi("foo,bar,baz" ).build()) .build(); -
BeanContext.REST_pojoSwaps replaced withBeanContext.BEAN_swaps(and builder methods as well). -
New Bean Property Interceptor API for intercepting calls to bean getters/setters.
BeanInterceptorBean(interceptor)
BeanContext.Builder.beanInterceptor(Class,Class)
-
Fluent setters that follow the
withX convention are now automatically detected by all parsers.// A bean with a fluent setter. public class MyBean {public MyBean withFoo(Stringfoo ) {this .foo =foo ;return this ; } }@BeanProperty annotation to identify these setters.
juneau-rest-server
-
New
RestContext.REST_context REST_context/Rest.context() @Rest(context)setting to allow you to extend theRestContext
class. -
Rest
-annotated classes can now implement the following interfaces directly instead of having to define secondary classes and hook them up through annotations:RestCallHandler- Normally defined throughorg.apache.juneau.rest.annotation.Rest.callHandler() @Rest.callHandler().RestInfoProvider- Normally defined throughorg.apache.juneau.rest.annotation.Rest.infoProvider() @Rest.infoProvider().RestCallLogger- Normally defined throughorg.apache.juneau.rest.annotation.Rest.callLogger() @Rest.callLogger().ClasspathResourceFinder- Normally defined throughorg.apache.juneau.rest.annotation.Rest.classpathResourceFinder() @Rest.classpathResourceFinder().
The methods added forRestInfoProviderare:RestServletgetSwagger(RestRequest)getSiteName(RestRequest)getTitle(RestRequest)getDescription(RestRequest)getMethodSummary(Method,RestRequest)getMethodDescription(Method,RestRequest)
BasicRestgetSwagger(RestRequest)getSiteName(RestRequest)getTitle(RestRequest)getDescription(RestRequest)getMethodSummary(Method,RestRequest)getMethodDescription(Method,RestRequest)
The methods added forRestCallLoggerare:RestServletRestServlet.log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse) log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse)
BasicRestBasicRest.log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse) log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse)
The methods added forClasspathResourceFinderare:RestServletRestServlet.findResource(Class,String,Locale) findResource(Class,String,Locale)
BasicRestBasicRest.findResource(Class,String,Locale) findResource(Class,String,Locale)
The methods added forRestResourceResolverare:RestServletorg.apache.juneau.rest.RestServlet.resolve(Object,Class,RestContext.Builder,Object...) resolve(Object,Class<T>,RestContext.Builder,Object...)
BasicRestorg.apache.juneau.rest.BasicRest.resolve(Object,Class,RestContext.Builder,Object...) resolve(Object,Class<T>,RestContext.Builder,Object...)
-
Added the following convenience hook methods on the
RestServletandBasicRestclasses:RestServletorg.apache.juneau.rest.RestServlet.onInit(RestContext.Builder) onInit(RestContext.Builder)RestServlet.onPostInit(RestContext) onPostInit(RestContext)RestServlet.onPostInitChildFirst(RestContext) onPostInitChildFirst(RestContext)RestServlet.onDestroy(RestContext) onDestroy(RestContext)RestServlet.onStartCall(HttpServletRequest,HttpServletResponse) onStartCall(HttpServletRequest,HttpServletResponse)RestServlet.onPreCall(RestRequest,RestResponse) onPreCall(RestRequest,RestResponse)RestServlet.onPostCall(RestRequest,RestResponse) onPostCall(RestRequest,RestResponse)RestServlet.onEndCall(HttpServletRequest,HttpServletResponse) onEndCall(HttpServletRequest,HttpServletResponse)
BasicRestorg.apache.juneau.rest.BasicRest.onInit(RestContext.Builder) onInit(RestContext.Builder)BasicRest.onPostInit(RestContext) onPostInit(RestContext)BasicRest.onPostInitChildFirst(RestContext) onPostInitChildFirst(RestContext)BasicRest.onDestroy(RestContext) onDestroy(RestContext)BasicRest.onStartCall(HttpServletRequest,HttpServletResponse) onStartCall(HttpServletRequest,HttpServletResponse)BasicRest.onPreCall(RestRequest,RestResponse) onPreCall(RestRequest,RestResponse)BasicRest.onPostCall(RestRequest,RestResponse) onPostCall(RestRequest,RestResponse)BasicRest.onEndCall(HttpServletRequest,HttpServletResponse) onEndCall(HttpServletRequest,HttpServletResponse)
-
New
@Rest(debugOn)
annotation for turning on debug mode using class/method identifiers:Example:
// Turn on debug per-request on the class and always on the doX() method .@Rest ( debugOn="MyResource=per-request,Mysource.doX=true" // Typically defined via system or env property . )public class MyResource {@RestMethod public void String doX() { ... } -
BasicRestConfighas been broken up intoBasicRestConfigandBasicRestMethodsso that you're not forced to implement methods such asgetOptions() andgetStats() if you're implementing the interface to configure your REST class. -
Any of the following classes can now be instantiated with
methods:public static create ()RestCallHandlerRestCallLoggerRestInfoProviderClasspathResourceFinderRestResourceResolverRestGuardResponseHandler
-
New
Path.required() @Path(required)annotation support.
A path can be marked as not-required when the path variable is resolved by a parent resource like so:@Rest (path="/parent/{p1}" ,children=Child.class )public class Parent { ... }@Rest (path="/child")public class Child {@RestMethod (path="/")public String doGet(@Path (name="p1" ,required=false ) String p1) {// p1 will be null when accessed via "/child" // p1 will be non-null when accessed via "/parent/p1/child". } ... }
This allows the child resource to be mapped to multiple parents that may resolve various different path variables. -
New
RestMethod.paths() @RestMethod(paths)annotation that allows you to map multiple paths to the same Java method.
Example:@RestMethod ( name=GET , paths={"/" ,"/{foo}" } )public String doGet(@Path (name="foo" ,required=false ) String foo) {...} -
The
RestMethod @RestMethodannotation is now implied on all unannotated methods of a@Rest
-annotated interface.@Rest public interface MyRest { String getFoo();// @RestMethod(name=GET,path="/foo") is implied. } -
Improved
RestContext.REST_messages REST_messagessupport (mostly bug fixes).
juneau-rest-server-springboot
-
JuneauRestInitializernow provides a no-arg constructor so that it can be used in the when unit testing using@ConfigurationContext (initializers=JuneauRestInitializer.class )@SpringBootTest . -
New
ResourceDescription.ResourceDescription(String,String,String)constructor andResourceDescriptions.append(String,String,String)method. -
New
Hyperlinkclass.
juneau-rest-client
-
Completely revamped RestClient API.
- All APIs now extend from HttpClient interfaces.
- Better integration with HttpClient.
- New fluent-style methods with many new convenience methods.
- Updated juneau-rest-client.
-
@RemoteMethod -annotated methods can now returnFutures
andCompletableFutures
for concurrent processing of requests.
Example:@Remote public interface MyInterface {public Future<String> doGet(); } MyInterface i = client.getRemote(MyInterface.class ,"http://localhost:12345/myInterface" ); Future<String> f = i.doGet();// Do other stuff. String result = f.get(); - Additions to
Remote
annotation:
juneau-rest-mock
-
The
MockRest andMockRemote classes have been remove entirely and all existing functions have been moved into the improvedMockRestClientclass. All REST test mocking can be done through this single class.
9.0.0 (TBD)
Juneau 9.0.0 is a major release. Deprecated APIs that have been accumulating over time have been removed.
Major changes include:
- The code has undergone significant refactoring to ease maintainability and improve overall performance. Configuration properties have been removed entirely. They have been replaced with a standard builder-based architecture. In addition to making the code more maintainable, it also improves performance when creating new serializers/parsers/rest clients (and others).
- REST servlets now seemlessly integrate with Spring Boot.
juneau-marshall
-
Eliminated the various
annotations and replaced them with the ability to apply targeted annotations directly to configuration classes and methods (such as REST classes/methods).@XConfig (applyX={...})@Rest (...)@Bean (on="MyBean1,MyBean2" ,sort=true )@UrlEncoding (onClass=MyList.class ,expandedParams=true )public class MyRestClass {@RestOp (...)@Bean (on="MyBean1,MyBean2" ,sort=false )@UrlEncoding (onClass=MyList.class ,expandedParams=false )public Object myRestMethod() { ... } } -
JSON 5
support. -
New
@Marshalled
annotation for non-bean classes. -
New
@BeanConfig(ignoreUnknownEnumValues)
annotation and support for ignoring unknown enum values during parsing. - Java Serialized Object marshalling support has been removed entirely due to security risks with usage (better safe than sorry).
juneau-rest-common
New module containing the common REST classes/annotations uses by both the client and server APIs.
These were previously contained within
-
Significant refactoring of the classes in the
org.apache.juneau.http
package and subpackages. Attempts were made to make classes as natural extensions to the Apache HttpComponents APIs. Significant new functionality here. -
@RemoteMethod annotation has been replaced with the following:
juneau-rest-server
- Significant refactoring done to allow for many extensible aspects of the API to be performed through injected beans in Spring. These include logging, debugging, REST method arg types, static files, file finders, swagger creators, thrown stores, response processors, serializers/parsers, JSON schema generators, statistics gathering stores, and default request attributes/headers and response headers.
-
@RestMethod annotation has been replaced with the following: -
Defining REST resources with predefined marshalling support is now much simpler. You can now extend from a basic REST servlet/object.
// A root resource that supports JSON/HTML marshalling. public class MyRootResourcesextends BasicRestServletGroup { ... }// A child resource that supports all available marshalling. public class MyChildResourceextends BasicRestObject { ... }
juneau-dto
-
Addition of OpenAPI 3.0 (
org.apache.juneau.dto.openapi3
package).
juneau-rest-server-springboot
-
The requirement for using
JuneauRestInitializer during App initialization to use bean injection has been eliminated. Instead, root resources should simply extend fromBasicSpringRestServlet
andBasicSpringRestServletGroup
. These will automatically hook into the Spring Boot framework for resolution of REST children and various extension beans added to the REST API framework.
juneau-rest-client
-
While the general usage pattern stays the same, the REST client code has undergone significant rewriting. It is now more inline
as an extension of the Apache HttpClient library. Much new functionality such as support for fluent assertions has been added.
// Create a basic REST client with JSON support and download a bean. MyBeanbean = RestClient.create () .json5() .build() .get(URI ) .run() .assertStatus().asCode().is(200) .assertHeader("Content-Type" ).matchesSimple("application/json*" ) .getContent().as(MyBean.class );
juneau-rest-mock
- Entirely rewritten. Changes too many to list.