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