001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.transform; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016 017import java.util.*; 018 019import org.apache.juneau.*; 020import org.apache.juneau.annotation.*; 021import org.apache.juneau.http.*; 022import org.apache.juneau.parser.*; 023import org.apache.juneau.serializer.*; 024 025/** 026 * Used to swap out non-serializable objects with serializable replacements during serialization, and vis-versa during 027 * parsing. 028 * 029 * <h5 class='topic'>Description</h5> 030 * 031 * <p> 032 * <code>PojoSwaps</code> are used to extend the functionality of the serializers and parsers to be able to handle 033 * POJOs that aren't automatically handled by the serializers or parsers. 034 * <br>For example, JSON does not have a standard representation for rendering dates. 035 * By defining a special {@code Date} swap and associating it with a serializer and parser, you can convert a 036 * {@code Date} object to a {@code String} during serialization, and convert that {@code String} object back into a 037 * {@code Date} object during parsing. 038 * 039 * <p> 040 * Swaps MUST declare a public no-arg constructor so that the bean context can instantiate them. 041 * 042 * <p> 043 * <code>PojoSwaps</code> are associated with serializers and parsers through the following: 044 * <ul> 045 * <li class='ja'>{@link Swap @Swap} 046 * <li class='ja'>{@link Swaps @Swaps} 047 * <li class='jm'>{@link BeanContextBuilder#pojoSwaps(Object...)} 048 * <li class='jm'>{@link BeanContextBuilder#pojoSwaps(Class...)} 049 * <li class='jm'>{@link BeanContextBuilder#pojoSwaps(boolean, Object...)} 050 * <li class='jm'>{@link BeanContextBuilder#pojoSwapsRemove(Object...)} 051 * </ul> 052 * 053 * <p> 054 * <code>PojoSwaps</code> have two parameters: 055 * <ol> 056 * <li>{@code <T>} - The normal representation of an object. 057 * <li>{@code <S>} - The swapped representation of an object. 058 * </ol> 059 * <br>{@link Serializer Serializers} use swaps to convert objects of type T into objects of type S, and on calls to 060 * {@link BeanMap#get(Object)}. 061 * <br>{@link Parser Parsers} use swaps to convert objects of type S into objects of type T, and on calls to 062 * {@link BeanMap#put(String,Object)}. 063 * 064 * <h5 class='topic'>Swap Class Type {@code <S>}</h5> 065 * 066 * <p> 067 * The swapped object representation of an object must be an object type that the serializers can natively convert to 068 * JSON (or language-specific equivalent). 069 * <br>The list of valid transformed types are as follows... 070 * <ul> 071 * <li> 072 * {@link String} 073 * <li> 074 * {@link Number} 075 * <li> 076 * {@link Boolean} 077 * <li> 078 * {@link Collection} containing anything on this list. 079 * <li> 080 * {@link Map} containing anything on this list. 081 * <li> 082 * A java bean with properties of anything on this list. 083 * <li> 084 * An array of anything on this list. 085 * </ul> 086 * 087 * <h5 class='topic'>Normal Class Type {@code <T>}</h5> 088 * 089 * <p> 090 * The normal object representation of an object. 091 * 092 * <h5 class='section'>See Also:</h5> 093 * <ul> 094 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoSwaps">Overview > juneau-marshall > PojoSwaps</a> 095 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.SwapAnnotation">Overview > juneau-marshall > @Swap Annotation</a> 096 * </ul> 097 * 098 * @param <T> The normal form of the class. 099 * @param <S> The swapped form of the class. 100 */ 101@SuppressWarnings({"unchecked","rawtypes"}) 102@BeanIgnore 103public abstract class PojoSwap<T,S> { 104 105 /** 106 * Represents a non-existent pojo swap. 107 */ 108 public static final PojoSwap NULL = new PojoSwap((Class)null, (Class)null) {}; 109 110 private final Class<T> normalClass; 111 private final Class<?> swapClass; 112 private ClassMeta<?> swapClassMeta; 113 114 // Unfortunately these cannot be made final because we want to allow for PojoSwaps with no-arg constructors 115 // which simplifies declarations. 116 private MediaType[] forMediaTypes; 117 private String template; 118 119 /** 120 * Constructor. 121 */ 122 protected PojoSwap() { 123 normalClass = (Class<T>)resolveParameterType(PojoSwap.class, 0, this.getClass()); 124 swapClass = resolveParameterType(PojoSwap.class, 1, this.getClass()); 125 forMediaTypes = forMediaTypes(); 126 template = withTemplate(); 127 } 128 129 /** 130 * Constructor for when the normal and transformed classes are already known. 131 * 132 * @param normalClass The normal class (cannot be serialized). 133 * @param swapClass The transformed class (serializable). 134 */ 135 protected PojoSwap(Class<T> normalClass, Class<?> swapClass) { 136 this.normalClass = normalClass; 137 this.swapClass = swapClass; 138 this.forMediaTypes = forMediaTypes(); 139 this.template = withTemplate(); 140 } 141 142 /** 143 * Returns the media types that this swap is applicable to. 144 * 145 * <p> 146 * This method can be overridden to programmatically specify what media types it applies to. 147 * 148 * <p> 149 * This method is the programmatic equivalent to the {@link Swap#mediaTypes() @Swap.mediaTypes()} annotation. 150 * 151 * <h5 class='topic'>Documentation</h5> 152 * <ul> 153 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PerMediaTypePojoSwaps">Overview > juneau-marshall > Per-media-type PojoSwaps</a> 154 * </ul> 155 * @return The media types that this swap is applicable to, or <jk>null</jk> if it's applicable for all media types. 156 */ 157 public MediaType[] forMediaTypes() { 158 return null; 159 } 160 161 /** 162 * Returns additional context information for this swap. 163 * 164 * <p> 165 * Typically this is going to be used to specify a template name, such as a FreeMarker template file name. 166 * 167 * <p> 168 * This method can be overridden to programmatically specify a template value. 169 * 170 * <p> 171 * This method is the programmatic equivalent to the {@link Swap#template() @Swap.template()} annotation. 172 * 173 * <h5 class='section'>See Also:</h5> 174 * <ul> 175 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.TemplatedSwaps">Overview > juneau-marshall > Templated Swaps</a> 176 * </ul> 177 * 178 * @return Additional context information, or <jk>null</jk> if not specified. 179 */ 180 public String withTemplate() { 181 return null; 182 } 183 184 /** 185 * Sets the media types that this swap is associated with. 186 * 187 * <h5 class='topic'>Documentation</h5> 188 * <ul> 189 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PerMediaTypePojoSwaps">Overview > juneau-marshall > Per-media-type PojoSwaps</a> 190 * </ul> 191 * 192 * @param mediaTypes The media types that this swap is associated with. 193 * @return This object (for method chaining). 194 */ 195 public PojoSwap<T,?> forMediaTypes(MediaType[] mediaTypes) { 196 this.forMediaTypes = mediaTypes; 197 return this; 198 } 199 200 /** 201 * Sets the template string on this swap. 202 * 203 * <h5 class='section'>See Also:</h5> 204 * <ul> 205 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.TemplatedSwaps">Overview > juneau-marshall > Templated Swaps</a> 206 * </ul> 207 * 208 * @param template The template string on this swap. 209 * @return This object (for method chaining). 210 */ 211 public PojoSwap<T,?> withTemplate(String template) { 212 this.template = template; 213 return this; 214 } 215 216 /** 217 * Returns a number indicating how well this swap matches the specified session. 218 * 219 * <p> 220 * Uses the {@link MediaType#match(MediaType, boolean)} method algorithm to produce a number whereby a 221 * larger value indicates a "better match". 222 * The idea being that if multiple swaps are associated with a given POJO, we want to pick the best one. 223 * 224 * <p> 225 * For example, if the session media type is <js>"text/json"</js>, then the match values are shown below: 226 * 227 * <ul> 228 * <li><js>"text/json"</js> = <code>100,000</code> 229 * <li><js>"*/json"</js> = <code>5,100</code> 230 * <li><js>"*/*"</js> = <code>5,000</code> 231 * <li>No media types specified on swap = <code>1</code> 232 * <li><js>"text/xml"</js> = <code>0</code> 233 * </ul> 234 * 235 * @param session The bean session. 236 * @return Zero if swap doesn't match the session, or a positive number if it does. 237 */ 238 public int match(BeanSession session) { 239 if (forMediaTypes == null) 240 return 1; 241 int i = 0; 242 MediaType mt = session.getMediaType(); 243 if (forMediaTypes != null) 244 for (MediaType mt2 : forMediaTypes) 245 i = Math.max(i, mt2.match(mt, false)); 246 return i; 247 } 248 249 /** 250 * If this transform is to be used to serialize non-serializable POJOs, it must implement this method. 251 * 252 * <p> 253 * The object must be converted into one of the following serializable types: 254 * <ul class='spaced-list'> 255 * <li> 256 * {@link String} 257 * <li> 258 * {@link Number} 259 * <li> 260 * {@link Boolean} 261 * <li> 262 * {@link Collection} containing anything on this list. 263 * <li> 264 * {@link Map} containing anything on this list. 265 * <li> 266 * A java bean with properties of anything on this list. 267 * <li> 268 * An array of anything on this list. 269 * </ul> 270 * 271 * @param session 272 * The bean session to use to get the class meta. 273 * This is always going to be the same bean context that created this swap. 274 * @param o The object to be transformed. 275 * @return The transformed object. 276 * @throws Exception If a problem occurred trying to convert the output. 277 */ 278 public S swap(BeanSession session, T o) throws Exception { 279 return swap(session, o, template); 280 } 281 282 /** 283 * Same as {@link #swap(BeanSession, Object)}, but can be used if your swap has a template associated with it. 284 * 285 * @param session 286 * The bean session to use to get the class meta. 287 * This is always going to be the same bean context that created this swap. 288 * @param o The object to be transformed. 289 * @param template 290 * The template string associated with this swap. 291 * @return The transformed object. 292 * @throws Exception If a problem occurred trying to convert the output. 293 */ 294 public S swap(BeanSession session, T o, String template) throws Exception { 295 throw new SerializeException("Swap method not implemented on PojoSwap ''{0}''", this.getClass().getName()); 296 } 297 298 /** 299 * If this transform is to be used to reconstitute POJOs that aren't true Java beans, it must implement this method. 300 * 301 * @param session 302 * The bean session to use to get the class meta. 303 * This is always going to be the same bean context that created this swap. 304 * @param f The transformed object. 305 * @param hint 306 * If possible, the parser will try to tell you the object type being created. 307 * For example, on a serialized date, this may tell you that the object being created must be of type 308 * {@code GregorianCalendar}. 309 * <br>This may be <jk>null</jk> if the parser cannot make this determination. 310 * @return The narrowed object. 311 * @throws Exception If this method is not implemented. 312 */ 313 public T unswap(BeanSession session, S f, ClassMeta<?> hint) throws Exception { 314 return unswap(session, f, hint, template); 315 } 316 317 /** 318 * Same as {@link #unswap(BeanSession, Object, ClassMeta)}, but can be used if your swap has a template associated with it. 319 * 320 * @param session 321 * The bean session to use to get the class meta. 322 * This is always going to be the same bean context that created this swap. 323 * @param f The transformed object. 324 * @param hint 325 * If possible, the parser will try to tell you the object type being created. 326 * For example, on a serialized date, this may tell you that the object being created must be of type 327 * {@code GregorianCalendar}. 328 * <br>This may be <jk>null</jk> if the parser cannot make this determination. 329 * @param template 330 * The template string associated with this swap. 331 * @return The transformed object. 332 * @throws Exception If a problem occurred trying to convert the output. 333 */ 334 public T unswap(BeanSession session, S f, ClassMeta<?> hint, String template) throws Exception { 335 throw new ParseException("Unswap method not implemented on PojoSwap ''{0}''", this.getClass().getName()); 336 } 337 338 /** 339 * Returns the T class, the normalized form of the class. 340 * 341 * @return The normal form of this class. 342 */ 343 public Class<T> getNormalClass() { 344 return normalClass; 345 } 346 347 /** 348 * Returns the G class, the generalized form of the class. 349 * 350 * <p> 351 * Subclasses must override this method if the generalized class is {@code Object}, meaning it can produce multiple 352 * generalized forms. 353 * 354 * @return The transformed form of this class. 355 */ 356 public Class<?> getSwapClass() { 357 return swapClass; 358 } 359 360 /** 361 * Returns the {@link ClassMeta} of the transformed class type. 362 * 363 * <p> 364 * This value is cached for quick lookup. 365 * 366 * @param session 367 * The bean context to use to get the class meta. 368 * This is always going to be the same bean context that created this swap. 369 * @return The {@link ClassMeta} of the transformed class type. 370 */ 371 public ClassMeta<?> getSwapClassMeta(BeanSession session) { 372 if (swapClassMeta == null) 373 swapClassMeta = session.getClassMeta(swapClass); 374 return swapClassMeta; 375 } 376 377 /** 378 * Checks if the specified object is an instance of the normal class defined on this swap. 379 * 380 * @param o The object to check. 381 * @return 382 * <jk>true</jk> if the specified object is a subclass of the normal class defined on this transform. 383 * <jk>null</jk> always return <jk>false</jk>. 384 */ 385 public boolean isNormalObject(Object o) { 386 if (o == null) 387 return false; 388 return isParentClass(normalClass, o.getClass()); 389 } 390 391 /** 392 * Checks if the specified object is an instance of the swap class defined on this swap. 393 * 394 * @param o The object to check. 395 * @return 396 * <jk>true</jk> if the specified object is a subclass of the transformed class defined on this transform. 397 * <jk>null</jk> always return <jk>false</jk>. 398 */ 399 public boolean isSwappedObject(Object o) { 400 if (o == null) 401 return false; 402 return isParentClass(swapClass, o.getClass()); 403 } 404 405 406 //-------------------------------------------------------------------------------- 407 // Overridden methods 408 //-------------------------------------------------------------------------------- 409 410 @Override /* Object */ 411 public String toString() { 412 return getClass().getSimpleName() + '<' + getNormalClass().getSimpleName() + "," + getSwapClass().getSimpleName() + '>'; 413 } 414}