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.rest.RestContext.*; 028import org.apache.juneau.rest.exception.*; 029import org.apache.juneau.rest.helper.*; 030import org.apache.juneau.rest.util.*; 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 String method = req.getMethod(); 145 String methodUC = method.toUpperCase(Locale.ENGLISH); 146 147 StreamResource r = null; 148 if (pathInfo != null) { 149 String p = pathInfo.substring(1); 150 if (context.isStaticFile(p)) { 151 StaticFile sf = context.resolveStaticFile(p); 152 r = sf.resource; 153 res.setResponseMeta(sf.meta); 154 } else if (p.equals("favicon.ico")) { 155 res.setOutput(null); 156 } 157 } 158 159 if (r != null) { 160 res.setStatus(SC_OK); 161 res.setOutput(r); 162 } else { 163 // If the specified method has been defined in a subclass, invoke it. 164 int rc = SC_METHOD_NOT_ALLOWED; 165 if (restCallRouters.containsKey(methodUC)) { 166 rc = restCallRouters.get(methodUC).invoke(pathInfo, req, res); 167 } else if (restCallRouters.containsKey("*")) { 168 rc = restCallRouters.get("*").invoke(pathInfo, req, res); 169 } 170 171 // If not invoked above, see if it's an OPTIONs request 172 if (rc != SC_OK) 173 handleNotFound(rc, req, res); 174 175 if (res.getStatus() == 0) 176 res.setStatus(rc); 177 } 178 179 if (res.hasOutput()) { 180 181 // Do any class-level transforming. 182 for (RestConverter converter : context.getConverters()) 183 res.setOutput(converter.convert(req, res.getOutput())); 184 185 // Now serialize the output if there was any. 186 // Some subclasses may write to the OutputStream or Writer directly. 187 handleResponse(req, res); 188 } 189 190 // Make sure our writer in RestResponse gets written. 191 res.flushBuffer(); 192 req.close(); 193 194 r1.setAttribute("ExecTime", System.currentTimeMillis() - startTime); 195 196 } catch (Throwable e) { 197 r1.setAttribute("Exception", e); 198 r1.setAttribute("ExecTime", System.currentTimeMillis() - startTime); 199 handleError(r1, r2, e); 200 } 201 202 context.finishCall(r1, r2); 203 204 logger.log(FINE, "HTTP: [{0} {1}] finished in {2}ms", r1.getMethod(), r1.getRequestURI(), System.currentTimeMillis()-startTime); 205 } 206 207 /** 208 * The main method for serializing POJOs passed in through the {@link RestResponse#setOutput(Object)} method or 209 * returned by the Java method. 210 * 211 * <p> 212 * Subclasses may override this method if they wish to modify the way the output is rendered or support other output 213 * formats. 214 * 215 * <p> 216 * The default implementation simply iterates through the response handlers on this resource 217 * looking for the first one whose {@link ResponseHandler#handle(RestRequest, RestResponse)} method returns 218 * <jk>true</jk>. 219 * 220 * @param req The HTTP request. 221 * @param res The HTTP response. 222 * @throws IOException 223 * @throws RestException 224 */ 225 @Override /* RestCallHandler */ 226 public void handleResponse(RestRequest req, RestResponse res) throws IOException, RestException, NotImplemented { 227 // Loop until we find the correct handler for the POJO. 228 for (ResponseHandler h : context.getResponseHandlers()) 229 if (h.handle(req, res)) 230 return; 231 Object output = res.getOutput(); 232 throw new NotImplemented("No response handlers found to process output of type '"+(output == null ? null : output.getClass().getName())+"'"); 233 } 234 235 /** 236 * Handle the case where a matching method was not found. 237 * 238 * <p> 239 * Subclasses can override this method to provide a 2nd-chance for specifying a response. 240 * The default implementation will simply throw an exception with an appropriate message. 241 * 242 * @param rc The HTTP response code. 243 * @param req The HTTP request. 244 * @param res The HTTP response. 245 */ 246 @Override /* RestCallHandler */ 247 public void handleNotFound(int rc, RestRequest req, RestResponse res) throws NotFound, PreconditionFailed, MethodNotAllowed, ServletException { 248 String pathInfo = req.getPathInfo(); 249 String methodUC = req.getMethod(); 250 String onPath = pathInfo == null ? " on no pathInfo" : String.format(" on path '%s'", pathInfo); 251 if (rc == SC_NOT_FOUND) 252 throw new NotFound("Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath); 253 else if (rc == SC_PRECONDITION_FAILED) 254 throw new PreconditionFailed("Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath); 255 else if (rc == SC_METHOD_NOT_ALLOWED) 256 throw new MethodNotAllowed("Method ''{0}'' not found on resource.", methodUC); 257 else 258 throw new ServletException("Invalid method response: " + rc); 259 } 260 261 /** 262 * Method for handling response errors. 263 * 264 * <p> 265 * Subclasses can override this method to provide their own custom error response handling. 266 * 267 * @param req The servlet request. 268 * @param res The servlet response. 269 * @param e The exception that occurred. 270 * @throws IOException Can be thrown if a problem occurred trying to write to the output stream. 271 */ 272 @Override /* RestCallHandler */ 273 public synchronized void handleError(HttpServletRequest req, HttpServletResponse res, Throwable e) throws IOException { 274 275 int occurrence = context == null ? 0 : context.getStackTraceOccurrence(e); 276 RestException e2 = (e instanceof RestException ? (RestException)e : new RestException(e, 500)).setOccurrence(occurrence); 277 278 Throwable t = e2.getRootCause(); 279 if (t != null) { 280 res.setHeader("Exception-Name", t.getClass().getName()); 281 res.setHeader("Exception-Message", t.getMessage()); 282 } 283 284 try { 285 res.setContentType("text/plain"); 286 res.setHeader("Content-Encoding", "identity"); 287 res.setStatus(e2.getStatus()); 288 289 PrintWriter w = null; 290 try { 291 w = res.getWriter(); 292 } catch (IllegalStateException x) { 293 w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), UTF8)); 294 } 295 296 try (PrintWriter w2 = w) { 297 String httpMessage = RestUtils.getHttpResponseText(e2.getStatus()); 298 if (httpMessage != null) 299 w2.append("HTTP ").append(String.valueOf(e2.getStatus())).append(": ").append(httpMessage).append("\n\n"); 300 if (context != null && context.isRenderResponseStackTraces()) 301 e.printStackTrace(w2); 302 else 303 w2.append(e2.getFullStackMessage(true)); 304 } 305 306 } catch (Exception e1) { 307 logger.onError(req, res, new RestException(e1, 0)); 308 } 309 310 if (context.isDebug()) { 311 String qs = req.getQueryString(); 312 String msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e2.getStatus() + '.' + e2.getOccurrence() + "] HTTP " + req.getMethod() + " " + e2.getStatus() + " " + req.getRequestURI() + (qs == null ? "" : "?" + qs); 313 System.err.println(msg); // NOT DEBUG 314 e.printStackTrace(System.err); 315 logger.log(Level.SEVERE, e, e.getLocalizedMessage()); 316 } 317 318 logger.onError(req, res, e2); 319 } 320 321 /** 322 * Returns the session objects for the specified request. 323 * 324 * <p> 325 * The default implementation simply returns a single map containing <code>{'req':req}</code>. 326 * 327 * @param req The REST request. 328 * @return The session objects for that request. 329 */ 330 @Override /* RestCallHandler */ 331 public Map<String,Object> getSessionObjects(RestRequest req) { 332 Map<String,Object> m = new HashMap<>(); 333 m.put(RequestVar.SESSION_req, req); 334 return m; 335 } 336}