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