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}