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<String,Object></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<String,Object></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<String,Object></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<String,Object></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<T> 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>&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<{@link RestMatcher} | Class<? <jk>extends</jk> {@link RestMatcher}>></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}