View Javadoc
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 }