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