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;
014
015import static javax.servlet.http.HttpServletResponse.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.internal.CollectionUtils.*;
018import static org.apache.juneau.internal.ObjectUtils.*;
019import static org.apache.juneau.internal.StringUtils.*;
020import static org.apache.juneau.internal.StringUtils.firstNonEmpty;
021import static org.apache.juneau.httppart.HttpPartType.*;
022import static org.apache.juneau.rest.RestContext.*;
023import static org.apache.juneau.rest.util.RestUtils.*;
024
025import java.lang.annotation.*;
026import java.lang.reflect.*;
027import java.util.*;
028import java.util.concurrent.*;
029
030import javax.servlet.*;
031import javax.servlet.http.*;
032
033import org.apache.juneau.*;
034import org.apache.juneau.annotation.*;
035import org.apache.juneau.encoders.*;
036import org.apache.juneau.http.*;
037import org.apache.juneau.http.annotation.*;
038import org.apache.juneau.httppart.*;
039import org.apache.juneau.httppart.bean.*;
040import org.apache.juneau.internal.*;
041import org.apache.juneau.internal.HttpUtils;
042import org.apache.juneau.jsonschema.*;
043import org.apache.juneau.parser.*;
044import org.apache.juneau.reflect.*;
045import org.apache.juneau.remote.*;
046import org.apache.juneau.rest.annotation.*;
047import org.apache.juneau.rest.annotation.Method;
048import org.apache.juneau.rest.exception.*;
049import org.apache.juneau.rest.guards.*;
050import org.apache.juneau.rest.util.*;
051import org.apache.juneau.rest.widget.*;
052import org.apache.juneau.serializer.*;
053import org.apache.juneau.svl.*;
054
055/**
056 * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
057 */
058@ConfigurableContext(nocache=true)
059public class RestMethodContext extends BeanContext implements Comparable<RestMethodContext>  {
060
061   //-------------------------------------------------------------------------------------------------------------------
062   // Configurable properties
063   //-------------------------------------------------------------------------------------------------------------------
064
065   static final String PREFIX = "RestMethodContext";
066
067   /**
068    * Configuration property:  Default request attributes.
069    *
070    * <h5 class='section'>Property:</h5>
071    * <ul>
072    *    <li><b>Name:</b>  <js>"RestMethodContext.defaultRequestAttributes.smo"</js>
073    *    <li><b>Data type:</b>  <c>Map&lt;String,Object&gt;</c>
074    *    <li><b>Default:</b>  <jk>null</jk>
075    *    <li><b>Session property:</b>  <jk>false</jk>
076    *    <li><b>Annotations:</b>
077    *       <ul>
078    *          <li class='ja'>{@link RestMethod#attrs()}
079    *       </ul>
080    * </ul>
081    *
082    * <h5 class='section'>Description:</h5>
083    * Default request attributes.
084    *
085    * <p>
086    * Specifies default values for request attributes if they are not already set on the request.
087    *
088    * <h5 class='section'>Example:</h5>
089    * <p class='bcode w800'>
090    *    <jc>// Assume "text/json" Accept value when Accept not specified</jc>
091    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/*"</js>, defaultRequestAttributes={<js>"Foo: bar"</js>})
092    *    <jk>public</jk> String doGet()  {...}
093    * </p>
094    *
095    * <ul class='notes'>
096    *    <li>
097    *       Supports {@doc DefaultRestSvlVariables}
098    *       (e.g. <js>"$S{mySystemProperty}"</js>).
099    * </ul>
100    *
101    * <ul class='seealso'>
102    *    <li class='jf'>{@link RestContext#REST_attrs}
103    * </ul>
104    */
105   public static final String RESTMETHOD_attrs = PREFIX + ".attrs.smo";
106
107   /**
108    * Configuration property:  Client version pattern matcher.
109    *
110    * <h5 class='section'>Property:</h5>
111    * <ul>
112    *    <li><b>Name:</b>  <js>"RestMethodContext.clientVersion.s"</js>
113    *    <li><b>Data type:</b>  <c>String</c>
114    *    <li><b>Default:</b>  empty string
115    *    <li><b>Session property:</b>  <jk>false</jk>
116    *    <li><b>Annotations:</b>
117    *       <ul>
118    *          <li class='ja'>{@link RestMethod#clientVersion()}
119    *       </ul>
120    * </ul>
121    *
122    * <h5 class='section'>Description:</h5>
123    * Specifies whether this method can be called based on the client version.
124    *
125    * <p>
126    * The client version is identified via the HTTP request header identified by
127    * {@link RestResource#clientVersionHeader() @RestResource(clientVersionHeader)} which by default is <js>"X-Client-Version"</js>.
128    *
129    * <p>
130    * This is a specialized kind of {@link RestMatcher} that allows you to invoke different Java methods for the same
131    * method/path based on the client version.
132    *
133    * <p>
134    * The format of the client version range is similar to that of OSGi versions.
135    *
136    * <p>
137    * In the following example, the Java methods are mapped to the same HTTP method and URL <js>"/foobar"</js>.
138    * <p class='bcode w800'>
139    *    <jc>// Call this method if X-Client-Version is at least 2.0.
140    *    // Note that this also matches 2.0.1.</jc>
141    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"2.0"</js>)
142    *    <jk>public</jk> Object method1()  {...}
143    *
144    *    <jc>// Call this method if X-Client-Version is at least 1.1, but less than 2.0.</jc>
145    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"[1.1,2.0)"</js>)
146    *    <jk>public</jk> Object method2()  {...}
147    *
148    *    <jc>// Call this method if X-Client-Version is less than 1.1.</jc>
149    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"[0,1.1)"</js>)
150    *    <jk>public</jk> Object method3()  {...}
151    * </p>
152    *
153    * <p>
154    * It's common to combine the client version with transforms that will convert new POJOs into older POJOs for
155    * backwards compatibility.
156    * <p class='bcode w800'>
157    *    <jc>// Call this method if X-Client-Version is at least 2.0.</jc>
158    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"2.0"</js>)
159    *    <jk>public</jk> NewPojo newMethod()  {...}
160    *
161    *    <jc>// Call this method if X-Client-Version is at least 1.1, but less than 2.0.</jc>
162    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/foobar"</js>, clientVersion=<js>"[1.1,2.0)"</js>, transforms={NewToOldPojoSwap.<jk>class</jk>})
163    *    <jk>public</jk> NewPojo oldMethod() {
164    *       <jk>return</jk> newMethod();
165    *    }
166    *
167    * <p>
168    * Note that in the previous example, we're returning the exact same POJO, but using a transform to convert it into
169    * an older form.
170    * The old method could also just return back a completely different object.
171    * The range can be any of the following:
172    * <ul>
173    *    <li><js>"[0,1.0)"</js> = Less than 1.0.  1.0 and 1.0.0 does not match.
174    *    <li><js>"[0,1.0]"</js> = Less than or equal to 1.0.  Note that 1.0.1 will match.
175    *    <li><js>"1.0"</js> = At least 1.0.  1.0 and 2.0 will match.
176    * </ul>
177    *
178    * <ul class='seealso'>
179    *    <li class='jf'>{@link RestContext#REST_clientVersionHeader}
180    * </ul>
181    */
182   public static final String RESTMETHOD_clientVersion = PREFIX + ".clientVersion.s";
183
184   /**
185    * Configuration property:  Debug mode.
186    *
187    * <h5 class='section'>Property:</h5>
188    * <ul>
189    *    <li><b>Name:</b>  <js>"RestMethodContext.debug.s"</js>
190    *    <li><b>Data type:</b>  {@link Enablement}
191    *    <li><b>Default:</b>  {@link Enablement#FALSE}
192    *    <li><b>Session property:</b>  <jk>false</jk>
193    *    <li><b>Annotations:</b>
194    *       <ul>
195    *          <li class='ja'>{@link RestMethod#debug()}
196    *       </ul>
197    * </ul>
198    *
199    * <h5 class='section'>Description:</h5>
200    * <p>
201    * Enables the following:
202    * <ul class='spaced-list'>
203    *    <li>
204    *       HTTP request/response bodies are cached in memory for logging purposes.
205    * </ul>
206    */
207   public static final String RESTMETHOD_debug = PREFIX + ".debug.s";
208
209   /**
210    * Configuration property:  Default form data.
211    *
212    * <h5 class='section'>Property:</h5>
213    * <ul>
214    *    <li><b>Name:</b>  <js>"RestMethodContext.defaultFormData.omo"</js>
215    *    <li><b>Data type:</b>  <c>Map&lt;String,Object&gt;</c>
216    *    <li><b>Default:</b>  empty map
217    *    <li><b>Session property:</b>  <jk>false</jk>
218    *    <li><b>Annotations:</b>
219    *       <ul>
220    *          <li class='ja'>{@link RestMethod#defaultFormData()}
221    *       </ul>
222    * </ul>
223    *
224    * <h5 class='section'>Description:</h5>
225    * Specifies default values for form-data parameters.
226    *
227    * <p>
228    * Strings are of the format <js>"name=value"</js>.
229    *
230    * <p>
231    * Affects values returned by {@link RestRequest#getFormData(String)} when the parameter is not present on the
232    * request.
233    *
234    * <h5 class='section'>Example:</h5>
235    * <p class='bcode w800'>
236    *    <ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/*"</js>, defaultFormData={<js>"foo=bar"</js>})
237    *    <jk>public</jk> String doGet(<ja>@FormData</ja>(<js>"foo"</js>) String foo)  {...}
238    * </p>
239    */
240   public static final String RESTMETHOD_defaultFormData = PREFIX + ".defaultFormData.omo";
241
242   /**
243    * Configuration property:  Default query parameters.
244    *
245    * <h5 class='section'>Property:</h5>
246    * <ul>
247    *    <li><b>Name:</b>  <js>"RestMethodContext.defaultQuery.omo"</js>
248    *    <li><b>Data type:</b>  <c>Map&lt;String,Object&gt;</c>
249    *    <li><b>Default:</b>  empty map
250    *    <li><b>Session property:</b>  <jk>false</jk>
251    *    <li><b>Annotations:</b>
252    *       <ul>
253    *          <li class='ja'>{@link RestMethod#defaultQuery()}
254    *       </ul>
255    * </ul>
256    *
257    * <h5 class='section'>Description:</h5>
258    * Specifies default values for query parameters.
259    *
260    * <p>
261    * Strings are of the format <js>"name=value"</js>.
262    *
263    * <p>
264    * Affects values returned by {@link RestRequest#getQuery(String)} when the parameter is not present on the request.
265    *
266    * <h5 class='section'>Example:</h5>
267    * <p class='bcode w800'>
268    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/*"</js>, defaultQuery={<js>"foo=bar"</js>})
269    *    <jk>public</jk> String doGet(<ja>@Query</ja>(<js>"foo"</js>) String foo)  {...}
270    * </p>
271    */
272   public static final String RESTMETHOD_defaultQuery = PREFIX + ".defaultQuery.omo";
273
274   /**
275    * Configuration property:  Default request headers.
276    *
277    * <h5 class='section'>Property:</h5>
278    * <ul>
279    *    <li><b>Name:</b>  <js>"RestMethodContext.defaultRequestHeaders.smo"</js>
280    *    <li><b>Data type:</b>  <c>Map&lt;String,Object&gt;</c>
281    *    <li><b>Default:</b>  <jk>null</jk>
282    *    <li><b>Session property:</b>  <jk>false</jk>
283    *    <li><b>Annotations:</b>
284    *       <ul>
285    *          <li class='ja'>{@link RestMethod#defaultRequestHeaders()}
286    *          <li class='ja'>{@link RestMethod#defaultAccept()}
287    *          <li class='ja'>{@link RestMethod#defaultContentType()}
288    *       </ul>
289    * </ul>
290    *
291    * <h5 class='section'>Description:</h5>
292    * Default request headers.
293    *
294    * <p>
295    * Specifies default values for request headers if they're not passed in through the request.
296    *
297    * <h5 class='section'>Example:</h5>
298    * <p class='bcode w800'>
299    *    <jc>// Assume "text/json" Accept value when Accept not specified</jc>
300    *    <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/*"</js>, defaultRequestHeaders={<js>"Accept: text/json"</js>})
301    *    <jk>public</jk> String doGet()  {...}
302    * </p>
303    *
304    * <ul class='notes'>
305    *    <li>
306    *       Supports {@doc DefaultRestSvlVariables}
307    *       (e.g. <js>"$S{mySystemProperty}"</js>).
308    * </ul>
309    *
310    * <ul class='seealso'>
311    *    <li class='jf'>{@link RestContext#REST_defaultRequestHeaders}
312    * </ul>
313    */
314   public static final String RESTMETHOD_defaultRequestHeaders = PREFIX + ".defaultRequestHeaders.smo";
315
316   /**
317    * Configuration property:  HTTP method name.
318    *
319    * <h5 class='section'>Property:</h5>
320    * <ul>
321    *    <li><b>Name:</b>  <js>"RestMethodContext.httpMethod.s"</js>
322    *    <li><b>Data type:</b>  <c>String</c>
323    *    <li><b>Default:</b>  <jk>null</jk>
324    *    <li><b>Session property:</b>  <jk>false</jk>
325    *    <li><b>Annotations:</b>
326    *       <ul>
327    *          <li class='ja'>{@link RestMethod#name()}
328    *          <li class='ja'>{@link RestMethod#method()}
329    *       </ul>
330    * </ul>
331    *
332    * <h5 class='section'>Description:</h5>
333    * REST method name.
334    *
335    * <p>
336    * Typically <js>"GET"</js>, <js>"PUT"</js>, <js>"POST"</js>, <js>"DELETE"</js>, or <js>"OPTIONS"</js>.
337    *
338    * <p>
339    * Method names are case-insensitive (always folded to upper-case).
340    *
341    * <p>
342    * Note that you can use {@link org.apache.juneau.http.HttpMethodName} for constant values.
343    *
344    * <p>
345    * Besides the standard HTTP method names, the following can also be specified:
346    * <ul class='spaced-list'>
347    *    <li>
348    *       <js>"*"</js>
349    *       - Denotes any method.
350    *       <br>Use this if you want to capture any HTTP methods in a single Java method.
351    *       <br>The {@link Method @Method} annotation and/or {@link RestRequest#getMethod()} method can be used to
352    *       distinguish the actual HTTP method name.
353    *    <li>
354    *       <js>""</js>
355    *       - Auto-detect.
356    *       <br>The method name is determined based on the Java method name.
357    *       <br>For example, if the method is <c>doPost(...)</c>, then the method name is automatically detected
358    *       as <js>"POST"</js>.
359    *       <br>Otherwise, defaults to <js>"GET"</js>.
360    *    <li>
361    *       <js>"RRPC"</js>
362    *       - Remote-proxy interface.
363    *       <br>This denotes a Java method that returns an object (usually an interface, often annotated with the
364    *       {@link RemoteInterface @RemoteInterface} annotation) to be used as a remote proxy using
365    *       <c>RestClient.getRemoteInterface(Class&lt;T&gt; interfaceClass, String url)</c>.
366    *       <br>This allows you to construct client-side interface proxies using REST as a transport medium.
367    *       <br>Conceptually, this is simply a fancy <c>POST</c> against the url <js>"/{path}/{javaMethodName}"</js>
368    *       where the arguments are marshalled from the client to the server as an HTTP body containing an array of
369    *       objects, passed to the method as arguments, and then the resulting object is marshalled back to the client.
370    *    <li>
371    *       Anything else
372    *       - Overloaded non-HTTP-standard names that are passed in through a <c>&amp;method=methodName</c> URL
373    *       parameter.
374    * </ul>
375    */
376   public static final String RESTMETHOD_httpMethod = PREFIX + ".httpMethod.s";
377
378   /**
379    * Configuration property:  Logging rules.
380    *
381    * <h5 class='section'>Property:</h5>
382    * <ul>
383    *    <li><b>Name:</b>  <js>"RestContext.logRules.lo"</js>
384    *    <li><b>Data type:</b>  <c>{@link RestCallLoggerConfig}</c>
385    *    <li><b>Default:</b>  empty list
386    *    <li><b>Session property:</b>  <jk>false</jk>
387    *    <li><b>Annotations:</b>
388    *       <ul>
389    *          <li class='ja'>{@link RestMethod#logging()}
390    *       </ul>
391    * </ul>
392    *
393    * <h5 class='section'>Description:</h5>
394    * <p>
395    * Specifies rules on how to handle logging of HTTP requests/responses.
396    *
397    * <ul class='seealso'>
398    *    <li class='link'>{@doc juneau-rest-server.LoggingAndDebugging}
399    * </ul>
400    */
401   public static final String RESTMETHOD_callLoggerConfig = PREFIX + ".callLoggerConfig.o";
402
403   /**
404    * Configuration property:  Method-level matchers.
405    *
406    * <h5 class='section'>Property:</h5>
407    * <ul>
408    *    <li><b>Name:</b>  <js>"RestMethodContext.matchers.lo"</js>
409    *    <li><b>Data type:</b>  <code>List&lt;{@link RestMatcher} | Class&lt;? <jk>extends</jk> {@link RestMatcher}&gt;&gt;</code>
410    *    <li><b>Default:</b>  empty list
411    *    <li><b>Session property:</b>  <jk>false</jk>
412    *    <li><b>Annotations:</b>
413    *       <ul>
414    *          <li class='ja'>{@link RestMethod#matchers()}
415    *       </ul>
416    * </ul>
417    *
418    * <h5 class='section'>Description:</h5>
419    * <p>
420    * Associates one or more {@link RestMatcher RestMatchers} with the specified method.
421    *
422    * <p>
423    * If multiple matchers are specified, <b>ONE</b> matcher must pass.
424    * <br>Note that this is different than guards where <b>ALL</b> guards needs to pass.
425    *
426    * <ul class='notes'>
427    *    <li>
428    *       Inner classes of the REST resource class are allowed.
429    * </ul>
430    *
431    * <ul class='seealso'>
432    *    <li class='link'>{@doc juneau-rest-server.RestMethod.RestMethodMatchers}
433    * </ul>
434    */
435   public static final String RESTMETHOD_matchers = PREFIX + ".matchers.lo";
436
437   /**
438    * Configuration property:  Resource method path.
439    *
440    * <h5 class='section'>Property:</h5>
441    * <ul>
442    *    <li><b>Name:</b>  <js>"RestMethodContext.path.s"</js>
443    *    <li><b>Data type:</b>  <c>String</c>
444    *    <li><b>Default:</b>  <jk>null</jk>
445    *    <li><b>Session property:</b>  <jk>false</jk>
446    *    <li><b>Annotations:</b>
447    *       <ul>
448    *          <li class='ja'>{@link RestMethod#path()}
449    *       </ul>
450    * </ul>
451    *
452    * <h5 class='section'>Description:</h5>
453    * <p>
454    * Identifies the URL subpath relative to the servlet class.
455    *
456    * <p>
457    * <ul class='notes'>
458    *    <li>
459    *       This method is only applicable for Java methods.
460    *    <li>
461    *       Slashes are trimmed from the path ends.
462    *       <br>As a convention, you may want to start your path with <js>'/'</js> simple because it make it easier to read.
463    * </ul>
464    */
465   public static final String RESTMETHOD_path = PREFIX + ".path.s";
466
467   /**
468    * Configuration property:  Priority
469    *
470    * <h5 class='section'>Property:</h5>
471    * <ul>
472    *    <li><b>Name:</b>  <js>"RestMethodContext.priority.i"</js>
473    *    <li><b>Data type:</b>  <c>Integer</c>
474    *    <li><b>Default:</b>  <c>0</c>
475    *    <li><b>Session property:</b>  <jk>false</jk>
476    *    <li><b>Annotations:</b>
477    *       <ul>
478    *          <li class='ja'>{@link RestMethod#priority()}
479    *       </ul>
480    * </ul>
481    *
482    * <h5 class='section'>Description:</h5>
483    * URL path pattern priority.
484    *
485    * <p>
486    * To force path patterns to be checked before other path patterns, use a higher priority number.
487    *
488    * <p>
489    * By default, it's <c>0</c>, which means it will use an internal heuristic to determine a best match.
490    */
491   public static final String RESTMETHOD_priority = PREFIX + ".priority.i";
492
493   //-------------------------------------------------------------------------------------------------------------------
494   // Instance
495   //-------------------------------------------------------------------------------------------------------------------
496
497   private final String httpMethod;
498   private final UrlPathPattern pathPattern;
499   final RestMethodParam[] methodParams;
500   private final RestGuard[] guards;
501   private final RestMatcher[] optionalMatchers;
502   private final RestMatcher[] requiredMatchers;
503   private final RestConverter[] converters;
504   @SuppressWarnings("deprecation")
505   private final RestMethodProperties properties;
506   private final Integer priority;
507   private final RestContext context;
508   final java.lang.reflect.Method method;
509   final MethodInfo mi;
510   final SerializerGroup serializers;
511   final ParserGroup parsers;
512   final EncoderGroup encoders;
513   final HttpPartSerializer partSerializer;
514   final HttpPartParser partParser;
515   final JsonSchemaGenerator jsonSchemaGenerator;
516   final Map<String,Object>
517      defaultRequestHeaders,
518      defaultQuery,
519      defaultFormData;
520   final ObjectMap defaultRequestAttributes;
521   final String defaultCharset;
522   final long maxInput;
523   final Map<String,Widget> widgets;
524   final List<MediaType>
525      supportedAcceptTypes,
526      supportedContentTypes;
527   final RestCallLoggerConfig callLoggerConfig;
528
529   final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>();
530   final Map<Class<?>,ResponsePartMeta> headerPartMetas = new ConcurrentHashMap<>();
531   final Map<Class<?>,ResponsePartMeta> bodyPartMetas = new ConcurrentHashMap<>();
532   final ResponseBeanMeta responseMeta;
533
534   final Enablement debug;
535
536   @SuppressWarnings("deprecation")
537   RestMethodContext(RestMethodContextBuilder b) throws ServletException {
538      super(b.getPropertyStore());
539
540      this.context = b.context;
541      this.method = b.method;
542      this.mi = MethodInfo.of(method);
543
544      // Need this to access methods in anonymous inner classes.
545      mi.setAccessible();
546
547      PropertyStore ps = getPropertyStore();
548      ResourceResolver rr = context.getResourceResolver();
549      Object r = context.getResource();
550
551      String _httpMethod = getProperty(RESTMETHOD_httpMethod, String.class, null);
552      if (_httpMethod == null)
553         _httpMethod = HttpUtils.detectHttpMethod(method, true, "GET");
554      if ("METHOD".equals(_httpMethod))
555         _httpMethod = "*";
556      this.httpMethod = _httpMethod.toUpperCase(Locale.ENGLISH);
557
558      this.defaultCharset = getProperty(REST_defaultCharset, String.class, "utf-8");
559
560      this.maxInput = StringUtils.parseLongWithSuffix(getProperty(REST_maxInput, String.class, "100M"));
561
562      this.serializers = SerializerGroup
563         .create()
564         .append(getArrayProperty(REST_serializers, Object.class))
565         .apply(ps)
566         .build();
567
568      this.parsers = ParserGroup
569         .create()
570         .append(getArrayProperty(REST_parsers, Object.class))
571         .apply(ps)
572         .build();
573
574      HttpPartParser hpp = context.getPartParser();
575      if (hpp instanceof Parser) {
576         Parser pp = (Parser)hpp;
577         hpp = (HttpPartParser)pp.builder().apply(ps).build();
578      }
579      this.partParser = hpp;
580
581      this.partSerializer = context.getPartSerializer();
582
583      this.responseMeta = ResponseBeanMeta.create(mi, ps);
584
585      this.pathPattern = new UrlPathPattern(getProperty(RESTMETHOD_path, String.class, HttpUtils.detectHttpPath(method, true)));
586
587      this.methodParams = context.findParams(mi, false, pathPattern);
588
589      this.converters = getInstanceArrayProperty(REST_converters, RestConverter.class, new RestConverter[0], rr, r, this);
590
591      List<RestGuard> _guards = new ArrayList<>();
592      _guards.addAll(Arrays.asList(getInstanceArrayProperty(REST_guards, RestGuard.class, new RestGuard[0], rr, r, this)));
593      Set<String> rolesDeclared = getSetProperty(REST_rolesDeclared, String.class, null);
594      Set<String> roleGuard = getSetProperty(REST_roleGuard, String.class, Collections.emptySet());
595
596      for (String rg : roleGuard) {
597         try {
598            _guards.add(new RoleBasedRestGuard(rolesDeclared, rg));
599         } catch (java.text.ParseException e1) {
600            throw new ServletException(e1);
601         }
602      }
603      this.guards = _guards.toArray(new RestGuard[_guards.size()]);
604
605      List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
606      for (RestMatcher matcher : getInstanceArrayProperty(RESTMETHOD_matchers, RestMatcher.class, new RestMatcher[0], rr, r, this)) {
607         if (matcher.mustMatch())
608            requiredMatchers.add(matcher);
609         else
610            optionalMatchers.add(matcher);
611      }
612      String clientVersion = getProperty(RESTMETHOD_clientVersion, String.class, null);
613      if (clientVersion != null)
614         requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), mi));
615
616      this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
617      this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
618
619      this.encoders = EncoderGroup
620         .create()
621         .append(IdentityEncoder.INSTANCE)
622         .append(getInstanceArrayProperty(REST_encoders, Encoder.class, new Encoder[0], rr, r, this))
623         .build();
624
625      this.jsonSchemaGenerator = JsonSchemaGenerator.create().apply(ps).build();
626
627      Map<String,Object> _defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
628      _defaultRequestHeaders.putAll(getMapProperty(RESTMETHOD_defaultRequestHeaders, Object.class));
629
630      ObjectMap _defaultRequestAttributes = new ObjectMap(context.getDefaultRequestAttributes()).appendAll(getMapProperty(RESTMETHOD_attrs, Object.class));
631
632      Map<String,Object> _defaultQuery = new LinkedHashMap<>(getMapProperty(RESTMETHOD_defaultQuery, Object.class));
633
634      Map<String,Object> _defaultFormData = new LinkedHashMap<>(getMapProperty(RESTMETHOD_defaultFormData, Object.class));
635
636      Type[] pt = method.getGenericParameterTypes();
637      Annotation[][] pa = method.getParameterAnnotations();
638      for (int i = 0; i < pt.length; i++) {
639         for (Annotation a : pa[i]) {
640            if (a instanceof Header) {
641               Header h = (Header)a;
642               if (h._default().length > 0) {
643                  try {
644                     _defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), parseAnything(joinnl(h._default())));
645                  } catch (ParseException e) {
646                     throw new ConfigException(e, "Malformed @Header annotation");
647                  }
648               }
649            } else if (a instanceof Query) {
650               Query q = (Query)a;
651               if (q._default().length > 0) {
652                  try {
653                     _defaultQuery.put(firstNonEmpty(q.name(), q.value()), parseAnything(joinnl(q._default())));
654                  } catch (ParseException e) {
655                     throw new ConfigException(e, "Malformed @Query annotation");
656                  }
657               }
658            } else if (a instanceof FormData) {
659               FormData f = (FormData)a;
660               if (f._default().length > 0) {
661                  try {
662                     _defaultFormData.put(firstNonEmpty(f.name(), f.value()), parseAnything(joinnl(f._default())));
663                  } catch (ParseException e) {
664                     throw new ConfigException(e, "Malformed @FormData annotation");
665                  }
666               }
667            }
668         }
669      }
670
671      this.defaultRequestHeaders = Collections.unmodifiableMap(_defaultRequestHeaders);
672      this.defaultRequestAttributes = _defaultRequestAttributes.unmodifiable();
673      this.defaultQuery = Collections.unmodifiableMap(_defaultQuery);
674      this.defaultFormData = Collections.unmodifiableMap(_defaultFormData);
675
676      this.priority = getIntegerProperty(RESTMETHOD_priority, 0);
677
678      Map<String,Widget> _widgets = new HashMap<>();
679      for (Widget w : getInstanceArrayProperty(REST_widgets, Widget.class, new Widget[0]))
680         _widgets.put(w.getName(), w);
681      this.widgets = unmodifiableMap(_widgets);
682
683      this.properties = b.properties;
684
685      this.supportedAcceptTypes = getListProperty(REST_produces, MediaType.class, serializers.getSupportedMediaTypes());
686      this.supportedContentTypes = getListProperty(REST_consumes, MediaType.class, parsers.getSupportedMediaTypes());
687
688      this.debug = getInstanceProperty(RESTMETHOD_debug, Enablement.class, context.getDebug());
689
690      Object clc = getProperty(RESTMETHOD_callLoggerConfig);
691      if (clc instanceof RestCallLoggerConfig)
692         this.callLoggerConfig = (RestCallLoggerConfig)clc;
693      else if (clc instanceof ObjectMap)
694         this.callLoggerConfig = RestCallLoggerConfig.create().parent(context.getCallLoggerConfig()).apply((ObjectMap)clc).build();
695      else
696         this.callLoggerConfig = context.getCallLoggerConfig();
697   }
698
699   ResponseBeanMeta getResponseBeanMeta(Object o) {
700      if (o == null)
701         return null;
702      Class<?> c = o.getClass();
703      ResponseBeanMeta rbm = responseBeanMetas.get(c);
704      if (rbm == null) {
705         rbm = ResponseBeanMeta.create(c, serializers.getPropertyStore());
706         if (rbm == null)
707            rbm = ResponseBeanMeta.NULL;
708         responseBeanMetas.put(c, rbm);
709      }
710      if (rbm == ResponseBeanMeta.NULL)
711         return null;
712      return rbm;
713   }
714
715   ResponsePartMeta getResponseHeaderMeta(Object o) {
716      if (o == null)
717         return null;
718      Class<?> c = o.getClass();
719      ResponsePartMeta pm = headerPartMetas.get(c);
720      if (pm == null) {
721         ResponseHeader a = c.getAnnotation(ResponseHeader.class);
722         if (a != null) {
723            HttpPartSchema schema = HttpPartSchema.create(a);
724            HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer);
725            pm = new ResponsePartMeta(HEADER, schema, serializer);
726         }
727         if (pm == null)
728            pm = ResponsePartMeta.NULL;
729         headerPartMetas.put(c, pm);
730      }
731      if (pm == ResponsePartMeta.NULL)
732         return null;
733      return pm;
734   }
735
736   ResponsePartMeta getResponseBodyMeta(Object o) {
737      if (o == null)
738         return null;
739      Class<?> c = o.getClass();
740      ResponsePartMeta pm = bodyPartMetas.get(c);
741      if (pm == null) {
742         ResponseBody a = c.getAnnotation(ResponseBody.class);
743         if (a != null) {
744            HttpPartSchema schema = HttpPartSchema.create(a);
745            HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer);
746            pm = new ResponsePartMeta(BODY, schema, serializer);
747         }
748         if (pm == null)
749            pm = ResponsePartMeta.NULL;
750         bodyPartMetas.put(c, pm);
751      }
752      if (pm == ResponsePartMeta.NULL)
753         return null;
754      return pm;
755   }
756
757   /**
758    * Returns <jk>true</jk> if this Java method has any guards or matchers.
759    */
760   boolean hasGuardsOrMatchers() {
761      return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
762   }
763
764   /**
765    * Returns the HTTP method name (e.g. <js>"GET"</js>).
766    */
767   String getHttpMethod() {
768      return httpMethod;
769   }
770
771   /**
772    * Returns the path pattern for this method.
773    */
774   String getPathPattern() {
775      return pathPattern.toString();
776   }
777
778   /**
779    * Returns <jk>true</jk> if the specified request object can call this method.
780    */
781   boolean isRequestAllowed(RestRequest req) {
782      for (RestGuard guard : guards) {
783         req.setJavaMethod(method);
784         if (! guard.isRequestAllowed(req))
785            return false;
786      }
787      return true;
788   }
789
790   boolean matches(UrlPathInfo pathInfo) {
791      return pathPattern.match(pathInfo) != null;
792   }
793
794   /**
795    * Workhorse method.
796    *
797    * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
798    * @return The HTTP response code.
799    */
800   int invoke(UrlPathInfo pathInfo, RestRequest req, RestResponse res) throws Throwable {
801
802      UrlPathPatternMatch pm = pathPattern.match(pathInfo);
803      if (pm == null)
804         return SC_NOT_FOUND;
805
806      RequestPath rp = req.getPathMatch();
807      for (Map.Entry<String,String> e : pm.getVars().entrySet())
808         rp.put(e.getKey(), e.getValue());
809      if (pm.getRemainder() != null)
810         rp.remainder(pm.getRemainder());
811
812      @SuppressWarnings("deprecation")
813      RequestProperties requestProperties = new RequestProperties(req.getVarResolverSession(), properties);
814
815      req.init(this, requestProperties);
816      res.init(this, requestProperties);
817
818      // If the method implements matchers, test them.
819      for (RestMatcher m : requiredMatchers)
820         if (! m.matches(req))
821            return SC_PRECONDITION_FAILED;
822      if (optionalMatchers.length > 0) {
823         boolean matches = false;
824         for (RestMatcher m : optionalMatchers)
825            matches |= m.matches(req);
826         if (! matches)
827            return SC_PRECONDITION_FAILED;
828      }
829
830      context.preCall(req, res);
831
832      Object[] args = new Object[methodParams.length];
833      for (int i = 0; i < methodParams.length; i++) {
834         try {
835            args[i] = methodParams[i].resolve(req, res);
836         } catch (RestException e) {
837            throw e;
838         } catch (Exception e) {
839            throw new BadRequest(e,
840               "Invalid data conversion.  Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
841               methodParams[i].getParamType().name(), methodParams[i].getName(), methodParams[i].getType(), mi.getDeclaringClass().getFullName(), mi.getSimpleName()
842            );
843         }
844      }
845
846      try {
847
848         for (RestGuard guard : guards)
849            if (! guard.guard(req, res))
850               return SC_OK;
851
852         Object output;
853         try {
854            output = method.invoke(context.getResource(), args);
855            if (res.getStatus() == 0)
856               res.setStatus(200);
857            if (! method.getReturnType().equals(Void.TYPE)) {
858               if (output != null || ! res.getOutputStreamCalled())
859                  res.setOutput(output);
860            }
861         } catch (InvocationTargetException e) {
862            Throwable e2 = e.getTargetException();    // Get the throwable thrown from the doX() method.
863            res.setStatus(500);
864            ResponsePartMeta rpm = getResponseBodyMeta(e2);
865            ResponseBeanMeta rbm = getResponseBeanMeta(e2);
866            if (rpm != null || rbm != null) {
867               res.setOutput(e2);
868               res.setResponseMeta(rbm);
869            } else {
870               throw e;
871            }
872         }
873
874         context.postCall(req, res);
875
876         if (res.hasOutput())
877            for (RestConverter converter : converters)
878               res.setOutput(converter.convert(req, res.getOutput()));
879
880      } catch (IllegalArgumentException e) {
881         throw new BadRequest(e,
882            "Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
883            mi.toString(), mi.getFullName()
884         );
885      } catch (InvocationTargetException e) {
886         Throwable e2 = e.getTargetException();    // Get the throwable thrown from the doX() method.
887         if (e2 instanceof RestException)
888            throw (RestException)e2;
889         if (e2 instanceof ParseException)
890            throw new BadRequest(e2);
891         if (e2 instanceof InvalidDataConversionException)
892            throw new BadRequest(e2);
893         throw e2;
894      }
895      return SC_OK;
896   }
897
898   /*
899    * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
900    * It maintains the order in which matches are made during requests.
901    */
902   @Override /* Comparable */
903   public int compareTo(RestMethodContext o) {
904      int c;
905
906      c = priority.compareTo(o.priority);
907      if (c != 0)
908         return c;
909
910      c = pathPattern.compareTo(o.pathPattern);
911      if (c != 0)
912         return c;
913
914      c = compare(o.requiredMatchers.length, requiredMatchers.length);
915      if (c != 0)
916         return c;
917
918      c = compare(o.optionalMatchers.length, optionalMatchers.length);
919      if (c != 0)
920         return c;
921
922      c = compare(o.guards.length, guards.length);
923      if (c != 0)
924         return c;
925
926      return 0;
927   }
928
929   /**
930    * Bean property getter:  <property>serializers</property>.
931    *
932    * @return The value of the <property>serializers</property> property on this bean, or <jk>null</jk> if it is not set.
933    */
934   public SerializerGroup getSerializers() {
935      return serializers;
936   }
937
938   /**
939    * Bean property getter:  <property>parsers</property>.
940    *
941    * @return The value of the <property>parsers</property> property on this bean, or <jk>null</jk> if it is not set.
942    */
943   public ParserGroup getParsers() {
944      return parsers;
945   }
946
947   /**
948    * Bean property getter:  <property>partSerializer</property>.
949    *
950    * @return The value of the <property>partSerializer</property> property on this bean, or <jk>null</jk> if it is not set.
951    */
952   public HttpPartSerializer getPartSerializer() {
953      return partSerializer;
954   }
955
956   /**
957    * Bean property getter:  <property>partParser</property>.
958    *
959    * @return The value of the <property>partParser</property> property on this bean, or <jk>null</jk> if it is not set.
960    */
961   public HttpPartParser getPartParser() {
962      return partParser;
963   }
964
965   /**
966    * Returns the JSON-Schema generator applicable to this Java method.
967    *
968    * @return The JSON-Schema generator applicable to this Java method.
969    */
970   public JsonSchemaGenerator getJsonSchemaGenerator() {
971      return jsonSchemaGenerator;
972   }
973
974   /**
975    * Returns whether debug is enabled on this method.
976    *
977    * @return <jk>true</jk> if debug is enabled on this method.
978    */
979   protected Enablement getDebug() {
980      return debug;
981   }
982
983   /**
984    * @return The REST call logger config for this method.
985    */
986   protected RestCallLoggerConfig getCallLoggerConfig() {
987      return callLoggerConfig;
988   }
989
990   @Override /* Object */
991   public boolean equals(Object o) {
992      if (! (o instanceof RestMethodContext))
993         return false;
994      return (compareTo((RestMethodContext)o) == 0);
995   }
996
997   @Override /* Object */
998   public int hashCode() {
999      return method.hashCode();
1000   }
1001
1002   //-----------------------------------------------------------------------------------------------------------------
1003   // Utility methods.
1004   //-----------------------------------------------------------------------------------------------------------------
1005
1006   static String[] resolveVars(VarResolver vr, String[] in) {
1007      String[] out = new String[in.length];
1008      for (int i = 0; i < in.length; i++)
1009         out[i] = vr.resolve(in[i]);
1010      return out;
1011   }
1012
1013   static HttpPartSerializer createPartSerializer(Class<? extends HttpPartSerializer> c, PropertyStore ps, HttpPartSerializer _default) {
1014      HttpPartSerializer hps = castOrCreate(HttpPartSerializer.class, c, true, ps);
1015      return hps == null ? _default : hps;
1016   }
1017
1018   //-----------------------------------------------------------------------------------------------------------------
1019   // Other methods.
1020   //-----------------------------------------------------------------------------------------------------------------
1021
1022   @Override /* Context */
1023   public ObjectMap toMap() {
1024      return super.toMap()
1025         .append("RestMethodContext", new DefaultFilteringObjectMap()
1026            .append("defaultFormData", defaultFormData)
1027            .append("defaultQuery", defaultQuery)
1028            .append("defaultRequestHeaders", defaultRequestHeaders)
1029            .append("httpMethod", httpMethod)
1030            .append("priority", priority)
1031         );
1032   }
1033}