blob: 29a268e15540e53646f1d8f76ebd457dc9d3d412 [file] [log] [blame]
<!--
/***************************************************************************************************************************
* 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&lt;String,Object&gt;</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>