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