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