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