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.IOUtils.*; 018import static org.apache.juneau.internal.StringUtils.*; 019 020import java.io.*; 021import java.util.*; 022import java.util.logging.*; 023 024import javax.servlet.*; 025import javax.servlet.http.*; 026 027import org.apache.juneau.http.StreamResource; 028import org.apache.juneau.rest.RestContext.*; 029import org.apache.juneau.rest.exception.*; 030import org.apache.juneau.rest.util.RestUtils; 031import org.apache.juneau.rest.vars.*; 032 033/** 034 * Default implementation of {@link RestCallHandler}. 035 * 036 * <p> 037 * Subclasses can override these methods to tailor how HTTP REST calls are handled. 038 * <br>Subclasses MUST implement a public constructor that takes in a {@link RestContext} object. 039 * 040 * <h5 class='section'>See Also:</h5> 041 * <ul> 042 * <li class='jf'>{@link RestContext#REST_callHandler} 043 * </ul> 044 */ 045public class BasicRestCallHandler implements RestCallHandler { 046 047 private final RestContext context; 048 private final RestLogger logger; 049 private final Map<String,RestCallRouter> restCallRouters; 050 051 /** 052 * Constructor. 053 * 054 * @param context The resource context. 055 */ 056 public BasicRestCallHandler(RestContext context) { 057 this.context = context; 058 this.logger = context.getLogger(); 059 this.restCallRouters = context.getCallRouters(); 060 } 061 062 /** 063 * Creates a {@link RestRequest} object based on the specified incoming {@link HttpServletRequest} object. 064 * 065 * <p> 066 * Subclasses may choose to override this method to provide a specialized request object. 067 * 068 * @param req The request object from the {@link #service(HttpServletRequest, HttpServletResponse)} method. 069 * @return The wrapped request object. 070 * @throws ServletException If any errors occur trying to interpret the request. 071 */ 072 @Override /* RestCallHandler */ 073 public RestRequest createRequest(HttpServletRequest req) throws ServletException { 074 return new RestRequest(context, req); 075 } 076 077 /** 078 * Creates a {@link RestResponse} object based on the specified incoming {@link HttpServletResponse} object 079 * and the request returned by {@link #createRequest(HttpServletRequest)}. 080 * 081 * <p> 082 * Subclasses may choose to override this method to provide a specialized response object. 083 * 084 * @param req The request object returned by {@link #createRequest(HttpServletRequest)}. 085 * @param res The response object from the {@link #service(HttpServletRequest, HttpServletResponse)} method. 086 * @return The wrapped response object. 087 * @throws ServletException If any errors occur trying to interpret the request or response. 088 */ 089 @Override /* RestCallHandler */ 090 public RestResponse createResponse(RestRequest req, HttpServletResponse res) throws ServletException { 091 return new RestResponse(context, req, res); 092 } 093 094 /** 095 * The main service method. 096 * 097 * <p> 098 * Subclasses can optionally override this method if they want to tailor the behavior of requests. 099 * 100 * @param r1 The incoming HTTP servlet request object. 101 * @param r2 The incoming HTTP servlet response object. 102 * @throws ServletException 103 * @throws IOException 104 */ 105 @Override /* RestCallHandler */ 106 public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException { 107 108 logger.log(FINE, "HTTP: {0} {1}", r1.getMethod(), r1.getRequestURI()); 109 long startTime = System.currentTimeMillis(); 110 RestRequest req = null; 111 112 try { 113 context.checkForInitException(); 114 115 String pathInfo = RestUtils.getPathInfoUndecoded(r1); // Can't use r1.getPathInfo() because we don't want '%2F' resolved. 116 117 // If this resource has child resources, try to recursively call them. 118 if (pathInfo != null && context.hasChildResources() && (! pathInfo.equals("/"))) { 119 int i = pathInfo.indexOf('/', 1); 120 String pathInfoPart = i == -1 ? pathInfo.substring(1) : pathInfo.substring(1, i); 121 RestContext childResource = context.getChildResource(pathInfoPart); 122 if (childResource != null) { 123 final String pathInfoRemainder = (i == -1 ? null : pathInfo.substring(i)); 124 final String servletPath = r1.getServletPath() + "/" + pathInfoPart; 125 final HttpServletRequest childRequest = new HttpServletRequestWrapper(r1) { 126 @Override /* ServletRequest */ 127 public String getPathInfo() { 128 return urlDecode(pathInfoRemainder); 129 } 130 @Override /* ServletRequest */ 131 public String getServletPath() { 132 return servletPath; 133 } 134 }; 135 childResource.getCallHandler().service(childRequest, r2); 136 return; 137 } 138 } 139 140 context.startCall(r1, r2); 141 142 req = createRequest(r1); 143 RestResponse res = createResponse(req, r2); 144 context.setRequest(req); 145 context.setResponse(res); 146 String method = req.getMethod(); 147 String methodUC = method.toUpperCase(Locale.ENGLISH); 148 149 StreamResource r = null; 150 if (pathInfo != null) { 151 String p = pathInfo.substring(1); 152 if (context.isStaticFile(p)) { 153 StaticFile sf = context.resolveStaticFile(p); 154 r = sf.resource; 155 res.setResponseMeta(sf.meta); 156 } else if (p.equals("favicon.ico")) { 157 res.setOutput(null); 158 } 159 } 160 161 if (r != null) { 162 res.setStatus(SC_OK); 163 res.setOutput(r); 164 } else { 165 // If the specified method has been defined in a subclass, invoke it. 166 int rc = SC_METHOD_NOT_ALLOWED; 167 if (restCallRouters.containsKey(methodUC)) { 168 rc = restCallRouters.get(methodUC).invoke(pathInfo, req, res); 169 } else if (restCallRouters.containsKey("*")) { 170 rc = restCallRouters.get("*").invoke(pathInfo, req, res); 171 } 172 173 // If not invoked above, see if it's an OPTIONs request 174 if (rc != SC_OK) 175 handleNotFound(rc, req, res); 176 177 if (res.getStatus() == 0) 178 res.setStatus(rc); 179 } 180 181 if (res.hasOutput()) { 182 183 // Do any class-level transforming. 184 for (RestConverter converter : context.getConverters()) 185 res.setOutput(converter.convert(req, res.getOutput())); 186 187 // Now serialize the output if there was any. 188 // Some subclasses may write to the OutputStream or Writer directly. 189 handleResponse(req, res); 190 } 191 192 // Make sure our writer in RestResponse gets written. 193 res.flushBuffer(); 194 req.close(); 195 196 r1.setAttribute("ExecTime", System.currentTimeMillis() - startTime); 197 198 } catch (Throwable e) { 199 r1.setAttribute("Exception", e); 200 r1.setAttribute("ExecTime", System.currentTimeMillis() - startTime); 201 handleError(r1, r2, e); 202 } finally { 203 context.clearState(); 204 } 205 206 context.finishCall(r1, r2); 207 208 logger.log(FINE, "HTTP: [{0} {1}] finished in {2}ms", r1.getMethod(), r1.getRequestURI(), System.currentTimeMillis()-startTime); 209 } 210 211 /** 212 * The main method for serializing POJOs passed in through the {@link RestResponse#setOutput(Object)} method or 213 * returned by the Java method. 214 * 215 * <p> 216 * Subclasses may override this method if they wish to modify the way the output is rendered or support other output 217 * formats. 218 * 219 * <p> 220 * The default implementation simply iterates through the response handlers on this resource 221 * looking for the first one whose {@link ResponseHandler#handle(RestRequest, RestResponse)} method returns 222 * <jk>true</jk>. 223 * 224 * @param req The HTTP request. 225 * @param res The HTTP response. 226 * @throws IOException 227 * @throws RestException 228 */ 229 @Override /* RestCallHandler */ 230 public void handleResponse(RestRequest req, RestResponse res) throws IOException, RestException, NotImplemented { 231 // Loop until we find the correct handler for the POJO. 232 for (ResponseHandler h : context.getResponseHandlers()) 233 if (h.handle(req, res)) 234 return; 235 Object output = res.getOutput(); 236 throw new NotImplemented("No response handlers found to process output of type '"+(output == null ? null : output.getClass().getName())+"'"); 237 } 238 239 /** 240 * Handle the case where a matching method was not found. 241 * 242 * <p> 243 * Subclasses can override this method to provide a 2nd-chance for specifying a response. 244 * The default implementation will simply throw an exception with an appropriate message. 245 * 246 * @param rc The HTTP response code. 247 * @param req The HTTP request. 248 * @param res The HTTP response. 249 */ 250 @Override /* RestCallHandler */ 251 public void handleNotFound(int rc, RestRequest req, RestResponse res) throws NotFound, PreconditionFailed, MethodNotAllowed, ServletException { 252 String pathInfo = req.getPathInfo(); 253 String methodUC = req.getMethod(); 254 String onPath = pathInfo == null ? " on no pathInfo" : String.format(" on path '%s'", pathInfo); 255 if (rc == SC_NOT_FOUND) 256 throw new NotFound("Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath); 257 else if (rc == SC_PRECONDITION_FAILED) 258 throw new PreconditionFailed("Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath); 259 else if (rc == SC_METHOD_NOT_ALLOWED) 260 throw new MethodNotAllowed("Method ''{0}'' not found on resource.", methodUC); 261 else 262 throw new ServletException("Invalid method response: " + rc); 263 } 264 265 /** 266 * Method for handling response errors. 267 * 268 * <p> 269 * Subclasses can override this method to provide their own custom error response handling. 270 * 271 * @param req The servlet request. 272 * @param res The servlet response. 273 * @param e The exception that occurred. 274 * @throws IOException Can be thrown if a problem occurred trying to write to the output stream. 275 */ 276 @Override /* RestCallHandler */ 277 public synchronized void handleError(HttpServletRequest req, HttpServletResponse res, Throwable e) throws IOException { 278 279 int occurrence = context == null ? 0 : context.getStackTraceOccurrence(e); 280 RestException e2 = (e instanceof RestException ? (RestException)e : new RestException(e, 500)).setOccurrence(occurrence); 281 282 Throwable t = e2.getRootCause(); 283 if (t != null) { 284 res.setHeader("Exception-Name", t.getClass().getName()); 285 res.setHeader("Exception-Message", t.getMessage()); 286 } 287 288 try { 289 res.setContentType("text/plain"); 290 res.setHeader("Content-Encoding", "identity"); 291 res.setStatus(e2.getStatus()); 292 293 PrintWriter w = null; 294 try { 295 w = res.getWriter(); 296 } catch (IllegalStateException x) { 297 w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), UTF8)); 298 } 299 300 try (PrintWriter w2 = w) { 301 String httpMessage = RestUtils.getHttpResponseText(e2.getStatus()); 302 if (httpMessage != null) 303 w2.append("HTTP ").append(String.valueOf(e2.getStatus())).append(": ").append(httpMessage).append("\n\n"); 304 if (context != null && context.isRenderResponseStackTraces()) 305 e.printStackTrace(w2); 306 else 307 w2.append(e2.getFullStackMessage(true)); 308 } 309 310 } catch (Exception e1) { 311 logger.onError(req, res, new RestException(e1, 0)); 312 } 313 314 if (context.isDebug()) { 315 String qs = req.getQueryString(); 316 String msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e2.getStatus() + '.' + e2.getOccurrence() + "] HTTP " + req.getMethod() + " " + e2.getStatus() + " " + req.getRequestURI() + (qs == null ? "" : "?" + qs); 317 System.err.println(msg); // NOT DEBUG 318 e.printStackTrace(System.err); 319 logger.log(Level.SEVERE, e, e.getLocalizedMessage()); 320 } 321 322 logger.onError(req, res, e2); 323 } 324 325 /** 326 * Returns the session objects for the specified request. 327 * 328 * <p> 329 * The default implementation simply returns a single map containing <code>{'req':req}</code>. 330 * 331 * @param req The REST request. 332 * @return The session objects for that request. 333 */ 334 @Override /* RestCallHandler */ 335 public Map<String,Object> getSessionObjects(RestRequest req) { 336 Map<String,Object> m = new HashMap<>(); 337 m.put(RequestVar.SESSION_req, req); 338 return m; 339 } 340 341 /** 342 * @deprecated Use {@link #handleError(HttpServletRequest, HttpServletResponse, Throwable)} 343 */ 344 @SuppressWarnings("javadoc") 345 @Deprecated 346 public synchronized void handleError(HttpServletRequest req, HttpServletResponse res, RestException e) throws IOException { 347 handleError(req, res, (Throwable)e); 348 } 349 350 /** 351 * @deprecated Use {@link #handleResponse(RestRequest, RestResponse)} 352 */ 353 @SuppressWarnings("javadoc") 354 @Deprecated 355 public void handleResponse(RestRequest req, RestResponse res, Object output) throws IOException, RestException { 356 handleResponse(req, res); 357 } 358 359 /** 360 * @deprecated Use {@link #handleError(HttpServletRequest, HttpServletResponse, Throwable)} 361 */ 362 @SuppressWarnings({ "javadoc", "unused" }) 363 @Deprecated 364 public void renderError(HttpServletRequest req, HttpServletResponse res, RestException e) throws IOException { 365 return; 366 } 367}