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.rest.httppart; 018 019import static org.apache.juneau.commons.utils.Utils.*; 020import static org.apache.juneau.httppart.HttpPartType.*; 021 022import java.lang.reflect.*; 023import java.time.*; 024import java.util.*; 025import java.util.regex.*; 026 027import org.apache.http.*; 028import org.apache.juneau.*; 029import org.apache.juneau.assertions.*; 030import org.apache.juneau.http.*; 031import org.apache.juneau.http.part.*; 032import org.apache.juneau.http.response.*; 033import org.apache.juneau.httppart.*; 034import org.apache.juneau.oapi.*; 035import org.apache.juneau.parser.ParseException; 036import org.apache.juneau.rest.*; 037 038/** 039 * Represents a single HTTP part on an HTTP request. 040 * 041 * Parent of the following classes: 042 * <ul class='javatreec'> 043 * <li class='jc'>{@link RequestHeader} 044 * <li class='jc'>{@link RequestQueryParam} 045 * <li class='jc'>{@link RequestFormParam} 046 * <li class='jc'>{@link RequestPathParam} 047 * </ul> 048 * 049 * <h5 class='section'>See Also:</h5><ul> 050 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HttpParts">HTTP Parts</a> 051 * </ul> 052 */ 053public class RequestHttpPart { 054 055 private final HttpPartType partType; 056 private final String name; 057 private final RestRequest request; 058 private HttpPartParserSession parser; 059 private HttpPartSchema schema; 060 String value; 061 062 /** 063 * Constructor. 064 * 065 * @param partType The HTTP part type. 066 * @param request The request object. 067 * @param name The part name. 068 * @param value The part value. 069 */ 070 public RequestHttpPart(HttpPartType partType, RestRequest request, String name, String value) { 071 this.partType = partType; 072 this.request = request; 073 this.name = name; 074 this.value = value; 075 parser(null); 076 } 077 078 /** 079 * Converts this part to the specified POJO type using the request {@link HttpPartParser} and optional schema. 080 * 081 * <p> 082 * If the specified type is an HTTP part type (extends from {@link org.apache.http.Header}/{@link NameValuePair}), then looks for 083 * one of the following constructors: 084 * <ul class='javatree'> 085 * <li class='jm><c><jk>public</jk> T(String <jv>value</jv>);</c> 086 * <li class='jm><c><jk>public</jk> T(String <jv>name</jv>, String <jv>value</jv>);</c> 087 * </ul> 088 * 089 * <p> 090 * If it doesn't find one of those constructors, then it parses it into the specified type using the part parser. 091 * 092 * @param <T> The type to convert to. 093 * @param type The type to convert to. 094 * @return The converted type, or {@link Optional#empty()} if the part is not present. 095 * @throws BasicHttpException If value could not be parsed. 096 */ 097 public <T> Optional<T> as(Class<T> type) throws BasicHttpException { 098 return as(request.getBeanSession().getClassMeta(type)); 099 } 100 101 /** 102 * Converts this part to the specified POJO type using the request {@link HttpPartParser} and optional schema. 103 * 104 * <p> 105 * If the specified type is an HTTP part type (extends from {@link org.apache.http.Header}/{@link NameValuePair}), then looks for 106 * one of the following constructors: 107 * <ul class='javatree'> 108 * <li class='jm><c><jk>public</jk> T(String <jv>value</jv>);</c> 109 * <li class='jm><c><jk>public</jk> T(String <jv>name</jv>, String <jv>value</jv>);</c> 110 * </ul> 111 * 112 * <p> 113 * If it doesn't find one of those constructors, then it parses it into the specified type using the part parser. 114 * 115 * @param <T> The type to convert to. 116 * @param type The type to convert to. 117 * @return The converted type, or {@link Optional#empty()} if the part is not present. 118 * @throws BasicHttpException If value could not be parsed. 119 */ 120 public <T> Optional<T> as(ClassMeta<T> type) throws BasicHttpException { 121 try { 122 if (HttpParts.isHttpPart(partType, type)) { 123 var cc = HttpParts.getConstructor(type).orElse(null); 124 if (nn(cc)) { 125 if (! isPresent()) 126 return opte(); 127 if (cc.hasParameterTypes(String.class)) 128 return opt(cc.newInstance(get())); 129 if (cc.hasParameterTypes(String.class, String.class)) 130 return opt(cc.newInstance(getName(), get())); 131 } 132 } 133 return opt(parser.parse(HEADER, schema, orElse(null), type)); 134 } catch (ParseException e) { 135 throw new BadRequest(e, "Could not parse {0} parameter ''{1}''.", partType.toString().toLowerCase(), getName()); 136 } 137 } 138 139 /** 140 * Converts this part to the specified POJO type using the request {@link HttpPartParser} and optional schema. 141 * 142 * <p> 143 * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}. 144 * 145 * @param <T> The type to convert to. 146 * @param type The type to convert to. 147 * @param args The type parameters. 148 * @return The converted type, or {@link Optional#empty()} if the part is not present. 149 * @throws BasicHttpException If value could not be parsed. 150 */ 151 public <T> Optional<T> as(Type type, Type...args) throws BasicHttpException { 152 return as(request.getBeanSession().getClassMeta(type, args)); 153 } 154 155 /** 156 * Returns the value of this parameter as a boolean. 157 * 158 * @return The value of this parameter as a boolean, or {@link Optional#empty()} if the parameter was not present. 159 */ 160 public Optional<Boolean> asBoolean() { 161 return asBooleanPart().asBoolean(); 162 } 163 164 /** 165 * Returns the value of this parameter as a {@link BasicBooleanPart}. 166 * 167 * @return The value of this parameter as a {@link BasicBooleanPart}, never <jk>null</jk>. 168 */ 169 public BasicBooleanPart asBooleanPart() { 170 return new BasicBooleanPart(getName(), getValue()); 171 } 172 173 /** 174 * Returns the value of this parameter as a list from a comma-delimited string. 175 * 176 * @return The value of this parameter as a list from a comma-delimited string, or {@link Optional#empty()} if the parameter was not present. 177 */ 178 public Optional<List<String>> asCsvArray() { 179 return asCsvArrayPart().asList(); 180 } 181 182 /** 183 * Returns the value of this parameter as a {@link BasicCsvArrayPart}. 184 * 185 * @return The value of this parameter as a {@link BasicCsvArrayPart}, never <jk>null</jk>. 186 */ 187 public BasicCsvArrayPart asCsvArrayPart() { 188 return new BasicCsvArrayPart(getName(), getValue()); 189 } 190 191 /** 192 * Returns the value of this parameter as a date. 193 * 194 * @return The value of this parameter as a date, or {@link Optional#empty()} if the parameter was not present. 195 */ 196 public Optional<ZonedDateTime> asDate() { 197 return asDatePart().asZonedDateTime(); 198 } 199 200 /** 201 * Returns the value of this parameter as a {@link BasicDatePart}. 202 * 203 * @return The value of this parameter as a {@link BasicDatePart}, never <jk>null</jk>. 204 */ 205 public BasicDatePart asDatePart() { 206 return new BasicDatePart(getName(), getValue()); 207 } 208 209 /** 210 * Returns the value of this parameter as an integer. 211 * 212 * @return The value of this parameter as an integer, or {@link Optional#empty()} if the parameter was not present. 213 */ 214 public Optional<Integer> asInteger() { 215 return asIntegerPart().asInteger(); 216 } 217 218 /** 219 * Returns the value of this parameter as a {@link BasicIntegerPart}. 220 * 221 * @return The value of this parameter as a {@link BasicIntegerPart}, never <jk>null</jk>. 222 */ 223 public BasicIntegerPart asIntegerPart() { 224 return new BasicIntegerPart(getName(), getValue()); 225 } 226 227 /** 228 * Returns the value of this parameter as a long. 229 * 230 * @return The value of this parameter as a long, or {@link Optional#empty()} if the parameter was not present. 231 */ 232 public Optional<Long> asLong() { 233 return asLongPart().asLong(); 234 } 235 236 /** 237 * Returns the value of this parameter as a {@link BasicLongPart}. 238 * 239 * @return The value of this parameter as a {@link BasicLongPart}, never <jk>null</jk>. 240 */ 241 public BasicLongPart asLongPart() { 242 return new BasicLongPart(getName(), getValue()); 243 } 244 245 /** 246 * Matches the specified pattern against this part value. 247 * 248 * <h5 class='section'>Example:</h5> 249 * <p class='bjava'> 250 * Matcher <jv>matcher</jv> = <jv>request</jv> 251 * .getHeader(<js>"Content-Type"</js>) 252 * .asMatcher(Pattern.<jsm>compile</jsm>(<js>"application/(.*)"</js>)); 253 * 254 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 255 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 256 * } 257 * </p> 258 * 259 * @param pattern The regular expression pattern to match. 260 * @return The matcher. 261 * @throws BasicHttpException If a connection error occurred. 262 */ 263 public Matcher asMatcher(Pattern pattern) throws BasicHttpException { 264 return pattern.matcher(orElse("")); 265 } 266 267 /** 268 * Matches the specified pattern against this part value. 269 * 270 * <h5 class='section'>Example:</h5> 271 * <p class='bjava'> 272 * Matcher <jv>matcher</jv> = <jv>request</jv> 273 * .getHeader(<js>"Content-Type"</js>) 274 * .asMatcher(<js>"application/(.*)"</js>); 275 * 276 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 277 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 278 * } 279 * </p> 280 * 281 * @param regex The regular expression pattern to match. 282 * @return The matcher. 283 * @throws BasicHttpException If a connection error occurred. 284 */ 285 public Matcher asMatcher(String regex) throws BasicHttpException { 286 return asMatcher(regex, 0); 287 } 288 289 /** 290 * Matches the specified pattern against this part value. 291 * 292 * <h5 class='section'>Example:</h5> 293 * <p class='bjava'> 294 * Matcher <jv>matcher</jv> = <jv>request</jv> 295 * .getHeader(<js>"Content-Type"</js>) 296 * .asMatcher(<js>"application/(.*)"</js>, <jsf>CASE_INSENSITIVE</jsf>); 297 * 298 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 299 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 300 * } 301 * </p> 302 * 303 * @param regex The regular expression pattern to match. 304 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 305 * @return The matcher. 306 * @throws BasicHttpException If a connection error occurred. 307 */ 308 public Matcher asMatcher(String regex, int flags) throws BasicHttpException { 309 return asMatcher(Pattern.compile(regex, flags)); 310 } 311 312 /** 313 * Provides the ability to perform fluent-style assertions on comma-separated string parameters. 314 * 315 * <h5 class='section'>Examples:</h5> 316 * <p class='bjava'> 317 * <jv>request</jv> 318 * .getQueryParam(<js>"allow"</js>) 319 * .assertCsvArray().contains(<js>"GET"</js>); 320 * </p> 321 * 322 * @return A new fluent assertion object. 323 */ 324 public FluentListAssertion<String,RequestHttpPart> assertCsvArray() { 325 return new FluentListAssertion<>(asCsvArrayPart().asList().orElse(null), this); 326 } 327 328 /** 329 * Provides the ability to perform fluent-style assertions on a date parameter. 330 * 331 * <h5 class='section'>Examples:</h5> 332 * <p class='bjava'> 333 * <jv>request</jv> 334 * .getQueryParam(<js>"time"</js>) 335 * .assertDate().isAfterNow(); 336 * </p> 337 * 338 * @return A new fluent assertion object. 339 */ 340 public FluentZonedDateTimeAssertion<RequestHttpPart> assertDate() { 341 return new FluentZonedDateTimeAssertion<>(asDatePart().asZonedDateTime().orElse(null), this); 342 } 343 344 /** 345 * Provides the ability to perform fluent-style assertions on an integer parameter. 346 * 347 * <h5 class='section'>Examples:</h5> 348 * <p class='bjava'> 349 * <jv>request</jv> 350 * .getQueryParam(<js>"age"</js>) 351 * .assertInteger().isGreaterThan(1); 352 * </p> 353 * 354 * @return A new fluent assertion object. 355 */ 356 public FluentIntegerAssertion<RequestHttpPart> assertInteger() { 357 return new FluentIntegerAssertion<>(asIntegerPart().asInteger().orElse(null), this); 358 } 359 360 /** 361 * Provides the ability to perform fluent-style assertions on a long parameter. 362 * 363 * <h5 class='section'>Examples:</h5> 364 * <p class='bjava'> 365 * <jv>request</jv> 366 * .getQueryParam(<js>"length"</js>) 367 * .assertLong().isLessThan(100000); 368 * </p> 369 * 370 * @return A new fluent assertion object. 371 */ 372 public FluentLongAssertion<RequestHttpPart> assertLong() { 373 return new FluentLongAssertion<>(asLongPart().asLong().orElse(null), this); 374 } 375 376 /** 377 * Provides the ability to perform fluent-style assertions on this parameter. 378 * 379 * <h5 class='section'>Examples:</h5> 380 * <p class='bjava'> 381 * <jv>request</jv> 382 * .getQueryParam(<js>"foo"</js>) 383 * .assertString().contains(<js>"bar"</js>); 384 * </p> 385 * 386 * <p> 387 * The assertion test returns the original object allowing you to chain multiple requests like so: 388 * <p class='bjava'> 389 * String <jv>foo</jv> = <jv>request</jv> 390 * .getQueryParam(<js>"foo"</js>) 391 * .assertString().contains(<js>"bar"</js>) 392 * .asString().get(); 393 * </p> 394 * 395 * @return A new fluent assertion object. 396 */ 397 public FluentStringAssertion<RequestHttpPart> assertString() { 398 return new FluentStringAssertion<>(orElse(null), this); 399 } 400 401 /** 402 * Returns the value of this part as a string. 403 * 404 * @return The value of this part as a string, or {@link Optional#empty()} if the part was not present. 405 */ 406 public Optional<String> asString() { 407 return opt(getValue()); 408 } 409 410 /** 411 * Returns the value of this parameter as a {@link BasicStringPart}. 412 * 413 * @return The value of this parameter as a {@link BasicStringPart}, never <jk>null</jk>. 414 */ 415 public BasicStringPart asStringPart() { 416 return new BasicStringPart(getName(), getValue()); 417 } 418 419 /** 420 * Returns the value of this parameter as a {@link BasicUriPart}. 421 * 422 * @return The value of this parameter as a {@link BasicUriPart}, never <jk>null</jk>. 423 */ 424 public BasicUriPart asUriPart() { 425 return new BasicUriPart(getName(), getValue()); 426 } 427 428 /** 429 * Sets a default value for this part. 430 * 431 * @param def The default value. 432 * @return This object. 433 */ 434 public RequestHttpPart def(String def) { 435 if (value == null) 436 value = def; 437 return this; 438 } 439 440 /** 441 * If a value is present, returns the value, otherwise throws {@link NoSuchElementException}. 442 * 443 * <p> 444 * This is a shortened form for calling <c>asString().get()</c>. 445 * 446 * @return The value if present. 447 */ 448 public String get() { 449 return asString().get(); 450 } 451 452 /** 453 * Gets the name of this part. 454 * 455 * @return The name of this part, never <jk>null</jk>. 456 */ 457 public String getName() { return name; } 458 459 /** 460 * Returns the value of this part. 461 * 462 * @return The value of this part. 463 */ 464 public String getValue() { return value; } 465 466 /** 467 * Returns <jk>true</jk> if this part exists on the request. 468 * 469 * <p> 470 * This is a shortened form for calling <c>asString().isPresent()</c>. 471 * 472 * @return <jk>true</jk> if this part exists on the request. 473 */ 474 public boolean isPresent() { return asString().isPresent(); } 475 476 /** 477 * Return the value if present, otherwise return other. 478 * 479 * <p> 480 * This is a shortened form for calling <c>asString().orElse(<jv>other</jv>)</c>. 481 * 482 * @param other The value to be returned if there is no value present, may be <jk>null</jk>. 483 * @return The value, if present, otherwise other. 484 */ 485 public String orElse(String other) { 486 return asString().orElse(other); 487 } 488 489 /** 490 * Specifies the part parser to use for this part. 491 * 492 * <p> 493 * If not specified, uses the part parser defined on the client by calling {@link org.apache.juneau.rest.RestContext.Builder#partParser()}. 494 * 495 * @param value 496 * The new part parser to use for this part. 497 * <br>If <jk>null</jk>, {@link SimplePartParser#DEFAULT} will be used. 498 * @return This object. 499 */ 500 public RequestHttpPart parser(HttpPartParserSession value) { 501 this.parser = value == null ? SimplePartParser.DEFAULT_SESSION : value; 502 return this; 503 } 504 505 /** 506 * Specifies the part schema for this part. 507 * 508 * <p> 509 * Used by schema-based part parsers such as {@link OpenApiParser}. 510 * 511 * @param value 512 * The part schema. 513 * @return This object. 514 */ 515 public RequestHttpPart schema(HttpPartSchema value) { 516 schema = value; 517 return this; 518 } 519 520 @Override /* Overridden from Object */ 521 public String toString() { 522 return getName() + "=" + getValue(); 523 } 524 525 /** 526 * Returns the request that created this part. 527 * 528 * @return The request that created this part. 529 */ 530 protected RestRequest getRequest() { return request; } 531}