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}