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