blob: bb47c618cf223fee31823926c1a50b0328c8f47b [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.
***************************************************************************************************************************/
-->
PojoSwaps
<p>
{@link oaj.transform.PojoSwap PojoSwaps} are a critical component of Juneau.
They allow the serializers and parsers to handle Java objects that wouldn't normally be serializable.
</p>
<p>
Swaps are, simply put, 'object swappers' that swap in serializable objects for
non-serializable ones during serialization, and vis-versa during parsing.
</p>
<p>
Some examples of non-serializable POJOs are <c>File</c>, <c>Reader</c>,
<c>Iterable</c>, etc...
These are classes that aren't beans and cannot be represented as simple maps, collections, or primitives.
</p>
<p>
In the following example, we introduce a <c>PojoSwap</c> that will swap in ISO8601 strings for
<c>Date</c> objects:
</p>
<p class='bpcode w800'>
<jc>// Sample swap for converting Dates to ISO8601 strings.</jc>
<jk>public class</jk> MyDateSwap <jk>extends</jk> PojoSwap&lt;Date,String&gt; {
<jc>// ISO8601 formatter.</jc>
<jk>private</jk> DateFormat <jf>format</jf> = <jk>new</jk> SimpleDateFormat(<js>"yyyy-MM-dd'T'HH:mm:ssZ"</js>);
<jc>// Converts a Date object to an ISO8601 string.</jc>
<ja>@Override</ja> <jc>/* PojoSwap */</jc>
<jk>public</jk> String swap(BeanSession session, Date o) {
<jk>return</jk> <jf>format</jf>.format(o);
}
<jc>// Converts an ISO8601 string to a Date object.</jc>
<ja>@Override</ja> <jc>/* PojoSwap */</jc>
<jk>public</jk> Date unswap(BeanSession session, String o, ClassMeta hint) <jk>throws</jk> Exception {
<jk>return</jk> <jf>format</jf>.parse(o);
}
}
</p>
<p>
The swap can then be associated with serializers and parsers like so:
</p>
<p class='bpcode w800'>
<jc>// Sample bean with a Date field.</jc>
<jk>public class</jk> MyBean {
<jk>public</jk> Date <jf>date</jf> = <jk>new</jk> Date(112, 2, 3, 4, 5, 6);
}
<jc>// Create a new JSON serializer, associate our date swap with it, and serialize a sample bean.</jc>
WriterSerializer s = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(MyDateSwap.<jk>class</jk>).build();
String json = s.serialize(<jk>new</jk> MyBean()); <jc>// == "{date:'2012-03-03T04:05:06-0500'}"</jc>
<jc>// Create a JSON parser, associate our date swap with it, and reconstruct our bean (including the date).</jc>
ReaderParser p = JsonParser.<jsm>create</jsm>().pojoSwaps(MyDateSwap.<jk>class</jk>).build();
MyBean bean = p.parse(json, MyBean.<jk>class</jk>);
<jk>int</jk> day = bean.<jf>date</jf>.getDay(); <jc>// == 3</jc>
</p>
<p>
The {@link oaj.BeanMap#get(Object)} and {@link oaj.BeanMap#put(String,Object)}
methods will automatically convert to swapped values as the following example shows:
</p>
<p class='bpcode w800'>
<jc>// Create a new bean context and add our swap.</jc>
BeanContext bc = BeanContext.<jsm>create</jsm>().pojoSwaps(MyDateSwap.<jk>class</jk>).build();
<jc>// Create a new bean.</jc>
MyBean myBean = <jk>new</jk> MyBean();
<jc>// Wrap it in a bean map.</jc>
BeanMap&lt;Bean&gt; beanMap = bc.forBean(myBean);
<jc>// Use the get() method to get the date field as an ISO8601 string.</jc>
String date = (String)beanMap.get(<js>"date"</js>); <jc>// == "2012-03-03T04:05:06-0500"</jc>
<jc>// Use the put() method to set the date field to an ISO8601 string.</jc>
beanMap.put(<js>"date"</js>, <js>"2013-01-01T12:30:00-0500"</js>); <jc>// Set it to a new value.</jc>
<jc>// Verify that the date changed on the original bean.</jc>
<jk>int</jk> year = myBean.<jf>date</jf>.getYear(); <jc>// == 113</jc>
</p>
<p>
Another example of a <c>PojoSwap</c> is one that converts <c><jk>byte</jk>[]</c> arrays to
BASE64-encoded strings:
</p>
<p class='bpcode w800'>
<jk>public class</jk> ByteArrayBase64Swap <jk>extends</jk> StringSwap&lt;<jk>byte</jk>[]&gt; {
<ja>@Override</ja> <jc>/* StringSwap */</jc>
<jk>public</jk> String swap(<jk>byte</jk>[] b) <jk>throws</jk> Exception {
ByteArrayOutputStream baos = <jk>new</jk> ByteArrayOutputStream();
OutputStream b64os = MimeUtility.encode(baos, <js>"base64"</js>);
b64os.write(b);
b64os.close();
<jk>return new</jk> String(baos.toByteArray());
}
<ja>@Override</ja> <jc>/* StringSwap */</jc>
<jk>public byte</jk>[] unswap(String s, ClassMeta&lt;?&gt; hint) <jk>throws</jk> Exception {
<jk>byte</jk>[] b = s.getBytes();
ByteArrayInputStream bais = <jk>new</jk> ByteArrayInputStream(b);
InputStream b64is = MimeUtility.decode(bais, <js>"base64"</js>);
<jk>byte</jk>[] tmp = <jk>new byte</jk>[b.length];
<jk>int</jk> n = b64is.read(tmp);
<jk>byte</jk>[] res = <jk>new byte</jk>[n];
System.<jsm>arraycopy</jsm>(tmp, 0, res, 0, n);
<jk>return</jk> res;
}
}
</p>
<p>
The following example shows the BASE64 swap in use:
</p>
<p class='bpcode w800'>
<jc>// Create a JSON serializer and register the BASE64 encoding swap with it.</jc>
WriterSerializer s = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(ByteArrayBase64Swap.<jk>class</jk>).build();
ReaderParser p = JsonParser.<jsm>create</jsm>().pojoSwaps(ByteArrayBase64Swap.<jk>class</jk>).build();
<jk>byte</jk>[] bytes = {1,2,3};
String json = s.serialize(bytes); <jc>// Produces "'AQID'"</jc>
bytes = p.parse(json, <jk>byte</jk>[].<jk>class</jk>); <jc>// Reproduces {1,2,3}</jc>
<jk>byte</jk>[][] bytes2d = {{1,2,3},{4,5,6},<jk>null</jk>};
json = s.serialize(bytes2d); <jc>// Produces "['AQID','BAUG',null]"</jc>
bytes2d = p.parse(json, <jk>byte</jk>[][].<jk>class</jk>); <jc>// Reproduces {{1,2,3},{4,5,6},null}</jc>
</p>
<p>
Several <c>PojoSwaps</c> are already provided for common Java objects:
</p>
<ul class='doctree'>
<li class='jp'><jk>org.apache.juneau.transforms</jk>
<ul>
<li class='jc'>
{@link oaj.transforms.ByteArrayBase64Swap}
<li class='jac'>
{@link oaj.transforms.CalendarSwap}
<li class='jac'>
{@link oaj.transforms.DateSwap}
<li class='jc'>
{@link oaj.transforms.EnumerationSwap}
<li class='jc'>
{@link oaj.transforms.IteratorSwap}
<li class='jc'>
{@link oaj.transforms.ReaderSwap}
<li class='jc'>
{@link oaj.transforms.XMLGregorianCalendarSwap}
</ul>
</li>
</ul>
<p>
In particular, the {@link oaj.transforms.CalendarSwap} and
{@link oaj.transforms.DateSwap} transforms provide a large number of customized swaps to
ISO, RFC, or localized strings.
</p>
<p>
Swaps have access to the session locale and timezone through the {@link oaj.BeanSession#getLocale()} and
{@link oaj.BeanSession#getTimeZone()} methods.
This allows you to specify localized swap values when needed.
</p>
<p>
If using the REST server API, the locale and timezone are set based on the <c>Accept-Language</c> and
<c>Time-Zone</c> headers on the request.
</p>
<ul class='doctree'>
<li class='info'>
The 'swapped' class type must be a serializable type.
<br>See the definition for Category 4 objects in {@doc PojoCategories}.
</ul>