| <!-- |
| /*************************************************************************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations under the License. |
| ***************************************************************************************************************************/ |
| --> |
| |
| OpenAPI Serializers |
| |
| <p> |
| The {@link oaj.oapi.OpenApiSerializer} class is used to convert POJOs to HTTP parts. |
| </p> |
| <p> |
| 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. <js>"1,2,3|4,5,6|7,8,9"</js>): |
| </p> |
| <p class='bpcode w800'> |
| <ja>@RestMethod</ja>(method=<js>"POST"</js>, path=<js>"/2dLongArray"</js>) |
| <jk>public void</jk> post2dLongArray( |
| <ja>@Body</ja>( |
| schema=<ja>@Schema</ja>( |
| items=<ja>@Items</ja>( |
| collectionFormat=<js>"pipes"</js>, |
| items=<ja>@SubItems</ja>( |
| collectionFormat=<js>"csv"</js>, |
| type=<js>"integer"</js>, |
| format=<js>"int64"</js>, |
| minimum=<js>"0"</js>, |
| maximum=<js>"100"</js> |
| minLength=1, |
| maxLength=10 |
| ) |
| ) |
| minLength=1, |
| maxLength=10 |
| ) |
| ) |
| Long[][] body |
| ) {...} |
| </p> |
| <p> |
| Under-the-covers, this gets converted to the following schema object: |
| </p> |
| <p class='bpcode w800'> |
| <jk>import static</jk> org.apache.juneau.httppart.HttpPartSchema.*; |
| |
| HttpPartSchema schema = <jsm>create</jsm>() |
| .items( |
| <jsm>create</jsm>() |
| .collectionFormat(<js>"pipes"</js>) |
| .items( |
| <jsm>create</jsm>() |
| .collectionFormat(<js>"csv"</js>) |
| .type(<js>"integer"</js>) |
| .format(<js>"int64"</js>) |
| .minimum(<js>"0"</js>) |
| .maximum(<js>"100"</js>) |
| .minLength(1) |
| .maxLength=(10) |
| ) |
| ) |
| .build(); |
| </p> |
| <p> |
| The following code shows how the schema above can be used to create our pipe+csv list of numbers: |
| </p> |
| <p class='bpcode w800'> |
| <jc>// Our POJO being serialized.</jc> |
| Long[][] input = .... |
| |
| <jc>// The serializer to use.</jc> |
| HttpPartSerializer s = OpenApiSerializer.<jsf>DEFAULT</jsf>; |
| |
| <jc>// Convert POJO to a string.</jc> |
| <jk>try</jk> { |
| String httpPart = s.serialize(schema, input); |
| } <jk>catch</jk> (SchemaValidationException e) { |
| <jc>// Oops, one of the restrictions were not met.</jc> |
| } |
| </p> |
| <p> |
| As a general rule, any POJO convertible to the intermediate type for the <c>type/format</c> of the schema can |
| be serialized using the OpenAPI serializer. |
| Here are the rules of POJO types allowed for various type/format combinations: |
| </p> |
| <table class='styled w800'> |
| <tr><th>Type</th><th>Format</th><th>Valid parameter types</th></tr> |
| <tr class='dark bb'> |
| <td rowspan='4'><c>string</c> or empty</td> |
| <td><c>byte<br>binary<br>binary-spaced</c></td> |
| <td> |
| <ul> |
| <li><c><jk>byte</jk>[]</c> (default) |
| <li>{@link java.io.InputStream} |
| <li>{@link java.io.Reader} - Read into String and then converted using {@link java.lang.String#getBytes()}. |
| <li>{@link java.lang.Object} - Converted to String and then converted using {@link java.lang.String#getBytes()}. |
| <li>Any POJO transformable to a <c><jk>byte</jk>[]</c> via the following methods: |
| <ul> |
| <li><c><jk>public byte</jk>[] toBytes() {...}</c> |
| <li><c><jk>public byte</jk>[]</jk> toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a <c><jk>byte</jk>[]</c> via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td><c>date<br>date-time</c></td> |
| <td> |
| <ul> |
| <li>{@link java.util.Calendar} (default) |
| <li>{@link java.util.Date} |
| <li>Any POJO transformable to a {@link java.util.Calendar} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Calendar toCalendar() {...}</c> |
| <li><c><jk>public</jk> Calendar toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a {@link java.util.Calendar} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td><c>uon</c></td> |
| <td> |
| <ul> |
| <li>Any {@doc PojoCategories Serializable POJO} type. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td>empty</td> |
| <td> |
| <ul> |
| <li>{@link java.lang.String} (default) |
| <li>Any POJO transformable to a {@link java.lang.String} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> String toString() {...}</c> |
| </ul> |
| <li>Any POJO transformable to a {@link java.lang.String} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='light bb'> |
| <td rowspan='1'><c>boolean</c></td> |
| <td>empty</td> |
| <td> |
| <ul> |
| <li>{@link java.lang.Boolean} (default) |
| <li><jk>boolean</jk> |
| <li>{@link java.lang.String} - Converted to a {@link java.lang.Boolean}. |
| <li>Any POJO transformable to a {@link java.lang.Boolean} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Boolean toBoolean() {...}</c> |
| <li><c><jk>public</jk> Boolean toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a {@link java.lang.Boolean} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td rowspan='2'><c>integer</c></td> |
| <td><c>int32</c></td> |
| <td> |
| <ul> |
| <li>{@link java.lang.Integer} (default) |
| <li><jk>int</jk> |
| <li>{@link java.lang.String} - Converted to an {@link java.lang.String}. |
| <li>Any POJO transformable to an {@link java.lang.Integer} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Integer toInteger() {...}</c> |
| <li><c><jk>public</jk> Integer toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to an {@link java.lang.Integer} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td><c>int64</c></td> |
| <td> |
| <ul> |
| <li>{@link java.lang.Long} (default) |
| <li><jk>long</jk> |
| <li>{@link java.lang.String} - Converted to a {@link java.lang.Long}. |
| <li>Any POJO transformable to a {@link java.lang.Long} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Long toLong() {...}</c> |
| <li><c><jk>public</jk> Long toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a {@link java.lang.Long} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='light bb'> |
| <td rowspan='2'><c>number</c></td> |
| <td><c>float</c></td> |
| <td> |
| <ul> |
| <li>{@link java.lang.Float} (default) |
| <li><jk>float</jk> |
| <li>{@link java.lang.String} - Converted to a {@link java.lang.Float}. |
| <li>Any POJO transformable to a {@link java.lang.Float} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Float toFloat() {...}</c> |
| <li><c><jk>public</jk> Float toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a {@link java.lang.Float} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='light bb'> |
| <td><c>double</c></td> |
| <td> |
| <ul> |
| <li>{@link java.lang.Double} (default) |
| <li><jk>double</jk> |
| <li>{@link java.lang.String} - Converted to a {@link java.lang.Double}. |
| <li>Any POJO transformable to a {@link java.lang.Double} via the following methods: |
| <ul> |
| <li><c><jk>public</jk> Double toDouble() {...}</c> |
| <li><c><jk>public</jk> Double toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to a {@link java.lang.Double} via a {@link oaj.transform.PojoSwap}. |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td rowspan='2'><c>array</c></td> |
| <td>empty</td> |
| <td> |
| <ul> |
| <li>Arrays or Collections of any defaults on this list. |
| <li>Any POJO transformable to arrays of the default types (e.g. <c>Integer[]</c>, <c>Boolean[][]</c>, etc...). |
| <br>For example: |
| <ul> |
| <li><c><jk>public</jk> Boolean[][] toFoo() {...}</c> (any method name starting with "to") |
| </ul> |
| <li>Any POJO transformable to arrays of the default types via a {@link oaj.transform.PojoSwap} |
| </ul> |
| </td> |
| </tr> |
| <tr class='dark bb'> |
| <td><c>uon</c></td> |
| <td> |
| <ul> |
| <li>Any {@doc PojoCategories Serializable POJO} type. |
| </ul> |
| </td> |
| </tr> |
| <tr class='light bb'> |
| <td rowspan='2'><c>object</c></td> |
| <td>empty</td> |
| <td> |
| <ul> |
| <li><c>Map<String,Object></c> (default) |
| <li>Beans with properties of anything on this list. |
| <li>Any POJO transformable to a map via a {@link oaj.transform.PojoSwap} |
| </ul> |
| </td> |
| </tr> |
| <tr class='light bb'> |
| <td><c>uon</c></td> |
| <td> |
| <ul> |
| <li>Any {@doc PojoCategories Serializable POJO} type. |
| </ul> |
| </td> |
| </tr> |
| </table> |
| <p> |
| For arrays, an example of "Any POJO transformable to arrays of the default types" is: |
| </p> |
| <p class='bpcode w800'> |
| <jc>// Sample POJO class convertable to a Long[][].</jc> |
| <jk>public class</jk> MyPojo { |
| |
| <jc>// toX method used by serializer.</jc> |
| <jk>public</jk> Long[][] to2dLongs() {...} |
| } |
| </p> |
| <p> |
| In the example above, our POJO class can be used to create our pipe-delimited list of comma-delimited numbers: |
| </p> |
| <p class='bpcode w800'> |
| <jc>// Our POJO being serialized.</jc> |
| MyPojo input = .... |
| |
| <jc>// The serializer to use.</jc> |
| HttpPartSerializer s = OpenApiSerializer.<jsf>DEFAULT</jsf>; |
| |
| <jc>// Convert POJO to a string.</jc> |
| <jk>try</jk> { |
| String httpPart = s.serialize(schema, input); |
| } <jk>catch</jk> (SchemaValidationException e) { |
| <jc>// Oops, one of the restrictions were not met.</jc> |
| } |
| </p> |
| <p> |
| The <c>object</c> type is not officially part of the OpenAPI standard. |
| However, Juneau supports serializing Maps and beans to HTTP parts using UON notation. |
| </p> |
| <p> |
| The following shows an example of a bean with several properties of various types. |
| </p> |
| <p class='bpcode w800'> |
| <jk>public class</jk> MyBean { |
| <jk>private static byte</jk>[] <jsf>FOOB</jsf> = <js>"foo"</js>.getBytes(); |
| |
| <jk>public</jk> String <jf>f1</jf> = <js>"foo"</js>; |
| <jk>public byte</jk>[] <jf>f2</jf> = <jsf>FOOB</jsf>; |
| <jk>public byte</jk>[] <jf>f3</jf> = <jsf>FOOB</jsf>; |
| <jk>public byte</jk>[] <jf>f4</jf> = <jsf>FOOB</jsf>; |
| <jk>public</jk> Calendar <jf>f5</jf> = <jsm>parseIsoCalendar</jsm>(<js>"2012-12-21T12:34:56Z"</js>); |
| <jk>public</jk> String <jf>f6</jf> = <js>"foo"</js>; |
| <jk>public int</jk> <jf>f7</jf> = 1; |
| <jk>public</jk> Long <jf>f8</jf> = 2l; |
| <jk>public float</jk> <jf>f9</jf> = 1.0; |
| <jk>public</jk> Double <jf>f10</jf> = 1.0; |
| <jk>public</jk> Boolean <jf>f11</jf> = <jk>true</jk>; |
| <jk>public</jk> Object <jf>fExtra</jf> = "1"; |
| } |
| </p> |
| <p> |
| We define the following schema: |
| </p> |
| <p class='bpcode w800'> |
| <jk>import static</jk> org.apache.juneau.httppart.HttpPartSchema.*; |
| |
| HttpPartSchema ps = <jsm>schema</jsm>(<js>"object"</js>) |
| .property(<js>"f1"</js>, <jsm>schema</jsm>(<js>"string"</js>)) |
| .property(<js>"f2"</js>, <jsm>schema</jsm>(<js>"string"</js>, <js>"byte"</js>)) |
| .property(<js>"f3"</js>, <jsm>schema</jsm>(<js>"string"</js>, <js>"binary"</js>)) |
| .property(<js>"f4"</js>, <jsm>schema</jsm>(<js>"string"</js>, <js>"binary-spaced"</js>)) |
| .property(<js>"f5"</js>, <jsm>schema</jsm>(<js>"string"</js>, <js>"date-time"</js>)) |
| .property(<js>"f6"</js>, <jsm>schema</jsm>(<js>"string"</js>, "<js>uon"</js>)) |
| .property(<js>"f7"</js>, <jsm>schema</jsm>(<js>"integer"</js>)) |
| .property(<js>"f8"</js>, <jsm>schema</jsm>(<js>"integer"</js>, <js>"int64"</js>)) |
| .property(<js>"f9"</js>, <jsm>schema</jsm>(<js>"number"</js>)) |
| .property(<js>"f10"</js>, <jsm>schema</jsm>(<js>"number"</js>, <js>"double"</js>)) |
| .property(<js>"f11"</js>, <jsm>schema</jsm>(<js>"boolean"</js>)) |
| .additionalProperties(<jsm>schema</jsm>(<js>"integer"</js>)) |
| .build(); |
| </p> |
| <p> |
| Then we serialize our bean: |
| </p> |
| <p class='bpcode w800'> |
| HttpPartSerializer s = OpenApiSerializer.<jsf>DEFAULT</jsf>; |
| String httpPart = s.serialize(schema, <jk>new</jk> MyBean()); |
| </p> |
| <p> |
| The results of this serialization is shown below: |
| </p> |
| <p class='bpcode w800'> |
| ( |
| 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 |
| ) |
| </p> |
| <p> |
| The following is an example of a bean with various array property types: |
| </p> |
| <p class='bpcode w800'> |
| <jk>public class</jk> MyBean { |
| <jk>private static byte</jk>[] <jsf>FOOB</jsf> = <js>"foo"</js>.getBytes(); |
| |
| <jk>public</jk> String[] <jf>f1</jf> = <jk>new</jk> String[]{<js>"a,b"</js>,<jk>null</jk>}, |
| <jk>public byte</jk>[][] <jf>f2</jf> = <jk>new byte</jk>[][]{<jsf>FOOB</jsf>,<jk>null</jk>}, |
| <jk>public byte</jk>[][] <jf>f3</jf> = <jk>new byte</jk>[][]{<jsf>FOOB</jsf>,<jk>null</jk>}, |
| <jk>public byte</jk>[][] <jf>f4</jf> = <jk>new byte</jk>[][]{<jsf>FOOB</jsf>,<jk>null</jk>}, |
| <jk>public</jk> Calendar[] <jf>f5</jf> = <jk>new</jk> Calendar[]{<jsm>parseIsoCalendar</jsm>(<js>"2012-12-21T12:34:56Z"</js>),<jk>null</jk>}, |
| <jk>public</jk> String[] <jf>f6</jf> = <jk>new</jk> String[]{<js>"a"</js>,<js>"b"</js>,<jk>null</jk>}, |
| <jk>public int</jk>[] <jf>f7</jf> = <jk>new int</jk>[]{1,2,<jk>null</jk>}, |
| <jk>public</jk> Integer[] <jf>f8</jf> = <jk>new</jk> Integer[]{3,4,<jk>null</jk>}, |
| <jk>public float</jk>[] <jf>f9</jf> = <jk>new float</jk>[]{1f,2f,<jk>null</jk>}, |
| <jk>public</jk> Float[] <jf>f10</jf> = <jk>new</jk> Float[]{3f,4f,<jk>null</jk>}, |
| <jk>public</jk> Boolean[] <jf>f11</jf> = <jk>new</jk> Boolean[]{<jk>true</jk>,<jk>false</jk>,<jk>null</jk>}, |
| <jk>public</jk> Object[] <jf>fExtra</jf> = <jk>new</jk> Object[]{1,<js>"2"</js>,<jk>null</jk>}; |
| } |
| </p> |
| <p> |
| For this bean, we define the following schema: |
| </p> |
| <p class='bpcode w800'> |
| HttpPartSchema ps = <jsm>schema</jsm>("object") |
| .property(<js>"f1"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>))) |
| .property(<js>"f2"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>, <js>"byte"</js>))) |
| .property(<js>"f3"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>, <js>"binary"</js>))) |
| .property(<js>"f4"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>, <js>"binary-spaced"</js>))) |
| .property(<js>"f5"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>, <js>"date-time"</js>))) |
| .property(<js>"f6"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"string"</js>, <js>"uon"</js>))) |
| .property(<js>"f7"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"integer"</js>))) |
| .property(<js>"f8"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"integer"</js>, <js>"int64"</js>))) |
| .property(<js>"f9"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"number"</js>))) |
| .property(<js>"f10"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"number"</js>, <js>"double"</js>))) |
| .property(<js>"f11"</js>, <jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"boolean"</js>))) |
| .additionalProperties(<jsm>schema</jsm>(<js>"array"</js>).items(<jsm>schema</jsm>(<js>"integer"</js>))) |
| .build(); |
| </p> |
| <p> |
| Serializing this bean produces the following output: |
| </p> |
| <p class='bpcode w800'> |
| ( |
| 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) |
| ) |
| </p> |
| <ul class='notes'> |
| <li> |
| Array properties can also use CSV/SSV/PIPES for array notation. |
| <br>Various notations can be mixed throughout. |
| <li> |
| Schemas and POJOs can be defined arbitrarily deep. |
| <li> |
| Schemas are optional. |
| They can be skipped or partially defined. |
| <li> |
| We make our best attempt to convert the input to the matching type. |
| However, you will get <c>SerializeExceptions</c> if you attempt an impossible conversion. |
| (e.g. trying to serialize the string "foo" as a boolean). |
| </ul> |