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