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.BeanContext.*;
017import static org.apache.juneau.internal.ClassUtils.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019import static org.apache.juneau.internal.StringUtils.*;
020import static org.apache.juneau.internal.Utils.*;
021import static org.apache.juneau.rest.RestContext.*;
022import static org.apache.juneau.rest.util.RestUtils.*;
023import static org.apache.juneau.httppart.HttpPartType.*;
024
025import java.lang.annotation.*;
026import java.lang.reflect.*;
027import java.util.*;
028import java.util.concurrent.*;
029
030import javax.servlet.http.*;
031
032import org.apache.juneau.*;
033import org.apache.juneau.encoders.*;
034import org.apache.juneau.http.*;
035import org.apache.juneau.http.annotation.*;
036import org.apache.juneau.http.annotation.FormData;
037import org.apache.juneau.http.annotation.Header;
038import org.apache.juneau.http.annotation.Query;
039import org.apache.juneau.http.annotation.Response;
040import org.apache.juneau.httppart.*;
041import org.apache.juneau.httppart.bean.*;
042import org.apache.juneau.internal.*;
043import org.apache.juneau.internal.HttpUtils;
044import org.apache.juneau.parser.*;
045import org.apache.juneau.rest.annotation.*;
046import org.apache.juneau.rest.exception.*;
047import org.apache.juneau.rest.util.RestUtils;
048import org.apache.juneau.rest.util.UrlPathPattern;
049import org.apache.juneau.rest.widget.*;
050import org.apache.juneau.serializer.*;
051import org.apache.juneau.svl.*;
052
053/**
054 * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
055 */
056public class RestJavaMethod implements Comparable<RestJavaMethod>  {
057   private final String httpMethod;
058   private final UrlPathPattern pathPattern;
059   final RestMethodParam[] methodParams;
060   private final RestGuard[] guards;
061   private final RestMatcher[] optionalMatchers;
062   private final RestMatcher[] requiredMatchers;
063   private final RestConverter[] converters;
064   private final RestMethodProperties properties;
065   private final Integer priority;
066   private final RestContext context;
067   final java.lang.reflect.Method method;
068   final PropertyStore propertyStore;
069   final SerializerGroup serializers;
070   final ParserGroup parsers;
071   final EncoderGroup encoders;
072   final HttpPartSerializer partSerializer;
073   final HttpPartParser partParser;
074   final Map<String,Object>
075      defaultRequestHeaders,
076      defaultQuery,
077      defaultFormData;
078   final String defaultCharset;
079   final long maxInput;
080   final BeanContext beanContext;
081   final Map<String,Widget> widgets;
082   final List<MediaType>
083      supportedAcceptTypes,
084      supportedContentTypes;
085
086   final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>();
087   final Map<Class<?>,ResponsePartMeta> headerPartMetas = new ConcurrentHashMap<>();
088   final Map<Class<?>,ResponsePartMeta> bodyPartMetas = new ConcurrentHashMap<>();
089   final ResponseBeanMeta responseMeta;
090
091   RestJavaMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
092      Builder b = new Builder(servlet, method, context);
093      this.context = context;
094      this.method = method;
095      this.httpMethod = b.httpMethod;
096      this.pathPattern = b.pathPattern;
097      this.methodParams = b.methodParams;
098      this.guards = b.guards;
099      this.optionalMatchers = b.optionalMatchers;
100      this.requiredMatchers = b.requiredMatchers;
101      this.converters = b.converters;
102      this.serializers = b.serializers;
103      this.parsers = b.parsers;
104      this.encoders = b.encoders;
105      this.partParser = b.partParser;
106      this.partSerializer = b.partSerializer;
107      this.beanContext = b.beanContext;
108      this.properties = b.properties;
109      this.propertyStore = b.propertyStore;
110      this.defaultRequestHeaders = b.defaultRequestHeaders;
111      this.defaultQuery = b.defaultQuery;
112      this.defaultFormData = b.defaultFormData;
113      this.defaultCharset = b.defaultCharset;
114      this.maxInput = b.maxInput;
115      this.priority = b.priority;
116      this.supportedAcceptTypes = b.supportedAcceptTypes;
117      this.supportedContentTypes = b.supportedContentTypes;
118      this.responseMeta = b.responseMeta;
119      this.widgets = unmodifiableMap(b.widgets);
120   }
121
122   private static final class Builder  {
123      String httpMethod, defaultCharset;
124      UrlPathPattern pathPattern;
125      RestMethodParam[] methodParams;
126      RestGuard[] guards;
127      RestMatcher[] optionalMatchers, requiredMatchers;
128      RestConverter[] converters;
129      SerializerGroup serializers;
130      ParserGroup parsers;
131      EncoderGroup encoders;
132      HttpPartParser partParser;
133      HttpPartSerializer partSerializer;
134      BeanContext beanContext;
135      RestMethodProperties properties;
136      PropertyStore propertyStore;
137      Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
138      long maxInput;
139      Integer priority;
140      Map<String,Widget> widgets;
141      List<MediaType> supportedAcceptTypes, supportedContentTypes;
142      ResponseBeanMeta responseMeta;
143
144      Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
145         String sig = method.getDeclaringClass().getName() + '.' + method.getName();
146
147         try {
148
149            RestMethod m = getAnnotation(RestMethod.class, method);
150            if (m == null)
151               throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
152
153            VarResolver vr = context.getVarResolver();
154
155            serializers = context.getSerializers();
156            parsers = context.getParsers();
157            partSerializer = context.getPartSerializer();
158            partParser = context.getPartParser();
159            beanContext = context.getBeanContext();
160            encoders = context.getEncoders();
161            properties = new RestMethodProperties(context.getProperties());
162            defaultCharset = context.getDefaultCharset();
163            maxInput = context.getMaxInput();
164
165            if (! m.defaultCharset().isEmpty())
166               defaultCharset = vr.resolve(m.defaultCharset());
167            if (! m.maxInput().isEmpty())
168               maxInput = StringUtils.parseLongWithSuffix(vr.resolve(m.maxInput()));
169
170            HtmlDocBuilder hdb = new HtmlDocBuilder(properties);
171
172            HtmlDoc hd = m.htmldoc();
173            hdb.process(hd);
174
175            widgets = new HashMap<>(context.getWidgets());
176            for (Class<? extends Widget> wc : hd.widgets()) {
177               Widget w = beanContext.newInstance(Widget.class, wc);
178               widgets.put(w.getName(), w);
179               hdb.script("INHERIT", "$W{"+w.getName()+".script}");
180               hdb.style("INHERIT", "$W{"+w.getName()+".style}");
181            }
182
183            SerializerGroupBuilder sgb = null;
184            ParserGroupBuilder pgb = null;
185            ParserBuilder uepb = null;
186            BeanContextBuilder bcb = null;
187            PropertyStore cps = context.getPropertyStore();
188
189            Object[] mSerializers = merge(cps.getArrayProperty(REST_serializers, Object.class), m.serializers());
190            Object[] mParsers = merge(cps.getArrayProperty(REST_parsers, Object.class), m.parsers());
191            Object[] mPojoSwaps = merge(cps.getArrayProperty(BEAN_pojoSwaps, Object.class), m.pojoSwaps());
192            Object[] mBeanFilters = merge(cps.getArrayProperty(BEAN_beanFilters, Object.class), m.beanFilters());
193
194            if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
195                  || m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0
196                  || m.bpx().length > 0) {
197               sgb = SerializerGroup.create();
198               pgb = ParserGroup.create();
199               uepb = Parser.create();
200               bcb = beanContext.builder();
201               sgb.append(mSerializers);
202               pgb.append(mParsers);
203            }
204
205            String p = m.path();
206            if (isEmpty(p))
207               p = HttpUtils.detectHttpPath(method, true);
208
209            httpMethod = emptyIfNull(firstNonEmpty(m.name(), m.method())).toUpperCase(Locale.ENGLISH);
210            if (httpMethod.isEmpty())
211               httpMethod = HttpUtils.detectHttpMethod(method, true, "GET");
212            if ("METHOD".equals(httpMethod))
213               httpMethod = "*";
214
215            priority = m.priority();
216
217            converters = new RestConverter[m.converters().length];
218            for (int i = 0; i < converters.length; i++)
219               converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]);
220
221            guards = new RestGuard[m.guards().length];
222            for (int i = 0; i < guards.length; i++)
223               guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]);
224
225            List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
226            for (int i = 0; i < m.matchers().length; i++) {
227               Class<? extends RestMatcher> c = m.matchers()[i];
228               RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method);
229               if (matcher.mustMatch())
230                  requiredMatchers.add(matcher);
231               else
232                  optionalMatchers.add(matcher);
233            }
234            if (! m.clientVersion().isEmpty())
235               requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method));
236
237            this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
238            this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
239
240            PropertyStoreBuilder psb = PropertyStore.create().add(properties).set(BEAN_beanFilters, mBeanFilters).set(BEAN_pojoSwaps, mPojoSwaps);
241            for (Property p1 : m.properties())
242               psb.set(p1.name(), p1.value());
243            for (String p1 : m.flags())
244               psb.set(p1, true);
245            this.propertyStore = psb.build();
246
247            if (sgb != null) {
248               sgb.apply(propertyStore);
249               if (m.bpi().length > 0) {
250                  Map<String,String> bpiMap = new LinkedHashMap<>();
251                  for (String s : m.bpi()) {
252                     for (String s2 : split(s, ';')) {
253                        int i = s2.indexOf(':');
254                        if (i == -1)
255                           throw new RestServletException(
256                              "Invalid format for @RestMethod(bpi) on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
257                        bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
258                     }
259                  }
260                  sgb.includeProperties(bpiMap);
261               }
262               if (m.bpx().length > 0) {
263                  Map<String,String> bpxMap = new LinkedHashMap<>();
264                  for (String s : m.bpx()) {
265                     for (String s2 : split(s, ';')) {
266                        int i = s2.indexOf(':');
267                        if (i == -1)
268                           throw new RestServletException(
269                              "Invalid format for @RestMethod(bpx) on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
270                        bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
271                     }
272                  }
273                  sgb.excludeProperties(bpxMap);
274               }
275               sgb.beanFilters(mBeanFilters);
276               sgb.pojoSwaps(mPojoSwaps);
277            }
278
279            if (pgb != null) {
280               pgb.apply(propertyStore);
281               pgb.beanFilters(mBeanFilters);
282               pgb.pojoSwaps(mPojoSwaps);
283            }
284
285            if (uepb != null) {
286               uepb.apply(propertyStore);
287               uepb.beanFilters(mBeanFilters);
288               uepb.pojoSwaps(mPojoSwaps);
289            }
290
291            if (bcb != null) {
292               bcb.apply(propertyStore);
293               bcb.pojoSwaps(mPojoSwaps);
294            }
295
296            if (m.properties().length > 0 || m.flags().length > 0) {
297               properties = new RestMethodProperties(properties);
298               for (Property p1 : m.properties())
299                  properties.put(p1.name(), p1.value());
300               for (String p1 : m.flags())
301                  properties.put(p1, true);
302            }
303
304            if (m.encoders().length > 0) {
305               EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE);
306               for (Class<?> c : m.encoders()) {
307                  try {
308                     g.append(c);
309                  } catch (Exception e) {
310                     throw new RestServletException(
311                        "Exception occurred while trying to instantiate ConfigEncoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e);
312                  }
313               }
314               encoders = g.build();
315            }
316
317            defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
318            for (String s : m.defaultRequestHeaders()) {
319               String[] h = RestUtils.parseKeyValuePair(vr.resolve(s));
320               if (h == null)
321                  throw new RestServletException(
322                     "Invalid default request header specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
323               defaultRequestHeaders.put(h[0], h[1]);
324            }
325
326            String defaultAccept = vr.resolve(m.defaultAccept());
327            if (isNotEmpty(defaultAccept))
328               defaultRequestHeaders.put("Accept", defaultAccept);
329
330            String defaultContentType = vr.resolve(m.defaultContentType());
331            if (isNotEmpty(defaultContentType))
332               defaultRequestHeaders.put("Content-Type", defaultAccept);
333
334            defaultQuery = new LinkedHashMap<>();
335            for (String s : m.defaultQuery()) {
336               String[] h = RestUtils.parseKeyValuePair(vr.resolve(s));
337               if (h == null)
338                  throw new RestServletException(
339                     "Invalid default query parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
340               defaultQuery.put(h[0], h[1]);
341            }
342
343            defaultFormData = new LinkedHashMap<>();
344            for (String s : m.defaultFormData()) {
345               String[] h = RestUtils.parseKeyValuePair(vr.resolve(s));
346               if (h == null)
347                  throw new RestServletException(
348                     "Invalid default form data parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
349               defaultFormData.put(h[0], h[1]);
350            }
351
352            Type[] pt = method.getGenericParameterTypes();
353            Annotation[][] pa = method.getParameterAnnotations();
354            for (int i = 0; i < pt.length; i++) {
355               for (Annotation a : pa[i]) {
356                  if (a instanceof Header) {
357                     Header h = (Header)a;
358                     if (h._default().length > 0)
359                        defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), parseAnything(joinnl(h._default())));
360                  } else if (a instanceof Query) {
361                     Query q = (Query)a;
362                     if (q._default().length > 0)
363                        defaultQuery.put(firstNonEmpty(q.name(), q.value()), parseAnything(joinnl(q._default())));
364                  } else if (a instanceof FormData) {
365                     FormData f = (FormData)a;
366                     if (f._default().length > 0)
367                        defaultFormData.put(firstNonEmpty(f.name(), f.value()), parseAnything(joinnl(f._default())));
368                  }
369               }
370            }
371
372            pathPattern = new UrlPathPattern(p);
373
374            if (sgb != null)
375               serializers = sgb.build();
376            if (pgb != null)
377               parsers = pgb.build();
378            if (uepb != null && partParser instanceof Parser) {
379               Parser pp = (Parser)partParser;
380               partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build();
381            }
382            if (bcb != null)
383               beanContext = bcb.build();
384
385            supportedAcceptTypes =
386               m.produces().length > 0
387               ? immutableList(MediaType.forStrings(resolveVars(vr, m.produces())))
388               : serializers.getSupportedMediaTypes();
389            supportedContentTypes =
390               m.consumes().length > 0
391               ? immutableList(MediaType.forStrings(resolveVars(vr, m.consumes())))
392               : parsers.getSupportedMediaTypes();
393
394            methodParams = context.findParams(method, false, pathPattern);
395
396            if (hasAnnotation(Response.class, method))
397               responseMeta = ResponseBeanMeta.create(method, serializers.getPropertyStore());
398
399            // Need this to access methods in anonymous inner classes.
400            setAccessible(method, true);
401         } catch (RestServletException e) {
402            throw e;
403         } catch (Exception e) {
404            throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e);
405         }
406      }
407   }
408
409   ResponseBeanMeta getResponseBeanMeta(Object o) {
410      if (o == null)
411         return null;
412      Class<?> c = o.getClass();
413      ResponseBeanMeta rbm = responseBeanMetas.get(c);
414      if (rbm == null) {
415         rbm = ResponseBeanMeta.create(c, serializers.getPropertyStore());
416         if (rbm == null)
417            rbm = ResponseBeanMeta.NULL;
418         responseBeanMetas.put(c, rbm);
419      }
420      if (rbm == ResponseBeanMeta.NULL)
421         return null;
422      return rbm;
423   }
424
425   ResponsePartMeta getResponseHeaderMeta(Object o) {
426      if (o == null)
427         return null;
428      Class<?> c = o.getClass();
429      ResponsePartMeta pm = headerPartMetas.get(c);
430      if (pm == null) {
431         ResponseHeader a = c.getAnnotation(ResponseHeader.class);
432         if (a != null) {
433            HttpPartSchema schema = HttpPartSchema.create(a);
434            HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer);
435            pm = new ResponsePartMeta(HEADER, schema, serializer);
436         }
437         if (pm == null)
438            pm = ResponsePartMeta.NULL;
439         headerPartMetas.put(c, pm);
440      }
441      if (pm == ResponsePartMeta.NULL)
442         return null;
443      return pm;
444   }
445
446   ResponsePartMeta getResponseBodyMeta(Object o) {
447      if (o == null)
448         return null;
449      Class<?> c = o.getClass();
450      ResponsePartMeta pm = bodyPartMetas.get(c);
451      if (pm == null) {
452         ResponseBody a = c.getAnnotation(ResponseBody.class);
453         if (a != null) {
454            HttpPartSchema schema = HttpPartSchema.create(a);
455            HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer);
456            pm = new ResponsePartMeta(BODY, schema, serializer);
457         }
458         if (pm == null)
459            pm = ResponsePartMeta.NULL;
460         bodyPartMetas.put(c, pm);
461      }
462      if (pm == ResponsePartMeta.NULL)
463         return null;
464      return pm;
465   }
466
467   /**
468    * Returns <jk>true</jk> if this Java method has any guards or matchers.
469    */
470   boolean hasGuardsOrMatchers() {
471      return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
472   }
473
474   /**
475    * Returns the HTTP method name (e.g. <js>"GET"</js>).
476    */
477   String getHttpMethod() {
478      return httpMethod;
479   }
480
481   /**
482    * Returns the path pattern for this method.
483    */
484   String getPathPattern() {
485      return pathPattern.toString();
486   }
487
488   /**
489    * Returns <jk>true</jk> if the specified request object can call this method.
490    */
491   boolean isRequestAllowed(RestRequest req) {
492      for (RestGuard guard : guards) {
493         req.setJavaMethod(method);
494         if (! guard.isRequestAllowed(req))
495            return false;
496      }
497      return true;
498   }
499
500   /**
501    * Workhorse method.
502    *
503    * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
504    * @return The HTTP response code.
505    */
506   int invoke(String pathInfo, RestRequest req, RestResponse res) throws Throwable {
507
508      String[] patternVals = pathPattern.match(pathInfo);
509      if (patternVals == null)
510         return SC_NOT_FOUND;
511
512      String remainder = null;
513      if (patternVals.length > pathPattern.getVars().length)
514         remainder = patternVals[pathPattern.getVars().length];
515      for (int i = 0; i < pathPattern.getVars().length; i++)
516         req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
517      req.getPathMatch().pattern(pathPattern.getPatternString()).remainder(remainder);
518
519      RequestProperties requestProperties = new RequestProperties(req.getVarResolverSession(), properties);
520
521      req.init(this, requestProperties);
522      res.init(this, requestProperties);
523
524      // Class-level guards
525      for (RestGuard guard : context.getGuards())
526         if (! guard.guard(req, res))
527            return SC_UNAUTHORIZED;
528
529      // If the method implements matchers, test them.
530      for (RestMatcher m : requiredMatchers)
531         if (! m.matches(req))
532            return SC_PRECONDITION_FAILED;
533      if (optionalMatchers.length > 0) {
534         boolean matches = false;
535         for (RestMatcher m : optionalMatchers)
536            matches |= m.matches(req);
537         if (! matches)
538            return SC_PRECONDITION_FAILED;
539      }
540
541      context.preCall(req, res);
542
543      Object[] args = new Object[methodParams.length];
544      for (int i = 0; i < methodParams.length; i++) {
545         try {
546            args[i] = methodParams[i].resolve(req, res);
547         } catch (RestException e) {
548            throw e;
549         } catch (Exception e) {
550            throw new BadRequest(e,
551               "Invalid data conversion.  Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
552               methodParams[i].getParamType().name(), methodParams[i].getName(), methodParams[i].getType(), method.getDeclaringClass().getName(), method.getName()
553            );
554         }
555      }
556
557      try {
558
559         for (RestGuard guard : guards)
560            if (! guard.guard(req, res))
561               return SC_OK;
562
563         Object output;
564         try {
565            output = method.invoke(context.getResource(), args);
566            if (res.getStatus() == 0)
567               res.setStatus(200);
568            if (! method.getReturnType().equals(Void.TYPE)) {
569               if (output != null || ! res.getOutputStreamCalled())
570                  res.setOutput(output);
571            }
572         } catch (InvocationTargetException e) {
573            Throwable e2 = e.getTargetException();    // Get the throwable thrown from the doX() method.
574            res.setStatus(500);
575            if (getResponseBodyMeta(e2) != null || getResponseBeanMeta(e2) != null) {
576               res.setOutput(e2);
577            } else {
578               throw e;
579            }
580         }
581
582         context.postCall(req, res);
583
584         if (res.hasOutput())
585            for (RestConverter converter : converters)
586               res.setOutput(converter.convert(req, res.getOutput()));
587
588      } catch (IllegalArgumentException e) {
589         throw new BadRequest(e,
590            "Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
591            method.toString(), getReadableClassNames(args)
592         );
593      } catch (InvocationTargetException e) {
594         Throwable e2 = e.getTargetException();    // Get the throwable thrown from the doX() method.
595         if (e2 instanceof RestException)
596            throw (RestException)e2;
597         if (e2 instanceof ParseException)
598            throw new BadRequest(e2);
599         if (e2 instanceof InvalidDataConversionException)
600            throw new BadRequest(e2);
601         throw e2;
602      }
603      return SC_OK;
604   }
605
606   @Override /* Object */
607   public String toString() {
608      return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString();
609   }
610
611   /*
612    * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
613    * It maintains the order in which matches are made during requests.
614    */
615   @Override /* Comparable */
616   public int compareTo(RestJavaMethod o) {
617      int c;
618
619      c = priority.compareTo(o.priority);
620      if (c != 0)
621         return c;
622
623      c = pathPattern.compareTo(o.pathPattern);
624      if (c != 0)
625         return c;
626
627      c = compare(o.requiredMatchers.length, requiredMatchers.length);
628      if (c != 0)
629         return c;
630
631      c = compare(o.optionalMatchers.length, optionalMatchers.length);
632      if (c != 0)
633         return c;
634
635      c = compare(o.guards.length, guards.length);
636      if (c != 0)
637         return c;
638
639      return 0;
640   }
641
642   /**
643    * Bean property getter:  <property>serializers</property>.
644    *
645    * @return The value of the <property>serializers</property> property on this bean, or <jk>null</jk> if it is not set.
646    */
647   public SerializerGroup getSerializers() {
648      return serializers;
649   }
650
651   /**
652    * Bean property getter:  <property>parsers</property>.
653    *
654    * @return The value of the <property>parsers</property> property on this bean, or <jk>null</jk> if it is not set.
655    */
656   public ParserGroup getParsers() {
657      return parsers;
658   }
659
660   /**
661    * Bean property getter:  <property>partSerializer</property>.
662    *
663    * @return The value of the <property>partSerializer</property> property on this bean, or <jk>null</jk> if it is not set.
664    */
665   public HttpPartSerializer getPartSerializer() {
666      return partSerializer;
667   }
668
669   /**
670    * Bean property getter:  <property>partParser</property>.
671    *
672    * @return The value of the <property>partParser</property> property on this bean, or <jk>null</jk> if it is not set.
673    */
674   public HttpPartParser getPartParser() {
675      return partParser;
676   }
677
678   @Override /* Object */
679   public boolean equals(Object o) {
680      if (! (o instanceof RestJavaMethod))
681         return false;
682      return (compareTo((RestJavaMethod)o) == 0);
683   }
684
685   @Override /* Object */
686   public int hashCode() {
687      return method.hashCode();
688   }
689
690   //-----------------------------------------------------------------------------------------------------------------
691   // Utility methods.
692   //-----------------------------------------------------------------------------------------------------------------
693   static String[] resolveVars(VarResolver vr, String[] in) {
694      String[] out = new String[in.length];
695      for (int i = 0; i < in.length; i++)
696         out[i] = vr.resolve(in[i]);
697      return out;
698   }
699
700   static HttpPartSerializer createPartSerializer(Class<? extends HttpPartSerializer> c, PropertyStore ps, HttpPartSerializer _default) {
701      HttpPartSerializer hps = ClassUtils.newInstance(HttpPartSerializer.class, c, true, ps);
702      return hps == null ? _default : hps;
703   }
704}