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.internal.StringUtils.*; 016 017import java.util.*; 018import java.util.function.*; 019 020import org.apache.http.*; 021import org.apache.juneau.http.*; 022import org.apache.juneau.json.*; 023 024/** 025 * Category of headers that consist of a single parameterized string value. 026 * 027 * <p> 028 * <h5 class='figure'>Example</h5> 029 * <p class='bcode w800'> 030 * Content-Type: application/json;charset=utf-8 031 * </p> 032 * 033 * <ul class='seealso'> 034 * <li class='extlink'>{@doc ExtRFC2616} 035 * </ul> 036*/ 037public class BasicMediaTypeHeader extends BasicStringHeader { 038 039 private static final long serialVersionUID = 1L; 040 041 /** 042 * Convenience creator. 043 * 044 * @param name The header name. 045 * @param value 046 * The header value. 047 * <br>Can be any of the following: 048 * <ul> 049 * <li>{@link String} 050 * <li>Anything else - Converted to <c>String</c> then parsed. 051 * </ul> 052 * @return A new {@link BasicMediaTypeHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>. 053 */ 054 public static BasicMediaTypeHeader of(String name, Object value) { 055 if (isEmpty(name) || value == null) 056 return null; 057 return new BasicMediaTypeHeader(name, value); 058 } 059 060 /** 061 * Convenience creator using supplier. 062 * 063 * <p> 064 * Header value is re-evaluated on each call to {@link #getValue()}. 065 * 066 * @param name The header name. 067 * @param value 068 * The header value supplier. 069 * <br>Can be any of the following: 070 * <ul> 071 * <li>{@link String} 072 * <li>Anything else - Converted to <c>String</c> then parsed. 073 * </ul> 074 * @return A new {@link BasicMediaTypeHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>. 075 */ 076 public static BasicMediaTypeHeader of(String name, Supplier<?> value) { 077 if (isEmpty(name) || value == null) 078 return null; 079 return new BasicMediaTypeHeader(name, value); 080 } 081 082 private MediaType parsed; 083 084 /** 085 * Constructor 086 * 087 * @param name The header name. 088 * @param value 089 * <br>Can be any of the following: 090 * <ul> 091 * <li>{@link String} 092 * <li>Anything else - Converted to <c>String</c> then parsed. 093 * <li>A {@link Supplier} of anything on this list. 094 * </ul> 095 */ 096 public BasicMediaTypeHeader(String name, Object value) { 097 super(name, value); 098 if (! isSupplier(value)) 099 parsed = parse(); 100 } 101 102 /** 103 * Returns this header as a {@link MediaType} object. 104 * 105 * @return This header as a {@link MediaType} object. 106 */ 107 public MediaType asMediaType() { 108 return parse(); 109 } 110 111 /** 112 * Given a list of media types, returns the best match for this <c>Content-Type</c> header. 113 * 114 * <p> 115 * Note that fuzzy matching is allowed on the media types where the <c>Content-Types</c> header may 116 * contain additional subtype parts. 117 * <br>For example, given a <c>Content-Type</c> value of <js>"text/json+activity"</js>, 118 * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js> 119 * isn't found. 120 * <br>The purpose for this is to allow parsers to match when artifacts such as <c>id</c> properties are 121 * present in the header. 122 * 123 * @param mediaTypes The media types to match against. 124 * @return The index into the array of the best match, or <c>-1</c> if no suitable matches could be found. 125 */ 126 public int match(List<MediaType> mediaTypes) { 127 int matchQuant = 0, matchIndex = -1; 128 129 for (int i = 0; i < mediaTypes.size(); i++) { 130 MediaType mt = mediaTypes.get(i); 131 int matchQuant2 = mt.match(asMediaType(), true); 132 if (matchQuant2 > matchQuant) { 133 matchQuant = matchQuant2; 134 matchIndex = i; 135 } 136 } 137 return matchIndex; 138 } 139 140 /** 141 * Returns the <js>'type'</js> fragment of the <js>'type/subType'</js> string. 142 * 143 * @return The media type. 144 */ 145 public final String getType() { 146 return asMediaType().getType(); 147 } 148 149 /** 150 * Returns the <js>'subType'</js> fragment of the <js>'type/subType'</js> string. 151 * 152 * @return The media subtype. 153 */ 154 public final String getSubType() { 155 return asMediaType().getSubType(); 156 } 157 158 /** 159 * Returns <jk>true</jk> if the subtype contains the specified <js>'+'</js> delimited subtype value. 160 * 161 * @param st 162 * The subtype string. 163 * Case is ignored. 164 * @return <jk>true</jk> if the subtype contains the specified subtype string. 165 */ 166 public final boolean hasSubType(String st) { 167 return asMediaType().hasSubType(st); 168 } 169 170 /** 171 * Returns the subtypes broken down by fragments delimited by <js>"'"</js>. 172 * 173 * <P> 174 * For example, the media type <js>"text/foo+bar"</js> will return a list of 175 * <code>[<js>'foo'</js>,<js>'bar'</js>]</code> 176 * 177 * @return An unmodifiable list of subtype fragments. Never <jk>null</jk>. 178 */ 179 public final List<String> getSubTypes() { 180 return asMediaType().getSubTypes(); 181 } 182 183 /** 184 * Returns <jk>true</jk> if this media type contains the <js>'*'</js> meta character. 185 * 186 * @return <jk>true</jk> if this media type contains the <js>'*'</js> meta character. 187 */ 188 public final boolean isMetaSubtype() { 189 return asMediaType().isMetaSubtype(); 190 } 191 192 /** 193 * Returns a match metric against the specified media type where a larger number represents a better match. 194 * 195 * <p> 196 * This media type can contain <js>'*'</js> metacharacters. 197 * <br>The comparison media type must not. 198 * 199 * <ul> 200 * <li>Exact matches (e.g. <js>"text/json"</js>/</js>"text/json"</js>) should match 201 * better than meta-character matches (e.g. <js>"text/*"</js>/</js>"text/json"</js>) 202 * <li>The comparison media type can have additional subtype tokens (e.g. <js>"text/json+foo"</js>) 203 * that will not prevent a match if the <c>allowExtraSubTypes</c> flag is set. 204 * The reverse is not true, e.g. the comparison media type must contain all subtype tokens found in the 205 * comparing media type. 206 * <ul> 207 * <li>We want the {@link JsonSerializer} (<js>"text/json"</js>) class to be able to handle requests for <js>"text/json+foo"</js>. 208 * <li>We want to make sure {@link org.apache.juneau.json.SimpleJsonSerializer} (<js>"text/json+simple"</js>) does not handle 209 * requests for <js>"text/json"</js>. 210 * </ul> 211 * More token matches should result in a higher match number. 212 * </ul> 213 * 214 * The formula is as follows for <c>type/subTypes</c>: 215 * <ul> 216 * <li>An exact match is <c>100,000</c>. 217 * <li>Add the following for type (assuming subtype match is <0): 218 * <ul> 219 * <li><c>10,000</c> for an exact match (e.g. <js>"text"</js>==<js>"text"</js>). 220 * <li><c>5,000</c> for a meta match (e.g. <js>"*"</js>==<js>"text"</js>). 221 * </ul> 222 * <li>Add the following for subtype (assuming type match is <0): 223 * <ul> 224 * <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>) 225 * <li><c>100</c> for every subtype entry match (e.g. <js>"json"</js>/<js>"json+foo"</js>) 226 * </ul> 227 * </ul> 228 * 229 * @param o The media type to compare with. 230 * @param allowExtraSubTypes If <jk>true</jk>, 231 * @return <jk>true</jk> if the media types match. 232 */ 233 public final int match(MediaType o, boolean allowExtraSubTypes) { 234 return asMediaType().match(o, allowExtraSubTypes); 235 } 236 237 /** 238 * Returns the additional parameters on this media type. 239 * 240 * <p> 241 * For example, given the media type string <js>"text/html;level=1"</js>, will return a map 242 * with the single entry <code>{level:[<js>'1'</js>]}</code>. 243 * 244 * @return The map of additional parameters, or an empty map if there are no parameters. 245 */ 246 public List<NameValuePair> getParameters() { 247 return asMediaType().getParameters(); 248 } 249 250 /** 251 * Returns a parameterized value of the header. 252 * 253 * <p class='bcode w800'> 254 * ContentType ct = ContentType.<jsm>of</jsm>(<js>"application/json;charset=foo"</js>); 255 * assertEquals(<js>"foo"</js>, ct.getParameter(<js>"charset"</js>); 256 * </p> 257 * 258 * @param name The header name. 259 * @return The header value, or <jk>null</jk> if the parameter is not present. 260 */ 261 public String getParameter(String name) { 262 return asMediaType().getParameter(name); 263 } 264 265 @Override /* Header */ 266 public String getValue() { 267 Object o = getRawValue(); 268 if (o == null) 269 return null; 270 return stringify(asMediaType()); 271 } 272 273 private MediaType parse() { 274 if (parsed != null) 275 return parsed; 276 Object o = getRawValue(); 277 if (o == null) 278 o = ""; 279 if (o instanceof MediaType) 280 return (MediaType)o; 281 return new MediaType(o.toString()); 282 } 283}