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.http.header; 014 015import static org.apache.juneau.common.internal.StringUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017 018import java.util.*; 019import java.util.function.*; 020 021import org.apache.http.*; 022import org.apache.juneau.*; 023import org.apache.juneau.json.*; 024 025/** 026 * Category of headers that consist of a single parameterized string value. 027 * 028 * <p> 029 * <h5 class='figure'>Example</h5> 030 * <p class='bcode'> 031 * Content-Type: application/json;charset=utf-8 032 * </p> 033 * 034 * <h5 class='section'>See Also:</h5><ul> 035 * <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</a> 036 * <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a> 037 * </ul> 038 * 039 * @serial exclude 040 */ 041public class BasicMediaTypeHeader extends BasicStringHeader { 042 043 //----------------------------------------------------------------------------------------------------------------- 044 // Static 045 //----------------------------------------------------------------------------------------------------------------- 046 047 private static final long serialVersionUID = 1L; 048 049 /** 050 * Static creator. 051 * 052 * @param name The header name. 053 * @param value 054 * The header value. 055 * <br>Must be parsable by {@link MediaType#of(String)}. 056 * <br>Can be <jk>null</jk>. 057 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 058 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 059 */ 060 public static BasicMediaTypeHeader of(String name, String value) { 061 return value == null ? null : new BasicMediaTypeHeader(name, value); 062 } 063 064 /** 065 * Static creator. 066 * 067 * @param name The header name. 068 * @param value 069 * The header value. 070 * <br>Can be <jk>null</jk>. 071 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 072 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 073 */ 074 public static BasicMediaTypeHeader of(String name, MediaType value) { 075 return value == null ? null : new BasicMediaTypeHeader(name, value); 076 } 077 078 //----------------------------------------------------------------------------------------------------------------- 079 // Instance 080 //----------------------------------------------------------------------------------------------------------------- 081 082 private final MediaType value; 083 private final Supplier<MediaType> supplier; 084 085 /** 086 * Constructor. 087 * 088 * @param name The header name. 089 * @param value 090 * The header value. 091 * <br>Must be parsable by {@link MediaType#of(String)}. 092 * <br>Can be <jk>null</jk>. 093 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 094 */ 095 public BasicMediaTypeHeader(String name, String value) { 096 super(name, value); 097 this.value = parse(value); 098 this.supplier = null; 099 } 100 101 /** 102 * Constructor. 103 * 104 * @param name The header name. 105 * @param value 106 * The header value. 107 * <br>Can be <jk>null</jk>. 108 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 109 */ 110 public BasicMediaTypeHeader(String name, MediaType value) { 111 super(name, stringify(value)); 112 this.value = value; 113 this.supplier = null; 114 } 115 116 /** 117 * Constructor with delayed value. 118 * 119 * <p> 120 * Header value is re-evaluated on each call to {@link #getValue()}. 121 * 122 * @param name The header name. 123 * @param value 124 * The supplier of the header value. 125 * <br>Can be <jk>null</jk>. 126 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 127 */ 128 public BasicMediaTypeHeader(String name, Supplier<MediaType> value) { 129 super(name, (String)null); 130 this.value = null; 131 this.supplier = value; 132 } 133 134 @Override /* Header */ 135 public String getValue() { 136 return stringify(value()); 137 } 138 139 /** 140 * Returns the header value as a {@link MediaType} wrapped in an {@link Optional}. 141 * 142 * @return The header value as a {@link MediaType} wrapped in an {@link Optional}. Never <jk>null</jk>. 143 */ 144 public Optional<MediaType> asMediaType() { 145 return optional(value()); 146 } 147 148 /** 149 * Returns the header value as a {@link MediaType}. 150 * 151 * @return The header value as a {@link MediaType}. Can be <jk>null</jk>. 152 */ 153 public MediaType toMediaType() { 154 return value(); 155 } 156 157 /** 158 * Given a list of media types, returns the best match for this <c>Content-Type</c> header. 159 * 160 * <p> 161 * Note that fuzzy matching is allowed on the media types where the <c>Content-Types</c> header may 162 * contain additional subtype parts. 163 * <br>For example, given a <c>Content-Type</c> value of <js>"text/json+activity"</js>, 164 * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js> 165 * isn't found. 166 * <br>The purpose for this is to allow parsers to match when artifacts such as <c>id</c> properties are 167 * present in the header. 168 * 169 * @param mediaTypes The media types to match against. 170 * @return The index into the array of the best match, or <c>-1</c> if no suitable matches could be found. 171 */ 172 public int match(List<MediaType> mediaTypes) { 173 int matchQuant = 0, matchIndex = -1; 174 175 for (int i = 0; i < mediaTypes.size(); i++) { 176 MediaType mt = mediaTypes.get(i); 177 int matchQuant2 = mt.match(orElse(MediaType.EMPTY), true); 178 if (matchQuant2 > matchQuant) { 179 matchQuant = matchQuant2; 180 matchIndex = i; 181 } 182 } 183 return matchIndex; 184 } 185 186 /** 187 * Returns the <js>'type'</js> fragment of the <js>'type/subType'</js> string. 188 * 189 * @return The media type. 190 */ 191 public final String getType() { 192 return orElse(MediaType.EMPTY).getType(); 193 } 194 195 /** 196 * Returns the <js>'subType'</js> fragment of the <js>'type/subType'</js> string. 197 * 198 * @return The media subtype. 199 */ 200 public final String getSubType() { 201 return orElse(MediaType.EMPTY).getSubType(); 202 } 203 204 /** 205 * Returns <jk>true</jk> if the subtype contains the specified <js>'+'</js> delimited subtype value. 206 * 207 * @param value 208 * The subtype string. 209 * Case is ignored. 210 * @return <jk>true</jk> if the subtype contains the specified subtype string. 211 */ 212 public final boolean hasSubType(String value) { 213 return orElse(MediaType.EMPTY).hasSubType(value); 214 } 215 216 /** 217 * Returns the subtypes broken down by fragments delimited by <js>"'"</js>. 218 * 219 * <P> 220 * For example, the media type <js>"text/foo+bar"</js> will return a list of 221 * <code>[<js>'foo'</js>,<js>'bar'</js>]</code> 222 * 223 * @return An unmodifiable list of subtype fragments. Can be <jk>null</jk>. 224 */ 225 public final List<String> getSubTypes() { 226 return orElse(MediaType.EMPTY).getSubTypes(); 227 } 228 229 /** 230 * Returns <jk>true</jk> if this media type contains the <js>'*'</js> meta character. 231 * 232 * @return <jk>true</jk> if this media type contains the <js>'*'</js> meta character. 233 */ 234 public final boolean isMetaSubtype() { 235 return orElse(MediaType.EMPTY).isMetaSubtype(); 236 } 237 238 /** 239 * Returns a match metric against the specified media type where a larger number represents a better match. 240 * 241 * <p> 242 * This media type can contain <js>'*'</js> metacharacters. 243 * <br>The comparison media type must not. 244 * 245 * <ul> 246 * <li>Exact matches (e.g. <js>"text/json"</js>/</js>"text/json"</js>) should match 247 * better than meta-character matches (e.g. <js>"text/*"</js>/</js>"text/json"</js>) 248 * <li>The comparison media type can have additional subtype tokens (e.g. <js>"text/json+foo"</js>) 249 * that will not prevent a match if the <c>allowExtraSubTypes</c> flag is set. 250 * The reverse is not true, e.g. the comparison media type must contain all subtype tokens found in the 251 * comparing media type. 252 * <ul> 253 * <li>We want the {@link JsonSerializer} (<js>"text/json"</js>) class to be able to handle requests for <js>"text/json+foo"</js>. 254 * <li>We want to make sure {@link org.apache.juneau.json.Json5Serializer} (<js>"text/json5"</js>) does not handle 255 * requests for <js>"text/json"</js>. 256 * </ul> 257 * More token matches should result in a higher match number. 258 * </ul> 259 * 260 * The formula is as follows for <c>type/subTypes</c>: 261 * <ul> 262 * <li>An exact match is <c>100,000</c>. 263 * <li>Add the following for type (assuming subtype match is <0): 264 * <ul> 265 * <li><c>10,000</c> for an exact match (e.g. <js>"text"</js>==<js>"text"</js>). 266 * <li><c>5,000</c> for a meta match (e.g. <js>"*"</js>==<js>"text"</js>). 267 * </ul> 268 * <li>Add the following for subtype (assuming type match is <0): 269 * <ul> 270 * <li><c>7,500</c> for an exact match (e.g. <js>"json+foo"</js>==<js>"json+foo"</js> or <js>"json+foo"</js>==<js>"foo+json"</js>) 271 * <li><c>100</c> for every subtype entry match (e.g. <js>"json"</js>/<js>"json+foo"</js>) 272 * </ul> 273 * </ul> 274 * 275 * @param o The media type to compare with. 276 * @param allowExtraSubTypes If <jk>true</jk>, 277 * @return <jk>true</jk> if the media types match. 278 */ 279 public final int match(MediaType o, boolean allowExtraSubTypes) { 280 return orElse(MediaType.EMPTY).match(o, allowExtraSubTypes); 281 } 282 283 /** 284 * Returns the additional parameters on this media type. 285 * 286 * <p> 287 * For example, given the media type string <js>"text/html;level=1"</js>, will return a map 288 * with the single entry <code>{level:[<js>'1'</js>]}</code>. 289 * 290 * @return The map of additional parameters, or an empty map if there are no parameters. 291 */ 292 public List<NameValuePair> getParameters() { 293 return orElse(MediaType.EMPTY).getParameters(); 294 } 295 296 /** 297 * Returns a parameterized value of the header. 298 * 299 * <p class='bjava'> 300 * ContentType <jv>contentType</jv> = ContentType.<jsm>of</jsm>(<js>"application/json;charset=foo"</js>); 301 * <jsm>assertEquals</jsm>(<js>"foo"</js>, <jv>contentType</jv>.getParameter(<js>"charset"</js>); 302 * </p> 303 * 304 * @param name The header name. 305 * @return The header value, or <jk>null</jk> if the parameter is not present. 306 */ 307 public String getParameter(String name) { 308 return orElse(MediaType.EMPTY).getParameter(name); 309 } 310 311 /** 312 * Return the value if present, otherwise return <c>other</c>. 313 * 314 * <p> 315 * This is a shortened form for calling <c>asMediaType().orElse(<jv>other</jv>)</c>. 316 * 317 * @param other The value to be returned if there is no value present, can be <jk>null</jk>. 318 * @return The value, if present, otherwise <c>other</c>. 319 */ 320 public MediaType orElse(MediaType other) { 321 MediaType x = value(); 322 return x != null ? x : other; 323 } 324 325 private MediaType parse(String value) { 326 // If this happens to be a multi-value, use the last value. 327 if (value != null) { 328 int i = value.indexOf(','); 329 if (i != -1) 330 value = value.substring(i+1); 331 } 332 return MediaType.of(value); 333 } 334 335 private MediaType value() { 336 if (supplier != null) 337 return supplier.get(); 338 return value; 339 } 340}