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