001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.serializer; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016 017import java.util.*; 018import java.util.concurrent.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.annotation.*; 022import org.apache.juneau.http.*; 023 024/** 025 * Represents a group of {@link Serializer Serializers} that can be looked up by media type. 026 * 027 * <h5 class='topic'>Description</h5> 028 * 029 * Provides the following features: 030 * <ul class='spaced-list'> 031 * <li> 032 * Finds serializers based on HTTP <c>Accept</c> header values. 033 * <li> 034 * Sets common properties on all serializers in a single method call. 035 * <li> 036 * Clones existing groups and all serializers within the group in a single method call. 037 * </ul> 038 * 039 * <h5 class='topic'>Match ordering</h5> 040 * 041 * Serializers are matched against <c>Accept</c> strings in the order they exist in this group. 042 * 043 * <p> 044 * Adding new entries will cause the entries to be prepended to the group. 045 * This allows for previous serializers to be overridden through subsequent calls. 046 * 047 * <p> 048 * For example, calling <code>g.append(S1.<jk>class</jk>,S2.<jk>class</jk>).append(S3.<jk>class</jk>,S4.<jk>class</jk>)</code> 049 * will result in the order <c>S3, S4, S1, S2</c>. 050 * 051 * <h5 class='section'>Example:</h5> 052 * <p class='bcode w800'> 053 * <jc>// Construct a new serializer group</jc> 054 * SerializerGroup g = SerializerGroup.<jsm>create</jsm>(); 055 * .append(JsonSerializer.<jk>class</jk>, XmlSerializer.<jk>class</jk>); <jc>// Add some serializers to it</jc> 056 * .ws().pojoSwaps(TemporalCalendarSwap.IsoLocalDateTime.<jk>class</jk>) <jc>// Change settings for all serializers in the group.</jc> 057 * .build(); 058 * 059 * <jc>// Find the appropriate serializer by Accept type</jc> 060 * WriterSerializer s = g.getWriterSerializer(<js>"text/foo, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0"</js>); 061 * 062 * <jc>// Serialize a bean to JSON text </jc> 063 * AddressBook addressBook = <jk>new</jk> AddressBook(); <jc>// Bean to serialize.</jc> 064 * String json = s.serialize(addressBook); 065 * </p> 066 */ 067@ConfigurableContext(nocache=true) 068public final class SerializerGroup extends BeanTraverseContext { 069 070 /** 071 * An unmodifiable empty serializer group. 072 */ 073 public static final SerializerGroup EMPTY = create().build(); 074 075 // Maps Accept headers to matching serializers. 076 private final ConcurrentHashMap<String,SerializerMatch> cache = new ConcurrentHashMap<>(); 077 078 private final MediaTypeRange[] mediaTypeRanges; 079 private final Serializer[] mediaTypeRangeSerializers; 080 081 private final List<MediaType> mediaTypesList; 082 private final List<Serializer> serializers; 083 084 /** 085 * Instantiates a new clean-slate {@link SerializerGroupBuilder} object. 086 * 087 * <p> 088 * This is equivalent to simply calling <code><jk>new</jk> SerializerGroupBuilder()</code>. 089 * 090 * @return A new {@link SerializerGroupBuilder} object. 091 */ 092 public static SerializerGroupBuilder create() { 093 return new SerializerGroupBuilder(); 094 } 095 096 /** 097 * Returns a builder that's a copy of the settings on this serializer group. 098 * 099 * @return A new {@link SerializerGroupBuilder} initialized to this group. 100 */ 101 @Override /* Context */ 102 public SerializerGroupBuilder builder() { 103 return new SerializerGroupBuilder(this); 104 } 105 106 /** 107 * Constructor. 108 * 109 * @param ps 110 * The modifiable properties that were used to initialize the serializers. 111 * A snapshot of these will be made so that we can clone and modify this group. 112 * @param serializers 113 * The serializers defined in this group. 114 * The order is important because they will be tried in reverse order (e.g.newer first) in which they will be tried 115 * to match against media types. 116 */ 117 public SerializerGroup(PropertyStore ps, Serializer[] serializers) { 118 super(ps); 119 this.serializers = immutableList(serializers); 120 121 List<MediaTypeRange> lmtr = new ArrayList<>(); 122 LinkedHashSet<MediaType> lmt = new LinkedHashSet<>(); 123 List<Serializer> l = new ArrayList<>(); 124 for (Serializer s : serializers) { 125 for (MediaTypeRange m: s.getMediaTypeRanges()) { 126 lmtr.add(m); 127 l.add(s); 128 } 129 for (MediaType mt : s.getAcceptMediaTypes()) 130 lmt.add(mt); 131 } 132 133 this.mediaTypeRanges = lmtr.toArray(new MediaTypeRange[lmt.size()]); 134 this.mediaTypesList = unmodifiableList(new ArrayList<>(lmt)); 135 this.mediaTypeRangeSerializers = l.toArray(new Serializer[l.size()]); 136 } 137 138 /** 139 * Searches the group for a serializer that can handle the specified <c>Accept</c> value. 140 * 141 * <p> 142 * The <c>accept</c> value complies with the syntax described in RFC2616, Section 14.1, as described below: 143 * <p class='bcode w800'> 144 * Accept = "Accept" ":" 145 * #( media-range [ accept-params ] ) 146 * 147 * media-range = ( "*\/*" 148 * | ( type "/" "*" ) 149 * | ( type "/" subtype ) 150 * ) *( ";" parameter ) 151 * accept-params = ";" "q" "=" qvalue *( accept-extension ) 152 * accept-extension = ";" token [ "=" ( token | quoted-string ) ] 153 * </p> 154 * 155 * <p> 156 * The returned object includes both the serializer and media type that matched. 157 * 158 * @param acceptHeader The HTTP <l>Accept</l> header string. 159 * @return The serializer and media type that matched the accept header, or <jk>null</jk> if no match was made. 160 */ 161 public SerializerMatch getSerializerMatch(String acceptHeader) { 162 SerializerMatch sm = cache.get(acceptHeader); 163 if (sm != null) 164 return sm; 165 166 Accept a = Accept.forString(acceptHeader); 167 int match = a.findMatch(mediaTypeRanges); 168 if (match >= 0) { 169 sm = new SerializerMatch(mediaTypeRanges[match].getMediaType(), mediaTypeRangeSerializers[match]); 170 cache.putIfAbsent(acceptHeader, sm); 171 } 172 173 return cache.get(acceptHeader); 174 } 175 176 /** 177 * Same as {@link #getSerializerMatch(String)} but matches using a {@link MediaType} instance. 178 * 179 * @param mediaType The HTTP media type. 180 * @return The serializer and media type that matched the media type, or <jk>null</jk> if no match was made. 181 */ 182 public SerializerMatch getSerializerMatch(MediaType mediaType) { 183 return getSerializerMatch(mediaType.toString()); 184 } 185 186 /** 187 * Same as {@link #getSerializerMatch(String)} but returns just the matched serializer. 188 * 189 * @param acceptHeader The HTTP <l>Accept</l> header string. 190 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 191 */ 192 public Serializer getSerializer(String acceptHeader) { 193 SerializerMatch sm = getSerializerMatch(acceptHeader); 194 return sm == null ? null : sm.getSerializer(); 195 } 196 197 /** 198 * Same as {@link #getSerializerMatch(MediaType)} but returns just the matched serializer. 199 * 200 * @param mediaType The HTTP media type. 201 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 202 */ 203 public Serializer getSerializer(MediaType mediaType) { 204 if (mediaType == null) 205 return null; 206 return getSerializer(mediaType.toString()); 207 } 208 209 /** 210 * Same as {@link #getSerializer(String)}, but casts it to a {@link WriterSerializer}. 211 * 212 * @param acceptHeader The HTTP <l>Accept</l> header string. 213 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 214 */ 215 public WriterSerializer getWriterSerializer(String acceptHeader) { 216 return (WriterSerializer)getSerializer(acceptHeader); 217 } 218 219 /** 220 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link WriterSerializer}. 221 * 222 * @param mediaType The HTTP media type. 223 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 224 */ 225 public WriterSerializer getWriterSerializer(MediaType mediaType) { 226 return (WriterSerializer)getSerializer(mediaType); 227 } 228 229 /** 230 * Same as {@link #getSerializer(String)}, but casts it to an {@link OutputStreamSerializer}. 231 * 232 * @param acceptHeader The HTTP <l>Accept</l> header string. 233 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 234 */ 235 public OutputStreamSerializer getStreamSerializer(String acceptHeader) { 236 return (OutputStreamSerializer)getSerializer(acceptHeader); 237 } 238 239 /** 240 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link OutputStreamSerializer}. 241 * 242 * @param mediaType The HTTP media type. 243 * @return The serializer that matched the accept header, or <jk>null</jk> if no match was made. 244 */ 245 public OutputStreamSerializer getStreamSerializer(MediaType mediaType) { 246 return (OutputStreamSerializer)getSerializer(mediaType); 247 } 248 249 /** 250 * Returns the media types that all serializers in this group can handle. 251 * 252 * <p> 253 * Entries are ordered in the same order as the serializers in the group. 254 * 255 * @return An unmodifiable list of media types. 256 */ 257 public List<MediaType> getSupportedMediaTypes() { 258 return mediaTypesList; 259 } 260 261 /** 262 * Returns a copy of the serializers in this group. 263 * 264 * @return An unmodifiable list of serializers in this group. 265 */ 266 public List<Serializer> getSerializers() { 267 return serializers; 268 } 269}