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