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