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