1 /* 2 * Copyright 2023 Web-Legacy 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.apache.tiles.request.jakarta.servlet; 17 18 import java.io.IOException; 19 import java.io.OutputStream; 20 import java.io.PrintWriter; 21 import java.io.Writer; 22 import java.util.Arrays; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Locale; 26 import java.util.Map; 27 28 import org.apache.tiles.request.AbstractClientRequest; 29 import org.apache.tiles.request.ApplicationContext; 30 import org.apache.tiles.request.attribute.Addable; 31 import org.apache.tiles.request.collection.HeaderValuesMap; 32 import org.apache.tiles.request.collection.ReadOnlyEnumerationMap; 33 import org.apache.tiles.request.collection.ScopeMap; 34 import org.apache.tiles.request.jakarta.servlet.extractor.HeaderExtractor; 35 import org.apache.tiles.request.jakarta.servlet.extractor.ParameterExtractor; 36 import org.apache.tiles.request.jakarta.servlet.extractor.RequestScopeExtractor; 37 import org.apache.tiles.request.jakarta.servlet.extractor.SessionScopeExtractor; 38 39 import jakarta.servlet.RequestDispatcher; 40 import jakarta.servlet.ServletException; 41 import jakarta.servlet.ServletResponse; 42 import jakarta.servlet.http.HttpServletRequest; 43 import jakarta.servlet.http.HttpServletResponse; 44 45 /** 46 * Servlet-based implementation of the TilesApplicationContext interface. 47 * 48 * <p>Copied from Apache tiles-request-servlet 1.0.7 and adapted for 49 * Jakarta EE 9.</p> 50 */ 51 public class ServletRequest extends AbstractClientRequest { 52 53 /** 54 * The native available scopes: request, session and application. 55 */ 56 private static final List<String> SCOPES 57 = Collections.unmodifiableList(Arrays.asList( 58 REQUEST_SCOPE, "session", APPLICATION_SCOPE)); 59 60 /** 61 * The request object to use. 62 */ 63 private HttpServletRequest request; 64 65 /** 66 * The response object to use. 67 */ 68 private HttpServletResponse response; 69 70 /** 71 * The response output stream, lazily initialized. 72 */ 73 private OutputStream outputStream; 74 75 /** 76 * The response writer, lazily initialized. 77 */ 78 private PrintWriter writer; 79 80 /** 81 * The lazily instantiated {@code Map} of header name-value combinations 82 * (immutable). 83 */ 84 private Map<String, String> header = null; 85 86 /** 87 * The lazily instantiated {@code Map} of header name-value combinations 88 * (write-only). 89 */ 90 private Addable<String> responseHeaders = null; 91 92 /** 93 * The lazily instantiated {@code Map} of header name-values combinations 94 * (immutable). 95 */ 96 private Map<String, String[]> headerValues = null; 97 98 /** 99 * The lazily instantiated {@code Map} of request parameter name-value. 100 */ 101 private Map<String, String> param = null; 102 103 /** 104 * The lazily instantiated {@code Map} of request scope attributes. 105 */ 106 private Map<String, Object> requestScope = null; 107 108 /** 109 * The lazily instantiated {@code Map} of session scope attributes. 110 */ 111 private Map<String, Object> sessionScope = null; 112 113 /** 114 * Creates a new instance of ServletTilesRequestContext. 115 * 116 * @param applicationContext The application context. 117 * @param request The request object. 118 * @param response The response object. 119 */ 120 public ServletRequest( 121 ApplicationContext applicationContext, 122 HttpServletRequest request, HttpServletResponse response) { 123 124 super(applicationContext); 125 this.request = request; 126 this.response = response; 127 } 128 129 /** 130 * Return an immutable Map that maps header names to the first (or only) 131 * header value (as a String). 132 * 133 * @return The header map. 134 */ 135 @Override 136 public Map<String, String> getHeader() { 137 if (header == null && request != null) { 138 header = new ReadOnlyEnumerationMap<String>( 139 new HeaderExtractor(request, null)); 140 } 141 142 return header; 143 } 144 145 /** 146 * Return an add-able object that can be used to write headers to the 147 * response. 148 * 149 * @return An add-able object. 150 */ 151 @Override 152 public Addable<String> getResponseHeaders() { 153 if (responseHeaders == null && response != null) { 154 responseHeaders = new HeaderExtractor(null, response); 155 } 156 157 return responseHeaders; 158 } 159 160 /** 161 * Return an immutable Map that maps header names to the set of all values 162 * specified in the request (as a String array). Header names must be 163 * matched in a case-insensitive manner. 164 * 165 * @return The header values map. 166 */ 167 @Override 168 public Map<String, String[]> getHeaderValues() { 169 if (headerValues == null && request != null) { 170 headerValues = new HeaderValuesMap( 171 new HeaderExtractor(request, response)); 172 } 173 174 return headerValues; 175 } 176 177 /** 178 * Return an immutable Map that maps request parameter names to the first 179 * (or only) value (as a String). 180 * 181 * @return The parameter map. 182 */ 183 @Override 184 public Map<String, String> getParam() { 185 if (param == null && request != null) { 186 param = new ReadOnlyEnumerationMap<String>( 187 new ParameterExtractor(request)); 188 } 189 190 return param; 191 } 192 193 /** 194 * Return an immutable Map that maps request parameter names to the set of 195 * all values (as a String array). 196 * 197 * @return The parameter values map. 198 */ 199 @Override 200 public Map<String, String[]> getParamValues() { 201 return request.getParameterMap(); 202 } 203 204 /** 205 * Returns a context map, given the scope name. 206 * 207 * <p>This method always return a map for all the scope names returned by 208 * {@link #getAvailableScopes()}. That map may be writable, or immutable, 209 * depending on the implementation. 210 * 211 * @param scope The name of the scope. 212 * 213 * @return The context. 214 */ 215 @Override 216 public Map<String, Object> getContext(String scope) { 217 if (REQUEST_SCOPE.equals(scope)) { 218 return getRequestScope(); 219 } else if ("session".equals(scope)) { 220 return getSessionScope(); 221 } else if (APPLICATION_SCOPE.equals(scope)) { 222 return getApplicationScope(); 223 } 224 225 throw new IllegalArgumentException(scope + " does not exist. " 226 + "Call getAvailableScopes() first to check."); 227 } 228 229 /** 230 * Returns the context map from request scope. 231 * 232 * @return the context map from request scope 233 */ 234 public Map<String, Object> getRequestScope() { 235 if (requestScope == null && request != null) { 236 requestScope = new ScopeMap(new RequestScopeExtractor(request)); 237 } 238 239 return requestScope; 240 } 241 242 /** 243 * Returns the context map from session scope. 244 * 245 * @return the context map from session scope 246 */ 247 public Map<String, Object> getSessionScope() { 248 if (sessionScope == null && request != null) { 249 sessionScope = new ScopeMap(new SessionScopeExtractor(request)); 250 } 251 252 return sessionScope; 253 } 254 255 /** 256 * Returns all available scopes. 257 * 258 * <p>The scopes are ordered according to their lifetime, the innermost, 259 * shorter lived scope appears first, and the outermost, longer lived scope 260 * appears last. Besides, the scopes "request" and "application" always 261 * included in the list.</p> 262 * 263 * @return All the available scopes. 264 */ 265 @Override 266 public List<String> getAvailableScopes() { 267 return SCOPES; 268 } 269 270 /** 271 * Forwards to a path. 272 * 273 * @param path The path to forward to. 274 * 275 * @throws IOException If something goes wrong when forwarding. 276 */ 277 @Override 278 public void doForward(String path) throws IOException { 279 if (response.isCommitted()) { 280 doInclude(path); 281 } else { 282 forward(path); 283 } 284 } 285 286 /** 287 * Includes the content of a resource (servlet, JSP page, HTML file) in the 288 * response. In essence, this method enables programmatic server-side includes. 289 * 290 * @param path a {@code String} specifying the pathname to the resource. If 291 * it is relative, it must be relative against the current servlet. 292 * 293 * @throws IOException if the included resource throws this exception 294 * 295 * @see RequestDispatcher#include(jakarta.servlet.ServletRequest, ServletResponse) 296 */ 297 public void doInclude(String path) throws IOException { 298 RequestDispatcher rd = request.getRequestDispatcher(path); 299 300 if (rd == null) { 301 throw new IOException("No request dispatcher returned for path '" 302 + path + "'"); 303 } 304 305 try { 306 rd.include(request, response); 307 } catch (ServletException ex) { 308 throw ServletUtil.wrapServletException( 309 ex, "ServletException including path '" + path + "'."); 310 } 311 } 312 313 /** 314 * Forwards to a path. 315 * 316 * @param path The path to forward to. 317 * 318 * @throws IOException If something goes wrong during the operation. 319 */ 320 private void forward(String path) throws IOException { 321 RequestDispatcher rd = request.getRequestDispatcher(path); 322 323 if (rd == null) { 324 throw new IOException("No request dispatcher returned for path '" 325 + path + "'"); 326 } 327 328 try { 329 rd.forward(request, response); 330 } catch (ServletException ex) { 331 throw ServletUtil.wrapServletException( 332 ex, "ServletException including path '" + path + "'."); 333 } 334 } 335 336 /** 337 * Returns a {@link jakarta.servlet.ServletOutputStream} suitable for 338 * writing binary data in the response. The servlet container does not 339 * encode the binary data. 340 * 341 * @return a {@link jakarta.servlet.ServletOutputStream} for writing binary 342 * data 343 * 344 * @throws IllegalStateException if the {@code getWriter} method has 345 * been called on this response 346 * @throws IOException if an input or output exception occurred 347 * 348 * @see HttpServletResponse#getOutputStream() 349 */ 350 public OutputStream getOutputStream() throws IOException { 351 if (outputStream == null) { 352 outputStream = response.getOutputStream(); 353 } 354 355 return outputStream; 356 } 357 358 /** 359 * Returns a {@code Writer} object that can send character text to the 360 * client. The {@code Writer} uses the character encoding returned by 361 * {@link HttpServletResponse#getCharacterEncoding()}. If the response's 362 * character encoding has not been specified as described in 363 * {@code getCharacterEncoding} (i.e., the method just returns the default 364 * value {@code ISO-8859-1}), {@code getWriter} updates it to 365 * {@code ISO-8859-1}. 366 * 367 * @return a {@code Writer} object that can return character data to the 368 * client 369 * 370 * @throws java.io.UnsupportedEncodingException if the character encoding 371 * returned by {@code getCharacterEncoding} 372 * cannot be used 373 * @throws IllegalStateException if the {@code getOutputStream} method has 374 * already been called for this response 375 * object 376 * @throws IOException if an input or output exception occurred 377 * 378 * @see #getPrintWriter() 379 * @see HttpServletResponse#getWriter() 380 */ 381 public Writer getWriter() throws IOException { 382 return getPrintWriter(); 383 } 384 385 /** 386 * Returns a {@code PrintWriter} object that can send character text to the 387 * client. The {@code PrintWriter} uses the character encoding returned by 388 * {@link HttpServletResponse#getCharacterEncoding()}. If the response's 389 * character encoding has not been specified as described in 390 * {@code getCharacterEncoding} (i.e., the method just returns the default 391 * value {@code ISO-8859-1}), {@code getWriter} updates it to 392 * {@code ISO-8859-1}. 393 * 394 * @return a {@code PrintWriter} object that can return character data to 395 * the client 396 * 397 * @throws java.io.UnsupportedEncodingException if the character encoding 398 * returned by {@code getCharacterEncoding} 399 * cannot be used 400 * @throws IllegalStateException if the {@code getOutputStream} method has 401 * already been called for this response 402 * object 403 * @throws IOException if an input or output exception occurred 404 * 405 * @see HttpServletResponse#getWriter() 406 */ 407 public PrintWriter getPrintWriter() throws IOException { 408 if (writer == null) { 409 writer = response.getWriter(); 410 } 411 412 return writer; 413 } 414 415 /** 416 * Returns a boolean indicating if the response has been committed. A 417 * committed response has already had its status code and headers written. 418 * 419 * @return a boolean indicating if the response has been committed 420 * 421 * @see HttpServletResponse#isCommitted() 422 */ 423 public boolean isResponseCommitted() { 424 return response.isCommitted(); 425 } 426 427 /** 428 * Sets the content type of the response being sent to the client, if the 429 * response has not been committed yet. The given content type may include 430 * a character encoding specification, for example, 431 * {@code>text/html;charset=UTF-8}. The response's character encoding is 432 * only set from the given content type if this method is called before 433 * {@code getWriter} is called. 434 * 435 * @param contentType a {@code String} specifying the MIME type of the 436 * content 437 * 438 * @see HttpServletResponse#setContentType(String) 439 */ 440 public void setContentType(String contentType) { 441 response.setContentType(contentType); 442 } 443 444 /** 445 * Returns the preferred {@code Locale} that the client will accept content 446 * in, based on the Accept-Language header. If the client request doesn't 447 * provide an Accept-Language header, this method returns the default 448 * locale for the server. 449 * 450 * @return the preferred {@code Locale} for the client 451 * 452 * @see HttpServletRequest#getLocale() 453 */ 454 public Locale getRequestLocale() { 455 return request.getLocale(); 456 } 457 458 /** 459 * Returns the request object to use. 460 * 461 * @return the request object to use 462 */ 463 public HttpServletRequest getRequest() { 464 return request; 465 } 466 467 /** 468 * Returns the response object to use. 469 * 470 * @return the response object to use 471 */ 472 public HttpServletResponse getResponse() { 473 return response; 474 } 475 476 /** 477 * Returns a boolean indicating whether the authenticated user is included 478 * in the specified logical "role". Roles and role membership can be 479 * defined using deployment descriptors. If the user has not been 480 * authenticated, the method returns {@code false}. 481 * 482 * @param role a {@code String} specifying the name of the role 483 * 484 * @return a {@code boolean} indicating whether the user making this 485 * request belongs to a given role; {@code false} if the user has 486 * not been authenticated 487 * 488 * @see HttpServletRequest#isUserInRole(String) 489 */ 490 public boolean isUserInRole(String role) { 491 return request.isUserInRole(role); 492 } 493 }