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