001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.serializer; 018 019import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023import static org.apache.juneau.commons.utils.Utils.*; 024 025import java.io.*; 026import java.lang.reflect.*; 027import java.text.*; 028import java.util.*; 029import java.util.function.*; 030import java.util.stream.*; 031 032import org.apache.juneau.*; 033import org.apache.juneau.commons.collections.FluentMap; 034import org.apache.juneau.commons.reflect.*; 035import org.apache.juneau.cp.*; 036import org.apache.juneau.httppart.*; 037import org.apache.juneau.parser.*; 038import org.apache.juneau.soap.*; 039import org.apache.juneau.svl.*; 040import org.apache.juneau.swap.*; 041 042/** 043 * Serializer session that lives for the duration of a single use of {@link Serializer}. 044 * 045 * <p> 046 * Used by serializers for the following purposes: 047 * <ul class='spaced-list'> 048 * <li> 049 * Keeping track of how deep it is in a model for indentation purposes. 050 * <li> 051 * Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model. 052 * <li> 053 * Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled. 054 * <li> 055 * Allowing serializer properties to be overridden on method calls. 056 * </ul> 057 * 058 * <h5 class='section'>Notes:</h5><ul> 059 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 060 * </ul> 061 * 062 * <h5 class='section'>See Also:</h5><ul> 063 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a> 064 065 * </ul> 066 */ 067public class SerializerSession extends BeanTraverseSession { 068 /** 069 * Builder class. 070 */ 071 public static class Builder extends BeanTraverseSession.Builder { 072 073 private HttpPartSchema schema; 074 private Method javaMethod; 075 private Serializer ctx; 076 private UriContext uriContext; 077 private VarResolverSession resolver; 078 079 /** 080 * Constructor 081 * 082 * @param ctx The context creating this session. 083 * <br>Cannot be <jk>null</jk>. 084 */ 085 protected Builder(Serializer ctx) { 086 super(assertArgNotNull("ctx", ctx)); 087 this.ctx = ctx; 088 mediaTypeDefault(ctx.getResponseContentType()); 089 uriContext = ctx.getUriContext(); 090 } 091 092 @Override /* Overridden from Builder */ 093 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 094 super.apply(type, apply); 095 return this; 096 } 097 098 @Override 099 public SerializerSession build() { 100 return new SerializerSession(this); 101 } 102 103 @Override /* Overridden from Builder */ 104 public Builder debug(Boolean value) { 105 super.debug(value); 106 return this; 107 } 108 109 /** 110 * The java method that called this serializer, usually the method in a REST servlet. 111 * 112 * @param value 113 * The new property value. 114 * <br>Can be <jk>null</jk> (value will not be set, existing value will be kept). 115 * @return This object. 116 */ 117 public Builder javaMethod(Method value) { 118 if (nn(value)) 119 javaMethod = value; 120 return this; 121 } 122 123 @Override /* Overridden from Builder */ 124 public Builder locale(Locale value) { 125 super.locale(value); 126 return this; 127 } 128 129 @Override /* Overridden from Builder */ 130 public Builder mediaType(MediaType value) { 131 super.mediaType(value); 132 return this; 133 } 134 135 @Override /* Overridden from Builder */ 136 public Builder mediaTypeDefault(MediaType value) { 137 super.mediaTypeDefault(value); 138 return this; 139 } 140 141 @Override /* Overridden from Builder */ 142 public Builder properties(Map<String,Object> value) { 143 super.properties(value); 144 return this; 145 } 146 147 @Override /* Overridden from Builder */ 148 public Builder property(String key, Object value) { 149 super.property(key, value); 150 return this; 151 } 152 153 /** 154 * String variable resolver. 155 * 156 * <p> 157 * If not specified, defaults to session created by {@link VarResolver#DEFAULT}. 158 * 159 * @param value 160 * The new property value. 161 * <br>Can be <jk>null</jk> (value will not be set, defaults to session created by {@link VarResolver#DEFAULT} when accessed). 162 * @return This object. 163 */ 164 public Builder resolver(VarResolverSession value) { 165 if (nn(value)) 166 resolver = value; 167 return this; 168 } 169 170 /** 171 * HTTP-part schema. 172 * 173 * <p> 174 * Used for schema-based serializers and parsers to define additional formatting. 175 * 176 * @param value 177 * The new value for this property. 178 * <br>Can be <jk>null</jk> (value will not be set, existing value will be kept). 179 * @return This object. 180 */ 181 public Builder schema(HttpPartSchema value) { 182 if (nn(value)) 183 schema = value; 184 return this; 185 } 186 187 /** 188 * Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set. 189 * 190 * @param value 191 * The new value for this property. 192 * <br>If <jk>null</jk>, then the locale defined on the context is used. 193 * @return This object. 194 */ 195 public Builder schemaDefault(HttpPartSchema value) { 196 if (nn(value) && schema == null) 197 schema = value; 198 return this; 199 } 200 201 @Override /* Overridden from Builder */ 202 public Builder timeZone(TimeZone value) { 203 super.timeZone(value); 204 return this; 205 } 206 207 @Override /* Overridden from Builder */ 208 public Builder timeZoneDefault(TimeZone value) { 209 super.timeZoneDefault(value); 210 return this; 211 } 212 213 @Override /* Overridden from Builder */ 214 public Builder unmodifiable() { 215 super.unmodifiable(); 216 return this; 217 } 218 219 /** 220 * URI context bean. 221 * 222 * <p> 223 * Bean used for resolution of URIs to absolute or root-relative form. 224 * 225 * <p> 226 * If not specified, defaults to {@link Serializer.Builder#uriContext(UriContext)}. 227 * 228 * @param value 229 * The new property value. 230 * <br>Can be <jk>null</jk> (value will not be set, defaults to {@link Serializer.Builder#uriContext(UriContext)} from context). 231 * @return This object. 232 */ 233 public Builder uriContext(UriContext value) { 234 if (nn(value)) 235 uriContext = value; 236 return this; 237 } 238 } 239 240 /** 241 * Creates a new builder for this object. 242 * 243 * @param ctx The context creating this session. 244 * <br>Cannot be <jk>null</jk>. 245 * @return A new builder. 246 */ 247 public static Builder create(Serializer ctx) { 248 return new Builder(assertArgNotNull("ctx", ctx)); 249 } 250 251 /** 252 * Create a "_type" property that contains the dictionary name of the bean. 253 * 254 * @param m The bean map to create a class property on. 255 * @param typeName The type name of the bean. 256 * @return A new bean property value. 257 */ 258 protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { 259 BeanMeta<?> bm = m.getMeta(); 260 return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null); 261 } 262 263 /** 264 * Converts the specified throwable to either a {@link RuntimeException} or {@link SerializeException}. 265 * 266 * @param <T> The throwable type. 267 * @param causedBy The exception to cast or wrap. 268 */ 269 protected static <T extends Throwable> void handleThrown(T causedBy) { 270 if (causedBy instanceof Error) 271 throw (Error)causedBy; 272 if (causedBy instanceof RuntimeException) 273 throw (RuntimeException)causedBy; 274 if (causedBy instanceof StackOverflowError) 275 throw new SerializeException( 276 "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop."); 277 if (causedBy instanceof SerializeException) 278 throw (SerializeException)causedBy; 279 throw new SerializeException(causedBy); 280 } 281 282 /** 283 * Converts the contents of the specified object array to a list. 284 * 285 * <p> 286 * Works on both object and primitive arrays. 287 * 288 * <p> 289 * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. 290 * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type 291 * <code><jk>int</jk>[]</code>. 292 * 293 * @param type The type of array. 294 * @param array The array being converted. 295 * @return The array as a list. 296 */ 297 protected static final List<Object> toList(Class<?> type, Object array) { 298 var componentType = type.getComponentType(); 299 if (componentType.isPrimitive()) { 300 var l = Array.getLength(array); 301 var list = new ArrayList<>(l); 302 for (var i = 0; i < l; i++) 303 list.add(Array.get(array, i)); 304 return list; 305 } 306 return l((Object[])array); 307 } 308 309 private final HttpPartSchema schema; 310 private final Method javaMethod; // Java method that invoked this serializer. 311 private final Serializer ctx; 312 private final SerializerListener listener; 313 private final UriResolver uriResolver; 314 private VarResolverSession vrs; 315 316 /** 317 * Constructor. 318 * 319 * @param builder The builder for this object. 320 */ 321 protected SerializerSession(Builder builder) { 322 super(builder); 323 ctx = builder.ctx; 324 javaMethod = builder.javaMethod; 325 schema = builder.schema; 326 UriContext uriContext = builder.uriContext; 327 uriResolver = UriResolver.of(ctx.getUriResolution(), ctx.getUriRelativity(), uriContext); 328 vrs = builder.resolver; 329 listener = BeanCreator.of(SerializerListener.class).type(ctx.getListener()).orElse(null); 330 } 331 332 /** 333 * Adds a session object to the {@link VarResolverSession} in this session. 334 * 335 * @param <T> The bean type. 336 * @param c The bean type being added. 337 * @param value The bean being added. 338 * @return This object. 339 */ 340 public <T> SerializerSession addVarBean(Class<T> c, T value) { 341 getVarResolver().bean(c, value); 342 return this; 343 } 344 345 /** 346 * Returns <jk>true</jk> if the specified value should not be serialized. 347 * 348 * @param cm The class type of the object being serialized. 349 * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute. 350 * @param value The object being serialized. 351 * @return <jk>true</jk> if the specified value should not be serialized. 352 * @throws SerializeException If recursion occurred. 353 */ 354 public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { 355 356 if (value == null && ! isKeepNullProperties()) 357 return true; 358 359 if (value == null) 360 return false; 361 362 if (cm == null) 363 cm = object(); 364 365 if (isTrimEmptyCollections()) { 366 if (cm.isArray() || (cm.isObject() && isArray(value))) { 367 if (((Object[])value).length == 0) 368 return true; 369 } 370 if (cm.isCollection() || (cm.isObject() && info(value).isChildOf(Collection.class))) { 371 if (((Collection<?>)value).isEmpty()) 372 return true; 373 } 374 } 375 376 if (isTrimEmptyMaps()) { 377 if (cm.isMap() || (cm.isObject() && info(value).isChildOf(Map.class))) { 378 if (((Map<?,?>)value).isEmpty()) 379 return true; 380 } 381 } 382 383 try { 384 if ((! isKeepNullProperties()) && (willRecurse(attrName, value, cm) || willExceedDepth())) 385 return true; 386 } catch (BeanRecursionException e) { 387 throw new SerializeException(e); 388 } 389 390 return false; 391 } 392 393 /** 394 * Consumes each entry in the list. 395 * 396 * @param <E> The element type. 397 * @param c The collection being sorted. 398 * @param consumer The entry consumer. 399 */ 400 public final <E> void forEachEntry(Collection<E> c, Consumer<E> consumer) { 401 if (c == null || c.isEmpty()) 402 return; 403 if (isSortCollections() && ! SortedSet.class.isInstance(c) && isSortable(c)) 404 c.stream().sorted().forEach(consumer); 405 else 406 c.forEach(consumer); 407 } 408 409 /** 410 * Consumes each map entry in the map. 411 * 412 * @param <K> The key type. 413 * @param <V> The value type. 414 * @param m The map being consumed. 415 * @param consumer The map entry consumer. 416 */ 417 @SuppressWarnings({ "unchecked", "rawtypes", "cast" }) 418 public final <K,V> void forEachEntry(Map<K,V> m, Consumer<Map.Entry<K,V>> consumer) { 419 if (m == null || m.isEmpty()) 420 return; 421 if (isSortMaps() && ! SortedMap.class.isInstance(m) && isSortable(m.keySet())) 422 ((Map)m).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(x -> consumer.accept((Map.Entry<K,V>)x)); 423 else 424 m.entrySet().forEach(consumer); 425 } 426 427 /** 428 * Returns the listener associated with this session. 429 * 430 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 431 */ 432 public SerializerListener getListener() { return listener; } 433 434 /** 435 * Returns the listener associated with this session. 436 * 437 * @param <T> The listener type. 438 * @param c The listener class to cast to. 439 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 440 */ 441 @SuppressWarnings("unchecked") 442 public <T extends SerializerListener> T getListener(Class<T> c) { 443 return (T)listener; 444 } 445 446 /** 447 * Optional method that specifies HTTP request headers for this serializer. 448 * 449 * <p> 450 * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header. 451 * 452 * <p> 453 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 454 * or client). 455 * 456 * <p> 457 * The default implementation of this method simply calls {@link Serializer#getResponseHeaders(SerializerSession)}. 458 * 459 * @return 460 * The HTTP headers to set on HTTP requests. 461 * Never <jk>null</jk>. 462 */ 463 public Map<String,String> getResponseHeaders() { return ctx.getResponseHeaders(this); } 464 465 /** 466 * HTTP part schema of object being serialized. 467 * 468 * @return HTTP part schema of object being serialized, or <jk>null</jk> if not specified. 469 */ 470 public final HttpPartSchema getSchema() { return schema; } 471 472 /** 473 * Returns the variable resolver session. 474 * 475 * @return The variable resolver session. 476 */ 477 public VarResolverSession getVarResolver() { 478 if (vrs == null) 479 vrs = createDefaultVarResolverSession(); 480 return vrs; 481 } 482 483 /** 484 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 485 * 486 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 487 */ 488 public boolean isWriterSerializer() { return false; } 489 490 /** 491 * Resolves any variables in the specified string. 492 * 493 * @param string The string to resolve values in. 494 * @return The string with variables resolved. 495 */ 496 public String resolve(String string) { 497 return getVarResolver().resolve(string); 498 } 499 500 /** 501 * Converts a String to an absolute URI based on the {@link UriContext} on this session. 502 * 503 * @param uri 504 * The input URI. 505 * Can be any of the following: 506 * <ul> 507 * <li>{@link java.net.URI} 508 * <li>{@link java.net.URL} 509 * <li>{@link CharSequence} 510 * </ul> 511 * URI can be any of the following forms: 512 * <ul> 513 * <li><js>"foo://foo"</js> - Absolute URI. 514 * <li><js>"/foo"</js> - Root-relative URI. 515 * <li><js>"/"</js> - Root URI. 516 * <li><js>"context:/foo"</js> - Context-root-relative URI. 517 * <li><js>"context:/"</js> - Context-root URI. 518 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 519 * <li><js>"servlet:/"</js> - Servlet-path URI. 520 * <li><js>"request:/foo"</js> - Request-path-relative URI. 521 * <li><js>"request:/"</js> - Request-path URI. 522 * <li><js>"foo"</js> - Path-info-relative URI. 523 * <li><js>""</js> - Path-info URI. 524 * </ul> 525 * @return The resolved URI. 526 */ 527 public final String resolveUri(Object uri) { 528 return uriResolver.resolve(uri); 529 } 530 531 /** 532 * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code> 533 * depending on the serializer type. 534 * 535 * @param o The object to serialize. 536 * @return 537 * The serialized object. 538 * <br>Character-based serializers will return a <c>String</c>. 539 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>. 540 * @throws SerializeException If a problem occurred trying to convert the output. 541 */ 542 public Object serialize(Object o) throws SerializeException { 543 throw unsupportedOp(); 544 } 545 546 /** 547 * Serialize the specified object using the specified session. 548 * 549 * @param out Where to send the output from the serializer. 550 * @param o The object to serialize. 551 * @throws SerializeException If a problem occurred trying to convert the output. 552 * @throws IOException Thrown by the underlying stream. 553 */ 554 public final void serialize(Object o, Object out) throws SerializeException, IOException { 555 try (SerializerPipe pipe = createPipe(out)) { 556 doSerialize(pipe, o); 557 } catch (SerializeException | IOException e) { 558 throw e; 559 } catch (@SuppressWarnings("unused") StackOverflowError e) { 560 throw new SerializeException(this, 561 "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop."); 562 } catch (Exception e) { 563 throw new SerializeException(this, e); 564 } finally { 565 checkForWarnings(); 566 } 567 } 568 569 /** 570 * Shortcut method for serializing an object to a String. 571 * 572 * @param o The object to serialize. 573 * @return 574 * The serialized object. 575 * <br>Character-based serializers will return a <c>String</c> 576 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer.Builder#binaryFormat(BinaryFormat)} setting. 577 * @throws SerializeException If a problem occurred trying to convert the output. 578 */ 579 public String serializeToString(Object o) throws SerializeException { 580 throw unsupportedOp(); 581 } 582 583 /** 584 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 585 * 586 * @param <E> The element type. 587 * @param c The collection being sorted. 588 * @return A new sorted {@link TreeSet}. 589 */ 590 public final <E> Collection<E> sort(Collection<E> c) { 591 if (c == null || c.isEmpty() || SortedSet.class.isInstance(c)) 592 return c; 593 if (isSortCollections() && isSortable(c)) 594 return c.stream().sorted().collect(Collectors.toList()); 595 return c; 596 } 597 598 /** 599 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 600 * 601 * @param <E> The element type. 602 * @param c The collection being sorted. 603 * @return A new sorted {@link TreeSet}. 604 */ 605 public final <E> List<E> sort(List<E> c) { 606 if (c == null || c.isEmpty()) 607 return c; 608 if (isSortCollections() && isSortable(c)) 609 return c.stream().sorted().collect(Collectors.toList()); 610 return c; 611 } 612 613 /** 614 * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>. 615 * 616 * @param <K> The key type. 617 * @param <V> The value type. 618 * @param m The map being sorted. 619 * @return A new sorted {@link TreeMap}. 620 */ 621 public final <K,V> Map<K,V> sort(Map<K,V> m) { 622 if (m == null || m.isEmpty() || SortedMap.class.isInstance(m)) 623 return m; 624 if (isSortMaps() && isSortable(m.keySet())) 625 return new TreeMap<>(m); 626 return m; 627 } 628 629 /** 630 * Converts the specified object to a <c>String</c>. 631 * 632 * <p> 633 * Also has the following effects: 634 * <ul> 635 * <li><c>Class</c> object is converted to a readable name. See {@link ClassInfo#getNameFull()}. 636 * <li>Whitespace is trimmed if the trim-strings setting is enabled. 637 * </ul> 638 * 639 * @param o The object to convert to a <c>String</c>. 640 * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>. 641 */ 642 public final String toString(Object o) { 643 if (o == null) 644 return null; 645 if (o.getClass() == Class.class) 646 return info((Class<?>)o).getNameFull(); 647 if (o.getClass() == ClassInfo.class) 648 return ((ClassInfo)o).getNameFull(); 649 if (o.getClass().isEnum()) 650 return getClassMetaForObject(o).toString(o); 651 var s = o.toString(); 652 if (isTrimStrings()) 653 s = s.trim(); 654 return s; 655 } 656 657 /** 658 * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>. 659 * 660 * @param o The input string to trim. 661 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 662 */ 663 public final String trim(Object o) { 664 if (o == null) 665 return null; 666 var s = o.toString(); 667 if (isTrimStrings()) 668 s = s.trim(); 669 return s; 670 } 671 672 private static boolean isSortable(Collection<?> c) { 673 if (c == null) 674 return false; 675 for (var o : c) 676 if (! (o instanceof Comparable)) 677 return false; 678 return true; 679 } 680 681 /** 682 * Adds a session object to the {@link VarResolverSession} in this session. 683 * 684 * @return This object. 685 */ 686 protected VarResolverSession createDefaultVarResolverSession() { 687 return VarResolver.DEFAULT.createSession(); 688 } 689 690 /** 691 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into 692 * a stream or reader. 693 * 694 * @param output 695 * The output location. 696 * <br>For character-based serializers, this can be any of the following types: 697 * <ul> 698 * <li>{@link Writer} 699 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 700 * <li>{@link File} - Output will be written as system-default encoded stream. 701 * <li>{@link StringBuilder} 702 * </ul> 703 * <br>For byte-based serializers, this can be any of the following types: 704 * <ul> 705 * <li>{@link OutputStream} 706 * <li>{@link File} 707 * </ul> 708 * @return 709 * A new {@link ParserPipe} wrapper around the specified input object. 710 */ 711 protected SerializerPipe createPipe(Object output) { 712 return new SerializerPipe(output); 713 } 714 715 /** 716 * Serializes a POJO to the specified pipe. 717 * 718 * <p> 719 * This method should NOT close the context object. 720 * 721 * <p> 722 * The default implementation of this method simply calls {@link Serializer#doSerialize(SerializerSession,SerializerPipe,Object)}. 723 * 724 * @param pipe Where to send the output from the serializer. 725 * @param o The object to serialize. 726 * @throws IOException Thrown by underlying stream. 727 * @throws SerializeException Problem occurred trying to serialize object. 728 */ 729 protected void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException { 730 ctx.doSerialize(this, pipe, o); 731 } 732 733 /** 734 * Generalize the specified object if a POJO swap is associated with it. 735 * 736 * @param o The object to generalize. 737 * @param type The type of object. 738 * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>. 739 * @throws SerializeException If a problem occurred trying to convert the output. 740 */ 741 @SuppressWarnings({ "rawtypes", "unchecked" }) 742 protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { 743 try { 744 if (o == null) 745 return null; 746 ObjectSwap f = (type == null || type.isObject() || type.isString() ? getClassMeta(o.getClass()).getSwap(this) : type.getSwap(this)); 747 if (f == null) 748 return o; 749 return f.swap(this, o); 750 } catch (SerializeException e) { 751 throw e; 752 } catch (Exception e) { 753 throw new SerializeException(e); 754 } 755 } 756 757 /** 758 * Resolves the dictionary name for the actual type. 759 * 760 * @param session The current serializer session. 761 * @param eType The expected type of the bean property. 762 * @param aType The actual type of the bean property. 763 * @param pMeta The current bean property being serialized. 764 * @return The bean dictionary name, or <jk>null</jk> if a name could not be found. 765 */ 766 @SuppressWarnings("null") 767 protected final String getBeanTypeName(SerializerSession session, ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { 768 if (eType == aType || ! (isAddBeanTypes() || (session.isRoot() && isAddRootType()))) 769 return null; 770 771 String eTypeTn = eType.getBeanDictionaryName(); 772 773 // First see if it's defined on the actual type. 774 String tn = aType.getBeanDictionaryName(); 775 if (nn(tn) && ! tn.equals(eTypeTn)) { 776 return tn; 777 } 778 779 // Then see if it's defined on the expected type. 780 // The expected type might be an interface with mappings for implementation classes. 781 BeanRegistry br = eType.getBeanRegistry(); 782 if (nn(br)) { 783 tn = br.getTypeName(aType); 784 if (nn(tn) && ! tn.equals(eTypeTn)) 785 return tn; 786 } 787 788 // Then look on the bean property. 789 br = pMeta == null ? null : pMeta.getBeanRegistry(); 790 if (nn(br)) { 791 tn = br.getTypeName(aType); 792 if (nn(tn) && ! tn.equals(eTypeTn)) 793 return tn; 794 } 795 796 // Finally look in the session. 797 br = getBeanRegistry(); 798 if (nn(br)) { 799 tn = br.getTypeName(aType); 800 if (nn(tn) && ! tn.equals(eTypeTn)) 801 return tn; 802 } 803 804 return null; 805 } 806 807 /** 808 * Returns the parser-side expected type for the object. 809 * 810 * <p> 811 * The return value depends on the {@link Serializer.Builder#addRootType()} setting. 812 * When disabled, the parser already knows the Java POJO type being parsed, so there is 813 * no reason to add <js>"_type"</js> attributes to the root-level object. 814 * 815 * @param o The object to get the expected type on. 816 * @return The expected type. 817 */ 818 protected final ClassMeta<?> getExpectedRootType(Object o) { 819 if (isAddRootType()) 820 return object(); 821 var cm = getClassMetaForObject(o); 822 if (nn(cm) && cm.isOptional()) 823 return cm.getElementType(); 824 return cm; 825 } 826 827 /** 828 * Returns the Java method that invoked this serializer. 829 * 830 * <p> 831 * When using the REST API, this is the Java method invoked by the REST call. 832 * Can be used to access annotations defined on the method or class. 833 * 834 * @return The Java method that invoked this serializer. 835 */ 836 protected final Method getJavaMethod() { return javaMethod; } 837 838 /** 839 * URI context bean. 840 * 841 * @see Serializer.Builder#uriContext(UriContext) 842 * @return 843 * Bean used for resolution of URIs to absolute or root-relative form. 844 */ 845 protected final UriContext getUriContext() { return ctx.getUriContext(); } 846 847 /** 848 * URI relativity. 849 * 850 * @see Serializer.Builder#uriRelativity(UriRelativity) 851 * @return 852 * Defines what relative URIs are relative to when serializing any of the following: 853 */ 854 protected final UriRelativity getUriRelativity() { return ctx.getUriRelativity(); } 855 856 /** 857 * URI resolution. 858 * 859 * @see Serializer.Builder#uriResolution(UriResolution) 860 * @return 861 * Defines the resolution level for URIs when serializing URIs. 862 */ 863 protected final UriResolution getUriResolution() { return ctx.getUriResolution(); } 864 865 /** 866 * Returns the URI resolver. 867 * 868 * @return The URI resolver. 869 */ 870 protected final UriResolver getUriResolver() { return uriResolver; } 871 872 /** 873 * Add <js>"_type"</js> properties when needed. 874 * 875 * @see Serializer.Builder#addBeanTypes() 876 * @return 877 * <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred 878 * through reflection. 879 */ 880 protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); } 881 882 /** 883 * Add type attribute to root nodes. 884 * 885 * @see Serializer.Builder#addRootType() 886 * @return 887 * <jk>true</jk> if type property should be added to root node. 888 */ 889 protected final boolean isAddRootType() { return ctx.isAddRootType(); } 890 891 /** 892 * Don't trim null bean property values. 893 * 894 * @see Serializer.Builder#keepNullProperties() 895 * @return 896 * <jk>true</jk> if null bean values are serialized to the output. 897 */ 898 protected final boolean isKeepNullProperties() { return ctx.isKeepNullProperties(); } 899 900 /** 901 * Sort arrays and collections alphabetically. 902 * 903 * @see Serializer.Builder#sortCollections() 904 * @return 905 * <jk>true</jk> if arrays and collections are copied and sorted before serialization. 906 */ 907 protected final boolean isSortCollections() { return ctx.isSortCollections(); } 908 909 /** 910 * Sort maps alphabetically. 911 * 912 * @see Serializer.Builder#sortMaps() 913 * @return 914 * <jk>true</jk> if maps are copied and sorted before serialization. 915 */ 916 protected final boolean isSortMaps() { return ctx.isSortMaps(); } 917 918 /** 919 * Trim empty lists and arrays. 920 * 921 * @see Serializer.Builder#trimEmptyCollections() 922 * @return 923 * <jk>true</jk> if empty lists and arrays are not serialized to the output. 924 */ 925 protected final boolean isTrimEmptyCollections() { return ctx.isTrimEmptyCollections(); } 926 927 /** 928 * Trim empty maps. 929 * 930 * @see Serializer.Builder#trimEmptyMaps() 931 * @return 932 * <jk>true</jk> if empty map values are not serialized to the output. 933 */ 934 protected final boolean isTrimEmptyMaps() { return ctx.isTrimEmptyMaps(); } 935 936 /** 937 * Trim strings. 938 * 939 * @see Serializer.Builder#trimStrings() 940 * @return 941 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 942 */ 943 protected boolean isTrimStrings() { return ctx.isTrimStrings(); } 944 945 /** 946 * Specialized warning when an exception is thrown while executing a bean getter. 947 * 948 * @param p The bean map entry representing the bean property. 949 * @param t The throwable that the bean getter threw. 950 * @throws SerializeException Thrown if ignoreInvocationExceptionOnGetters is false. 951 */ 952 protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) throws SerializeException { 953 if (nn(listener)) 954 listener.onBeanGetterException(this, t, p); 955 String prefix = (isDebug() ? getStack(false) + ": " : ""); 956 addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t)); 957 if (! isIgnoreInvocationExceptionsOnGetters()) 958 throw new SerializeException(this, "{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t)) 959 .initCause(t); 960 } 961 962 /** 963 * Logs a warning message. 964 * 965 * @param t The throwable that was thrown (if there was one). 966 * @param msg The warning message. 967 * @param args Optional {@link MessageFormat}-style arguments. 968 */ 969 @Override 970 protected void onError(Throwable t, String msg, Object...args) { 971 if (nn(listener)) 972 listener.onError(this, t, f(msg, args)); 973 super.onError(t, msg, args); 974 } 975 976 @Override /* Overridden from BeanTraverseSession */ 977 protected FluentMap<String,Object> properties() { 978 return super.properties() 979 .a("uriResolver", uriResolver); 980 } 981 982 /** 983 * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}. 984 * 985 * @param attrName The attribute name. 986 * @param o The current object being traversed. 987 * @param eType The expected class type. 988 * @return 989 * The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed 990 * once (since they can be expensive). 991 * @throws SerializeException If recursion occurred. 992 */ 993 protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { 994 try { 995 return super.push(attrName, o, eType); 996 } catch (BeanRecursionException e) { 997 throw new SerializeException(e); 998 } 999 } 1000 1001 /** 1002 * Opposite of {@link #resolveUri(Object)}. 1003 * 1004 * <p> 1005 * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter. 1006 * 1007 * <p> 1008 * Both parameters can be any of the following: 1009 * <ul> 1010 * <li>{@link java.net.URI} 1011 * <li>{@link java.net.URL} 1012 * <li>{@link CharSequence} 1013 * </ul> 1014 * 1015 * <p> 1016 * Both URIs can be any of the following forms: 1017 * <ul> 1018 * <li><js>"foo://foo"</js> - Absolute URI. 1019 * <li><js>"/foo"</js> - Root-relative URI. 1020 * <li><js>"/"</js> - Root URI. 1021 * <li><js>"context:/foo"</js> - Context-root-relative URI. 1022 * <li><js>"context:/"</js> - Context-root URI. 1023 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 1024 * <li><js>"servlet:/"</js> - Servlet-path URI. 1025 * <li><js>"request:/foo"</js> - Request-path-relative URI. 1026 * <li><js>"request:/"</js> - Request-path URI. 1027 * <li><js>"foo"</js> - Path-info-relative URI. 1028 * <li><js>""</js> - Path-info URI. 1029 * </ul> 1030 * 1031 * @param relativeTo The URI to relativize against. 1032 * @param uri The URI to relativize. 1033 * @return The relativized URI. 1034 */ 1035 protected final String relativizeUri(Object relativeTo, Object uri) { 1036 return uriResolver.relativize(relativeTo, uri); 1037 } 1038 1039 /** 1040 * Invokes the specified swap on the specified object if the swap is not null. 1041 * 1042 * @param swap The swap to invoke. Can be <jk>null</jk>. 1043 * @param o The input object. 1044 * @return The swapped object. 1045 * @throws SerializeException If swap method threw an exception. 1046 */ 1047 @SuppressWarnings({ "rawtypes", "unchecked" }) 1048 protected Object swap(ObjectSwap swap, Object o) throws SerializeException { 1049 try { 1050 if (swap == null) 1051 return o; 1052 return swap.swap(this, o); 1053 } catch (Exception e) { 1054 throw new SerializeException(e); 1055 } 1056 } 1057}