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 java.io.*; 016import java.lang.reflect.*; 017import java.util.*; 018 019import javax.servlet.http.*; 020 021import org.apache.juneau.httppart.bean.*; 022import org.apache.juneau.rest.util.*; 023 024/** 025 * A wrapper around a single HttpServletRequest/HttpServletResponse pair. 026 */ 027public class RestCall { 028 029 /** 030 * Request attribute name for passing path variables from parent to child. 031 */ 032 private static final String REST_PATHVARS_ATTR = "juneau.pathVars"; 033 034 private HttpServletRequest req; 035 private HttpServletResponse res; 036 private RestRequest rreq; 037 private RestResponse rres; 038 private RestContext context; 039 private RestMethodContext rmethod; 040 private UrlPathInfo urlPathInfo; 041 private String pathInfoUndecoded; 042 private long startTime = System.currentTimeMillis(); 043 private RestCallLogger logger; 044 private RestCallLoggerConfig loggerConfig; 045 046 private UrlPathPatternMatch urlPathPatternMatch; 047 048 /** 049 * Constructor. 050 * 051 * @param context The REST context object. 052 * @param req The incoming HTTP servlet request object. 053 * @param res The incoming HTTP servlet response object. 054 */ 055 public RestCall(RestContext context, HttpServletRequest req, HttpServletResponse res) { 056 context(context).request(req).response(res); 057 } 058 059 //------------------------------------------------------------------------------------------------------------------ 060 // Request/response objects. 061 //------------------------------------------------------------------------------------------------------------------ 062 063 /** 064 * Overrides the request object on the REST call. 065 * 066 * @param req The new HTTP servlet request. 067 * @return This object (for method chaining). 068 */ 069 public RestCall request(HttpServletRequest req) { 070 this.req = req; 071 this.urlPathInfo = null; 072 this.pathInfoUndecoded = null; 073 return this; 074 } 075 076 /** 077 * Overrides the response object on the REST call. 078 * 079 * @param res The new HTTP servlet response. 080 * @return This object (for method chaining). 081 */ 082 public RestCall response(HttpServletResponse res) { 083 this.res = res; 084 return this; 085 } 086 087 /** 088 * Overrides the context object on this call. 089 * 090 * @param context The context that's creating this call. 091 * @return This object (for method chaining). 092 */ 093 public RestCall context(RestContext context) { 094 this.context = context; 095 return this; 096 } 097 098 /** 099 * Sets the method context on this call. 100 * 101 * Used for logging statistics on the method. 102 * 103 * @param value The new value. 104 * @return This object (for method chaining). 105 */ 106 public RestCall restMethodContext(RestMethodContext value) { 107 this.rmethod = value; 108 return this; 109 } 110 111 /** 112 * Set the {@link RestRequest} object on this REST call. 113 * 114 * @param rreq The {@link RestRequest} object on this REST call. 115 * @return This object (for method chaining). 116 */ 117 public RestCall restRequest(RestRequest rreq) { 118 request(rreq); 119 this.rreq = rreq; 120 return this; 121 } 122 123 /** 124 * Set the {@link RestResponse} object on this REST call. 125 * 126 * @param rres The {@link RestResponse} object on this REST call. 127 * @return This object (for method chaining). 128 */ 129 public RestCall restResponse(RestResponse rres) { 130 response(rres); 131 this.rres = rres; 132 this.rreq.setResponse(rres); 133 return this; 134 } 135 136 /** 137 * Returns the HTTP servlet request of this REST call. 138 * 139 * @return the HTTP servlet request of this REST call. 140 */ 141 public HttpServletRequest getRequest() { 142 return req; 143 } 144 145 /** 146 * Returns the HTTP servlet response of this REST call. 147 * 148 * @return the HTTP servlet response of this REST call. 149 */ 150 public HttpServletResponse getResponse() { 151 return res; 152 } 153 154 /** 155 * Returns the REST request of this REST call. 156 * 157 * @return the REST request of this REST call. 158 */ 159 public RestRequest getRestRequest() { 160 return rreq; 161 } 162 163 /** 164 * Returns the REST response of this REST call. 165 * 166 * @return the REST response of this REST call. 167 */ 168 public RestResponse getRestResponse() { 169 return rres; 170 } 171 172 /** 173 * Returns the method context of this call. 174 * 175 * @return The method context of this call. 176 */ 177 public RestMethodContext getRestMethodContext() { 178 return rmethod; 179 } 180 181 /** 182 * Returns the Java method of this call. 183 * 184 * @return The java method of this call, or <jk>null</jk> if it hasn't been determined yet. 185 */ 186 public Method getJavaMethod() { 187 return rmethod == null ? null : rmethod.method; 188 } 189 190 /** 191 * Adds resolved <c><ja>@Resource</ja>(path)</c> variable values to this call. 192 * 193 * @param vars The variables to add to this call. 194 */ 195 @SuppressWarnings("unchecked") 196 public void addPathVars(Map<String,String> vars) { 197 if (vars != null && ! vars.isEmpty()) { 198 Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR); 199 if (m == null) { 200 m = new TreeMap<>(); 201 req.setAttribute(REST_PATHVARS_ATTR, m); 202 } 203 m.putAll(vars); 204 } 205 } 206 207 /** 208 * Returns resolved <c><ja>@Resource</ja>(path)</c> variable values on this call. 209 * 210 * @return Resolved <c><ja>@Resource</ja>(path)</c> variable values on this call. 211 */ 212 @SuppressWarnings("unchecked") 213 public Map<String,String> getPathVars() { 214 Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR); 215 return m == null ? Collections.emptyMap() : m; 216 } 217 218 //------------------------------------------------------------------------------------------------------------------ 219 // Setters. 220 //------------------------------------------------------------------------------------------------------------------ 221 222 /** 223 * Sets the logger to use when logging this call. 224 * 225 * @param logger The logger to use when logging this call. 226 * @return This object (for method chaining). 227 */ 228 public RestCall logger(RestCallLogger logger) { 229 this.logger = logger; 230 return this; 231 } 232 233 /** 234 * Sets the logging configuration to use when logging this call. 235 * 236 * @param config The logging configuration to use when logging this call. 237 * @return This object (for method chaining). 238 */ 239 public RestCall loggerConfig(RestCallLoggerConfig config) { 240 this.loggerConfig = config; 241 return this; 242 } 243 244 /** 245 * Enables or disabled debug mode on this call. 246 * 247 * @param b The debug flag value. 248 * @return This object (for method chaining). 249 * @throws IOException Occurs if request body could not be cached into memory. 250 */ 251 public RestCall debug(boolean b) throws IOException { 252 if (b) { 253 req = CachingHttpServletRequest.wrap(req); 254 res = CachingHttpServletResponse.wrap(res); 255 req.setAttribute("Debug", true); 256 } else { 257 req.removeAttribute("Debug"); 258 } 259 return this; 260 } 261 262 /** 263 * Sets the HTTP status on this call. 264 * 265 * @param code The status code. 266 * @return This object (for method chaining). 267 */ 268 public RestCall status(int code) { 269 res.setStatus(code); 270 return this; 271 } 272 273 /** 274 * Identifies that an exception occurred during this call. 275 * 276 * @param e The thrown exception. 277 * @return This object (for method chaining). 278 */ 279 public RestCall exception(Throwable e) { 280 req.setAttribute("Exception", e); 281 return this; 282 } 283 284 /** 285 * Sets metadata about the response. 286 * 287 * @param meta The metadata about the response. 288 * @return This object (for method chaining). 289 */ 290 public RestCall responseMeta(ResponseBeanMeta meta) { 291 if (rres != null) 292 rres.setResponseMeta(meta); 293 return this; 294 } 295 296 /** 297 * Sets the output object to serialize as the response of this call. 298 * 299 * @param output The response output POJO. 300 * @return This object (for method chaining). 301 */ 302 public RestCall output(Object output) { 303 if (rres != null) 304 rres.setOutput(output); 305 return this; 306 } 307 308 /** 309 * Sets the URL path pattern match on this call. 310 * 311 * @param urlPathPatternMatch The match pattern. 312 * @return This object (for method chaining). 313 */ 314 public RestCall urlPathPatternMatch(UrlPathPatternMatch urlPathPatternMatch) { 315 this.urlPathPatternMatch = urlPathPatternMatch; 316 return this; 317 } 318 319 /** 320 * Returns the URL path pattern match on this call. 321 * 322 * @return The URL path pattern match on this call. 323 */ 324 public UrlPathPatternMatch getUrlPathPatternMatch() { 325 return urlPathPatternMatch; 326 } 327 328 //------------------------------------------------------------------------------------------------------------------ 329 // Lifecycle methods. 330 //------------------------------------------------------------------------------------------------------------------ 331 332 /** 333 * Called at the end of a call to finish any remaining tasks such as flushing buffers and logging the response. 334 * 335 * @return This object (for method chaining). 336 */ 337 public RestCall finish() { 338 try { 339 res.flushBuffer(); 340 req.setAttribute("ExecTime", System.currentTimeMillis() - startTime); 341 if (rreq != null) 342 rreq.close(); 343 } catch (Exception e) { 344 exception(e); 345 } 346 if (logger != null) 347 logger.log(loggerConfig, req, res); 348 return this; 349 } 350 351 352 //------------------------------------------------------------------------------------------------------------------ 353 // Pass-through convenience methods. 354 //------------------------------------------------------------------------------------------------------------------ 355 356 /** 357 * Shortcut for calling <c>getRequest().getServletPath()</c>. 358 * 359 * @return The request servlet path. 360 */ 361 public String getServletPath() { 362 return req.getServletPath(); 363 } 364 365 /** 366 * Returns the request path info as a {@link UrlPathInfo} bean. 367 * 368 * @return The request path info as a {@link UrlPathInfo} bean. 369 */ 370 public UrlPathInfo getUrlPathInfo() { 371 if (urlPathInfo == null) 372 urlPathInfo = new UrlPathInfo(getPathInfoUndecoded()); 373 return urlPathInfo; 374 } 375 376 /** 377 * Shortcut for calling <c>getRequest().getPathInfo()</c>. 378 * 379 * @return The request servlet path info. 380 */ 381 public String getPathInfo() { 382 return req.getPathInfo(); 383 } 384 385 /** 386 * Same as {@link #getPathInfo()} but doesn't decode encoded characters. 387 * 388 * @return The undecoded request servlet path info. 389 */ 390 public String getPathInfoUndecoded() { 391 if (pathInfoUndecoded == null) 392 pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req); 393 return pathInfoUndecoded; 394 } 395 396 /** 397 * Returns the HTTP method name. 398 * 399 * @return The HTTP method name, always uppercased. 400 */ 401 public String getMethod() { 402 if (rreq != null) 403 return rreq.getMethod().toUpperCase(Locale.ENGLISH); 404 return req.getMethod().toUpperCase(Locale.ENGLISH); 405 } 406 407 /** 408 * Shortcut for calling <c>getRequest().getStatus()</c>. 409 * 410 * @return The response status code. 411 */ 412 public int getStatus() { 413 return res.getStatus(); 414 } 415 416 /** 417 * Shortcut for calling <c>getRestResponse().hasOutput()</c>. 418 * 419 * @return <jk>true</jk> if response has output. 420 */ 421 public boolean hasOutput() { 422 if (rres != null) 423 return rres.hasOutput(); 424 return false; 425 } 426 427 /** 428 * Shortcut for calling <c>getRestResponse().getOutput()</c>. 429 * 430 * @return The response output. 431 */ 432 public Object getOutput() { 433 if (rres != null) 434 return rres.getOutput(); 435 return null; 436 } 437 438 /** 439 * Shortcut for calling <c>getRestRequest().isDebug()</c>. 440 * 441 * @return <jk>true</jk> if debug is enabled for this request. 442 */ 443 public boolean isDebug() { 444 if (rreq != null) 445 return rreq.isDebug(); 446 return false; 447 } 448 449 /** 450 * Returns the context that created this call. 451 * 452 * @return The context that created this call. 453 */ 454 public RestContext getContext() { 455 return context; 456 } 457}