001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server;
018
019
020import org.apache.juneau.commons.collections.*;
021import org.apache.juneau.commons.inject.*;
022import org.apache.juneau.http.*;
023import org.apache.juneau.http.response.*;
024import org.apache.juneau.marshall.*;
025import org.apache.juneau.rest.server.processor.*;
026import static org.apache.juneau.commons.utils.Shorts.*;
027
028/**
029 * A session for a single HTTP request.
030 *
031 * <p>
032 * This session object gets created by {@link RestSession} once the Java method to be invoked has been determined.
033 *
034 * <h5 class='section'>Notes:</h5><ul>
035 *    <li class='warn'>This class is not thread safe.
036 * </ul>
037 *
038 */
039@SuppressWarnings({
040   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
041   "resource" // delegate access — RestSession owns the bean store and closes it; fluent add() calls return the same store.
042})
043public class RestOpSession extends ContextSession {
044
045   /**
046    * Builder class.
047    */
048   @SuppressWarnings({
049      "rawtypes" // Raw types required: annotation type parameter is unknown at static analysis time.
050   })
051   public static class Builder extends ContextSession.Builder {
052
053      protected final RestOpContext ctx;
054      protected final RestSession session;
055
056      /**
057       * Constructor.
058       *
059       * @param ctx The context object of the Java method being invoked.
060       *    <br>Cannot be <jk>null</jk>.
061       * @param session The REST session object creating this object.
062       *    <br>Cannot be <jk>null</jk>.
063       */
064      public Builder(RestOpContext ctx, RestSession session) {
065         super(reqnn("ctx", ctx));
066         this.ctx = ctx;
067         this.session = reqnn("session", session);
068      }
069
070      @Override /* Overridden from Session.Builder */
071      public RestOpSession build() {
072         return new RestOpSession(this);
073      }
074   }
075
076   /**
077    * Static creator.
078    *
079    * @param ctx The context object of the Java method being invoked.
080    *    <br>Cannot be <jk>null</jk>.
081    * @param session The REST session object creating this object.
082    *    <br>Cannot be <jk>null</jk>.
083    * @return A new builder.
084    */
085   public static Builder create(RestOpContext ctx, RestSession session) {
086      return new Builder(reqnn("ctx", ctx), reqnn("session", session));
087   }
088
089   private final RestOpContext ctx;
090   private final RestRequest req;
091   private final RestResponse res;
092   private final RestSession session;
093
094   /**
095    * Constructor.
096    *
097    * @param builder The builder for this object.
098    *    <br>Cannot be <jk>null</jk>.
099    */
100   protected RestOpSession(Builder builder) {
101      super(builder);
102      ctx = builder.ctx;
103      session = builder.session;
104      try {
105         req = session.getBeanStore().add(RestRequest.class, ctx.createRequest(session));
106         res = session.getBeanStore().add(RestResponse.class, ctx.createResponse(session, req));
107      } catch (RuntimeException e) {
108         throw e;
109      } catch (Exception e) {
110         throw new InternalServerError(e);
111      }
112   }
113
114   /**
115    * Called at the end of a call to finish any remaining tasks such as flushing buffers and logging the response.
116    *
117    * @return This object.
118    */
119   public RestOpSession finish() {
120      // When AsyncResponseProcessor has handed off to a real AsyncContext, the response will be
121      // committed by AsyncContext.complete() inside the future's whenComplete callback — synchronously
122      // flushing here would commit the response prematurely.
123      if (AsyncResponseProcessor.isAsyncDispatchOwned(this))
124         return this;
125      try {
126         res.flushBuffer();
127         req.close();
128      } catch (Exception e) {
129         if (! session.logClientAbort(e))
130            session.exception(e);
131      }
132      return this;
133   }
134
135   /**
136    * Closes the underlying request resources on the async response-completion path.
137    *
138    * <p>
139    * When a request is handed off to a real {@code AsyncContext}, {@link #finish()} skips the synchronous
140    * {@code req.close()} so cached bytes remain readable by completion-path debug emission. The completion hook calls
141    * this method after the body has been written and rendered, before completing the async context.
142    */
143   public void closeRequest() {
144      req.close();
145   }
146
147   /**
148    * Returns the bean store for this session.
149    *
150    * @return The bean store for this session.
151    */
152   public WritableBeanStore getBeanStore() { return session.getBeanStore(); }
153
154   @Override /* Overridden from ContextSession */
155   public RestOpContext getContext() { return ctx; }
156
157   /**
158    * Returns the REST request object for this session.
159    *
160    * @return The REST request object for this session.
161    */
162   public RestRequest getRequest() { return req; }
163
164   /**
165    * Returns the REST response object for this session.
166    *
167    * @return The REST response object for this session.
168    */
169   public RestResponse getResponse() { return res; }
170
171   /**
172    * Returns the context of the parent class of this Java method.
173    *
174    * @return The context of the parent class of this Java method.
175    */
176   public RestContext getRestContext() { return session.getContext(); }
177
178   /**
179    * Returns the session of the parent class of this Java method.
180    *
181    * @return The session of the parent class of this Java method.
182    */
183   public RestSession getRestSession() { return session; }
184
185   /**
186    * Runs this session.
187    *
188    * <p>
189    * Does the following:
190    * <ol>
191    *    <li>Runs the guards on the method.
192    *    <li>Finds the parameter values to pass to the Java method.
193    *    <li>Invokes the Java method.
194    *    <li>Sets the output and status on the response.
195    *    <li>Calls the converters on the Java method.
196    * </ol>
197    *
198    * @throws Exception Any exception can be thrown.
199    */
200   public void run() throws Exception {
201      for (var guard : ctx.getGuards()) {
202         if (! guard.guard(req, res))
203            return;
204      }
205
206      ctx.getMethodInvoker().invokeOp(this);
207
208      if (res.hasContent())
209         for (var converter : ctx.getConverters())
210            res.setContent(converter.convert(req, res.getContent().orElse(null)));
211   }
212
213   /**
214    * Sets the status of the response.
215    *
216    * @param value The new status.
217    *    <br>Can be <jk>null</jk> (ignored).
218    * @return This object.
219    */
220   public RestOpSession status(HttpStatusLine value) {
221      session.status(value);
222      return this;
223   }
224
225   @Override /* Overridden from ContextSession */
226   protected FluentMap<String,Object> properties() {
227      return super.properties()
228         .a("ctx", ctx)
229         .a("session", session);
230   }
231}