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