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