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 java.util.logging.Level.*; 016import static javax.servlet.http.HttpServletResponse.*; 017import static org.apache.juneau.internal.StringUtils.*; 018import static org.apache.juneau.rest.HttpRuntimeException.*; 019import static org.apache.juneau.rest.annotation.HookEvent.*; 020 021import java.io.*; 022import java.lang.reflect.Method; 023import java.text.*; 024import java.util.*; 025import java.util.function.*; 026import java.util.logging.*; 027 028import javax.servlet.*; 029import javax.servlet.http.*; 030 031import org.apache.juneau.internal.*; 032import org.apache.juneau.reflect.*; 033import org.apache.juneau.rest.annotation.*; 034import org.apache.juneau.cp.*; 035import org.apache.juneau.dto.swagger.*; 036import org.apache.juneau.http.exception.*; 037 038/** 039 * Servlet implementation of a REST resource. 040 * 041 * <ul class='seealso'> 042 * <li class='link'>{@doc RestServlet} 043 * </ul> 044 */ 045public abstract class RestServlet extends HttpServlet implements RestInfoProvider, RestCallLogger, RestResourceResolver, ResourceFinder { 046 047 private static final long serialVersionUID = 1L; 048 049 private RestContextBuilder builder; 050 private volatile RestContext context; 051 private volatile Exception initException; 052 private boolean isInitialized = false; // Should not be volatile. 053 private volatile RestResourceResolver resourceResolver = new BasicRestResourceResolver(); 054 private Logger logger = Logger.getLogger(getClass().getName()); 055 private RestInfoProvider infoProvider; 056 private RestCallLogger callLogger; 057 private ResourceFinder resourceFinder; 058 059 @Override /* Servlet */ 060 public final synchronized void init(ServletConfig servletConfig) throws ServletException { 061 try { 062 if (context != null) 063 return; 064 builder = RestContext.create(servletConfig, this.getClass(), null); 065 if (resourceResolver != null) 066 builder.resourceResolver(resourceResolver); 067 builder.init(this); 068 super.init(servletConfig); 069 builder.servletContext(this.getServletContext()); 070 setContext(builder.build()); 071 context.postInitChildFirst(); 072 } catch (ServletException e) { 073 initException = e; 074 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 075 throw e; 076 } catch (Throwable e) { 077 initException = toHttpException(e, InternalServerError.class); 078 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 079 } 080 } 081 082 /* 083 * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly. 084 * Used when subclasses of RestServlet are attached as child resources. 085 */ 086 synchronized void innerInit(ServletConfig servletConfig) throws ServletException { 087 super.init(servletConfig); 088 } 089 090 /** 091 * Sets the context object for this servlet. 092 * 093 * @param context Sets the context object on this servlet. 094 * @throws ServletException If error occurred during post-initialiation. 095 */ 096 public synchronized void setContext(RestContext context) throws ServletException { 097 this.builder = context.builder; 098 this.context = context; 099 isInitialized = true; 100 infoProvider = new BasicRestInfoProvider(context); 101 callLogger = new BasicRestCallLogger(context); 102 resourceFinder = new RecursiveResourceFinder(); 103 context.postInit(); 104 } 105 106 /** 107 * Returns <jk>true</jk> if this servlet has been initialized and {@link #getContext()} returns a value. 108 * 109 * @return <jk>true</jk> if this servlet has been initialized and {@link #getContext()} returns a value. 110 */ 111 public synchronized boolean isInitialized() { 112 return isInitialized; 113 } 114 115 /** 116 * Sets the resource resolver to use for this servlet and all child servlets. 117 * <p> 118 * This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called. 119 * Otherwise calling this method will have no effect. 120 * 121 * @param resourceResolver The resolver instance. Can be <jk>null</jk>. 122 * @return This object (for method chaining). 123 */ 124 public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) { 125 this.resourceResolver = resourceResolver; 126 return this; 127 } 128 129 /** 130 * Returns the path defined on this servlet if it's defined via {@link Rest#path()}. 131 * 132 * @return The path defined on this servlet, or an empty string if not specified. 133 */ 134 @SuppressWarnings("deprecation") 135 public synchronized String getPath() { 136 if (context != null) 137 return context.getPath(); 138 ClassInfo ci = ClassInfo.of(getClass()); 139 String path = ""; 140 for (Rest rr : ci.getAnnotations(Rest.class)) 141 if (! rr.path().isEmpty()) 142 path = trimSlashes(rr.path()); 143 if (! path.isEmpty()) 144 return path; 145 for (RestResource rr : ci.getAnnotations(RestResource.class)) 146 if (! rr.path().isEmpty()) 147 path = trimSlashes(rr.path()); 148 return ""; 149 } 150 151 @Override /* GenericServlet */ 152 public synchronized RestContextBuilder getServletConfig() { 153 return builder; 154 } 155 156 //----------------------------------------------------------------------------------------------------------------- 157 // Context methods. 158 //----------------------------------------------------------------------------------------------------------------- 159 160 /** 161 * Returns the read-only context object that contains all the configuration information about this resource. 162 * 163 * <p> 164 * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time 165 * {@link #init()} is called. 166 * 167 * <p> 168 * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get 169 * access to this context object: 170 * <p class='bcode w800'> 171 * <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception; 172 * </p> 173 * 174 * @return The context information on this servlet. 175 */ 176 public synchronized RestContext getContext() { 177 if (context == null) 178 throw new InternalServerError("RestContext object not set on resource."); 179 return context; 180 } 181 182 /** 183 * Convenience method for calling <c>getContext().getProperties();</c> 184 * 185 * @return The resource properties as an {@link RestContextProperties}. 186 * @see RestContext#getProperties() 187 */ 188 public RestContextProperties getProperties() { 189 return getContext().getProperties(); 190 } 191 192 193 //----------------------------------------------------------------------------------------------------------------- 194 // Convenience logger methods 195 //----------------------------------------------------------------------------------------------------------------- 196 197 /** 198 * Log a message at {@link Level#INFO} level. 199 * 200 * <p> 201 * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}. 202 * 203 * @param msg The message to log. 204 */ 205 @Override /* GenericServlet */ 206 public void log(String msg) { 207 doLog(Level.INFO, null, () -> msg); 208 } 209 210 /** 211 * Log a message. 212 * 213 * <p> 214 * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}. 215 * 216 * @param msg The message to log. 217 * @param cause The cause. 218 */ 219 @Override /* GenericServlet */ 220 public void log(String msg, Throwable cause) { 221 doLog(Level.INFO, null, () -> msg); 222 } 223 224 /** 225 * Log a message. 226 * 227 * <p> 228 * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}. 229 * 230 * @param level The log level. 231 * @param msg The message to log. 232 * @param args Optional {@link MessageFormat}-style arguments. 233 */ 234 public void log(Level level, String msg, Object...args) { 235 doLog(level, null, () -> StringUtils.format(msg, args)); 236 } 237 238 /** 239 * Log a message. 240 * 241 * <p> 242 * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}. 243 * 244 * @param level The log level. 245 * @param cause The cause. 246 * @param msg The message to log. 247 * @param args Optional {@link MessageFormat}-style arguments. 248 */ 249 public void log(Level level, Throwable cause, String msg, Object...args) { 250 doLog(level, cause, () -> StringUtils.format(msg, args)); 251 } 252 253 /** 254 * Main logger method. 255 * 256 * <p> 257 * The default behavior logs a message to the Java logger of the class name. 258 * 259 * <p> 260 * Subclasses can override this method to implement their own logger handling. 261 * 262 * @param level The log level. 263 * @param cause Optional throwable. 264 * @param msg The message to log. 265 */ 266 protected void doLog(Level level, Throwable cause, Supplier<String> msg) { 267 logger.log(level, cause, msg); 268 } 269 270 //----------------------------------------------------------------------------------------------------------------- 271 // Lifecycle methods 272 //----------------------------------------------------------------------------------------------------------------- 273 274 /** 275 * The main service method. 276 * 277 * <p> 278 * Subclasses can optionally override this method if they want to tailor the behavior of requests. 279 */ 280 @Override /* Servlet */ 281 public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException { 282 try { 283 // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check. 284 if (! isInitialized) { 285 if (initException != null) 286 throw initException; 287 if (context == null) 288 throw new InternalServerError("Servlet {0} not initialized. init(ServletConfig) was not called. This can occur if you've overridden this method but didn't call super.init(RestConfig).", getClass().getName()); 289 isInitialized = true; 290 } 291 292 context.execute(r1, r2); 293 294 } catch (Throwable e) { 295 r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); 296 } 297 } 298 299 @Override /* GenericServlet */ 300 public synchronized void destroy() { 301 if (context != null) 302 context.destroy(); 303 super.destroy(); 304 } 305 306 //----------------------------------------------------------------------------------------------------------------- 307 // Hook events 308 //----------------------------------------------------------------------------------------------------------------- 309 310 /** 311 * Method that gets called during servlet initialization. 312 * 313 * <p> 314 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContextBuilder} 315 * object has been created and initialized with the annotations defined on the class, but before the 316 * {@link RestContext} object has been created. 317 * 318 * <p> 319 * An example of this is the <c>PetStoreResource</c> class that uses an init method to perform initialization 320 * of an internal data structure. 321 * 322 * <h5 class='figure'>Example:</h5> 323 * <p class='bcode w800'> 324 * <ja>@Rest</ja>(...) 325 * <jk>public class</jk> PetStoreResource <jk>extends</jk> ResourceJena { 326 * 327 * <jc>// Our database.</jc> 328 * <jk>private</jk> Map<Integer,Pet> <jf>petDB</jf>; 329 * 330 * <ja>@Override</ja> 331 * <jk>public void</jk> onInit(RestContextBuilder builder) <jk>throws</jk> Exception { 332 * <jc>// Load our database from a local JSON file.</jc> 333 * <jf>petDB</jf> = JsonParser.<jsf>DEFAULT</jsf>.parse(getClass().getResourceAsStream(<js>"PetStore.json"</js>), LinkedHashMap.<jk>class</jk>, Integer.<jk>class</jk>, Pet.<jk>class</jk>); 334 * } 335 * } 336 * </p> 337 * 338 * <ul class='notes'> 339 * <li> 340 * The default implementation of this method is a no-op. 341 * <li> 342 * Multiple INIT methods can be defined on a class. 343 * <br>INIT methods on parent classes are invoked before INIT methods on child classes. 344 * <br>The order of INIT method invocations within a class is alphabetical, then by parameter count, then by parameter types. 345 * <li> 346 * The method can throw any exception causing initialization of the servlet to fail. 347 * </ul> 348 * 349 * @param builder Context builder which can be used to configure the servlet. 350 * @throws Exception Any exception thrown will cause servlet to fail startup. 351 */ 352 @RestHook(INIT) 353 public void onInit(RestContextBuilder builder) throws Exception {} 354 355 /** 356 * Method that gets called immediately after servlet initialization. 357 * 358 * <p> 359 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContext} 360 * object has been created. 361 * 362 * <ul class='notes'> 363 * <li> 364 * The default implementation of this method is a no-op. 365 * <li> 366 * Multiple POST_INIT methods can be defined on a class. 367 * <br>POST_INIT methods on parent classes are invoked before POST_INIT methods on child classes. 368 * <br>The order of POST_INIT method invocations within a class is alphabetical, then by parameter count, then by parameter types. 369 * <li> 370 * The method can throw any exception causing initialization of the servlet to fail. 371 * </ul> 372 * 373 * @param context The initialized context object. 374 * @throws Exception Any exception thrown will cause servlet to fail startup. 375 */ 376 @RestHook(POST_INIT) 377 public void onPostInit(RestContext context) throws Exception {} 378 379 /** 380 * Identical to {@link #onPostInit(RestContext)} except the order of execution is child-resources first. 381 * 382 * <p> 383 * Use this method if you need to perform any kind of initialization on child resources before the parent resource. 384 * 385 * <p> 386 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContext} 387 * object has been created and after the {@link HookEvent#POST_INIT} methods have been called. 388 * 389 * <p> 390 * The only valid parameter type for this method is {@link RestContext} which can be used to retrieve information 391 * about the servlet. 392 * 393 * <ul class='notes'> 394 * <li> 395 * The default implementation of this method is a no-op. 396 * <li> 397 * Multiple POST_INIT_CHILD_FIRST methods can be defined on a class. 398 * <br>POST_INIT_CHILD_FIRST methods on parent classes are invoked before POST_INIT_CHILD_FIRST methods on child classes. 399 * <br>The order of POST_INIT_CHILD_FIRST method invocations within a class is alphabetical, then by parameter count, then by parameter types. 400 * <li> 401 * The method can throw any exception causing initialization of the servlet to fail. 402 * </ul> 403 * 404 * @param context The initialized context object. 405 * @throws Exception Any exception thrown will cause servlet to fail startup. 406 */ 407 @RestHook(POST_INIT_CHILD_FIRST) 408 public void onPostInitChildFirst(RestContext context) throws Exception {} 409 410 /** 411 * Method that gets called during servlet destroy. 412 * 413 * <p> 414 * This method is called from within the {@link Servlet#destroy()}. 415 * 416 * <h5 class='figure'>Example:</h5> 417 * <p class='bcode w800'> 418 * <ja>@Rest</ja>(...) 419 * <jk>public class</jk> PetStoreResource <jk>extends</jk> ResourceJena { 420 * 421 * <jc>// Our database.</jc> 422 * <jk>private</jk> Map<Integer,Pet> <jf>petDB</jf>; 423 * 424 * <ja>@Override</ja> 425 * <jk>public void</jk> onDestroy(RestContext context) { 426 * <jf>petDB</jf> = <jk>null</jk>; 427 * } 428 * } 429 * </p> 430 * 431 * <ul class='notes'> 432 * <li> 433 * The default implementation of this method is a no-op. 434 * <li> 435 * Multiple DESTROY methods can be defined on a class. 436 * <br>DESTROY methods on child classes are invoked before DESTROY methods on parent classes. 437 * <br>The order of DESTROY method invocations within a class is alphabetical, then by parameter count, then by parameter types. 438 * <li> 439 * In general, destroy methods should not throw any exceptions, although if any are thrown, the stack trace will be 440 * printed to <c>System.err</c>. 441 * </ul> 442 * 443 * @param context The initialized context object. 444 * @throws Exception Any exception thrown will cause stack trace to be printed to <c>System.err</c>. 445 */ 446 @RestHook(DESTROY) 447 public void onDestroy(RestContext context) throws Exception {} 448 449 /** 450 * A method that is called immediately after the <c>HttpServlet.service(HttpServletRequest, HttpServletResponse)</c> 451 * method is called. 452 * 453 * <p> 454 * Note that you only have access to the raw request and response objects at this point. 455 * 456 * <h5 class='figure'>Example:</h5> 457 * <p class='bcode w800'> 458 * <ja>@Rest</ja>(...) 459 * <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet { 460 * 461 * <jc>// Add a request attribute to all incoming requests.</jc> 462 * <ja>@Override</ja> 463 * <jk>public void</jk> onStartCall(HttpServletRequest req, HttpServletResponse res) { 464 * req.setAttribute(<js>"foobar"</js>, <jk>new</jk> FooBar()); 465 * } 466 * } 467 * </p> 468 * 469 * <ul class='notes'> 470 * <li> 471 * The default implementation of this method is a no-op. 472 * <li> 473 * Multiple START_CALL methods can be defined on a class. 474 * <br>START_CALL methods on parent classes are invoked before START_CALL methods on child classes. 475 * <br>The order of START_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types. 476 * <li> 477 * The method can throw any exception. 478 * <br>{@link HttpException HttpExceptions} can be thrown to cause a particular HTTP error status code. 479 * <br>All other exceptions cause an HTTP 500 error status code. 480 * </ul> 481 * 482 * @param req The HTTP servlet request object. 483 * @param res The HTTP servlet response object. 484 * @throws Exception Any exception. 485 */ 486 @RestHook(START_CALL) 487 public void onStartCall(HttpServletRequest req, HttpServletResponse res) throws Exception {} 488 489 /** 490 * Method that gets called immediately before the <ja>@RestMethod</ja> annotated method gets called. 491 * 492 * <p> 493 * At this point, the {@link RestRequest} object has been fully initialized, and all {@link RestGuard} and 494 * {@link RestMatcher} objects have been called. 495 * 496 * <ul class='notes'> 497 * <li> 498 * The default implementation of this method is a no-op. 499 * <li> 500 * Multiple PRE_CALL methods can be defined on a class. 501 * <br>PRE_CALL methods on parent classes are invoked before PRE_CALL methods on child classes. 502 * <br>The order of PRE_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types. 503 * <li> 504 * The method can throw any exception. 505 * <br>{@link HttpException HttpExceptions} can be thrown to cause a particular HTTP error status code. 506 * <br>All other exceptions cause an HTTP 500 error status code. 507 * <li> 508 * It's advisable not to mess around with the HTTP body itself since you may end up consuming the body 509 * before the actual REST method has a chance to use it. 510 * </ul> 511 * 512 * @param req The request object. 513 * @param res The response object. 514 * @throws Exception Any exception. 515 */ 516 @RestHook(PRE_CALL) 517 public void onPreCall(RestRequest req, RestResponse res) throws Exception {} 518 519 /** 520 * Method that gets called immediately after the <ja>@RestMethod</ja> annotated method gets called. 521 * 522 * <p> 523 * At this point, the output object returned by the method call has been set on the response, but 524 * {@link RestConverter RestConverters} have not yet been executed and the response has not yet been written. 525 * 526 * <ul class='notes'> 527 * <li> 528 * The default implementation of this method is a no-op. 529 * <li> 530 * Multiple POST_CALL methods can be defined on a class. 531 * <br>POST_CALL methods on parent classes are invoked before POST_CALL methods on child classes. 532 * <br>The order of POST_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types. 533 * <li> 534 * The method can throw any exception, although at this point it is too late to set an HTTP error status code. 535 * </ul> 536 * 537 * @param req The request object. 538 * @param res The response object. 539 * @throws Exception Any exception. 540 */ 541 @RestHook(POST_CALL) 542 public void onPostCall(RestRequest req, RestResponse res) throws Exception {} 543 544 /** 545 * Method that gets called right before we exit the servlet service method. 546 * 547 * <p> 548 * At this point, the output has been written and flushed. 549 * 550 * <p> 551 * The following attributes are set on the {@link HttpServletRequest} object that can be useful for logging purposes: 552 * <ul> 553 * <li><js>"Exception"</js> - Any exceptions thrown during the request. 554 * <li><js>"ExecTime"</js> - Execution time of the request. 555 * </ul> 556 * 557 * <ul class='notes'> 558 * <li> 559 * The default implementation of this method is a no-op. 560 * <li> 561 * Multiple END_CALL methods can be defined on a class. 562 * <br>END_CALL methods on parent classes are invoked before END_CALL methods on child classes. 563 * <br>The order of END_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types. 564 * <li> 565 * The method can throw any exception, although at this point it is too late to set an HTTP error status code. 566 * <li> 567 * Note that if you override a parent method, you probably need to call <code><jk>super</jk>.parentMethod(...)</code>. 568 * <br>The method is still considered part of the parent class for ordering purposes even though it's 569 * overridden by the child class. 570 * </ul> 571 * 572 * @param req The HTTP servlet request object. 573 * @param res The HTTP servlet response object. 574 * @throws Exception Any exception. 575 */ 576 @RestHook(END_CALL) 577 public void onEndCall(HttpServletRequest req, HttpServletResponse res) throws Exception {} 578 579 //----------------------------------------------------------------------------------------------------------------- 580 // Other methods. 581 //----------------------------------------------------------------------------------------------------------------- 582 583 /** 584 * Returns the current HTTP request. 585 * 586 * @return The current HTTP request, or <jk>null</jk> if it wasn't created. 587 */ 588 public synchronized RestRequest getRequest() { 589 return getContext().getRequest(); 590 } 591 592 /** 593 * Returns the current HTTP response. 594 * 595 * @return The current HTTP response, or <jk>null</jk> if it wasn't created. 596 */ 597 public synchronized RestResponse getResponse() { 598 return getContext().getResponse(); 599 } 600 601 602 //----------------------------------------------------------------------------------------------------------------- 603 // RestInfoProvider 604 //----------------------------------------------------------------------------------------------------------------- 605 606 @Override /* RestInfoProvider */ 607 public Swagger getSwagger(RestRequest req) throws Exception { 608 return infoProvider.getSwagger(req); 609 } 610 611 @Override /* RestInfoProvider */ 612 public String getSiteName(RestRequest req) throws Exception { 613 return infoProvider.getSiteName(req); 614 } 615 616 @Override /* RestInfoProvider */ 617 public String getTitle(RestRequest req) throws Exception { 618 return infoProvider.getTitle(req); 619 } 620 621 @Override /* RestInfoProvider */ 622 public String getDescription(RestRequest req) throws Exception { 623 return infoProvider.getDescription(req); 624 } 625 626 @Override /* RestInfoProvider */ 627 public String getMethodSummary(Method method, RestRequest req) throws Exception { 628 return infoProvider.getMethodSummary(method, req); 629 } 630 631 @Override /* RestInfoProvider */ 632 public String getMethodDescription(Method method, RestRequest req) throws Exception { 633 return infoProvider.getMethodDescription(method, req); 634 } 635 636 //----------------------------------------------------------------------------------------------------------------- 637 // RestCallLogger 638 //----------------------------------------------------------------------------------------------------------------- 639 640 @Override /* RestCallLogger */ 641 public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) { 642 callLogger.log(config, req, res); 643 } 644 645 //----------------------------------------------------------------------------------------------------------------- 646 // ClasspathResourceFinder 647 //----------------------------------------------------------------------------------------------------------------- 648 649 @Override /* ClasspathResourceFinder */ 650 public InputStream findResource(Class<?> baseClass, String name, Locale locale) throws IOException { 651 return resourceFinder.findResource(baseClass, name, locale); 652 } 653 654 //----------------------------------------------------------------------------------------------------------------- 655 // RestResourceResolver 656 //----------------------------------------------------------------------------------------------------------------- 657 658 @Override /* RestResourceResolver */ 659 public <T> T resolve(Object parent, Class<T> c, Object... args) { 660 return resourceResolver.resolve(parent, c, args); 661 } 662 663 @Override /* RestResourceResolver */ 664 public <T> T resolve(Object parent, Class<T> c, RestContextBuilder builder, Object... args) throws Exception { 665 return resourceResolver.resolve(parent, c, builder, args); 666 } 667}