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.serializer; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017import static org.apache.juneau.serializer.Serializer.*; 018 019import java.io.*; 020import java.lang.reflect.*; 021import java.text.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.parser.*; 026import org.apache.juneau.reflect.*; 027import org.apache.juneau.soap.*; 028import org.apache.juneau.svl.*; 029import org.apache.juneau.transform.*; 030 031/** 032 * Serializer session that lives for the duration of a single use of {@link Serializer}. 033 * 034 * <p> 035 * Used by serializers for the following purposes: 036 * <ul class='spaced-list'> 037 * <li> 038 * Keeping track of how deep it is in a model for indentation purposes. 039 * <li> 040 * Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model. 041 * <li> 042 * Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled. 043 * <li> 044 * Allowing serializer properties to be overridden on method calls. 045 * </ul> 046 * 047 * <p> 048 * This class is NOT thread safe. 049 * It is typically discarded after one-time use although it can be reused within the same thread. 050 */ 051public abstract class SerializerSession extends BeanTraverseSession { 052 053 private final Serializer ctx; 054 private final UriResolver uriResolver; 055 private VarResolverSession vrs; 056 057 private final Method javaMethod; // Java method that invoked this serializer. 058 059 // Writable properties 060 private final SerializerListener listener; 061 062 /** 063 * Create a new session using properties specified in the context. 064 * 065 * @param ctx 066 * The context creating this session object. 067 * The context contains all the configuration settings for this object. 068 * Can be <jk>null</jk>. 069 * @param args 070 * Runtime arguments. 071 * These specify session-level information such as locale and URI context. 072 * It also include session-level properties that override the properties defined on the bean and 073 * serializer contexts. 074 */ 075 protected SerializerSession(Serializer ctx, SerializerSessionArgs args) { 076 super(ctx, args == null ? SerializerSessionArgs.DEFAULT : args); 077 this.ctx = ctx; 078 args = args == null ? SerializerSessionArgs.DEFAULT : args; 079 this.javaMethod = args.javaMethod; 080 this.uriResolver = new UriResolver(ctx.getUriResolution(), ctx.getUriRelativity(), getProperty(SERIALIZER_uriContext, UriContext.class, ctx.getUriContext())); 081 this.listener = castOrCreate(SerializerListener.class, ctx.getListener()); 082 this.vrs = args.resolver; 083 } 084 085 /** 086 * Adds a session object to the {@link VarResolverSession} in this session. 087 * 088 * @param name The session object key. 089 * @param value The session object. 090 * @return This object (for method chaining). 091 */ 092 public SerializerSession varSessionObject(String name, Object value) { 093 getVarResolver().sessionObject(name, value); 094 return this; 095 } 096 097 /** 098 * Adds a session object to the {@link VarResolverSession} in this session. 099 * 100 * @return This object (for method chaining). 101 */ 102 protected VarResolverSession createDefaultVarResolverSession() { 103 return VarResolver.DEFAULT.createSession(); 104 } 105 106 /** 107 * Returns the variable resolver session. 108 * 109 * @return The variable resolver session. 110 */ 111 public VarResolverSession getVarResolver() { 112 if (vrs == null) 113 vrs = createDefaultVarResolverSession(); 114 return vrs; 115 } 116 117 /** 118 * Default constructor. 119 * 120 * @param args 121 * Runtime arguments. 122 * These specify session-level information such as locale and URI context. 123 * It also include session-level properties that override the properties defined on the bean and 124 * serializer contexts. 125 */ 126 protected SerializerSession(SerializerSessionArgs args) { 127 this(Serializer.DEFAULT, args); 128 } 129 130 //----------------------------------------------------------------------------------------------------------------- 131 // Abstract methods 132 //----------------------------------------------------------------------------------------------------------------- 133 134 /** 135 * Serializes a POJO to the specified output stream or writer. 136 * 137 * <p> 138 * This method should NOT close the context object. 139 * 140 * @param pipe Where to send the output from the serializer. 141 * @param o The object to serialize. 142 * @throws IOException Thrown by underlying stream. 143 * @throws SerializeException Problem occurred trying to serialize object. 144 */ 145 protected abstract void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException; 146 147 /** 148 * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code> 149 * depending on the serializer type. 150 * 151 * @param o The object to serialize. 152 * @return 153 * The serialized object. 154 * <br>Character-based serializers will return a <c>String</c>. 155 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>. 156 * @throws SerializeException If a problem occurred trying to convert the output. 157 */ 158 public abstract Object serialize(Object o) throws SerializeException; 159 160 /** 161 * Shortcut method for serializing an object to a String. 162 * 163 * @param o The object to serialize. 164 * @return 165 * The serialized object. 166 * <br>Character-based serializers will return a <c>String</c> 167 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer#OSSERIALIZER_binaryFormat} setting. 168 * @throws SerializeException If a problem occurred trying to convert the output. 169 */ 170 public abstract String serializeToString(Object o) throws SerializeException; 171 172 /** 173 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 174 * 175 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 176 */ 177 public abstract boolean isWriterSerializer(); 178 179 /** 180 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into 181 * a stream or reader. 182 * 183 * @param output 184 * The output location. 185 * <br>For character-based serializers, this can be any of the following types: 186 * <ul> 187 * <li>{@link Writer} 188 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 189 * <li>{@link File} - Output will be written as system-default encoded stream. 190 * <li>{@link StringBuilder} 191 * </ul> 192 * <br>For byte-based serializers, this can be any of the following types: 193 * <ul> 194 * <li>{@link OutputStream} 195 * <li>{@link File} 196 * </ul> 197 * @return 198 * A new {@link ParserPipe} wrapper around the specified input object. 199 */ 200 protected abstract SerializerPipe createPipe(Object output); 201 202 //----------------------------------------------------------------------------------------------------------------- 203 // Other methods 204 //----------------------------------------------------------------------------------------------------------------- 205 206 /** 207 * Serialize the specified object using the specified session. 208 * 209 * @param out Where to send the output from the serializer. 210 * @param o The object to serialize. 211 * @throws SerializeException If a problem occurred trying to convert the output. 212 * @throws IOException Thrown by the underlying stream. 213 */ 214 public final void serialize(Object o, Object out) throws SerializeException, IOException { 215 try (SerializerPipe pipe = createPipe(out)) { 216 doSerialize(pipe, o); 217 } catch (SerializeException | IOException e) { 218 throw e; 219 } catch (StackOverflowError e) { 220 throw new SerializeException(this, 221 "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.").initCause(e); 222 } catch (Exception e) { 223 throw new SerializeException(this, e); 224 } finally { 225 checkForWarnings(); 226 } 227 } 228 229 /** 230 * Returns the Java method that invoked this serializer. 231 * 232 * <p> 233 * When using the REST API, this is the Java method invoked by the REST call. 234 * Can be used to access annotations defined on the method or class. 235 * 236 * @return The Java method that invoked this serializer. 237 */ 238 protected final Method getJavaMethod() { 239 return javaMethod; 240 } 241 242 /** 243 * Returns the URI resolver. 244 * 245 * @return The URI resolver. 246 */ 247 protected final UriResolver getUriResolver() { 248 return uriResolver; 249 } 250 251 /** 252 * Specialized warning when an exception is thrown while executing a bean getter. 253 * 254 * @param p The bean map entry representing the bean property. 255 * @param t The throwable that the bean getter threw. 256 */ 257 protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) { 258 if (listener != null) 259 listener.onBeanGetterException(this, t, p); 260 String prefix = (isDebug() ? getStack(false) + ": " : ""); 261 addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, 262 p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage()); 263 } 264 265 /** 266 * Logs a warning message. 267 * 268 * @param t The throwable that was thrown (if there was one). 269 * @param msg The warning message. 270 * @param args Optional {@link MessageFormat}-style arguments. 271 */ 272 @Override 273 protected void onError(Throwable t, String msg, Object... args) { 274 if (listener != null) 275 listener.onError(this, t, format(msg, args)); 276 super.onError(t, msg, args); 277 } 278 279 /** 280 * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>. 281 * 282 * @param o The input string to trim. 283 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 284 */ 285 public final String trim(Object o) { 286 if (o == null) 287 return null; 288 String s = o.toString(); 289 if (isTrimStrings()) 290 s = s.trim(); 291 return s; 292 } 293 294 /** 295 * Generalize the specified object if a POJO swap is associated with it. 296 * 297 * @param o The object to generalize. 298 * @param type The type of object. 299 * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>. 300 * @throws SerializeException If a problem occurred trying to convert the output. 301 */ 302 @SuppressWarnings({ "rawtypes", "unchecked" }) 303 protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { 304 try { 305 if (o == null) 306 return null; 307 PojoSwap f = (type == null || type.isObject() ? getClassMeta(o.getClass()).getPojoSwap(this) : type.getPojoSwap(this)); 308 if (f == null) 309 return o; 310 return f.swap(this, o); 311 } catch (SerializeException e) { 312 throw e; 313 } catch (Exception e) { 314 throw new SerializeException(e); 315 } 316 } 317 318 /** 319 * Returns <jk>true</jk> if the specified value should not be serialized. 320 * 321 * @param cm The class type of the object being serialized. 322 * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute. 323 * @param value The object being serialized. 324 * @return <jk>true</jk> if the specified value should not be serialized. 325 * @throws SerializeException If recursion occurred. 326 */ 327 public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { 328 329 if (isTrimNullProperties() && value == null) 330 return true; 331 332 if (value == null) 333 return false; 334 335 if (cm == null) 336 cm = object(); 337 338 if (isTrimEmptyCollections()) { 339 if (cm.isArray() || (cm.isObject() && value.getClass().isArray())) { 340 if (((Object[])value).length == 0) 341 return true; 342 } 343 if (cm.isCollection() || (cm.isObject() && ClassInfo.of(value).isChildOf(Collection.class))) { 344 if (((Collection<?>)value).isEmpty()) 345 return true; 346 } 347 } 348 349 if (isTrimEmptyMaps()) { 350 if (cm.isMap() || (cm.isObject() && ClassInfo.of(value).isChildOf(Map.class))) { 351 if (((Map<?,?>)value).isEmpty()) 352 return true; 353 } 354 } 355 356 try { 357 if (isTrimNullProperties() && willRecurse(attrName, value, cm)) 358 return true; 359 } catch (BeanRecursionException e) { 360 throw new SerializeException(e); 361 } 362 363 return false; 364 } 365 366 /** 367 * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>. 368 * 369 * @param m The map being sorted. 370 * @return A new sorted {@link TreeMap}. 371 */ 372 public final <K,V> Map<K,V> sort(Map<K,V> m) { 373 if (isSortMaps() && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable<?>) 374 return new TreeMap<>(m); 375 return m; 376 } 377 378 /** 379 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 380 * 381 * @param c The collection being sorted. 382 * @return A new sorted {@link TreeSet}. 383 */ 384 public final <E> Collection<E> sort(Collection<E> c) { 385 if (isSortCollections() && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable<?>) 386 return new TreeSet<>(c); 387 return c; 388 } 389 390 /** 391 * Converts the contents of the specified object array to a list. 392 * 393 * <p> 394 * Works on both object and primitive arrays. 395 * 396 * <p> 397 * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. 398 * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type 399 * <code><jk>int</jk>[]</code>. 400 * 401 * @param type The type of array. 402 * @param array The array being converted. 403 * @return The array as a list. 404 */ 405 protected static final List<Object> toList(Class<?> type, Object array) { 406 Class<?> componentType = type.getComponentType(); 407 if (componentType.isPrimitive()) { 408 int l = Array.getLength(array); 409 List<Object> list = new ArrayList<>(l); 410 for (int i = 0; i < l; i++) 411 list.add(Array.get(array, i)); 412 return list; 413 } 414 return Arrays.asList((Object[])array); 415 } 416 417 /** 418 * Converts a String to an absolute URI based on the {@link UriContext} on this session. 419 * 420 * @param uri 421 * The input URI. 422 * Can be any of the following: 423 * <ul> 424 * <li>{@link java.net.URI} 425 * <li>{@link java.net.URL} 426 * <li>{@link CharSequence} 427 * </ul> 428 * URI can be any of the following forms: 429 * <ul> 430 * <li><js>"foo://foo"</js> - Absolute URI. 431 * <li><js>"/foo"</js> - Root-relative URI. 432 * <li><js>"/"</js> - Root URI. 433 * <li><js>"context:/foo"</js> - Context-root-relative URI. 434 * <li><js>"context:/"</js> - Context-root URI. 435 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 436 * <li><js>"servlet:/"</js> - Servlet-path URI. 437 * <li><js>"request:/foo"</js> - Request-path-relative URI. 438 * <li><js>"request:/"</js> - Request-path URI. 439 * <li><js>"foo"</js> - Path-info-relative URI. 440 * <li><js>""</js> - Path-info URI. 441 * </ul> 442 * @return The resolved URI. 443 */ 444 public final String resolveUri(Object uri) { 445 return uriResolver.resolve(uri); 446 } 447 448 /** 449 * Opposite of {@link #resolveUri(Object)}. 450 * 451 * <p> 452 * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter. 453 * 454 * <p> 455 * Both parameters can be any of the following: 456 * <ul> 457 * <li>{@link java.net.URI} 458 * <li>{@link java.net.URL} 459 * <li>{@link CharSequence} 460 * </ul> 461 * 462 * <p> 463 * Both URIs can be any of the following forms: 464 * <ul> 465 * <li><js>"foo://foo"</js> - Absolute URI. 466 * <li><js>"/foo"</js> - Root-relative URI. 467 * <li><js>"/"</js> - Root URI. 468 * <li><js>"context:/foo"</js> - Context-root-relative URI. 469 * <li><js>"context:/"</js> - Context-root URI. 470 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 471 * <li><js>"servlet:/"</js> - Servlet-path URI. 472 * <li><js>"request:/foo"</js> - Request-path-relative URI. 473 * <li><js>"request:/"</js> - Request-path URI. 474 * <li><js>"foo"</js> - Path-info-relative URI. 475 * <li><js>""</js> - Path-info URI. 476 * </ul> 477 * 478 * @param relativeTo The URI to relativize against. 479 * @param uri The URI to relativize. 480 * @return The relativized URI. 481 */ 482 protected final String relativizeUri(Object relativeTo, Object uri) { 483 return uriResolver.relativize(relativeTo, uri); 484 } 485 486 /** 487 * Converts the specified object to a <c>String</c>. 488 * 489 * <p> 490 * Also has the following effects: 491 * <ul> 492 * <li><c>Class</c> object is converted to a readable name. See {@link ClassInfo#getFullName()}. 493 * <li>Whitespace is trimmed if the trim-strings setting is enabled. 494 * </ul> 495 * 496 * @param o The object to convert to a <c>String</c>. 497 * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>. 498 */ 499 public final String toString(Object o) { 500 if (o == null) 501 return null; 502 if (o.getClass() == Class.class) 503 return ClassInfo.of((Class<?>)o).getFullName(); 504 if (o.getClass() == ClassInfo.class) 505 return ((ClassInfo)o).getFullName(); 506 if (o.getClass().isEnum()) 507 return getClassMetaForObject(o).toString(o); 508 String s = o.toString(); 509 if (isTrimStrings()) 510 s = s.trim(); 511 return s; 512 } 513 514 /** 515 * Create a "_type" property that contains the dictionary name of the bean. 516 * 517 * @param m The bean map to create a class property on. 518 * @param typeName The type name of the bean. 519 * @return A new bean property value. 520 */ 521 protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { 522 BeanMeta<?> bm = m.getMeta(); 523 return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null); 524 } 525 526 /** 527 * Resolves the dictionary name for the actual type. 528 * 529 * @param eType The expected type of the bean property. 530 * @param aType The actual type of the bean property. 531 * @param pMeta The current bean property being serialized. 532 * @return The bean dictionary name, or <jk>null</jk> if a name could not be found. 533 */ 534 protected final String getBeanTypeName(ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { 535 if (eType == aType) 536 return null; 537 538 if (! isAddBeanTypes()) 539 return null; 540 541 String eTypeTn = eType.getDictionaryName(); 542 543 // First see if it's defined on the actual type. 544 String tn = aType.getDictionaryName(); 545 if (tn != null && ! tn.equals(eTypeTn)) { 546 return tn; 547 } 548 549 // Then see if it's defined on the expected type. 550 // The expected type might be an interface with mappings for implementation classes. 551 BeanRegistry br = eType.getBeanRegistry(); 552 if (br != null) { 553 tn = br.getTypeName(aType); 554 if (tn != null && ! tn.equals(eTypeTn)) 555 return tn; 556 } 557 558 // Then look on the bean property. 559 br = pMeta == null ? null : pMeta.getBeanRegistry(); 560 if (br != null) { 561 tn = br.getTypeName(aType); 562 if (tn != null && ! tn.equals(eTypeTn)) 563 return tn; 564 } 565 566 // Finally look in the session. 567 br = getBeanRegistry(); 568 if (br != null) { 569 tn = br.getTypeName(aType); 570 if (tn != null && ! tn.equals(eTypeTn)) 571 return tn; 572 } 573 574 return null; 575 } 576 577 /** 578 * Returns the parser-side expected type for the object. 579 * 580 * <p> 581 * The return value depends on the {@link Serializer#SERIALIZER_addRootType} setting. 582 * When disabled, the parser already knows the Java POJO type being parsed, so there is 583 * no reason to add <js>"_type"</js> attributes to the root-level object. 584 * 585 * @param o The object to get the expected type on. 586 * @return The expected type. 587 */ 588 protected final ClassMeta<?> getExpectedRootType(Object o) { 589 if (isAddRootType()) 590 return object(); 591 ClassMeta<?> cm = getClassMetaForObject(o); 592 if (cm != null && cm.isOptional()) 593 return cm.getElementType(); 594 return cm; 595 } 596 597 /** 598 * Optional method that specifies HTTP request headers for this serializer. 599 * 600 * <p> 601 * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header. 602 * 603 * <p> 604 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 605 * or client). 606 * 607 * @return 608 * The HTTP headers to set on HTTP requests. 609 * Never <jk>null</jk>. 610 */ 611 public Map<String,String> getResponseHeaders() { 612 return Collections.emptyMap(); 613 } 614 615 /** 616 * Returns the listener associated with this session. 617 * 618 * @param c The listener class to cast to. 619 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 620 */ 621 @SuppressWarnings("unchecked") 622 public <T extends SerializerListener> T getListener(Class<T> c) { 623 return (T)listener; 624 } 625 626 /** 627 * Resolves any variables in the specified string. 628 * 629 * @param string The string to resolve values in. 630 * @return The string with variables resolved. 631 */ 632 public String resolve(String string) { 633 return getVarResolver().resolve(string); 634 } 635 636 /** 637 * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}. 638 * 639 * @param attrName The attribute name. 640 * @param o The current object being traversed. 641 * @param eType The expected class type. 642 * @return 643 * The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed 644 * once (since they can be expensive). 645 * @throws SerializeException If recursion occurred. 646 */ 647 protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { 648 try { 649 return super.push(attrName, o, eType); 650 } catch (BeanRecursionException e) { 651 throw new SerializeException(e); 652 } 653 } 654 655 /** 656 * Invokes the specified swap on the specified object. 657 * 658 * @param swap The swap to invoke. 659 * @param o The input object. 660 * @return The swapped object. 661 * @throws SerializeException If swap method threw an exception. 662 */ 663 @SuppressWarnings({ "rawtypes", "unchecked" }) 664 protected Object swap(PojoSwap swap, Object o) throws SerializeException { 665 try { 666 return swap.swap(this, o); 667 } catch (Exception e) { 668 throw new SerializeException(e); 669 } 670 } 671 672 //----------------------------------------------------------------------------------------------------------------- 673 // Properties 674 //----------------------------------------------------------------------------------------------------------------- 675 676 /** 677 * Configuration property: Add <js>"_type"</js> properties when needed. 678 * 679 * @see Serializer#SERIALIZER_addBeanTypes 680 * @return 681 * <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred 682 * through reflection. 683 */ 684 protected boolean isAddBeanTypes() { 685 return ctx.isAddBeanTypes(); 686 } 687 688 /** 689 * Configuration property: Add type attribute to root nodes. 690 * 691 * @see Serializer#SERIALIZER_addRootType 692 * @return 693 * <jk>true</jk> if type property should be added to root node. 694 */ 695 protected final boolean isAddRootType() { 696 return ctx.isAddRootType(); 697 } 698 699 /** 700 * Returns the listener associated with this session. 701 * 702 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 703 */ 704 public SerializerListener getListener() { 705 return listener; 706 } 707 708 /** 709 * Configuration property: Sort arrays and collections alphabetically. 710 * 711 * @see Serializer#SERIALIZER_sortCollections 712 * @return 713 * <jk>true</jk> if arrays and collections are copied and sorted before serialization. 714 */ 715 protected final boolean isSortCollections() { 716 return ctx.isSortCollections(); 717 } 718 719 /** 720 * Configuration property: Sort maps alphabetically. 721 * 722 * @see Serializer#SERIALIZER_sortMaps 723 * @return 724 * <jk>true</jk> if maps are copied and sorted before serialization. 725 */ 726 protected final boolean isSortMaps() { 727 return ctx.isSortMaps(); 728 } 729 730 /** 731 * Configuration property: Trim empty lists and arrays. 732 * 733 * @see Serializer#SERIALIZER_trimEmptyCollections 734 * @return 735 * <jk>true</jk> if empty lists and arrays are not serialized to the output. 736 */ 737 protected final boolean isTrimEmptyCollections() { 738 return ctx.isTrimEmptyCollections(); 739 } 740 741 /** 742 * Configuration property: Trim empty maps. 743 * 744 * @see Serializer#SERIALIZER_trimEmptyMaps 745 * @return 746 * <jk>true</jk> if empty map values are not serialized to the output. 747 */ 748 protected final boolean isTrimEmptyMaps() { 749 return ctx.isTrimEmptyMaps(); 750 } 751 752 /** 753 * Configuration property: Trim null bean property values. 754 * 755 * @see Serializer#SERIALIZER_trimNullProperties 756 * @return 757 * <jk>true</jk> if null bean values are not serialized to the output. 758 */ 759 protected final boolean isTrimNullProperties() { 760 return ctx.isTrimNullProperties(); 761 } 762 763 /** 764 * Configuration property: Trim strings. 765 * 766 * @see Serializer#SERIALIZER_trimStrings 767 * @return 768 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 769 */ 770 protected boolean isTrimStrings() { 771 return ctx.isTrimStrings(); 772 } 773 774 /** 775 * Configuration property: URI context bean. 776 * 777 * @see Serializer#SERIALIZER_uriContext 778 * @return 779 * Bean used for resolution of URIs to absolute or root-relative form. 780 */ 781 protected final UriContext getUriContext() { 782 return ctx.getUriContext(); 783 } 784 785 /** 786 * Configuration property: URI relativity. 787 * 788 * @see Serializer#SERIALIZER_uriRelativity 789 * @return 790 * Defines what relative URIs are relative to when serializing any of the following: 791 */ 792 protected final UriRelativity getUriRelativity() { 793 return ctx.getUriRelativity(); 794 } 795 796 /** 797 * Configuration property: URI resolution. 798 * 799 * @see Serializer#SERIALIZER_uriResolution 800 * @return 801 * Defines the resolution level for URIs when serializing URIs. 802 */ 803 protected final UriResolution getUriResolution() { 804 return ctx.getUriResolution(); 805 } 806 807 //----------------------------------------------------------------------------------------------------------------- 808 // Other methods 809 //----------------------------------------------------------------------------------------------------------------- 810 811 @Override /* Session */ 812 public ObjectMap toMap() { 813 return super.toMap() 814 .append("SerializerSession", new DefaultFilteringObjectMap() 815 .append("uriResolver", uriResolver) 816 ); 817 } 818}