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