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 java.util.stream.Collectors.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017import static org.apache.juneau.internal.ObjectUtils.*; 018 019import java.util.*; 020import java.util.concurrent.*; 021import java.util.function.*; 022import java.util.stream.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.cp.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.reflect.*; 028 029/** 030 * Represents a group of {@link Serializer Serializers} that can be looked up by media type. 031 * 032 * <h5 class='topic'>Description</h5> 033 * 034 * Provides the following features: 035 * <ul class='spaced-list'> 036 * <li> 037 * Finds serializers based on HTTP <c>Accept</c> header values. 038 * <li> 039 * Sets common properties on all serializers in a single method call. 040 * <li> 041 * Clones existing groups and all serializers within the group in a single method call. 042 * </ul> 043 * 044 * <h5 class='topic'>Match ordering</h5> 045 * 046 * Serializers are matched against <c>Accept</c> strings in the order they exist in this group. 047 * 048 * <p> 049 * Adding new entries will cause the entries to be prepended to the group. 050 * This allows for previous serializers to be overridden through subsequent calls. 051 * 052 * <p> 053 * For example, calling <code>g.append(S1.<jk>class</jk>,S2.<jk>class</jk>).append(S3.<jk>class</jk>,S4.<jk>class</jk>)</code> 054 * will result in the order <c>S3, S4, S1, S2</c>. 055 * 056 * <h5 class='section'>Example:</h5> 057 * <p class='bjava'> 058 * <jc>// Construct a new serializer group</jc> 059 * SerializerSet <jv>serializers</jv> = SerializerSet.<jsm>create</jsm>(); 060 * .add(JsonSerializer.<jk>class</jk>, UrlEncodingSerializer.<jk>class</jk>) <jc>// Add some serializers to it</jc> 061 * .forEach(<jv>x</jv> -> <jv>x</jv>.swaps(TemporalCalendarSwap.IsoLocalDateTime.<jk>class</jk>)) 062 * .forEachWS(<jv>x</jv> -> <jv>x</jv>.ws()) 063 * .build(); 064 * 065 * <jc>// Find the appropriate serializer by Accept type</jc> 066 * WriterSerializer <jv>serializer</jv> = <jv>serializers</jv> 067 * .getWriterSerializer(<js>"text/foo, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0"</js>); 068 * 069 * <jc>// Serialize a bean to JSON text </jc> 070 * AddressBook <jv>addressBook</jv> = <jk>new</jk> AddressBook(); <jc>// Bean to serialize.</jc> 071 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jv>addressBook</jv>); 072 * </p> 073 * 074 * <h5 class='section'>Notes:</h5><ul> 075 * <li class='note'>This class is thread safe and reusable. 076 * </ul> 077 * 078 * <h5 class='section'>See Also:</h5><ul> 079 * <li class='link'><a class="doclink" href="../../../../index.html#jm.SerializersAndParsers">Serializers and Parsers</a> 080 081 * </ul> 082 */ 083public final class SerializerSet { 084 085 //----------------------------------------------------------------------------------------------------------------- 086 // Static 087 //----------------------------------------------------------------------------------------------------------------- 088 089 /** 090 * An identifier that the previous entries in this group should be inherited. 091 * <p> 092 * Used by {@link SerializerSet.Builder#set(Class...)} 093 */ 094 @SuppressWarnings("javadoc") 095 public static abstract class Inherit extends Serializer { 096 public Inherit(Serializer.Builder builder) { 097 super(builder); 098 } 099 } 100 101 /** 102 * An identifier that the previous entries in this group should not be inherited. 103 * <p> 104 * Used by {@link SerializerSet.Builder#add(Class...)} 105 */ 106 @SuppressWarnings("javadoc") 107 public static abstract class NoInherit extends Serializer { 108 public NoInherit(Serializer.Builder builder) { 109 super(builder); 110 } 111 } 112 113 /** 114 * Static creator. 115 * 116 * @param beanStore The bean store to use for creating beans. 117 * @return A new builder for this object. 118 */ 119 public static Builder create(BeanStore beanStore) { 120 return new Builder(beanStore); 121 } 122 123 /** 124 * Static creator. 125 * 126 * @return A new builder for this object. 127 */ 128 public static Builder create() { 129 return new Builder(BeanStore.INSTANCE); 130 } 131 132 //----------------------------------------------------------------------------------------------------------------- 133 // Builder 134 //----------------------------------------------------------------------------------------------------------------- 135 136 /** 137 * Builder class. 138 */ 139 @FluentSetters 140 public static class Builder extends BeanBuilder<SerializerSet> { 141 142 List<Object> entries; 143 private BeanContext.Builder bcBuilder; 144 145 /** 146 * Create an empty serializer group builder. 147 * 148 * @param beanStore The bean store to use for creating beans. 149 */ 150 protected Builder(BeanStore beanStore) { 151 super(SerializerSet.class, beanStore); 152 this.entries = list(); 153 } 154 155 /** 156 * Clone an existing serializer group. 157 * 158 * @param copyFrom The serializer group that we're copying settings and serializers from. 159 */ 160 protected Builder(SerializerSet copyFrom) { 161 super(copyFrom.getClass()); 162 this.entries = list((Object[])copyFrom.entries); 163 } 164 165 /** 166 * Clone an existing serializer group builder. 167 * 168 * <p> 169 * Serializer builders will be cloned during this process. 170 * 171 * @param copyFrom The serializer group that we're copying settings and serializers from. 172 */ 173 protected Builder(Builder copyFrom) { 174 super(copyFrom); 175 bcBuilder = copyFrom.bcBuilder == null ? null : copyFrom.bcBuilder.copy(); 176 entries = list(); 177 copyFrom.entries.stream().map(x -> copyBuilder(x)).forEach(x -> entries.add(x)); 178 } 179 180 private Object copyBuilder(Object o) { 181 if (o instanceof Serializer.Builder) { 182 Serializer.Builder x = (Serializer.Builder)o; 183 Serializer.Builder x2 = x.copy(); 184 if (ne(x.getClass(), x2.getClass())) 185 throw new BasicRuntimeException("Copy method not implemented on class {0}", x.getClass().getName()); 186 x = x2; 187 if (bcBuilder != null) 188 x.beanContext(bcBuilder); 189 return x; 190 } 191 return o; 192 } 193 194 @Override /* BeanBuilder */ 195 protected SerializerSet buildDefault() { 196 return new SerializerSet(this); 197 } 198 199 /** 200 * Makes a copy of this builder. 201 * 202 * @return A new copy of this builder. 203 */ 204 public Builder copy() { 205 return new Builder(this); 206 } 207 208 //------------------------------------------------------------------------------------------------------------- 209 // Properties 210 //------------------------------------------------------------------------------------------------------------- 211 212 /** 213 * Associates an existing bean context builder with all serializer builders in this group. 214 * 215 * @param value The bean contest builder to associate. 216 * @return This object. 217 */ 218 public Builder beanContext(BeanContext.Builder value) { 219 bcBuilder = value; 220 forEach(x -> x.beanContext(value)); 221 return this; 222 } 223 224 /** 225 * Applies an operation to the bean context builder. 226 * 227 * @param operation The operation to apply. 228 * @return This object. 229 */ 230 public final Builder beanContext(Consumer<BeanContext.Builder> operation) { 231 if (bcBuilder != null) 232 operation.accept(bcBuilder); 233 return this; 234 } 235 236 /** 237 * Adds the specified serializers to this group. 238 * 239 * <p> 240 * Entries are added in-order to the beginning of the list in the group. 241 * 242 * <p> 243 * The {@link NoInherit} class can be used to clear out the existing list of serializers before adding the new entries. 244 * 245 * <h5 class='section'>Example:</h5> 246 * <p class='bjava'> 247 * SerializerSet.Builder <jv>builder</jv> = SerializerSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 248 * 249 * <jv>builder</jv>.add(FooSerializer.<jk>class</jk>); <jc>// Now contains: [FooSerializer]</jc> 250 * 251 * <jv>builder</jv>.add(BarSerializer.<jk>class</jk>, BazSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer,FooSerializer]</jc> 252 * 253 * <jv>builder</jv>.add(NoInherit.<jk>class</jk>, QuxSerializer.<jk>class</jk>); <jc>// Now contains: [QuxSerializer]</jc> 254 * </p> 255 * 256 * @param values The serializers to add to this group. 257 * @return This object. 258 * @throws IllegalArgumentException If one or more values do not extend from {@link Serializer}. 259 */ 260 public Builder add(Class<?>...values) { 261 List<Object> l = list(); 262 for (Class<?> e : values) { 263 if (Serializer.class.isAssignableFrom(e)) { 264 l.add(createBuilder(e)); 265 } else { 266 throw new BasicRuntimeException("Invalid type passed to SerializeGroup.Builder.add(): {0}", e.getName()); 267 } 268 } 269 entries.addAll(0, l); 270 return this; 271 } 272 273 /** 274 * Sets the specified serializers for this group. 275 * 276 * <p> 277 * Existing values are overwritten. 278 * 279 * <p> 280 * The {@link Inherit} class can be used to insert existing entries in this group into the position specified. 281 * 282 * <h5 class='section'>Example:</h5> 283 * <p class='bjava'> 284 * SerializerSet.Builder <jv>builder</jv> = SerializerSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 285 * 286 * <jv>builder</jv>.set(FooSerializer.<jk>class</jk>); <jc>// Now contains: [FooSerializer]</jc> 287 * 288 * <jv>builder</jv>.set(BarSerializer.<jk>class</jk>, BazSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer]</jc> 289 * 290 * <jv>builder</jv>.set(Inherit.<jk>class</jk>, QuxSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer,QuxSerializer]</jc> 291 * </p> 292 * 293 * @param values The serializers to set in this group. 294 * @return This object. 295 * @throws IllegalArgumentException If one or more values do not extend from {@link Serializer} or named <js>"Inherit"</js>. 296 */ 297 public Builder set(Class<?>...values) { 298 List<Object> l = list(); 299 for (Class<?> e : values) { 300 if (e.getSimpleName().equals("Inherit")) { 301 l.addAll(entries); 302 } else if (Serializer.class.isAssignableFrom(e)) { 303 l.add(createBuilder(e)); 304 } else { 305 throw new BasicRuntimeException("Invalid type passed to SerializeGroup.Builder.set(): {0}", e.getName()); 306 } 307 } 308 entries = l; 309 return this; 310 } 311 312 private Object createBuilder(Object o) { 313 if (o instanceof Class) { 314 315 // Check for no-arg constructor. 316 ConstructorInfo ci = ClassInfo.of((Class<?>)o).getPublicConstructor(x -> x.hasNoParams()); 317 if (ci != null) 318 return ci.invoke(); 319 320 // Check for builder create method. 321 @SuppressWarnings("unchecked") 322 Serializer.Builder b = Serializer.createSerializerBuilder((Class<? extends Serializer>)o); 323 if (bcBuilder != null) 324 b.beanContext(bcBuilder); 325 o = b; 326 } 327 return o; 328 } 329 330 /** 331 * Registers the specified serializers with this group. 332 * 333 * <p> 334 * When passing in pre-instantiated serializers to this group, applying properties and transforms to the group 335 * do not affect them. 336 * 337 * @param s The serializers to append to this group. 338 * @return This object. 339 */ 340 public Builder add(Serializer...s) { 341 prependAll(entries, (Object[])s); 342 return this; 343 } 344 345 /** 346 * Clears out any existing serializers in this group. 347 * 348 * @return This object. 349 */ 350 public Builder clear() { 351 entries.clear(); 352 return this; 353 } 354 355 /** 356 * Returns <jk>true</jk> if at least one of the specified annotations can be applied to at least one serializer builder in this group. 357 * 358 * @param work The work to check. 359 * @return <jk>true</jk> if at least one of the specified annotations can be applied to at least one serializer builder in this group. 360 */ 361 public boolean canApply(AnnotationWorkList work) { 362 for (Object o : entries) 363 if (o instanceof Serializer.Builder) 364 if (((Serializer.Builder)o).canApply(work)) 365 return true; 366 return false; 367 } 368 369 /** 370 * Applies the specified annotations to all applicable serializer builders in this group. 371 * 372 * @param work The annotations to apply. 373 * @return This object. 374 */ 375 public Builder apply(AnnotationWorkList work) { 376 return forEach(x -> x.apply(work)); 377 } 378 379 /** 380 * Performs an action on all serializer builders in this group. 381 * 382 * @param action The action to perform. 383 * @return This object. 384 */ 385 public Builder forEach(Consumer<Serializer.Builder> action) { 386 builders(Serializer.Builder.class).forEach(action); 387 return this; 388 } 389 390 /** 391 * Performs an action on all writer serializer builders in this group. 392 * 393 * @param action The action to perform. 394 * @return This object. 395 */ 396 public Builder forEachWS(Consumer<WriterSerializer.Builder> action) { 397 return forEach(WriterSerializer.Builder.class, action); 398 } 399 400 /** 401 * Performs an action on all output stream serializer builders in this group. 402 * 403 * @param action The action to perform. 404 * @return This object. 405 */ 406 public Builder forEachOSS(Consumer<OutputStreamSerializer.Builder> action) { 407 return forEach(OutputStreamSerializer.Builder.class, action); 408 } 409 410 /** 411 * Performs an action on all serializer builders of the specified type in this group. 412 * 413 * @param <T> The serializer builder type. 414 * @param type The serializer builder type. 415 * @param action The action to perform. 416 * @return This object. 417 */ 418 public <T extends Serializer.Builder> Builder forEach(Class<T> type, Consumer<T> action) { 419 builders(type).forEach(action); 420 return this; 421 } 422 423 /** 424 * Returns direct access to the {@link Serializer} and {@link Serializer.Builder} objects in this builder. 425 * 426 * <p> 427 * Provided to allow for any extraneous modifications to the list not accomplishable via other methods on this builder such 428 * as re-ordering/adding/removing entries. 429 * 430 * <p> 431 * Note that it is up to the user to ensure that the list only contains {@link Serializer} and {@link Serializer.Builder} objects. 432 * 433 * @return The inner list of entries in this builder. 434 */ 435 public List<Object> inner() { 436 return entries; 437 } 438 439 private <T extends Serializer.Builder> Stream<T> builders(Class<T> type) { 440 return entries.stream().filter(x -> type.isInstance(x)).map(x -> type.cast(x)); 441 } 442 443 // <FluentSetters> 444 445 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 446 public Builder impl(Object value) { 447 super.impl(value); 448 return this; 449 } 450 451 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 452 public Builder type(Class<?> value) { 453 super.type(value); 454 return this; 455 } 456 457 // </FluentSetters> 458 459 //------------------------------------------------------------------------------------------------------------- 460 // Other methods 461 //------------------------------------------------------------------------------------------------------------- 462 463 @Override /* Object */ 464 public String toString() { 465 return entries.stream().map(x -> toString(x)).collect(joining(",","[","]")); 466 } 467 468 private String toString(Object o) { 469 if (o == null) 470 return "null"; 471 if (o instanceof Serializer.Builder) 472 return "builder:" + o.getClass().getName(); 473 return "serializer:" + o.getClass().getName(); 474 } 475 } 476 477 //----------------------------------------------------------------------------------------------------------------- 478 // Instance 479 //----------------------------------------------------------------------------------------------------------------- 480 481 // Maps Accept headers to matching serializers. 482 private final ConcurrentHashMap<String,SerializerMatch> cache = new ConcurrentHashMap<>(); 483 484 private final MediaRange[] mediaRanges; 485 private final List<MediaRange> mediaRangesList; 486 private final Serializer[] mediaTypeRangeSerializers; 487 488 private final MediaType[] mediaTypes; 489 private final List<MediaType> mediaTypesList; 490 final Serializer[] entries; 491 private final List<Serializer> entriesList; 492 493 /** 494 * Constructor. 495 * 496 * @param builder The builder for this bean. 497 */ 498 protected SerializerSet(Builder builder) { 499 500 this.entries = builder.entries.stream().map(x -> build(x)).toArray(Serializer[]::new); 501 this.entriesList = ulist(entries); 502 503 List<MediaRange> lmtr = list(); 504 Set<MediaType> lmt = set(); 505 List<Serializer> l = list(); 506 for (Serializer e : entries) { 507 e.getMediaTypeRanges().forEachRange(x -> { 508 lmtr.add(x); 509 l.add(e); 510 }); 511 e.forEachAcceptMediaType(x -> lmt.add(x)); 512 } 513 514 this.mediaRanges = lmtr.toArray(new MediaRange[lmtr.size()]); 515 this.mediaRangesList = ulist(mediaRanges); 516 this.mediaTypes = lmt.toArray(new MediaType[lmt.size()]); 517 this.mediaTypesList = ulist(mediaTypes); 518 this.mediaTypeRangeSerializers = l.toArray(new Serializer[l.size()]); 519 } 520 521 private Serializer build(Object o) { 522 if (o instanceof Serializer) 523 return (Serializer)o; 524 return ((Serializer.Builder)o).build(); 525 } 526 527 /** 528 * Creates a copy of this serializer group. 529 * 530 * @return A new copy of this serializer group. 531 */ 532 public Builder copy() { 533 return new Builder(this); 534 } 535 536 /** 537 * Searches the group for a serializer that can handle the specified <c>Accept</c> value. 538 * 539 * <p> 540 * The <c>accept</c> value complies with the syntax described in RFC2616, Section 14.1, as described below: 541 * <p class='bcode'> 542 * Accept = "Accept" ":" 543 * #( media-range [ accept-params ] ) 544 * 545 * media-range = ( "*\/*" 546 * | ( type "/" "*" ) 547 * | ( type "/" subtype ) 548 * ) *( ";" parameter ) 549 * accept-params = ";" "q" "=" qvalue *( accept-extension ) 550 * accept-extension = ";" token [ "=" ( token | quoted-string ) ] 551 * </p> 552 * 553 * <p> 554 * The returned object includes both the serializer and media type that matched. 555 * 556 * @param acceptHeader The HTTP <l>Accept</l> header string. 557 * @return The serializer and media type that matched the accept header, or <jk>null</jk> if no match was made. 558 */ 559 public SerializerMatch getSerializerMatch(String acceptHeader) { 560 if (acceptHeader == null) 561 return null; 562 SerializerMatch sm = cache.get(acceptHeader); 563 if (sm != null) 564 return sm; 565 566 MediaRanges a = MediaRanges.of(acceptHeader); 567 int match = a.match(mediaRangesList); 568 if (match >= 0) { 569 sm = new SerializerMatch(mediaRanges[match], mediaTypeRangeSerializers[match]); 570 cache.putIfAbsent(acceptHeader, sm); 571 } 572 573 return cache.get(acceptHeader); 574 } 575 576 /** 577 * Same as {@link #getSerializerMatch(String)} but matches using a {@link MediaType} instance. 578 * 579 * @param mediaType The HTTP media type. 580 * @return The serializer and media type that matched the media type, or <jk>null</jk> if no match was made. 581 */ 582 public SerializerMatch getSerializerMatch(MediaType mediaType) { 583 return getSerializerMatch(mediaType.toString()); 584 } 585 586 /** 587 * Same as {@link #getSerializerMatch(String)} but returns just the matched serializer. 588 * 589 * @param acceptHeader The HTTP <l>Accept</l> header string. 590 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 591 */ 592 public Serializer getSerializer(String acceptHeader) { 593 SerializerMatch sm = getSerializerMatch(acceptHeader); 594 return sm == null ? null : sm.getSerializer(); 595 } 596 597 /** 598 * Same as {@link #getSerializerMatch(MediaType)} but returns just the matched serializer. 599 * 600 * @param mediaType The HTTP media type. 601 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 602 */ 603 public Serializer getSerializer(MediaType mediaType) { 604 if (mediaType == null) 605 return null; 606 return getSerializer(mediaType.toString()); 607 } 608 609 /** 610 * Same as {@link #getSerializer(String)}, but casts it to a {@link WriterSerializer}. 611 * 612 * @param acceptHeader The HTTP <l>Accept</l> header string. 613 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 614 */ 615 public WriterSerializer getWriterSerializer(String acceptHeader) { 616 return (WriterSerializer)getSerializer(acceptHeader); 617 } 618 619 /** 620 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link WriterSerializer}. 621 * 622 * @param mediaType The HTTP media type. 623 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 624 */ 625 public WriterSerializer getWriterSerializer(MediaType mediaType) { 626 return (WriterSerializer)getSerializer(mediaType); 627 } 628 629 /** 630 * Same as {@link #getSerializer(String)}, but casts it to an {@link OutputStreamSerializer}. 631 * 632 * @param acceptHeader The HTTP <l>Accept</l> header string. 633 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 634 */ 635 public OutputStreamSerializer getStreamSerializer(String acceptHeader) { 636 return (OutputStreamSerializer)getSerializer(acceptHeader); 637 } 638 639 /** 640 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link OutputStreamSerializer}. 641 * 642 * @param mediaType The HTTP media type. 643 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 644 */ 645 public OutputStreamSerializer getStreamSerializer(MediaType mediaType) { 646 return (OutputStreamSerializer)getSerializer(mediaType); 647 } 648 649 /** 650 * Returns the media types that all serializers in this group can handle. 651 * 652 * <p> 653 * Entries are ordered in the same order as the serializers in the group. 654 * 655 * @return An unmodifiable list of media types. 656 */ 657 public List<MediaType> getSupportedMediaTypes() { 658 return mediaTypesList; 659 } 660 661 /** 662 * Returns a copy of the serializers in this group. 663 * 664 * @return An unmodifiable list of serializers in this group. 665 */ 666 public List<Serializer> getSerializers() { 667 return entriesList; 668 } 669 670 /** 671 * Returns <jk>true</jk> if this group contains no serializers. 672 * 673 * @return <jk>true</jk> if this group contains no serializers. 674 */ 675 public boolean isEmpty() { 676 return entries.length == 0; 677 } 678}