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}