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}