ServletRequest.java
/*
* Copyright 2023 Web-Legacy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tiles.request.jakarta.servlet;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.tiles.request.AbstractClientRequest;
import org.apache.tiles.request.ApplicationContext;
import org.apache.tiles.request.attribute.Addable;
import org.apache.tiles.request.collection.HeaderValuesMap;
import org.apache.tiles.request.collection.ReadOnlyEnumerationMap;
import org.apache.tiles.request.collection.ScopeMap;
import org.apache.tiles.request.jakarta.servlet.extractor.HeaderExtractor;
import org.apache.tiles.request.jakarta.servlet.extractor.ParameterExtractor;
import org.apache.tiles.request.jakarta.servlet.extractor.RequestScopeExtractor;
import org.apache.tiles.request.jakarta.servlet.extractor.SessionScopeExtractor;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Servlet-based implementation of the TilesApplicationContext interface.
*
* <p>Copied from Apache tiles-request-servlet 1.0.7 and adapted for
* Jakarta EE 9.</p>
*/
public class ServletRequest extends AbstractClientRequest {
/**
* The native available scopes: request, session and application.
*/
private static final List<String> SCOPES
= Collections.unmodifiableList(Arrays.asList(
REQUEST_SCOPE, "session", APPLICATION_SCOPE));
/**
* The request object to use.
*/
private HttpServletRequest request;
/**
* The response object to use.
*/
private HttpServletResponse response;
/**
* The response output stream, lazily initialized.
*/
private OutputStream outputStream;
/**
* The response writer, lazily initialized.
*/
private PrintWriter writer;
/**
* The lazily instantiated {@code Map} of header name-value combinations
* (immutable).
*/
private Map<String, String> header = null;
/**
* The lazily instantiated {@code Map} of header name-value combinations
* (write-only).
*/
private Addable<String> responseHeaders = null;
/**
* The lazily instantiated {@code Map} of header name-values combinations
* (immutable).
*/
private Map<String, String[]> headerValues = null;
/**
* The lazily instantiated {@code Map} of request parameter name-value.
*/
private Map<String, String> param = null;
/**
* The lazily instantiated {@code Map} of request scope attributes.
*/
private Map<String, Object> requestScope = null;
/**
* The lazily instantiated {@code Map} of session scope attributes.
*/
private Map<String, Object> sessionScope = null;
/**
* Creates a new instance of ServletTilesRequestContext.
*
* @param applicationContext The application context.
* @param request The request object.
* @param response The response object.
*/
public ServletRequest(
ApplicationContext applicationContext,
HttpServletRequest request, HttpServletResponse response) {
super(applicationContext);
this.request = request;
this.response = response;
}
/**
* Return an immutable Map that maps header names to the first (or only)
* header value (as a String).
*
* @return The header map.
*/
@Override
public Map<String, String> getHeader() {
if (header == null && request != null) {
header = new ReadOnlyEnumerationMap<String>(
new HeaderExtractor(request, null));
}
return header;
}
/**
* Return an add-able object that can be used to write headers to the
* response.
*
* @return An add-able object.
*/
@Override
public Addable<String> getResponseHeaders() {
if (responseHeaders == null && response != null) {
responseHeaders = new HeaderExtractor(null, response);
}
return responseHeaders;
}
/**
* Return an immutable Map that maps header names to the set of all values
* specified in the request (as a String array). Header names must be
* matched in a case-insensitive manner.
*
* @return The header values map.
*/
@Override
public Map<String, String[]> getHeaderValues() {
if (headerValues == null && request != null) {
headerValues = new HeaderValuesMap(
new HeaderExtractor(request, response));
}
return headerValues;
}
/**
* Return an immutable Map that maps request parameter names to the first
* (or only) value (as a String).
*
* @return The parameter map.
*/
@Override
public Map<String, String> getParam() {
if (param == null && request != null) {
param = new ReadOnlyEnumerationMap<String>(
new ParameterExtractor(request));
}
return param;
}
/**
* Return an immutable Map that maps request parameter names to the set of
* all values (as a String array).
*
* @return The parameter values map.
*/
@Override
public Map<String, String[]> getParamValues() {
return request.getParameterMap();
}
/**
* Returns a context map, given the scope name.
*
* <p>This method always return a map for all the scope names returned by
* {@link #getAvailableScopes()}. That map may be writable, or immutable,
* depending on the implementation.
*
* @param scope The name of the scope.
*
* @return The context.
*/
@Override
public Map<String, Object> getContext(String scope) {
if (REQUEST_SCOPE.equals(scope)) {
return getRequestScope();
} else if ("session".equals(scope)) {
return getSessionScope();
} else if (APPLICATION_SCOPE.equals(scope)) {
return getApplicationScope();
}
throw new IllegalArgumentException(scope + " does not exist. "
+ "Call getAvailableScopes() first to check.");
}
/**
* Returns the context map from request scope.
*
* @return the context map from request scope
*/
public Map<String, Object> getRequestScope() {
if (requestScope == null && request != null) {
requestScope = new ScopeMap(new RequestScopeExtractor(request));
}
return requestScope;
}
/**
* Returns the context map from session scope.
*
* @return the context map from session scope
*/
public Map<String, Object> getSessionScope() {
if (sessionScope == null && request != null) {
sessionScope = new ScopeMap(new SessionScopeExtractor(request));
}
return sessionScope;
}
/**
* Returns all available scopes.
*
* <p>The scopes are ordered according to their lifetime, the innermost,
* shorter lived scope appears first, and the outermost, longer lived scope
* appears last. Besides, the scopes "request" and "application" always
* included in the list.</p>
*
* @return All the available scopes.
*/
@Override
public List<String> getAvailableScopes() {
return SCOPES;
}
/**
* Forwards to a path.
*
* @param path The path to forward to.
*
* @throws IOException If something goes wrong when forwarding.
*/
@Override
public void doForward(String path) throws IOException {
if (response.isCommitted()) {
doInclude(path);
} else {
forward(path);
}
}
/**
* Includes the content of a resource (servlet, JSP page, HTML file) in the
* response. In essence, this method enables programmatic server-side includes.
*
* @param path a {@code String} specifying the pathname to the resource. If
* it is relative, it must be relative against the current servlet.
*
* @throws IOException if the included resource throws this exception
*
* @see RequestDispatcher#include(jakarta.servlet.ServletRequest, ServletResponse)
*/
public void doInclude(String path) throws IOException {
RequestDispatcher rd = request.getRequestDispatcher(path);
if (rd == null) {
throw new IOException("No request dispatcher returned for path '"
+ path + "'");
}
try {
rd.include(request, response);
} catch (ServletException ex) {
throw ServletUtil.wrapServletException(
ex, "ServletException including path '" + path + "'.");
}
}
/**
* Forwards to a path.
*
* @param path The path to forward to.
*
* @throws IOException If something goes wrong during the operation.
*/
private void forward(String path) throws IOException {
RequestDispatcher rd = request.getRequestDispatcher(path);
if (rd == null) {
throw new IOException("No request dispatcher returned for path '"
+ path + "'");
}
try {
rd.forward(request, response);
} catch (ServletException ex) {
throw ServletUtil.wrapServletException(
ex, "ServletException including path '" + path + "'.");
}
}
/**
* Returns a {@link jakarta.servlet.ServletOutputStream} suitable for
* writing binary data in the response. The servlet container does not
* encode the binary data.
*
* @return a {@link jakarta.servlet.ServletOutputStream} for writing binary
* data
*
* @throws IllegalStateException if the {@code getWriter} method has
* been called on this response
* @throws IOException if an input or output exception occurred
*
* @see HttpServletResponse#getOutputStream()
*/
public OutputStream getOutputStream() throws IOException {
if (outputStream == null) {
outputStream = response.getOutputStream();
}
return outputStream;
}
/**
* Returns a {@code Writer} object that can send character text to the
* client. The {@code Writer} uses the character encoding returned by
* {@link HttpServletResponse#getCharacterEncoding()}. If the response's
* character encoding has not been specified as described in
* {@code getCharacterEncoding} (i.e., the method just returns the default
* value {@code ISO-8859-1}), {@code getWriter} updates it to
* {@code ISO-8859-1}.
*
* @return a {@code Writer} object that can return character data to the
* client
*
* @throws java.io.UnsupportedEncodingException if the character encoding
* returned by {@code getCharacterEncoding}
* cannot be used
* @throws IllegalStateException if the {@code getOutputStream} method has
* already been called for this response
* object
* @throws IOException if an input or output exception occurred
*
* @see #getPrintWriter()
* @see HttpServletResponse#getWriter()
*/
public Writer getWriter() throws IOException {
return getPrintWriter();
}
/**
* Returns a {@code PrintWriter} object that can send character text to the
* client. The {@code PrintWriter} uses the character encoding returned by
* {@link HttpServletResponse#getCharacterEncoding()}. If the response's
* character encoding has not been specified as described in
* {@code getCharacterEncoding} (i.e., the method just returns the default
* value {@code ISO-8859-1}), {@code getWriter} updates it to
* {@code ISO-8859-1}.
*
* @return a {@code PrintWriter} object that can return character data to
* the client
*
* @throws java.io.UnsupportedEncodingException if the character encoding
* returned by {@code getCharacterEncoding}
* cannot be used
* @throws IllegalStateException if the {@code getOutputStream} method has
* already been called for this response
* object
* @throws IOException if an input or output exception occurred
*
* @see HttpServletResponse#getWriter()
*/
public PrintWriter getPrintWriter() throws IOException {
if (writer == null) {
writer = response.getWriter();
}
return writer;
}
/**
* Returns a boolean indicating if the response has been committed. A
* committed response has already had its status code and headers written.
*
* @return a boolean indicating if the response has been committed
*
* @see HttpServletResponse#isCommitted()
*/
public boolean isResponseCommitted() {
return response.isCommitted();
}
/**
* Sets the content type of the response being sent to the client, if the
* response has not been committed yet. The given content type may include
* a character encoding specification, for example,
* {@code>text/html;charset=UTF-8}. The response's character encoding is
* only set from the given content type if this method is called before
* {@code getWriter} is called.
*
* @param contentType a {@code String} specifying the MIME type of the
* content
*
* @see HttpServletResponse#setContentType(String)
*/
public void setContentType(String contentType) {
response.setContentType(contentType);
}
/**
* Returns the preferred {@code Locale} that the client will accept content
* in, based on the Accept-Language header. If the client request doesn't
* provide an Accept-Language header, this method returns the default
* locale for the server.
*
* @return the preferred {@code Locale} for the client
*
* @see HttpServletRequest#getLocale()
*/
public Locale getRequestLocale() {
return request.getLocale();
}
/**
* Returns the request object to use.
*
* @return the request object to use
*/
public HttpServletRequest getRequest() {
return request;
}
/**
* Returns the response object to use.
*
* @return the response object to use
*/
public HttpServletResponse getResponse() {
return response;
}
/**
* Returns a boolean indicating whether the authenticated user is included
* in the specified logical "role". Roles and role membership can be
* defined using deployment descriptors. If the user has not been
* authenticated, the method returns {@code false}.
*
* @param role a {@code String} specifying the name of the role
*
* @return a {@code boolean} indicating whether the user making this
* request belongs to a given role; {@code false} if the user has
* not been authenticated
*
* @see HttpServletRequest#isUserInRole(String)
*/
public boolean isUserInRole(String role) {
return request.isUserInRole(role);
}
}