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 org.apache.juneau.internal.CollectionUtils.*;
016
017import java.io.*;
018import java.util.*;
019
020import jakarta.servlet.http.*;
021
022import org.apache.http.*;
023import org.apache.juneau.*;
024import org.apache.juneau.cp.*;
025import org.apache.juneau.http.response.*;
026import org.apache.juneau.rest.annotation.*;
027import org.apache.juneau.rest.logger.*;
028import org.apache.juneau.rest.util.*;
029
030/**
031 * Represents a single HTTP request.
032 *
033 * <h5 class='section'>Notes:</h5><ul>
034 *    <li class='warn'>This class is not thread safe.
035 * </ul>
036 *
037 * <h5 class='section'>See Also:</h5><ul>
038 * </ul>
039 */
040public class RestSession extends ContextSession {
041
042   //-----------------------------------------------------------------------------------------------------------------
043   // Static
044   //-----------------------------------------------------------------------------------------------------------------
045
046   /**
047    * Request attribute name for passing path variables from parent to child.
048    */
049   private static final String REST_PATHVARS_ATTR = "juneau.pathVars";
050
051   /**
052    * Creates a builder of this object.
053    *
054    * @param ctx The context creating this builder.
055    * @return A new builder.
056    */
057   public static Builder create(RestContext ctx) {
058      return new Builder(ctx);
059   }
060
061   //-----------------------------------------------------------------------------------------------------------------
062   // Builder
063   //-----------------------------------------------------------------------------------------------------------------
064
065   /**
066    * Builder class.
067    */
068   public static class Builder extends ContextSession.Builder {
069
070      RestContext ctx;
071      Object resource;
072      HttpServletRequest req;
073      HttpServletResponse res;
074      CallLogger logger;
075      String pathInfoUndecoded;
076      UrlPath urlPath;
077
078      /**
079       * Constructor.
080       *
081       * @param ctx The context creating this session.
082       */
083      protected Builder(RestContext ctx) {
084         super(ctx);
085         this.ctx = ctx;
086      }
087
088      /**
089       * Specifies the servlet implementation bean.
090       *
091       * @param value The value for this setting.
092       * @return This object.
093       */
094      public Builder resource(Object value) {
095         resource = value;
096         return this;
097      }
098
099      /**
100       * Specifies the HTTP servlet request object on this call.
101       *
102       * @param value The value for this setting.
103       * @return This object.
104       */
105      public Builder req(HttpServletRequest value) {
106         req = value;
107         return this;
108      }
109
110      /**
111       * Returns the HTTP servlet request object on this call.
112       *
113       * @return The HTTP servlet request object on this call.
114       */
115      public HttpServletRequest req() {
116         urlPath = null;
117         pathInfoUndecoded = null;
118         return req;
119      }
120
121      /**
122       * Specifies the HTTP servlet response object on this call.
123       *
124       * @param value The value for this setting.
125       * @return This object.
126       */
127      public Builder res(HttpServletResponse value) {
128         res = value;
129         return this;
130      }
131
132      /**
133       * Returns the HTTP servlet response object on this call.
134       *
135       * @return The HTTP servlet response object on this call.
136       */
137      public HttpServletResponse res() {
138         return res;
139      }
140
141      /**
142       * Specifies the logger to use for this session.
143       *
144       * @param value The value for this setting.
145       * @return This object.
146       */
147      public Builder logger(CallLogger value) {
148         logger = value;
149         return this;
150      }
151
152      @Override /* Session.Builder */
153      public RestSession build() {
154         return new RestSession(this);
155      }
156
157      /**
158       * Returns the request path info as a {@link UrlPath} bean.
159       *
160       * @return The request path info as a {@link UrlPath} bean.
161       */
162      public UrlPath getUrlPath() {
163         if (urlPath == null)
164            urlPath = UrlPath.of(getPathInfoUndecoded());
165         return urlPath;
166      }
167
168      /**
169       * Returns the request path info as a {@link UrlPath} bean.
170       *
171       * @return The request path info as a {@link UrlPath} bean.
172       */
173      public String getPathInfoUndecoded() {
174         if (pathInfoUndecoded == null)
175            pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req);
176         return pathInfoUndecoded;
177      }
178
179      /**
180       * Adds resolved <c><ja>@Resource</ja>(path)</c> variable values to this call.
181       *
182       * @param value The variables to add to this call.
183       * @return This object.
184       */
185      @SuppressWarnings("unchecked")
186      public Builder pathVars(Map<String,String> value) {
187         if (value != null && ! value.isEmpty()) {
188            Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
189            if (m == null) {
190               m = new TreeMap<>();
191               req.setAttribute(REST_PATHVARS_ATTR, m);
192            }
193            m.putAll(value);
194         }
195         return this;
196      }
197   }
198
199   //-----------------------------------------------------------------------------------------------------------------
200   // Static
201   //-----------------------------------------------------------------------------------------------------------------
202
203   private final Object resource;
204   private final RestContext context;
205   private HttpServletRequest req;
206   private HttpServletResponse res;
207
208   private CallLogger logger;
209   private UrlPath urlPath;
210   private String pathInfoUndecoded;
211   private long startTime = System.currentTimeMillis();
212   private BeanStore beanStore;
213   private Map<String,String[]> queryParams;
214   private String method;
215   private RestOpSession opSession;
216
217   private UrlPathMatch urlPathMatch;
218
219   /**
220    * Constructor.
221    *
222    * @param builder The builder for this object.
223    */
224   public RestSession(Builder builder) {
225      super(builder);
226      context = builder.ctx;
227      resource = builder.resource;
228      beanStore = BeanStore.of(context.getBeanStore(), resource).addBean(RestContext.class, context);
229
230      req = beanStore.add(HttpServletRequest.class, builder.req);
231      res = beanStore.add(HttpServletResponse.class, builder.res);
232      logger = beanStore.add(CallLogger.class, builder.logger);
233      urlPath = beanStore.add(UrlPath.class, builder.urlPath);
234      pathInfoUndecoded = builder.pathInfoUndecoded;
235   }
236
237   //------------------------------------------------------------------------------------------------------------------
238   // Fluent setters.
239   //------------------------------------------------------------------------------------------------------------------
240
241   /**
242    * Sets the logger to use when logging this call.
243    *
244    * @param value The new value for this setting.  Can be <jk>null</jk>.
245    * @return This object.
246    */
247   public RestSession logger(CallLogger value) {
248      logger = beanStore.add(CallLogger.class, value);
249      return this;
250   }
251
252   /**
253    * Enables or disabled debug mode on this call.
254    *
255    * @param value The new value for this setting.
256    * @return This object.
257    * @throws IOException Occurs if request content could not be cached into memory.
258    */
259   public RestSession debug(boolean value) throws IOException {
260      if (value) {
261         req = CachingHttpServletRequest.wrap(req);
262         res = CachingHttpServletResponse.wrap(res);
263         req.setAttribute("Debug", true);
264      } else {
265         req.removeAttribute("Debug");
266      }
267      return this;
268   }
269
270   /**
271    * Sets the HTTP status on this call.
272    *
273    * @param value The status code.
274    * @return This object.
275    */
276   public RestSession status(int value) {
277      res.setStatus(value);
278      return this;
279   }
280
281   /**
282    * Sets the HTTP status on this call.
283    *
284    * @param value The status code.
285    * @return This object.
286    */
287   public RestSession status(StatusLine value) {
288      if (value != null)
289         res.setStatus(value.getStatusCode());
290      return this;
291   }
292
293   /**
294    * Identifies that an exception occurred during this call.
295    *
296    * @param value The thrown exception.
297    * @return This object.
298    */
299   public RestSession exception(Throwable value) {
300      req.setAttribute("Exception", value);
301      beanStore.addBean(Throwable.class, value);
302      return this;
303   }
304
305   /**
306    * Sets the URL path pattern match on this call.
307    *
308    * @param value The match pattern.
309    * @return This object.
310    */
311   public RestSession urlPathMatch(UrlPathMatch value) {
312      urlPathMatch = beanStore.add(UrlPathMatch.class, value);
313      return this;
314   }
315
316   //------------------------------------------------------------------------------------------------------------------
317   // Getters
318   //------------------------------------------------------------------------------------------------------------------
319
320   /**
321    * Returns the HTTP servlet request of this REST call.
322    *
323    * @return the HTTP servlet request of this REST call.
324    */
325   public HttpServletRequest getRequest() {
326      return req;
327   }
328
329   /**
330    * Returns the HTTP servlet response of this REST call.
331    *
332    * @return the HTTP servlet response of this REST call.
333    */
334   public HttpServletResponse getResponse() {
335      return res;
336   }
337
338   /**
339    * Returns the bean store of this call.
340    *
341    * @return The bean store of this call.
342    */
343   public BeanStore getBeanStore() {
344      return beanStore;
345   }
346
347   /**
348    * Returns resolved <c><ja>@Resource</ja>(path)</c> variable values on this call.
349    *
350    * @return Resolved <c><ja>@Resource</ja>(path)</c> variable values on this call.
351    */
352   @SuppressWarnings("unchecked")
353   public Map<String,String> getPathVars() {
354      Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
355      return m == null ? Collections.emptyMap() : m;
356   }
357
358   /**
359    * Returns the URL path pattern match on this call.
360    *
361    * @return The URL path pattern match on this call.
362    */
363   public UrlPathMatch getUrlPathMatch() {
364      return urlPathMatch;
365   }
366
367   /**
368    * Returns the exception that occurred during this call.
369    *
370    * @return The exception that occurred during this call.
371    */
372   public Throwable getException() {
373      return (Throwable)req.getAttribute("Exception");
374   }
375
376   //------------------------------------------------------------------------------------------------------------------
377   // Lifecycle methods.
378   //------------------------------------------------------------------------------------------------------------------
379
380   /**
381    * Called at the end of a call to finish any remaining tasks such as flushing buffers and logging the response.
382    *
383    * @return This object.
384    */
385   public RestSession finish() {
386      try {
387         req.setAttribute("ExecTime", System.currentTimeMillis() - startTime);
388         if (opSession != null)
389            opSession.finish();
390         else {
391            res.flushBuffer();
392         }
393      } catch (Exception e) {
394         exception(e);
395      }
396      if (logger != null)
397         logger.log(req, res);
398      return this;
399   }
400
401   //------------------------------------------------------------------------------------------------------------------
402   // Pass-through convenience methods.
403   //------------------------------------------------------------------------------------------------------------------
404
405   /**
406    * Shortcut for calling <c>getRequest().getServletPath()</c>.
407    *
408    * @return The request servlet path.
409    */
410   public String getServletPath() {
411      return req.getServletPath();
412   }
413
414   /**
415    * Returns the request path info as a {@link UrlPath} bean.
416    *
417    * @return The request path info as a {@link UrlPath} bean.
418    */
419   public UrlPath getUrlPath() {
420      if (urlPath == null)
421         urlPath = UrlPath.of(getPathInfoUndecoded());
422      return urlPath;
423   }
424
425   /**
426    * Shortcut for calling <c>getRequest().getPathInfo()</c>.
427    *
428    * @return The request servlet path info.
429    */
430   public String getPathInfo() {
431      return req.getPathInfo();
432   }
433
434   /**
435    * Same as {@link #getPathInfo()} but doesn't decode encoded characters.
436    *
437    * @return The undecoded request servlet path info.
438    */
439   public String getPathInfoUndecoded() {
440      if (pathInfoUndecoded == null)
441         pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req);
442      return pathInfoUndecoded;
443   }
444
445   /**
446    * Returns the query parameters on the request.
447    *
448    * <p>
449    * Unlike {@link HttpServletRequest#getParameterMap()}, this doesn't parse the content if it's a POST.
450    *
451    * @return The query parameters on the request.
452    */
453   public Map<String,String[]> getQueryParams() {
454      if (queryParams == null) {
455         if (req.getMethod().equalsIgnoreCase("POST"))
456            queryParams = RestUtils.parseQuery(req.getQueryString(), map());
457         else
458            queryParams = req.getParameterMap();
459      }
460      return queryParams;
461   }
462
463   /**
464    * Returns the HTTP method name.
465    *
466    * @return The HTTP method name, always uppercased.
467    */
468   public String getMethod() {
469      if (method == null) {
470
471         Set<String> s1 = context.getAllowedMethodParams();
472         Set<String> s2 = context.getAllowedMethodHeaders();
473
474         if (! s1.isEmpty()) {
475            String[] x = getQueryParams().get("method");
476            if (x != null && (s1.contains("*") || s1.contains(x[0])))
477               method = x[0];
478         }
479
480         if (method == null && ! s2.isEmpty()) {
481            String x = req.getHeader("X-Method");
482            if (x != null && (s2.contains("*") || s2.contains(x)))
483               method = x;
484         }
485
486         if (method == null)
487            method = req.getMethod();
488
489         method = method.toUpperCase(Locale.ENGLISH);
490      }
491
492      return method;
493   }
494
495   /**
496    * Shortcut for calling <c>getRequest().getStatus()</c>.
497    *
498    * @return The response status code.
499    */
500   public int getStatus() {
501      return res.getStatus();
502   }
503
504   /**
505    * Returns the context that created this call.
506    *
507    * @return The context that created this call.
508    */
509   @Override
510   public RestContext getContext() {
511      return context;
512   }
513
514   /**
515    * Returns the REST object.
516    *
517    * @return The rest object.
518    */
519   public Object getResource() {
520      return resource;
521   }
522
523   /**
524    * Returns the operation session of this REST session.
525    *
526    * <p>
527    * The operation session is created once the Java method to be invoked has been determined.
528    *
529    * @return The operation session of this REST session.
530    * @throws InternalServerError If operation session has not been created yet.
531    */
532   public RestOpSession getOpSession() throws InternalServerError {
533      if (opSession == null)
534         throw new InternalServerError("Op Session not created.");
535      return opSession;
536   }
537
538   /**
539    * Runs this session.
540    *
541    * <p>
542    * Does the following:
543    * <ol>
544    *    <li>Finds the Java method to invoke and creates a {@link RestOpSession} for it.
545    *    <li>Invokes {@link RestPreCall} methods by calling {@link RestContext#preCall(RestOpSession)}.
546    *    <li>Invokes Java method by calling {@link RestOpSession#run()}.
547    *    <li>Invokes {@link RestPostCall} methods by calling {@link RestContext#postCall(RestOpSession)}.
548    *    <li>If the Java method produced output, finds the response processor for it and runs it by calling {@link RestContext#processResponse(RestOpSession)}.
549    *    <li>If no Java method matched, generates a 404/405/412 by calling {@link RestContext#handleNotFound(RestSession)}.
550    * </ol>
551    *
552    * @throws Throwable Any throwable can be thrown.
553    */
554   public void run() throws Throwable {
555      try {
556         opSession = context.getRestOperations().findOperation(this).createSession(this).build();
557         context.preCall(opSession);
558         opSession.run();
559         context.postCall(opSession);
560         if (res.getStatus() == 0)
561            res.setStatus(200);
562         if (opSession.getResponse().hasContent()) {
563            // Now serialize the output if there was any.
564            // Some subclasses may write to the OutputStream or Writer directly.
565            context.processResponse(opSession);
566         }
567      } catch (NotFound e) {
568         if (getStatus() == 0)
569            status(404);
570         exception(e);
571         context.handleNotFound(this);
572      }
573   }
574}