http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/JsonSerializerWriter.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/JsonSerializerWriter.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/JsonSerializerWriter.java new file mode 100755 index 0000000..4fa6458 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/JsonSerializerWriter.java @@ -0,0 +1,262 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.json; + +import java.io.*; + +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.*; + +/** + * Specialized writer for serializing JSON. + * <p> + * <b>Note: This class is not intended for external use.</b> + * + * @author James Bognar ([email protected]) + */ +public final class JsonSerializerWriter extends SerializerWriter { + + private final boolean laxMode, escapeSolidus; + + // Characters that trigger special handling of serializing attribute values. + private static final AsciiSet + encodedChars = new AsciiSet("\n\t\b\f\r'\"\\"), + encodedChars2 = new AsciiSet("\n\t\b\f\r'\"\\/"); + + private static final KeywordSet reservedWords = new KeywordSet( + "arguments","break","case","catch","class","const","continue","debugger","default","delete", + "do","else","enum","eval","export","extends","false","finally","for","function","if", + "implements","import","in","instanceof","interface","let","new","null","package", + "private","protected","public","return","static","super","switch","this","throw", + "true","try","typeof","var","void","while","with","undefined","yield" + ); + + + // Characters that represent attribute name characters that don't trigger quoting. + // These are actually more strict than the actual Javascript specification, but + // can be narrowed in the future if necessary. + // For example, we quote attributes that start with $ even though we don't need to. + private static final AsciiSet validAttrChars = new AsciiSet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); + private static final AsciiSet validFirstAttrChars = new AsciiSet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"); + + private final AsciiSet ec; + + /** + * Constructor. + * @param out The writer being wrapped. + * @param useIndentation If <jk>true</jk>, tabs will be used in output. + * @param useWhitespace If <jk>true</jk>, whitespace will be used in output. + * @param escapeSolidus If <jk>true</jk>, forward slashes should be escaped in the output. + * @param quoteChar The quote character to use (i.e. <js>'\''</js> or <js>'"'</js>) + * @param laxMode If <jk>true</jk>, JSON attributes will only be quoted when necessary. + * @param relativeUriBase The base (e.g. <js>https://localhost:9443/contextPath"</js>) for relative URIs (e.g. <js>"my/path"</js>). + * @param absolutePathUriBase The base (e.g. <js>https://localhost:9443"</js>) for relative URIs with absolute paths (e.g. <js>"/contextPath/my/path"</js>). + */ + protected JsonSerializerWriter(Writer out, boolean useIndentation, boolean useWhitespace, boolean escapeSolidus, char quoteChar, boolean laxMode, String relativeUriBase, String absolutePathUriBase) { + super(out, useIndentation, useWhitespace, quoteChar, relativeUriBase, absolutePathUriBase); + this.laxMode = laxMode; + this.escapeSolidus = escapeSolidus; + this.ec = escapeSolidus ? encodedChars2 : encodedChars; + } + + /** + * Serializes the specified object as a JSON string value. + * @param o The object being serialized. + * @return This object (for method chaining). + * @throws IOException Should never happen. + */ + public JsonSerializerWriter stringValue(Object o) throws IOException { + /* + * Fixes up a Java string so that it can be used as a JSON string.<br> + * Does the following:<br> + * <ul> + * <li> Replaces {@code \r?\n} with {@code \\n}<br> + * <li> Replaces {@code \t} with {@code \\t}<br> + * <li> Replaces {@code '} with {@code \\'}<br> + * <li> Replaces {@code "} with {@code \\"}<br> + * </ul> + */ + if (o == null) + return this; + String s = o.toString(); + boolean doConvert = false; + for (int i = 0; i < s.length() && ! doConvert; i++) { + char c = s.charAt(i); + doConvert |= ec.contains(c); + } + q(); + if (! doConvert) { + out.append(s); + } else { + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (ec.contains(c)) { + if (c == '\n') + out.append('\\').append('n'); + else if (c == '\t') + out.append('\\').append('t'); + else if (c == '\b') + out.append('\\').append('b'); + else if (c == '\f') + out.append('\\').append('f'); + else if (c == quoteChar) + out.append('\\').append(quoteChar); + else if (c == '\\') + out.append('\\').append('\\'); + else if (c == '/' && escapeSolidus) + out.append('\\').append('/'); + else if (c != '\r') + out.append(c); + } else { + out.append(c); + } + } + } + q(); + return this; + } + + /** + * Serializes the specified object as a JSON attribute name. + * @param o The object being serialized. + * @return This object (for method chaining). + * @throws IOException Should never happen. + */ + public JsonSerializerWriter attr(Object o) throws IOException { + /* + * Converts a Java string to an acceptable JSON attribute name. If + * useStrictJson is false, then quotes will only be used if the attribute + * name consists of only alphanumeric characters. + */ + boolean doConvert = ! laxMode; // Always convert when not in lax mode. + + String s = null; + + // If the attribute is null, it must always be printed as null without quotes. + // Technically, this isn't part of the JSON spec, but it does allow for null key values. + if (o == null) { + s = "null"; + doConvert = false; + + } else { + s = o.toString(); + + // Look for characters that would require the attribute to be quoted. + // All possible numbers should be caught here. + if (! doConvert) { + for (int i = 0; i < s.length() && ! doConvert; i++) { + char c = s.charAt(i); + doConvert |= ! (i == 0 ? validFirstAttrChars.contains(c) : validAttrChars.contains(c)); + } + } + + // Reserved words and blanks must be quoted. + if (! doConvert) { + if (s.isEmpty() || reservedWords.contains(s)) + doConvert = true; + } + } + + // If no conversion necessary, just print the attribute as-is. + if (doConvert) + stringValue(s); + else + out.append(s); + + return this; + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* SerializerWriter */ + public JsonSerializerWriter cr(int depth) throws IOException { + super.cr(depth); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter appendln(int indent, String text) throws IOException { + super.appendln(indent, text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter appendln(String text) throws IOException { + super.appendln(text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter append(int indent, String text) throws IOException { + super.append(indent, text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter append(int indent, char c) throws IOException { + super.append(indent, c); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter s() throws IOException { + super.s(); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter q() throws IOException { + super.q(); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter i(int indent) throws IOException { + super.i(indent); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter nl() throws IOException { + super.nl(); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter append(Object text) throws IOException { + super.append(text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter append(String text) throws IOException { + super.append(text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter appendIf(boolean b, String text) throws IOException { + super.appendIf(b, text); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter appendIf(boolean b, char c) throws IOException { + super.appendIf(b, c); + return this; + } + + @Override /* SerializerWriter */ + public JsonSerializerWriter append(char c) throws IOException { + super.append(c); + return this; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.class new file mode 100755 index 0000000..b375a8d Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.java new file mode 100755 index 0000000..d4b0f0a --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/Json.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.json.annotation; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.*; + +/** + * Annotation for specifying various JSON options for the JSON serializers and parsers. + * <p> + * Can be applied to Java types. + * <p> + * Can be used for the following: + * <ul> + * <li>Wrap bean instances inside wrapper object (e.g. <code>{'wrapperAttr':bean}</code>). + * </ul> + * + * @author James Bognar ([email protected]) + */ +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +@Inherited +public @interface Json { + + /** + * Wraps beans in a JSON object with the specified attribute name. + * <p> + * Applies only to {@link ElementType#TYPE}. + * <p> + * This annotation can be applied to beans as well as other objects serialized to other types (e.g. strings). + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <ja>@Json</ja>(wrapperAttr=<js>"myWrapper"</js>) + * <jk>public class</jk> MyBean { + * <jk>public int</jk> f1 = 123; + * } + * </p> + * <p> + * Without the <ja>@Xml</ja> annotations, serializing this bean as JSON would have produced the following... + * </p> + * <p class='bcode'> + * { + * f1: 123 + * } + * </p> + * <p> + * With the annotations, serializing this bean as XML produces the following... + * </p> + * <p class='bcode'> + * { + * myWrapper: { + * f1: 123 + * } + * } + * </p> + * </dd> + * </dl> + */ + String wrapperAttr() default ""; +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/package.html ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/package.html b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/package.html new file mode 100755 index 0000000..3d37d51 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/annotation/package.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<!-- + Licensed Materials - Property of IBM + (c) Copyright IBM Corporation 2014. All Rights Reserved. + + Note to U.S. Government Users Restricted Rights: + Use, duplication or disclosure restricted by GSA ADP Schedule + Contract with IBM Corp. + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>JSON annotations</p> +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_HTML.png ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_HTML.png b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_HTML.png new file mode 100755 index 0000000..b4a3576 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_HTML.png differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSON.png ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSON.png b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSON.png new file mode 100755 index 0000000..13b5c22 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSON.png differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSchema.png ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSchema.png b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSchema.png new file mode 100755 index 0000000..bf1cdc6 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSchema.png differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSimple.png ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSimple.png b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSimple.png new file mode 100755 index 0000000..935e8a9 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/doc-files/Example_JSONSimple.png differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/package.html ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/package.html b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/package.html new file mode 100755 index 0000000..d9a78ec --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/json/package.html @@ -0,0 +1,1460 @@ +<!DOCTYPE HTML> +<!-- + Licensed Materials - Property of IBM + (c) Copyright IBM Corporation 2014. All Rights Reserved. + + Note to U.S. Government Users Restricted Rights: + Use, duplication or disclosure restricted by GSA ADP Schedule + Contract with IBM Corp. + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>JSON serialization and parsing support</p> +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> + +<a id='TOC'></a><h5 class='toc'>Table of Contents</h5> +<ol class='toc'> + <li><p><a class='doclink' href='#Overview'>JSON support overview</a></p> + <ol> + <li><p><a class='doclink' href='#OverviewExample'>Example</a></p> + </ol> + <li><p><a class='doclink' href='#JsonSerializer'>JsonSerializer class</a></p> + <ol> + <li><p><a class='doclink' href='#BeanAnnotations'>@Bean and @BeanProperty annotations</a></p> + <li><p><a class='doclink' href='#Collections'>Collections</a></p> + <li><p><a class='doclink' href='#JsonSchemaSupport'>JSON-Schema support</a></p> + <li><p><a class='doclink' href='#Recursion'> Non-tree models and recursion detection</a></p> + <li><p><a class='doclink' href='#SerializerConfigurableProperties'>Configurable properties</a></p> + <li><p><a class='doclink' href='#SerializerOtherNotes'>Other notes</a></p> + </ol> + <li><p><a class='doclink' href='#JsonParser'>JsonParser class</a></p> + <ol> + <li><p><a class='doclink' href='#GenericParsing'>Parsing into generic POJO models</a></p> + <li><p><a class='doclink' href='#ParserConfigurableProperties'>Configurable properties</a></p> + <li><p><a class='doclink' href='#ParserOtherNotes'>Other notes</a></p> + </ol> + <li><p><a class='doclink' href='#RestApiSupport'>REST API support</a></p> + <ol> + <li><p><a class='doclink' href='#RestServerSupport'>REST server support</a></p> + <ol> + <li><p><a class='doclink' href='#RestServletDefault'>Using RestServletDefault</a></p> + <li><p><a class='doclink' href='#RestServlet'>Using RestServlet with annotations</a></p> + <li><p><a class='doclink' href='#DefaultProvider'>Using JAX-RS DefaultProvider</a></p> + <li><p><a class='doclink' href='#BaseProvider'>Using JAX-RS BaseProvider with annotations</a></p> + </ol> + <li><p><a class='doclink' href='#RestClientSupport'>REST client support</a></p> + </ol> +</ol> + +<!-- ======================================================================================================== --> +<a id="Overview"></a> +<h2 class='topic' onclick='toggle(this)'>1 -JSON support overview</h2> +<div class='topic'> + <p> + Juno supports converting arbitrary POJOs to and from JSON using ultra-efficient serializers and parsers.<br> + The JSON serializer converts POJOs directly to JSON without the need for intermediate DOM objects using a highly-efficient state machine.<br> + Likewise, the JSON parser creates POJOs directly from JSON without the need for intermediate DOM objects. + </p> + <p> + Juno can serialize and parse instances of any of the following POJO types: + </p> + <ul> + <li>Java primitives and primitive objects (e.g. <code>String</code>, <code>Integer</code>, <code>Boolean</code>, <code>Float</code>). + <li>Java Collections Framework objects (e.g. <code>HashSet</code>, <code>TreeMap</code>) containing anything on this list. + <li>Multi-dimensional arrays of any type on this list. + <li>Java Beans with properties of any type on this list. + <li>Classes with standard transformations to and from <code>Strings</code> (e.g. classes containing <code>toString()</code>, <code>fromString()</code>, <code>valueOf()</code>, <code>constructor(String)</code>). + <li>Non-serializable classes and properties with associated <code>PojoFilters</code> that convert them to serializable forms. + </ul> + <p> + Refer to <a href='../package-summary.html#PojoCategories' class='doclink'>POJO Categories</a> for a complete definition of supported POJOs. + </p> + <h6 class='topic'>Prerequisites</h6> + <p> + The Juno JSON serialization and parsing support does not require any external prerequisites. + It only requires Java 1.6 or above. + </p> + + <!-- ======================================================================================================== --> + <a id="OverviewExample"></a> + <h3 class='topic' onclick='toggle(this)'>1.1 - JSON support overview - example</h3> + <div class='topic'> + <p> + The example shown here is from the Address Book resource located in the <code>com.ibm.juno.sample.war</code> application.<br> + The POJO model consists of a <code>List</code> of <code>Person</code> beans, with each <code>Person</code> containing + zero or more <code>Address</code> beans. + </p> + <p> + When you point a browser at <code>/sample/addressBook</code>, the POJO is rendered as HTML: + </p> + <img class='bordered' src="doc-files/Example_HTML.png"> + <p> + By appending <code>?Accept=<i>mediaType</i>&plainText=true</code> to the URL, you can view the data in the various supported JSON formats: + </p> + + <h6 class='figure'>Normal JSON</h6> + <img class='bordered' src="doc-files/Example_JSON.png"> + + <h6 class='figure'>Simple JSON</h6> + <img class='bordered' src="doc-files/Example_JSONSimple.png"> + + <p> + In addition to serializing POJOs to JSON, Juno includes support for serializing POJO metamodels to JSON Schema. + </p> + + <h6 class='figure'>JSON Schema</h6> + <img class='bordered' src="doc-files/Example_JSONSchema.png"> + + <p> + The JSON data type produced depends on the Java object type being serialized. + </p> + <ul> + <li>Primitives and primitive objects are converted to JSON primitives.<br> + <li>Beans and Maps are converted to JSON objects.<br> + <li>Collections and arrays are converted to JSON arrays.<br> + <li>Anything else is converted to JSON strings. + </ul> + + <h6 class='figure'>Examples</h6> + <table class='styled'> + <tr> + <th>POJO type</th> + <th>Example</th> + <th>Serialized form</th> + </tr> + <tr> + <td>String</td> + <td><code>serializer.serialize(<js>"foobar"</js>);</code></td> + <td><code><js>'foobar'</js></code> + </tr> + <tr> + <td>Number</td> + <td><code>serializer.serialize(123);</code></td> + <td><code><jk>123</jk></code> + </tr> + <tr> + <td>Boolean</td> + <td><code>serializer.serialize(<jk>true</jk>);</code></td> + <td><code><jk>true</jk></code> + </tr> + <tr> + <td>Null</td> + <td><code>serializer.serialize(<jk>null</jk>);</code></td> + <td><code><jk>null</jk></code> + </tr> + <tr> + <td>Beans with properties of any type on this list</td> + <td><code>serializer.serialize(<jk>new</jk> MyBean());</code></td> + <td><code>{p1:<js>'val1'</js>,p2:<jk>true</jk>}</code> + </tr> + <tr> + <td>Maps with values of any type on this list</td> + <td><code>serializer.serialize(<jk>new</jk> TreeMap());</code></td> + <td><code>{key1:<js>'val1'</js>,key2:<jk>true</jk>}</code> + </tr> + <tr> + <td>Collections and arrays of any type on this list</td> + <td><code>serializer.serialize(<jk>new</jk> Object[]{1,<js>"foo"</js>,<jk>true</jk>});</code></td> + <td><code>[1,'foo',true]</code> + </tr> + </table> + <p> + In addition, filters can be used to convert non-serializable POJOs into serializable forms, such as converting + <code>Calendar</code> object to ISO8601 strings, or <code><jk>byte</jk>[]</code> arrays to Base-64 encoded strings.<br> + These filters can be associated at various levels: + </p> + <ul> + <li>On serializer and parser instances to handle all objects of the class type globally. + <li>On classes through the <code><ja>@Bean</ja></code> annotation. + <li>On bean properties through the <code><ja>@BeanProperty</ja></code> annotations. + </ul> + <p> + For more information about filters, refer to {@link com.ibm.juno.core.filter}. + </p> + </div> +</div> + +<!-- ======================================================================================================== --> +<a id="JsonSerializer"></a> +<h2 class='topic' onclick='toggle(this)'>2 - JsonSerializer class</h2> +<div class='topic'> + <p> + {@link com.ibm.juno.core.json.JsonSerializer} is the class used to convert POJOs to JSON.<br> + {@link com.ibm.juno.core.json.JsonSchemaSerializer} is the class used to generate JSON-Schema from POJOs.<br> + </p> + <p> + The JSON serializer includes several configurable settings.<br> + Static reusable instances of Json serializers are provided with commonly-used settings: + </p> + <ul> + <li>{@link com.ibm.juno.core.json.JsonSerializer#DEFAULT} - All default settings + <li>{@link com.ibm.juno.core.json.JsonSerializer#DEFAULT_LAX} - Single quotes, only quote attributes when necessary. + <li>{@link com.ibm.juno.core.json.JsonSerializer#DEFAULT_LAX_READABLE} - Readable output. + </ul> + <h6 class='topic'>Notes about examples</h6> + <p> + The examples shown in this document will use single-quote, readable settings.<br> + For brevity, the examples will use public fields instead of getters/setters to reduce the size of the examples.<br> + In the real world, you'll typically want to use standard bean getters and setters. + </p> + <p> + To start off simple, we'll begin with the following simplified bean and build upon it. + </p> + <p class='bcode'> + <jk>public class</jk> Person { + <jc>// Bean properties</jc> + <jk>public int</jk> <jf>id</jf>; + <jk>public</jk> String <jf>name</jf>; + + <jc>// Bean constructor (needed by parser)</jc> + <jk>public</jk> Person() {} + + <jc>// Normal constructor</jc> + <jk>public</jk> Person(<jk>int</jk> id, String name) { + <jk>this</jk>.<jf>id</jf> = id; + <jk>this</jk>.<jf>name</jf> = name; + } + } + </p> + <p> + The following code shows how to convert this to simple JSON: + </p> + <p class='bcode'> + <jc>// Use serializer with readable output, simple mode.</jc> + JsonSerializer s = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>; + + <jc>// Create our bean.</jc> + Person p = <jk>new</jk> Person(1, <js>"John Smith"</js>); + + <jc>// Serialize the bean to JSON.</jc> + String json = s.serialize(p); + </p> + <p> + We could have also created a new serializer with the same settings using the following code: + </p> + <p class='bcode'> + JsonSerializer s = <jk>new</jk> JsonSerializer() + .setProperty(SerializerProperties.<jsf>SERIALIZER_useIndentation</jsf>, <jk>true</jk>) + .setProperty(JsonSerializerProperties.<jsf>JSON_useWhitespace</jsf>, <jk>true</jk>) + .setProperty(JsonSerializerProperties.<jsf>JSON_simpleMode</jsf>, <jk>true</jk>) + .setProperty(SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, <js>'\''</js>); + </p> + + <p> + The code above produces the following output: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js> + } + </p> + + + <!-- ======================================================================================================== --> + <a id="BeanAnnotations"></a> + <h3 class='topic' onclick='toggle(this)'>2.1 - @Bean and @BeanProperty annotations</h3> + <div class='topic'> + <p> + The {@link com.ibm.juno.core.annotation.Bean @Bean} and {@link com.ibm.juno.core.annotation.BeanProperty @BeanProperty} annotations + are used to customize the behavior of beans across the entire framework.<br> + They have various uses: + </p> + <ul> + <li>Hiding bean properties. + <li>Specifying the ordering of bean properties. + <li>Overriding the names of bean properties. + <li>Associating filters at both the class and property level (to convert non-serializable POJOs to serializable forms). + </ul> + <p> + For example, we now add a <code>birthDate</code> property, and associate a filter with it to transform + it to an ISO8601 date-time string in GMT time.<br> + We'll also add a couple of <code>URI</code> properties.<br> + By default, <code>Calendars</code> are treated as beans by the framework, which is usually not how you want them serialized.<br> + Using filters, we can convert them to standardized string forms. + </p> + <p class='bcode'> + <jk>public class</jk> Person { + <jc>// Bean properties</jc> + <jk>public int</jk> <jf>id</jf>; + <jk>public</jk> String <jf>name</jf>; + <jk>public</jk> URI <jf>uri</jf>; + <jk>public</jk> URI <jf>addressBookUri</jf>; + + <ja>@BeanProperty</ja>(filter=CalendarFilter.ISO8601DTZ.<jk>class</jk>) <jk>public</jk> Calendar <jf>birthDate</jf>; + + + <jc>// Bean constructor (needed by parser)</jc> + <jk>public</jk> Person() {} + + <jc>// Normal constructor</jc> + <jk>public</jk> Person(<jk>int</jk> id, String name, String uri, String addressBookUri, String birthDate) <jk>throws</jk> Exception { + <jk>this</jk>.<jf>id</jf> = id; + <jk>this</jk>.<jf>name</jf> = name; + <jk>this</jk>.<jf>uri</jf> = <jk>new</jk> URI(uri); + <jk>this</jk>.<jf>addressBookUri</jf> = <jk>new</jk> URI(addressBookUri); + <jk>this</jk>.<jf>birthDate</jf> = <jk>new</jk> GregorianCalendar(); + <jk>this</jk>.<jf>birthDate</jf>.setTime(DateFormat.<jsm>getDateInstance</jsm>(DateFormat.<jsf>MEDIUM</jsf>).parse(birthDate)); + } + } + </p> + <p> + Next, we alter our code to pass in the birthdate: + </p> + <p class='bcode'> + <jc>// Create our bean.</jc> + Person p = <jk>new</jk> Person(1, <js>"John Smith"</js>, <js>"http://sample/addressBook/person/1"</js>, <js>"http://sample/addressBook"</js>, <js>"Aug 12, 1946"</js>); + </p> + <p> + Now when we rerun the sample code, we'll get the following: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js> + } + </p> + <p> + Another useful feature is the {@link com.ibm.juno.core.annotation.Bean#propertyNamer()} annotation that allows you to plug in your own + logic for determining bean property names.<br> + The {@link com.ibm.juno.core.PropertyNamerDashedLC} is an example of an alternate property namer. + It converts bean property names to lowercase-dashed format. + </p> + <h6 class='figure'>Example</h6> + <p class='bcode'> + <ja>@Bean</ja>(propertyNamer=PropertyNamerDashedLC.<jk>class</jk>) + <jk>public class</jk> Person { + ... + </p> + <h6 class='figure'>Results</h6> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + <js>'address-book-uri'</js>: <js>'http://sample/addressBook'</js>, + <js>'birth-date'</js>: <js>'1946-08-12T00:00:00Z'</js> + } + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="Collections"></a> + <h3 class='topic' onclick='toggle(this)'>2.2 - Collections</h3> + <div class='topic'> + <p> + In our example, let's add a list-of-beans property to our sample class: + </p> + <p class='bcode'> + <jk>public class</jk> Person { + + <jc>// Bean properties</jc> + <jk>public</jk> LinkedList<Address> <jf>addresses</jf> = <jk>new</jk> LinkedList<Address>(); + ... + } + </p> + <p> + The <code>Address</code> class has the following properties defined: + </p> + <p class='bcode'> + <jk>public class</jk> Address { + + <jc>// Bean properties</jc> + <jk>public</jk> URI <jf>uri</jf>; + <jk>public</jk> URI <jf>personUri</jf>; + <jk>public int</jk> <jf>id</jf>; + <jk>public</jk> String <jf>street</jf>, <jf>city</jf>, <jf>state</jf>; + <jk>public int</jk> <jf>zip</jf>; + <jk>public boolean</jk> <jf>isCurrent</jf>; + } + </p> + <p> + Next, add some quick-and-dirty code to add an address to our person bean: + </p> + <p class='bcode'> + <jc>// Use serializer with readable output, simple mode.</jc> + JsonSerializer s = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>; + + <jc>// Create our bean.</jc> + Person p = <jk>new</jk> Person(1, <js>"John Smith"</js>, <js>"http://sample/addressBook/person/1"</js>, <js>"http://sample/addressBook"</js>, <js>"Aug 12, 1946"</js>); + Address a = <jk>new</jk> Address(); + a.<jf>uri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/address/1"</js>); + a.<jf>personUri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/person/1"</js>); + a.<jf>id</jf> = 1; + a.<jf>street</jf> = <js>"100 Main Street"</js>; + a.<jf>city</jf> = <js>"Anywhereville"</js>; + a.<jf>state</jf> = <js>"NY"</js>; + a.<jf>zip</jf> = 12345; + a.<jf>isCurrent</jf> = <jk>true</jk>; + p.<jf>addresses</jf>.add(a); + </p> + <p> + Now when we run the sample code, we get the following: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js>, + addresses: [ + { + uri: <js>'http://sample/addressBook/address/1'</js>, + personUri: <js>'http://sample/addressBook/person/1'</js>, + id: <jk>1</jk>, + street: <js>'100 Main Street'</js>, + city: <js>'Anywhereville'</js>, + state: <js>'NY'</js>, + zip: <jk>12345</jk>, + isCurrent: <jk>true</jk> + } + ] + } + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="JsonSchemaSupport"></a> + <h3 class='topic' onclick='toggle(this)'>2.3 - JSON-Schema support</h3> + <div class='topic'> + <p> + Juno provides the {@link com.ibm.juno.core.json.JsonSchemaSerializer} class for generating JSON-Schema documents + that describe the output generated by the {@link com.ibm.juno.core.json.JsonSerializer} class.<br> + This class shares the same properties as <code>JsonSerializer</code>.<br> + For convenience the {@link com.ibm.juno.core.json.JsonSerializer#getSchemaSerializer()} method + has been added for creating instances of schema serializers from the regular serializer instance. + </p> + <p> + <i>Note:</i> As of this writing, JSON-Schema has not been standardized, so the output generated by the schema + serializer may be subject to future modifications. + </p> + <p> + Lets start with the classes from the previous examples: + </p> + <p class='bcode'> + <jk>public class</jk> Person { + <jc>// Bean properties</jc> + <jk>public int</jk> <jf>id</jf>; + <jk>public</jk> String <jf>name</jf>; + <jk>public</jk> URI <jf>uri</jf>; + <jk>public</jk> URI <jf>addressBookUri</jf>; + <ja>@BeanProperty</ja>(filter=CalendarFilter.ISO8601DTZ.<jk>class</jk>) <jk>public</jk> Calendar <jf>birthDate</jf>; + <jk>public</jk> LinkedList<Address> <jf>addresses</jf> = <jk>new</jk> LinkedList<Address>(); + + <jc>// Bean constructor (needed by parser)</jc> + <jk>public</jk> Person() {} + + <jc>// Normal constructor</jc> + <jk>public</jk> Person(<jk>int</jk> id, String name, String uri, String addressBookUri, String birthDate) <jk>throws</jk> Exception { + <jk>this</jk>.<jf>id</jf> = id; + <jk>this</jk>.<jf>name</jf> = name; + <jk>this</jk>.<jf>uri</jf> = <jk>new</jk> URI(uri); + <jk>this</jk>.<jf>addressBookUri</jf> = <jk>new</jk> URI(addressBookUri); + <jk>this</jk>.<jf>birthDate</jf> = <jk>new</jk> GregorianCalendar(); + <jk>this</jk>.<jf>birthDate</jf>.setTime(DateFormat.getDateInstance(DateFormat.<jsf>MEDIUM</jsf>).parse(birthDate)); + } + } + + <jk>public class</jk> Address { + <jc>// Bean properties</jc> + <jk>public</jk> URI <jf>uri</jf>; + <jk>public</jk> URI <jf>personUri</jf>; + <jk>public int</jk> <jf>id</jf>; + <jk>public</jk> String <jf>street</jf>, <jf>city</jf>, <jf>state</jf>; + <jk>public int</jk> <jf>zip</jf>; + <jk>public boolean</jk> <jf>isCurrent</jf>; + } + </p> + <p> + The code for creating our POJO model and generating JSON-Schema is shown below: + </p> + <p class='bcode'> + <jc>// Get the schema serializer for one of the default JSON serializers.</jc> + JsonSchemaSerializer s = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>.getSchemaSerializer(); + + <jc>// Create our bean.</jc> + Person p = <jk>new</jk> Person(1, <js>"John Smith"</js>, <js>"http://sample/addressBook/person/1"</js>, <js>"http://sample/addressBook"</js>, <js>"Aug 12, 1946"</js>); + Address a = <jk>new</jk> Address(); + a.<jf>uri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/address/1"</js>); + a.<jf>personUri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/person/1"</js>); + a.<jf>id</jf> = 1; + a.<jf>street</jf> = <js>"100 Main Street"</js>; + a.<jf>city</jf> = <js>"Anywhereville"</js>; + a.<jf>state</jf> = <js>"NY"</js>; + a.<jf>zip</jf> = 12345; + a.<jf>isCurrent</jf> = <jk>true</jk>; + p.<jf>addresses</jf>.add(a); + + <jc>// Get the JSON Schema corresponding to the JSON generated above.</jc> + String jsonSchema = s.serialize(p); + </p> + <h6 class='figure'>Results</h6> + <p class='bcode'> + { + type: <js>'object'</js>, + description: <js>'com.ibm.juno.sample.Person'</js>, + properties: { + id: { + type: <js>'number'</js>, + description: <js>'int'</js> + }, + name: { + type: <js>'string'</js>, + description: <js>'java.lang.String'</js> + }, + uri: { + type: <js>'any'</js>, + description: <js>'java.net.URI'</js> + }, + addressBookUri: { + type: <js>'any'</js>, + description: <js>'java.net.URI'</js> + }, + birthDate: { + type: <js>'any'</js>, + description: <js>'java.util.Calendar'</js> + }, + addresses: { + type: <js>'array'</js>, + description: <js>'java.util.LinkedList<com.ibm.juno.sample.Address>'</js>, + items: { + type: <js>'object'</js>, + description: <js>'com.ibm.juno.sample.Address'</js>, + properties: { + uri: { + type: <js>'any'</js>, + description: <js>'java.net.URI'</js> + }, + personUri: { + type: <js>'any'</js>, + description: <js>'java.net.URI'</js> + }, + id: { + type: <js>'number'</js>, + description: <js>'int'</js> + }, + street: { + type: <js>'string'</js>, + description: <js>'java.lang.String'</js> + }, + city: { + type: <js>'string'</js>, + description: <js>'java.lang.String'</js> + }, + state: { + type: <js>'string'</js>, + description: <js>'java.lang.String'</js> + }, + zip: { + type: <js>'number'</js>, + description: <js>'int'</js> + }, + isCurrent: { + type: <js>'boolean'</js>, + description: <js>'boolean'</js> + } + } + } + } + } + } + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="Recursion"></a> + <h3 class='topic' onclick='toggle(this)'>2.4 - Non-tree models and recursion detection</h3> + <div class='topic'> + <p> + The JSON serializer is designed to be used against POJO tree structures. <br> + It expects that there not be loops in the POJO model (e.g. children with references to parents, etc...).<br> + If you try to serialize models with loops, you will usually cause a <code>StackOverflowError</code> to + be thrown (if {@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_maxDepth} is not reached first). + </p> + <p> + If you still want to use the JSON serializer on such models, Juno provides the + {@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_detectRecursions} setting.<br> + 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. + </p> + <p> + For example, let's make a POJO model out of the following classes: + </p> + <p class='bcode'> + <jk>public class</jk> A { + <jk>public</jk> B b; + } + + <jk>public class</jk> B { + <jk>public</jk> C c; + } + + <jk>public class</jk> C { + <jk>public</jk> A a; + } + </p> + <p> + Now we create a model with a loop and serialize the results. + </p> + <p class='bcode'> + <jc>// Clone an existing serializer and set property for detecting recursions.</jc> + JsonSerializer s = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>.clone().setProperty(SerializerProperties.<jsf>SERIALIZER_detectRecursions</jsf>, <jk>true</jk>); + + <jc>// Create a recursive loop.</jc> + A a = <jk>new</jk> A(); + a.<jf>b</jf> = <jk>new</jk> B(); + a.<jf>b</jf>.<jf>c</jf> = <jk>new</jk> C(); + a.<jf>b</jf>.<jf>c</jf>.<jf>a</jf> = a; + + <jc>// Serialize to JSON.</jc> + String json = s.serialize(a); + </p> + <p> + What we end up with is the following, which does not serialize the contents of the <code>c</code> field: + </p> + <p class='bcode'> + { + b: { + c: { + } + } + } + </p> + <p> + Without recursion detection enabled, this would cause a stack-overflow error. + </p> + <p> + Recursion detection introduces a performance penalty of around 20%.<br> + For this reason the setting is disabled by default. + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="SerializerConfigurableProperties"></a> + <h3 class='topic' onclick='toggle(this)'>2.5 - Configurable properties</h3> + <div class='topic'> + <p> + The full list of configurable settings applicable to the <code>JsonSerializer</code> class is shown below: + </p> + <table class='styled' style='border-collapse: collapse;'> + <tr><th>Property</th><th>Short Description</th></tr> + <tr> + <td>{@link com.ibm.juno.core.json.JsonSerializerProperties#JSON_simpleMode}</td> + <td>Simple JSON mode</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.json.JsonSerializerProperties#JSON_useWhitespace}</td> + <td>Use whitespace in output</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_maxDepth}</td> + <td>Maximum serialization depth</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_detectRecursions}</td> + <td>Automatically detect POJO recursions</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_useIndentation}</td> + <td>Use indentation in output</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_quoteChar}</td> + <td>Quote character</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_trimNullProperties}</td> + <td>Trim null bean property values from output</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_trimEmptyLists}</td> + <td>Trim empty lists and arrays from output</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_trimEmptyMaps}</td> + <td>Trim empty maps from output</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_relativeUriBase}</td> + <td>URI context root for relative URIs</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.serializer.SerializerProperties#SERIALIZER_absolutePathUriBase}</td> + <td>URI authority for absolute path relative URIs</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beansRequireDefaultConstructor}</td> + <td>Beans require no-arg constructors</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beansRequireSerializable}</td> + <td>Beans require <code>Serializable</code> interface</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beansRequireSettersForGetters}</td> + <td>Beans require setters for getters</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beansRequireSomeProperties}</td> + <td>Beans require some properties</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beanConstructorVisibility}</td> + <td>Look for bean constructors with the specified minimum visibility</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beanClassVisibility}</td> + <td>Look for bean classes with the specified minimum visibility</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_beanFieldVisibility}</td> + <td>Look for bean fields with the specified minimum visibility</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_methodVisibility}</td> + <td>Look for bean methods with the specified minimum visibility</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_useJavaBeanIntrospector}</td> + <td>Use Java bean Introspector for determining bean properties</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_useInterfaceProxies}</td> + <td>Use interface proxies</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_ignoreUnknownBeanProperties}</td> + <td>Ignore unknown properties</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_ignoreUnknownNullBeanProperties}</td> + <td>Ignore unknown properties with null values</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_ignorePropertiesWithoutSetters}</td> + <td>Ignore properties without setters</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_ignoreInvocationExceptionsOnGetters}</td> + <td>Ignore invocation errors when calling getters</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_ignoreInvocationExceptionsOnSetters}</td> + <td>Ignore invocation errors when calling setters</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_addNotBeanPackages}</td> + <td>Add to the list of packages whose classes should not be considered beans</td> + </tr> + <tr> + <td>{@link com.ibm.juno.core.BeanContextProperties#BEAN_removeNotBeanPackages}</td> + <td>Remove from the list of packages whose classes should not be considered beans</td> + </tr> + </table> + </div> + + + <!-- ======================================================================================================== --> + <a id="SerializerOtherNotes"></a> + <h3 class='topic' onclick='toggle(this)'>2.6 - Other notes</h3> + <div class='topic'> + <ul> + <li>Like all other Juno serializers, the JSON serializer is thread safe and maintains an internal cache of bean classes encountered.<br> + For performance reasons, it's recommended that serializers be reused whenever possible instead of always creating new instances. + </ul> + </div> +</div> + + +<!-- ======================================================================================================== --> +<a id="JsonParser"></a> +<h2 class='topic' onclick='toggle(this)'>3 - JsonParser class</h2> +<div class='topic'> + <p> + The {@link com.ibm.juno.core.json.JsonParser} class is the class used to parse JSON back into POJOs. + </p> + <p> + The JSON parser supports ALL valid JSON, including: + </p> + <ul> + <li>Javascript comments. + <li>Single or double quoted values. + <li>Quoted (strict) or unquoted (non-strict) attributes. + <li>JSON fragments (such as string, numeric, or boolean primitive values). + <li>Concatenated strings. + </ul> + <p> + A static reusable instance of <code>JsonParser</code> is also provided for convenience: + </p> + <ul> + <li>{@link com.ibm.juno.core.json.JsonParser#DEFAULT} + </ul> + <p> + Let's build upon the previous example and parse the generated JSON back into the original bean.<br> + We start with the JSON that was generated. + </p> + <p class='bcode'> + <jc>// Use serializer with readable output, simple mode.</jc> + JsonSerializer s = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>; + + <jc>// Create our bean.</jc> + Person p = <jk>new</jk> Person(1, <js>"John Smith"</js>, <js>"http://sample/addressBook/person/1"</js>, <js>"http://sample/addressBook"</js>, <js>"Aug 12, 1946"</js>); + Address a = <jk>new</jk> Address(); + a.<jf>uri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/address/1"</js>); + a.<jf>personUri</jf> = <jk>new</jk> URI(<js>"http://sample/addressBook/person/1"</js>); + a.<jf>id</jf> = 1; + a.<jf>street</jf> = <js>"100 Main Street"</js>; + a.<jf>city</jf> = <js>"Anywhereville"</js>; + a.<jf>state</jf> = <js>"NY"</js>; + a.<jf>zip</jf> = 12345; + a.<jf>isCurrent</jf> = <jk>true</jk>; + p.<jf>addresses</jf>.add(a); + + <jc>// Serialize the bean to JSON.</jc> + String json = s.serialize(p); + </p> + <p> + This code produced the following: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js>, + addresses: [ + { + uri: <js>'http://sample/addressBook/address/1'</js>, + personUri: <js>'http://sample/addressBook/person/1'</js>, + id: <jk>1</jk>, + street: <js>'100 Main Street'</js>, + city: <js>'Anywhereville'</js>, + state: <js>'NY'</js>, + zip: <jk>12345</jk>, + isCurrent: <jk>true</jk> + } + ] + } + </p> + <p> + The code to convert this back into a bean is: + </p> + <p class='bcode'> + <jc>// Parse it back into a bean using the reusable JSON parser.</jc> + Person p = JsonParser.<jsf>DEFAULT</jsf>.parse(json, Person.<jk>class</jk>); + + <jc>// Render it back as JSON.</jc> + json = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>.serialize(p); + </p> + <p> + We print it back out to JSON to show that all the data has been preserved: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js>, + addresses: [ + { + uri: <js>'http://sample/addressBook/address/1'</js>, + personUri: <js>'http://sample/addressBook/person/1'</js>, + id: <jk>1</jk>, + street: <js>'100 Main Street'</js>, + city: <js>'Anywhereville'</js>, + state: <js>'NY'</js>, + zip: <jk>12345</jk>, + isCurrent: <jk>true</jk> + } + ] + } + </p> + + + <!-- ======================================================================================================== --> + <a id="GenericParsing"></a> + <h3 class='topic' onclick='toggle(this)'>3.1 - Parsing into generic POJO models</h3> + <div class='topic'> + <p> + The JSON parser is not limited to parsing back into the original bean classes.<br> + 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 <code>Maps</code>, <code>Collections</code>, and primitive + objects. + </p> + <p> + You can parse into any <code>Map</code> type (e.g. <code>HashMap</code>, <code>TreeMap</code>), but + using {@link com.ibm.juno.core.ObjectMap} is recommended since it has many convenience methods + for converting values to various types.<br> + The same is true when parsing collections. You can use any Collection (e.g. <code>HashSet</code>, <code>LinkedList</code>) + or array (e.g. <code>Object[]</code>, <code>String[]</code>, <code>String[][]</code>), but using + {@link com.ibm.juno.core.ObjectList} is recommended. + </p> + <p> + When the map or list type is not specified, or is the abstract <code>Map</code>, <code>Collection</code>, or <code>List</code> types, + the parser will use <code>ObjectMap</code> and <code>ObjectList</code> by default. + </p> + <p> + Starting back with our original JSON: + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js>, + addresses: [ + { + uri: <js>'http://sample/addressBook/address/1'</js>, + personUri: <js>'http://sample/addressBook/person/1'</js>, + id: <jk>1</jk>, + street: <js>'100 Main Street'</js>, + city: <js>'Anywhereville'</js>, + state: <js>'NY'</js>, + zip: <jk>12345</jk>, + isCurrent: <jk>true</jk> + } + ] + } + </p> + <p> + We can parse this into a generic <code>ObjectMap</code>: + </p> + <p class='bcode'> + <jc>// Parse JSON into a generic POJO model.</jc> + ObjectMap m = JsonParser.<jsf>DEFAULT</jsf>.parse(json, ObjectMap.<jk>class</jk>); + + <jc>// Convert it back to JSON.</jc> + String json = JsonSerializer.<jsf>DEFAULT_LAX_READABLE</jsf>.serialize(m); + </p> + <p> + What we end up with is the exact same output.<br> + Even the numbers and booleans are preserved because they are parsed into <code>Number</code> and <code>Boolean</code> objects + when parsing into generic models. + </p> + <p class='bcode'> + { + id: <jk>1</jk>, + name: <js>'John Smith'</js>, + uri: <js>'http://sample/addressBook/person/1'</js>, + addressBookUri: <js>'http://sample/addressBook'</js>, + birthDate: <js>'1946-08-12T00:00:00Z'</js>, + addresses: [ + { + uri: <js>'http://sample/addressBook/address/1'</js>, + personUri: <js>'http://sample/addressBook/person/1'</js>, + id: <jk>1</jk>, + street: <js>'100 Main Street'</js>, + city: <js>'Anywhereville'</js>, + state: <js>'NY'</js>, + zip: <jk>12345</jk>, + isCurrent: <jk>true</jk> + } + ] + } + </p> + <p> + Once parsed into a generic model, various convenience methods are provided on the <code>ObjectMap</code> + and <code>ObjectList</code> classes to retrieve values: + </p> + <p class='bcode'> + <jc>// Parse JSON into a generic POJO model.</jc> + ObjectMap m = JsonParser.<jsf>DEFAULT</jsf>.parse(json, ObjectMap.<jk>class</jk>); + + <jc>// Get some simple values.</jc> + String name = m.getString(<js>"name"</js>); + <jk>int</jk> id = m.getInt(<js>"id"</js>); + + <jc>// Get a value convertable from a String.</jc> + URI uri = m.get(URI.<jk>class</jk>, <js>"uri"</js>); + + <jc>// Get a value using a filter.</jc> + CalendarFilter filter = <jk>new</jk> CalendarFilter.ISO8601DTZ(); + Calendar birthDate = m.get(filter, <js>"birthDate"</js>); + + <jc>// Get the addresses.</jc> + ObjectList addresses = m.getObjectList(<js>"addresses"</js>); + + <jc>// Get the first address and convert it to a bean.</jc> + Address address = addresses.get(Address.<jk>class</jk>, 0); + </p> + + <p> + As a general rule, parsing into beans is often more efficient than parsing into generic models.<br> + And working with beans is often less error prone than working with generic models. + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="ParserConfigurableProperties"></a> + <h3 class='topic' onclick='toggle(this)'>3.2 - Configurable properties</h3> + <div class='topic'> + <p> + The <code>JsonParser</code> class does not currently have any configurable properties. + </p> + </div> + + + <!-- ======================================================================================================== --> + <a id="ParserOtherNotes"></a> + <h3 class='topic' onclick='toggle(this)'>3.3 - Other notes</h3> + <div class='topic'> + <ul> + <li>Like all other Juno parsers, the JSON parser is thread safe and maintains an internal cache of bean classes encountered.<br> + For performance reasons, it's recommended that parser be reused whenever possible instead of always creating new instances. + </ul> + </div> + +</div> + + +<!-- ======================================================================================================== --> +<a id="RestApiSupport"></a> +<h2 class='topic' onclick='toggle(this)'>4 - REST API support</h2> +<div class='topic'> + <p> + Juno provides fully-integrated support for JSON serialization/parsing in the REST server and client APIs.<br> + The next two sections describe these in detail. + </p> + + <!-- ======================================================================================================== --> + <a id="RestServerSupport"></a> + <h3 class='topic' onclick='toggle(this)'>4.1 - REST server support</h3> + <div class='topic'> + <p> + There are four general ways of defining REST interfaces with support for JSON. + Two using the built-in Juno Server API, and two using the JAX-RS integration component. + </p> + <ul> + <li>Create a servlet that subclasses from {@link com.ibm.juno.server.RestServletDefault}.<br> + This includes JSON serialization/parsing support by default, in addition to several other media types.<br><br> + <li>Create a servlet that subclasses from {@link com.ibm.juno.server.RestServlet} and specify the + a JSON serializer and/or parser using the {@link com.ibm.juno.server.annotation.RestResource#serializers()} and + {@link com.ibm.juno.server.annotation.RestResource#parsers()} on the entire servlet class, or + the {@link com.ibm.juno.server.annotation.RestMethod#serializers()} and {@link com.ibm.juno.server.annotation.RestMethod#parsers()} + annotations on individual methods within the class.<br><br> + <li>Register {@link com.ibm.juno.server.jaxrs.DefaultProvider} with JAX-RS.<br> + This includes JSON serialization/parsing support by default, in addition to several other media types.<br><br> + <li>Create and register a subclass of {@link com.ibm.juno.server.jaxrs.BaseProvider} and specify the serializers and parsers to use on JAX-RS resources. + </ul> + <p> + In general, the Juno REST server API is much more configurable and easier to use than JAX-RS, but beware that the author may be slightly biased in this statement. + </p> + + <!-- ======================================================================================================== --> + <a id="RestServletDefault"></a> + <h4 class='topic' onclick='toggle(this)'>4.1.1 - Using RestServletDefault</h4> + <div class='topic'> + <p> + The quickest way to implement a REST resource with JSON support is to create a subclass of {@link com.ibm.juno.server.RestServletDefault}.<br> + This class provides support for JSON, XML, HTML, URL-Encoding, and others. + </p> + <p> + The <code>AddressBookResource</code> example shown in the first chapter uses the <code>RestServletJenaDefault</code> class + which is a subclass of <code>RestServletDefault</code> with additional support for RDF languages.<br> + The start of the class definition is shown below: + </p> + <p class='bcode'> + <jc>// Proof-of-concept resource that shows off the capabilities of working with POJO resources. + // Consists of an in-memory address book repository.</jc> + <ja>@RestResource</ja>( + messages=<js>"nls/AddressBookResource"</js>, + properties={ + <ja>@Property</ja>(name=SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>), + <ja>@Property</ja>(name=HtmlSerializerProperties.<jsf>HTML_uriAnchorText</jsf>, value=<jsf>TO_STRING</jsf>), + <ja>@Property</ja>(name=HtmlDocSerializerProperties.<jsf>HTMLDOC_title</jsf>, value=<js>"$L{title}"</js>), + <ja>@Property</ja>(name=HtmlDocSerializerProperties.<jsf>HTMLDOC_description</jsf>, value=<js>"$L{description}"</js>), + <ja>@Property</ja>(name=HtmlDocSerializerProperties.<jsf>HTMLDOC_links</jsf>, value=<js>"{options:'?method=OPTIONS',doc:'doc'}"</js>) + }, + encoders=GzipEncoder.<jk>class</jk> + ) + <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServletJenaDefault { + </p> + <p> + Notice how serializer and parser properties can be specified using the <code>@RestResource.properties()</code> annotation.<br> + The <jsf>SERIALIZER_quoteChar</jsf> is a property common to all serializers, including the JSON serializer. + The remaining properties are specific to the HTML serializer. + </p> + <p> + The <code>$L{...}</code> variable represent localized strings pulled from the resource bundle identified by the <code>messages</code> annotation. + These variables are replaced at runtime based on the HTTP request locale. + Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables. + See {@link com.ibm.juno.server.RestServlet#getVarResolver()} for more information. + </p> + <p> + This document won't go into all the details of the Juno <code>RestServlet</code> class.<br> + Refer to the {@link com.ibm.juno.server} documentation for more information on the REST servlet class in general. + </p> + <p> + The rest of the code in the resource class consists of REST methods that simply accept and return POJOs.<br> + The framework takes care of all content negotiation, serialization/parsing, and error handling.<br> + Below are 3 of those methods to give you a general idea of the concept: + </p> + <p class='bcode'> + <jc>// GET person request handler</jc> + <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/people/{id}/*"</js>, rc={200,404}) + <jk>public</jk> Person getPerson(RestRequest req, <ja>@Attr</ja> <jk>int</jk> id) throws Exception { + properties.put(HtmlDocSerializerProperties.<jsf>HTMLDOC_title</jsf>, req.getPathInfo()); + <jk>return</jk> findPerson(id); + } + + <jc>// POST person handler</jc> + <ja>@RestMethod</ja>(name=<js>"POST"</js>, path=<js>"/people"</js>, guards=AdminGuard.<jk>class</jk>, rc={307,404}) + <jk>public void</jk> createPerson(RestResponse res, <ja>@Content</ja> CreatePerson cp) <jk>throws</jk> Exception { + Person p = addressBook.createPerson(cp); + res.sendRedirect(p.<jf>uri</jf>); + } + + <jc>// DELETE person handler</jc> + <ja>@RestMethod</ja>(name=<js>"DELETE"</js>, path=<js>"/people/{id}"</js>, guards=AdminGuard.<jk>class</jk>, rc={200,404}) + <jk>public</jk> String deletePerson(RestResponse res, <ja>@Attr</ja> <jk>int</jk> id) <jk>throws</jk> Exception { + Person p = findPerson(id); + addressBook.remove(p); + <jk>return</jk> <js>"DELETE successful"</js>; + } + </p> + <p> + The resource class can be registered with the web application like any other servlet, or can be + defined as a child of another resource through the {@link com.ibm.juno.server.annotation.RestResource#children()} annotation. + </div> + + <!-- ======================================================================================================== --> + <a id="RestServlet"></a> + <h4 class='topic' onclick='toggle(this)'>4.1.2 - Using RestServlet with annotations</h4> + <div class='topic'> + <p> + For fine-tuned control of media types, the {@link com.ibm.juno.server.RestServlet} class + can be subclassed directly.<br> + The serializers/parsers can be specified through annotations at the class and/or method levels. + </p> + <p> + An equivalent <code>AddressBookResource</code> class could be defined to only support JSON using + the following definition: + </p> + <p class='bcode'> + <ja>@RestResource</ja>( + serializers={JsonSerializer.<jk>class</jk>}, + parsers={JsonParser.<jk>class</jk>}, + properties={ + <ja>@Property</ja>(name=SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>) + } + ) + <jk>public class</jk> AddressBookResource <jk>extends</jk> RestServlet { + </p> + <p> + Likewise, serializers and parsers can be specified/augmented/overridden at the method level like so: + </p> + <p class='bcode'> + <jc>// GET person request handler</jc> + <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/people/{id}/*"</js>, rc={200,404}, + serializers={JsonSerializer.<jk>class</jk>}, + parsers={JsonParser.<jk>class</jk>}, + properties={ + <ja>@Property</ja>(name=SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>) + } + ) + <jk>public</jk> Person getPerson(RestRequest req, <ja>@Attr</ja> <jk>int</jk> id) throws Exception { + properties.put(HtmlDocSerializerProperties.<jsf>HTMLDOC_title</jsf>, req.getPathInfo()); + <jk>return</jk> findPerson(id); + } + </p> + <p> + The {@link com.ibm.juno.server.annotation.RestMethod#serializersInherit()} and + {@link com.ibm.juno.server.annotation.RestMethod#parsersInherit()} control how various artifacts + are inherited from the parent class.<br> + Refer to {@link com.ibm.juno.server} for additional information on using these annotations. + </p> + </div> + + <!-- ======================================================================================================== --> + <a id="DefaultProvider"></a> + <h4 class='topic' onclick='toggle(this)'>4.1.3 - Using JAX-RS DefaultProvider</h4> + <div class='topic'> + <p> + JSON media type support in JAX-RS can be achieved by using the {@link com.ibm.juno.server.jaxrs.DefaultProvider} class.<br> + It implements the JAX-RS <code>MessageBodyReader</code> and <code>MessageBodyWriter</code> interfaces for all Juno supported media types. + </p> + <p> + The <code>DefaultProvider</code> class definition is shown below: + </p> + <p class='bcode'> + <ja>@Provider</ja> + <ja>@Produces</ja>({ + <js>"application/json"</js>, <js>"text/json"</js>, <jc>// JsonSerializer</jc> + <js>"application/json+simple"</js>,<js>"text/json+simple"</js>, <jc>// JsonSerializer.Simple</jc> + <js>"application/json+schema"</js>,<js>"text/json+schema"</js>, <jc>// JsonSchemaSerializer</jc> + <js>"text/xml"</js>, <jc>// XmlDocSerializer</jc> + <js>"text/xml+simple"</js>, <jc>// XmlDocSerializer.Simple</jc> + <js>"text/xml+schema"</js>, <jc>// XmlSchemaDocSerializer</jc> + <js>"text/html"</js>, <jc>// HtmlDocSerializer</jc> + <js>"application/x-www-form-urlencoded"</js>, <jc>// UrlEncodingSerializer</jc> + <js>"text/xml+soap"</js>, <jc>// SoapXmlSerializer</jc> + <js>"application/x-java-serialized-object"</js> <jc>// JavaSerializedObjectSerializer</jc> + }) + <ja>@Consumes</ja>({ + <js>"application/json"</js>, <js>"text/json"</js>, <jc>// JsonParser</jc> + <js>"text/xml"</js>, <jc>// XmlParser</jc> + <js>"text/html"</js>, <jc>// HtmlParser</jc> + <js>"application/x-www-form-urlencoded"</js>, <jc>// UrlEncodingParser</jc> + <js>"application/x-java-serialized-object"</js> <jc>// JavaSerializedObjectParser</jc> + }) + <ja>@JunoProvider</ja>( + serializers={ + JsonSerializer.<jk>class</jk>, + JsonSerializer.Simple.<jk>class</jk>, + JsonSchemaSerializer.<jk>class</jk>, + XmlDocSerializer.<jk>class</jk>, + XmlDocSerializer.Simple.<jk>class</jk>, + XmlSchemaDocSerializer.<jk>class</jk>, + HtmlDocSerializer.<jk>class</jk>, + UrlEncodingSerializer.<jk>class</jk>, + SoapXmlSerializer.<jk>class</jk>, + JavaSerializedObjectSerializer.<jk>class</jk> + }, + parsers={ + JsonParser.<jk>class</jk>, + XmlParser.<jk>class</jk>, + HtmlParser.<jk>class</jk>, + UrlEncodingParser.<jk>class</jk>, + JavaSerializedObjectParser.<jk>class</jk>, + } + ) + <jk>public final class</jk> DefaultProvider <jk>extends</jk> BaseProvider {} + </p> + <p> + That's the entire class. It consists of only annotations to hook up media types to Juno serializers and parsers. + The <ja>@Provider</ja>, <ja>@Produces</ja>, and <ja>@Consumes</ja> annotations are standard JAX-RS annotations, and the <ja>@JunoProvider</ja> annotation is from Juno. + </p> + <p> + To enable the provider, you need to make the JAX-RS environment aware of it. + In Wink, this is accomplished by adding an entry to a config file. + </p> + <p class='bcode'> + <xt><web-app</xt> <xa>version</xa>=<xs>"2.3"</xs><xt>></xt> + <xt><servlet></xt> + <xt><servlet-name></xt>WinkService<xt></servlet-name></xt> + <xt><servlet-class></xt>org.apache.wink.server.internal.servlet.RestServlet<xt></servlet-class></xt> + <xt><init-param></xt> + <xt><param-name></xt>applicationConfigLocation<xt></param-name></xt> + <xt><param-value></xt>/WEB-INF/wink.cfg<xt></param-value></xt> + <xt></init-param></xt> + <xt></servlet></xt> + </p> + <p> + Simply include a reference to the provider in the configuration file. + <p class='bcode'> + com.ibm.juno.server.jaxrs.DefaultProvider + </p> + <p> + Properties can be specified on providers through the {@link com.ibm.juno.server.jaxrs.JunoProvider#properties()} annotation.<br> + Properties can also be specified at the method level by using the {@link com.ibm.juno.server.annotation.RestMethod#properties} annotation, like so: + </p> + <p class='bcode'> + <ja>@GET</ja> + <ja>@Produces</ja>(<js>"*/*"</js>) + <ja>@RestMethod</ja>( <jc>/* Override some properties */</jc> + properties={ + <ja>@Property</ja>(name=SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>) + } + ) + <jk>public</jk> Message getMessage() { + <jk>return</jk> message; + } + </p> + <h6 class='topic'>Limitations</h6> + <p> + In general, the Juno REST API is considerably more flexible than the JAX-RS API, since you can specify and override + serializers, parsers, properties, filters, converters, guards, etc... at both the class and method levels.<br> + Therefore, the JAX-RS API has the following limitations that the Juno Server API does not: + </p> + <ul> + <li>The ability to specify different media type providers at the class and method levels.<br> + For example, you may want to use <code>JsonSerializer</code> with one set of properties on + one class, and another instance with different properties on another class.<br> + There is currently no way to define this at the class level.<br> + You can override properties at the method level, but this can be cumbersome since it would have to be + done for all methods in the resource.<br><br> + <li>The Juno Server API allows you to manipulate properties programatically through the {@link com.ibm.juno.server.RestResponse#setProperty(String,Object)} + method, and through the {@link com.ibm.juno.server.annotation.Properties} annotation.<br> + There is no equivalent in JAX-RS. + </ul> + </div> + + <!-- ======================================================================================================== --> + <a id="BaseProvider"></a> + <h4 class='topic' onclick='toggle(this)'>4.1.4 - Using JAX-RS BaseProvider with annotations</h4> + <div class='topic'> + <p> + To provide support for only JSON media types, you can define your own provider class, like so: + </p> + <p class='bcode'> + <ja>@Provider</ja> + <ja>@Produces</ja>({ + <js>"application/json"</js>, <js>"text/json"</js>, <jc>// JsonSerializer</jc> + <js>"application/json+simple"</js>,<js>"text/json+simple"</js>, <jc>// JsonSerializer.Simple</jc> + <js>"application/json+schema"</js>,<js>"text/json+schema"</js> <jc>// JsonSchemaSerializer</jc> + }) + <ja>@Consumes</ja>({ + <js>"application/json"</js>, <js>"text/json"</js> <jc>// JsonParser</jc> + }) + <ja>@JunoProvider</ja>( + serializers={ + JsonSerializer.<jk>class</jk>, + JsonSerializer.Simple.<jk>class</jk>, + JsonSchemaSerializer.<jk>class</jk>, + }, + parsers={ + JsonParser.<jk>class</jk>, + } + properties={ + <ja>@Property</ja>(name=SerializerProperties.<jsf>SERIALIZER_quoteChar</jsf>, value=<js>"'"</js>) + } + ) + <jk>public final class</jk> MyRdfProvider <jk>extends</jk> BaseProvider {} + </p> + <p> + Then register it with Wink the same way as <code>DefaultProvider</code>. + </p> + </div> + + </div> + + <!-- ======================================================================================================== --> + <a id="RestClientSupport"></a> + <h3 class='topic' onclick='toggle(this)'>4.2 - REST client support</h3> + <div class='topic'> + <p> + The {@link com.ibm.juno.client.RestClient} class provides an easy-to-use REST client interface with + pluggable media type handling using any of the Juno serializers and parsers.<br> + Defining a client to support JSON media types on HTTP requests and responses can be done in one line of code: + </p> + <p class='bcode'> + <jc>// Create a client to handle JSON requests and responses.</jc> + RestClient client = <jk>new</jk> RestClient(JsonSerializer.<jk>class</jk>, JsonParser.<jk>class</jk>); + </p> + <p> + The client handles all content negotiation based on the registered serializers and parsers. + </p> + <p> + The following code is pulled from the main method of the <code>ClientTest</code> class in the sample web application, and + is run against the <code>AddressBookResource</code> class running within the sample app.<br> + It shows how the client can be used to interact with the REST API while completely hiding the negotiated content type and working with nothing more than beans. + </p> + <h6 class='figure'>Example</h6> + <p class='bcode'> + String root = <js>"http://localhost:9080/sample/addressBook"</js>; + + <jc>// Get the current contents of the address book</jc> + AddressBook ab = client.doGet(root).getResponse(AddressBook.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Number of entries = "</js> + ab.size()); + + <jc>// Delete the existing entries</jc> + <jk>for</jk> (Person p : ab) { + String r = client.doDelete(p.<jf>uri</jf>).getResponse(String.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Deleted person "</js> + p.<jf>name</jf> + <js>", response = "</js> + r); + } + + <jc>// Make sure they're gone</jc> + ab = client.doGet(root).getResponse(AddressBook.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Number of entries = "</js> + ab.size()); + + <jc>// Add 1st person again</jc> + CreatePerson cp = <jk>new</jk> CreatePerson( + <js>"Barack Obama"</js>, + <jsm>toCalendar</jsm>(<js>"Aug 4, 1961"</js>), + <jk>new</jk> CreateAddress(<js>"1600 Pennsylvania Ave"</js>, <js>"Washington"</js>, <js>"DC"</js>, 20500, <jk>true</jk>), + <jk>new</jk> CreateAddress(<js>"5046 S Greenwood Ave"</js>, <js>"Chicago"</js>, <js>"IL"</js>, 60615, <jk>false</jk>) + ); + Person p = client.doPost(root + <js>"/people"</js>, cp).getResponse(Person.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Created person "</js> + p.<jf>name</jf> + <js>", uri = "</js> + p.<jf>uri</jf>); + + <jc>// Add 2nd person again, but add addresses separately</jc> + cp = <jk>new</jk> CreatePerson( + <js>"George Walker Bush"</js>, + toCalendar(<js>"Jul 6, 1946"</js>) + ); + p = client.doPost(root + <js>"/people"</js>, cp).getResponse(Person.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Created person "</js> + p.<jf>name</jf> + <js>", uri = "</js> + p.<jf>uri</jf>); + + <jc>// Add addresses to 2nd person</jc> + CreateAddress ca = <jk>new</jk> CreateAddress(<js>"43 Prairie Chapel Rd"</js>, <js>"Crawford"</js>, <js>"TX"</js>, 76638, <jk>true</jk>); + Address a = client.doPost(p.<jf>uri</jf> + <js>"/addresses"</js>, ca).getResponse(Address.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Created address "</js> + a.<jf>uri</jf>); + + ca = <jk>new</jk> CreateAddress(<js>"1600 Pennsylvania Ave"</js>, <js>"Washington"</js>, <js>"DC"</js>, 20500, <jk>false</jk>); + a = client.doPost(p.<jf>uri</jf> + "/addresses"</js>, ca).getResponse(Address.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Created address "</js> + a.<jf>uri</jf>); + + <jc>// Find 1st person, and change name</jc> + Person[] pp = client.doGet(root + <js>"?q={name:\"'Barack+Obama'\"}"</js>).getResponse(Person[].<jk>class</jk>); + String r = client.doPut(pp[0].<jf>uri</jf> + <js>"/name"</js>, <js>"Barack Hussein Obama"</js>).getResponse(String.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"Changed name, response = "</js> + r); + p = client.doGet(pp[0].<jf>uri</jf>).getResponse(Person.<jk>class</jk>); + System.<jsm>out</jsm>.println(<js>"New name = "</js> + p.<jf>name</jf>); + </p> + <h6 class='figure'>Results</h6> + <p class='bcode'> + Number of entries = 2 + Deleted person Barack Obama, response = DELETE successful + Deleted person George Walker Bush, response = DELETE successful + Number of entries = 0 + Created person Barack Obama, uri = http://localhost:9080/sample/addressBook/people/3 + Created person George Walker Bush, uri = http://localhost:9080/sample/addressBook/people/4 + Created address http://localhost:9080/sample/addressBook/addresses/7 + Created address http://localhost:9080/sample/addressBook/addresses/8 + Changed name, response = PUT successful + New name = Barack Hussein Obama + </p> + </div> +</div> +<p align="center"><i><b>*** fÃn ***</b></i></p> + +</body> +</html> \ No newline at end of file
