/* * Copyright 2017 OmniFaces * * 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.omnifaces.util; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.logging.Level.FINE; import static java.util.regex.Pattern.quote; import static javax.faces.application.ProjectStage.Development; import static javax.faces.application.ProjectStage.PROJECT_STAGE_JNDI_NAME; import static javax.faces.application.ProjectStage.PROJECT_STAGE_PARAM_NAME; import static javax.servlet.RequestDispatcher.ERROR_REQUEST_URI; import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING; import static javax.servlet.RequestDispatcher.FORWARD_REQUEST_URI; import static org.omnifaces.util.JNDI.lookup; import static org.omnifaces.util.Utils.coalesce; import static org.omnifaces.util.Utils.decodeURL; import static org.omnifaces.util.Utils.encodeURI; import static org.omnifaces.util.Utils.encodeURL; import static org.omnifaces.util.Utils.isEmpty; import static org.omnifaces.util.Utils.isOneOf; import static org.omnifaces.util.Utils.startsWithOneOf; import static org.omnifaces.util.Utils.unmodifiableSet; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import javax.faces.FactoryFinder; import javax.faces.application.Application; import javax.faces.application.ResourceHandler; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.lifecycle.Lifecycle; import javax.faces.lifecycle.LifecycleFactory; import javax.faces.webapp.FacesServlet; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import org.omnifaces.component.ParamHolder; import org.omnifaces.facesviews.FacesViews; /** * <p> * Collection of utility methods for the Servlet API in general. Most of them are internally used by {@link Faces} * and {@link FacesLocal}, however they may also be useful in a "plain vanilla" servlet or servlet filter. * <p> * There are as of now also five special methods related to JSF without needing a {@link FacesContext}: * <ul> * <li>The {@link #getFacesLifecycle(ServletContext)} which returns the JSF lifecycle, allowing you a.o. to * programmatically register JSF application's phase listeners. * <li>The {@link #isFacesAjaxRequest(HttpServletRequest)} which is capable of checking if the current request is a JSF * ajax request. * <li>The {@link #isFacesResourceRequest(HttpServletRequest)} which is capable of checking if the current request is a * JSF resource request. * <li>The {@link #facesRedirect(HttpServletRequest, HttpServletResponse, String, String...)} which is capable * of distinguishing JSF ajax requests from regular requests and altering the redirect logic on it, exactly like as * {@link ExternalContext#redirect(String)} does. In other words, this method behaves exactly the same as * {@link Faces#redirect(String, String...)}. * <li>The {@link #isFacesDevelopment(ServletContext)} which is capable of checking if the current JSF application * configuration is set to development project stage. * </ul> * <p> * Those methods can be used in for example a servlet filter. * * @author Arjan Tijms * @author Bauke Scholtz * @since 1.6 */ public final class Servlets { // Constants ------------------------------------------------------------------------------------------------------ private static final Logger logger = Logger.getLogger(Servlets.class.getName()); private static final String CONTENT_DISPOSITION_HEADER = "%s;filename=\"%2$s\"; filename*=UTF-8''%2$s"; private static final Set<String> FACES_AJAX_HEADERS = unmodifiableSet("partial/ajax", "partial/process"); private static final String FACES_AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<partial-response><redirect url=\"%s\"></redirect></partial-response>"; // Variables ------------------------------------------------------------------------------------------------------ private static volatile Boolean facesDevelopment; // Constructors --------------------------------------------------------------------------------------------------- private Servlets() { // Hide constructor. } // HttpServletRequest --------------------------------------------------------------------------------------------- /** * Returns the HTTP request hostname. This is the entire domain, without any scheme and slashes. Noted should be * that this value is extracted from the request URL, not from {@link HttpServletRequest#getServerName()} as its * outcome can be influenced by proxies. * @param request The involved HTTP servlet request. * @return The HTTP request hostname. * @throws IllegalArgumentException When the URL is malformed. This is however unexpected as the request would * otherwise not have hit the server at all. * @see HttpServletRequest#getRequestURL() */ public static String getRequestHostname(HttpServletRequest request) { try { return new URL(request.getRequestURL().toString()).getHost(); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); } } /** * Returns the HTTP request domain URL. This is the URL with the scheme and domain, without any trailing slash. * @param request The involved HTTP servlet request. * @return The HTTP request domain URL. * @see HttpServletRequest#getRequestURL() * @see HttpServletRequest#getRequestURI() */ public static String getRequestDomainURL(HttpServletRequest request) { String url = request.getRequestURL().toString(); return url.substring(0, url.length() - request.getRequestURI().length()); } /** * Returns the HTTP request base URL. This is the URL from the scheme, domain until with context path, including * the trailing slash. This is the value you could use in HTML <code><base></code> tag. * @param request The involved HTTP servlet request. * @return The HTTP request base URL. * @see HttpServletRequest#getRequestURL() * @see HttpServletRequest#getRequestURI() * @see HttpServletRequest#getContextPath() */ public static String getRequestBaseURL(HttpServletRequest request) { return getRequestDomainURL(request) + request.getContextPath() + "/"; } /** * Returns the HTTP request URI, regardless of any forward or error dispatch. This is the part after the domain in * the request URL, including the leading slash. * @param request The involved HTTP servlet request. * @return The HTTP request URI, regardless of any forward or error dispatch. * @since 2.4 * @see HttpServletRequest#getRequestURI() * @see RequestDispatcher#FORWARD_REQUEST_URI * @see RequestDispatcher#ERROR_REQUEST_URI */ public static String getRequestURI(HttpServletRequest request) { return coalesce((String) request.getAttribute(ERROR_REQUEST_URI), (String) request.getAttribute(FORWARD_REQUEST_URI), request.getRequestURI()); } /** * Returns the HTTP request path info, taking into account whether FacesViews is used with MultiViews enabled. * If the resource is prefix mapped (e.g. <code>/faces/*</code>), then this returns the whole part after the prefix * mapping, with a leading slash. If the resource is suffix mapped (e.g. <code>*.xhtml</code>), then this returns * <code>null</code>. * @param request The involved HTTP servlet request. * @return The HTTP request path info. * @since 2.5 * @see HttpServletRequest#getPathInfo() * @see FacesViews#FACES_VIEWS_ORIGINAL_PATH_INFO */ public static String getRequestPathInfo(HttpServletRequest request) { return coalesce((String) request.getAttribute(FacesViews.FACES_VIEWS_ORIGINAL_PATH_INFO), request.getPathInfo()); } /** * Returns the HTTP request query string, regardless of any forward. * @param request The involved HTTP servlet request. * @return The HTTP request query string, regardless of any forward. * @since 2.4 * @see HttpServletRequest#getRequestURI() * @see RequestDispatcher#FORWARD_QUERY_STRING */ public static String getRequestQueryString(HttpServletRequest request) { return coalesce((String) request.getAttribute(FORWARD_QUERY_STRING), request.getQueryString()); } /** * Returns the HTTP request query string as parameter values map. Note this method returns <strong>only</strong> * the request URL (GET) parameters, as opposed to {@link HttpServletRequest#getParameterMap()}, which contains both * the request URL (GET) parameters and and the request body (POST) parameters. * The map entries are in the same order as they appear in the query string. * @param request The involved HTTP servlet request. * @return The HTTP request query string as parameter values map. */ public static Map<String, List<String>> getRequestQueryStringMap(HttpServletRequest request) { String queryString = getRequestQueryString(request); if (isEmpty(queryString)) { return new LinkedHashMap<>(0); } return toParameterMap(queryString); } /** * Returns the HTTP request parameter map. Note this method returns the values as a <code>List<String></code>, * as opposed to {@link HttpServletRequest#getParameterMap()}, which returns the values as <code>String[]</code>. * The map entries are not per definition ordered, but the values are. * @param request The involved HTTP servlet request. * @return The HTTP request parameter map. * @since 2.6 */ public static Map<String, List<String>> getRequestParameterMap(HttpServletRequest request) { Map<String, List<String>> parameterMap = new HashMap<>(request.getParameterMap().size()); for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) { parameterMap.put(entry.getKey(), asList(entry.getValue())); } return parameterMap; } /** * Returns the HTTP request URI with query string, regardless of any forward. This is the part after the domain in * the request URL, including the leading slash and the request query string. * @param request The involved HTTP servlet request. * @return The HTTP request URI with query string. * @see #getRequestURI(HttpServletRequest) * @see #getRequestQueryString(HttpServletRequest) */ public static String getRequestURIWithQueryString(HttpServletRequest request) { String requestURI = getRequestURI(request); String queryString = getRequestQueryString(request); return (queryString == null) ? requestURI : (requestURI + "?" + queryString); } /** * Returns the HTTP request URI relative to the context root, regardless of any forward. This is the request URI * minus the context path. Note that this includes path parameters. * @param request The involved HTTP servlet request. * @return The HTTP request URI relative to the context root. * @since 1.8 */ public static String getRequestRelativeURI(HttpServletRequest request) { return getRequestURI(request).substring(request.getContextPath().length()); } /** * Returns the HTTP request URI relative to the context root without path parameters, regardless of any forward. * This is the request URI minus the context path and path parameters. * @param request The involved HTTP servlet request. * @return The HTTP request URI relative to the context root without path parameters. * @since 1.8 */ public static String getRequestRelativeURIWithoutPathParameters(HttpServletRequest request) { return getRequestRelativeURI(request).split(";", 2)[0]; } /** * Returns the HTTP request URL with query string, regardless of any forward. This is the full request URL without * query string as the enduser sees in browser address bar. * @param request The involved HTTP servlet request. * @return The HTTP request URL without query string, regardless of any forward. * @since 2.4 * @see HttpServletRequest#getRequestURL() */ public static String getRequestURL(HttpServletRequest request) { return getRequestDomainURL(request) + getRequestURI(request); } /** * Returns the HTTP request URL with query string. This is the full request URL with query string as the enduser * sees in browser address bar. * @param request The involved HTTP servlet request. * @return The HTTP request URL with query string, regardless of any forward. * @see HttpServletRequest#getRequestURL() * @see HttpServletRequest#getQueryString() */ public static String getRequestURLWithQueryString(HttpServletRequest request) { return getRequestDomainURL(request) + getRequestURIWithQueryString(request); } /** * Returns the original HTTP request URI behind this forwarded request, if any. * This does not include the request query string. * @param request The involved HTTP servlet request. * @return The original HTTP request URI behind this forwarded request, if any. * @since 1.8 * @deprecated Since 2.4. Use {@link #getRequestURI(HttpServletRequest)} instead. */ @Deprecated // TODO: Remove in OmniFaces 3.0. public static String getForwardRequestURI(HttpServletRequest request) { return (String) request.getAttribute(FORWARD_REQUEST_URI); } /** * Returns the original HTTP request query string behind this forwarded request, if any. * @param request The involved HTTP servlet request. * @return The original HTTP request query string behind this forwarded request, if any. * @since 1.8 * @deprecated Since 2.4. Use {@link #getRequestQueryString(HttpServletRequest)} instead. */ @Deprecated // TODO: Remove in OmniFaces 3.0. public static String getForwardRequestQueryString(HttpServletRequest request) { return (String) request.getAttribute(FORWARD_QUERY_STRING); } /** * Returns the original HTTP request URI with query string behind this forwarded request, if any. * @param request The involved HTTP servlet request. * @return The original HTTP request URI with query string behind this forwarded request, if any. * @since 1.8 * @deprecated Since 2.4. Use {@link #getRequestURIWithQueryString(HttpServletRequest)} instead. */ @Deprecated // TODO: Remove in OmniFaces 3.0. public static String getForwardRequestURIWithQueryString(HttpServletRequest request) { String requestURI = getForwardRequestURI(request); String queryString = getForwardRequestQueryString(request); return (queryString == null) ? requestURI : (requestURI + "?" + queryString); } /** * Converts the given request query string to request parameter values map. * @param queryString The request query string. * @return The request query string as request parameter values map. * @since 1.7 */ public static Map<String, List<String>> toParameterMap(String queryString) { String[] parameters = queryString.split(quote("&")); Map<String, List<String>> parameterMap = new LinkedHashMap<>(parameters.length); for (String parameter : parameters) { if (parameter.contains("=")) { String[] pair = parameter.split(quote("=")); String key = decodeURL(pair[0]); String value = (pair.length > 1 && !isEmpty(pair[1])) ? decodeURL(pair[1]) : ""; List<String> values = parameterMap.get(key); if (values == null) { values = new ArrayList<>(1); parameterMap.put(key, values); } values.add(value); } } return parameterMap; } /** * Converts the given request parameter values map to request query string. * Empty names and null values will be skipped. * @param parameterMap The request parameter values map. * @return The request parameter values map as request query string. * @since 2.0 */ public static String toQueryString(Map<String, List<String>> parameterMap) { StringBuilder queryString = new StringBuilder(); for (Entry<String, List<String>> entry : parameterMap.entrySet()) { if (isEmpty(entry.getKey())) { continue; } String name = encodeURL(entry.getKey()); for (String value : entry.getValue()) { if (value == null) { continue; } if (queryString.length() > 0) { queryString.append("&"); } queryString.append(name).append("=").append(encodeURL(value)); } } return queryString.toString(); } /** * Converts the given parameter values list to request query string. * Empty names and null values will be skipped. * @param params The parameter values list. * @return The parameter values list as request query string. * @since 2.2 */ public static String toQueryString(List<ParamHolder> params) { StringBuilder queryString = new StringBuilder(); for (ParamHolder param : params) { if (isEmpty(param.getName())) { continue; } Object value = param.getValue(); if (value != null) { if (queryString.length() > 0) { queryString.append("&"); } queryString.append(encodeURL(param.getName())).append("=").append(encodeURL(value.toString())); } } return queryString.toString(); } /** * Returns the Internet Protocol (IP) address of the client that sent the request. This will first check the * <code>X-Forwarded-For</code> request header and if it's present, then return its first IP address, else just * return {@link HttpServletRequest#getRemoteAddr()} unmodified. * @param request The involved HTTP servlet request. * @return The IP address of the client. * @see HttpServletRequest#getRemoteAddr() * @since 2.3 */ public static String getRemoteAddr(HttpServletRequest request) { String forwardedFor = request.getHeader("X-Forwarded-For"); return isEmpty(forwardedFor) ? request.getRemoteAddr() : forwardedFor.split("\\s*,\\s*", 2)[0]; // It's a comma separated string: client,proxy1,proxy2,... } /** * Returns the submitted file name of the given part, making sure that any path is stripped off. Some browsers * are known to incorrectly include the client side path along with it. * @param part The part of a multipart/form-data request. * @return The submitted file name of the given part, or null if there is none. * @since 2.5 */ public static String getSubmittedFileName(Part part) { for (String cd : part.getHeader("Content-Disposition").split("\\s*;\\s*")) { if (cd.startsWith("filename")) { String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", ""); return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1); // MSIE fix. } } return null; } // HttpServletResponse -------------------------------------------------------------------------------------------- /** * <p>Set the cache headers. If the <code>expires</code> argument is larger than 0 seconds, then the following headers * will be set: * <ul> * <li><code>Cache-Control: public,max-age=[expiration time in seconds],must-revalidate</code></li> * <li><code>Expires: [expiration date of now plus expiration time in seconds]</code></li> * </ul> * <p>Else the method will delegate to {@link #setNoCacheHeaders(HttpServletResponse)}. * @param response The HTTP servlet response to set the headers on. * @param expires The expire time in seconds (not milliseconds!). * @since 2.2 */ public static void setCacheHeaders(HttpServletResponse response, long expires) { if (expires > 0) { response.setHeader("Cache-Control", "public,max-age=" + expires + ",must-revalidate"); response.setDateHeader("Expires", System.currentTimeMillis() + SECONDS.toMillis(expires)); response.setHeader("Pragma", ""); // Explicitly set pragma to prevent container from overriding it. } else { setNoCacheHeaders(response); } } /** * <p>Set the no-cache headers. The following headers will be set: * <ul> * <li><code>Cache-Control: no-cache,no-store,must-revalidate</code></li> * <li><code>Expires: [expiration date of 0]</code></li> * <li><code>Pragma: no-cache</code></li> * </ul> * Set the no-cache headers. * @param response The HTTP servlet response to set the headers on. * @since 2.2 */ public static void setNoCacheHeaders(HttpServletResponse response) { response.setHeader("Cache-Control", "no-cache,no-store,must-revalidate"); response.setDateHeader("Expires", 0); response.setHeader("Pragma", "no-cache"); // Backwards compatibility for HTTP 1.0. } /** * <p>Format an UTF-8 compatible content disposition header for the given filename and whether it's an attachment. * @param filename The filename to appear in "Save As" dialogue. * @param attachment Whether the content should be provided as an attachment or inline. * @return An UTF-8 compatible content disposition header. * @since 2.6 */ public static String formatContentDispositionHeader(String filename, boolean attachment) { return format(CONTENT_DISPOSITION_HEADER, (attachment ? "attachment" : "inline"), encodeURI(filename)); } // Cookies -------------------------------------------------------------------------------------------------------- /** * Returns the value of the HTTP request cookie associated with the given name. The value is implicitly URL-decoded * with a charset of UTF-8. * @param request The involved HTTP servlet request. * @param name The HTTP request cookie name. * @return The value of the HTTP request cookie associated with the given name. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @see HttpServletRequest#getCookies() * @since 2.0 */ public static String getRequestCookie(HttpServletRequest request, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(name)) { return decodeURL(cookie.getValue()); } } } return null; } /** * Add a cookie with given name, value and maxage to the HTTP response. * The cookie value will implicitly be URL-encoded with UTF-8 so that any special characters can be stored. * The cookie will implicitly be set in the domain and path of the current request URL. * The cookie will implicitly be set to HttpOnly as JavaScript is not supposed to manipulate server-created cookies. * The cookie will implicitly be set to secure when the current request is a HTTPS request. * @param request The involved HTTP servlet request. * @param response The involved HTTP servlet response. * @param name The cookie name. * @param value The cookie value. * @param maxAge The maximum age of the cookie, in seconds. If this is <code>0</code>, then the cookie will be * removed. Note that the name and path must be exactly the same as it was when the cookie was created. If this is * <code>-1</code> then the cookie will become a session cookie and thus live as long as the established HTTP * session. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @see HttpServletResponse#addCookie(Cookie) * @since 2.0 */ public static void addResponseCookie(HttpServletRequest request, HttpServletResponse response, String name, String value, int maxAge) { addResponseCookie(request, response, name, value, getRequestHostname(request), null, maxAge); } /** * Add a cookie with given name, value, path and maxage to the HTTP response. * The cookie value will implicitly be URL-encoded with UTF-8 so that any special characters can be stored. * The cookie will implicitly be set in the domain of the current request URL. * The cookie will implicitly be set to HttpOnly as JavaScript is not supposed to manipulate server-created cookies. * The cookie will implicitly be set to secure when the current request is a HTTPS request. * @param request The involved HTTP servlet request. * @param response The involved HTTP servlet response. * @param name The cookie name. * @param value The cookie value. * @param path The cookie path. If this is <code>/</code>, then the cookie is available in all pages of the webapp. * If this is <code>/somespecificpath</code>, then the cookie is only available in pages under the specified path. * @param maxAge The maximum age of the cookie, in seconds. If this is <code>0</code>, then the cookie will be * removed. Note that the name and path must be exactly the same as it was when the cookie was created. If this is * <code>-1</code> then the cookie will become a session cookie and thus live as long as the established HTTP * session. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @see HttpServletResponse#addCookie(Cookie) * @since 2.0 */ public static void addResponseCookie(HttpServletRequest request, HttpServletResponse response, String name, String value, String path, int maxAge) { addResponseCookie(request, response, name, value, getRequestHostname(request), path, maxAge); } /** * Add a cookie with given name, value, domain, path and maxage to the HTTP response. * The cookie value will implicitly be URL-encoded with UTF-8 so that any special characters can be stored. * The cookie will implicitly be set to HttpOnly as JavaScript is not supposed to manipulate server-created cookies. * The cookie will implicitly be set to secure when the current request is a HTTPS request. * @param request The involved HTTP servlet request. * @param response The involved HTTP servlet response. * @param name The cookie name. * @param value The cookie value. * @param domain The cookie domain. You can use <code>.example.com</code> (with a leading period) if you'd like the * cookie to be available to all subdomains of the domain. Note that you cannot set it to a different domain. * @param path The cookie path. If this is <code>/</code>, then the cookie is available in all pages of the webapp. * If this is <code>/somespecificpath</code>, then the cookie is only available in pages under the specified path. * @param maxAge The maximum age of the cookie, in seconds. If this is <code>0</code>, then the cookie will be * removed. Note that the name and path must be exactly the same as it was when the cookie was created. If this is * <code>-1</code> then the cookie will become a session cookie and thus live as long as the established HTTP * session. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @see HttpServletResponse#addCookie(Cookie) * @since 2.0 */ public static void addResponseCookie(HttpServletRequest request, HttpServletResponse response, String name, String value, String domain, String path, int maxAge) { Cookie cookie = new Cookie(name, encodeURL(value)); if (!isOneOf(domain, null, "localhost")) { // Chrome doesn't like domain:"localhost" on cookies. cookie.setDomain(domain); } if (path != null) { cookie.setPath(path); } cookie.setMaxAge(maxAge); cookie.setHttpOnly(true); cookie.setSecure(request.isSecure()); response.addCookie(cookie); } /** * Remove the cookie with given name and path from the HTTP response. Note that the name and path must be exactly * the same as it was when the cookie was created. * @param request The involved HTTP servlet request. * @param response The involved HTTP servlet response. * @param name The cookie name. * @param path The cookie path. * @see HttpServletResponse#addCookie(Cookie) * @since 2.0 */ public static void removeResponseCookie(HttpServletRequest request, HttpServletResponse response, String name, String path) { addResponseCookie(request, response, name, null, path, 0); } // ServletContext ------------------------------------------------------------------------------------------------- /** * Returns the application scope attribute value associated with the given name. * @param <T> The expected return type. * @param context The servlet context used for looking up the attribute. * @param name The application scope attribute name. * @return The application scope attribute value associated with the given name. * @throws ClassCastException When <code>T</code> is of wrong type. * @see ServletContext#getAttribute(String) */ @SuppressWarnings("unchecked") public static <T> T getApplicationAttribute(ServletContext context, String name) { return (T) context.getAttribute(name); } // JSF ------------------------------------------------------------------------------------------------------------ /** * Returns The {@link Lifecycle} associated with current Faces application. * @param context The involved servlet context. * @return The {@link Lifecycle} associated with current Faces application. * @see LifecycleFactory#getLifecycle(String) * @since 2.5 */ public static Lifecycle getFacesLifecycle(ServletContext context) { String lifecycleId = coalesce(context.getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR), LifecycleFactory.DEFAULT_LIFECYCLE); return ((LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY)).getLifecycle(lifecycleId); } /** * Returns <code>true</code> if the given HTTP servlet request is a JSF ajax request. This does exactly the same as * {@link Faces#isAjaxRequest()}, but then without the need for a {@link FacesContext}. The major advantage is that * you can perform the job inside a servlet filter, where the {@link FacesContext} is normally not available. * @param request The involved HTTP servlet request. * @return <code>true</code> if the given HTTP servlet request is a JSF ajax request. * @since 2.0 */ public static boolean isFacesAjaxRequest(HttpServletRequest request) { return FACES_AJAX_HEADERS.contains(request.getHeader("Faces-Request")); } /** * Returns <code>true</code> if the given HTTP servlet request is a JSF resource request. I.e. this request will * trigger the JSF {@link ResourceHandler} for among others CSS/JS/image resources. * @param request The involved HTTP servlet request. * @return <code>true</code> if the given HTTP servlet request is a JSF resource request. * @since 2.0 * @see ResourceHandler#RESOURCE_IDENTIFIER */ public static boolean isFacesResourceRequest(HttpServletRequest request) { return getRequestURI(request).startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/"); } /** * Returns <code>true</code> if we're in JSF development stage. This will be the case when the * <code>javax.faces.PROJECT_STAGE</code> context parameter in <code>web.xml</code> is set to * <code>Development</code>. * @param context The involved servlet context. * @return <code>true</code> if we're in development stage, otherwise <code>false</code>. * @since 2.1 * @see Application#getProjectStage() */ public static boolean isFacesDevelopment(ServletContext context) { if (facesDevelopment == null) { String projectStage = null; try { projectStage = lookup(PROJECT_STAGE_JNDI_NAME); } catch (IllegalStateException ignore) { logger.log(FINE, "Ignoring thrown exception; will only happen in buggy containers.", ignore); return false; // May happen in a.o. GlassFish 4.1 during startup. } if (projectStage == null) { projectStage = context.getInitParameter(PROJECT_STAGE_PARAM_NAME); } facesDevelopment = Development.name().equals(projectStage); } return facesDevelopment; } /** * Sends a temporary (302) JSF redirect to the given URL, supporting JSF ajax requests. This does exactly the same * as {@link Faces#redirect(String, String...)}, but without the need for a {@link FacesContext}. The major * advantage is that you can perform the job inside a servlet filter or even a plain vanilla servlet, where the * {@link FacesContext} is normally not available. This method also recognizes JSF ajax requests which requires a * special XML response in order to successfully perform the redirect. * <p> * If the given URL does <b>not</b> start with <code>http://</code>, <code>https://</code> or <code>/</code>, then * the request context path will be prepended, otherwise it will be the unmodified redirect URL. So, when * redirecting to another page in the same web application, always specify the full path from the context root on * (which in turn does not need to start with <code>/</code>). * <pre> * Servlets.facesRedirect(request, response, "some.xhtml"); * </pre> * <p> * You can use {@link String#format(String, Object...)} placeholder <code>%s</code> in the redirect URL to represent * placeholders for any request parameter values which needs to be URL-encoded. Here's a concrete example: * <pre> * Servlets.facesRedirect(request, response, "some.xhtml?foo=%s&bar=%s", foo, bar); * </pre> * @param request The involved HTTP servlet request. * @param response The involved HTTP servlet response. * @param url The URL to redirect the current response to. * @param paramValues The request parameter values which you'd like to put URL-encoded in the given URL. * @throws IOException Whenever something fails at I/O level. The caller should preferably not catch it, but just * redeclare it in the action method. The servletcontainer will handle it. * @since 2.0 */ public static void facesRedirect (HttpServletRequest request, HttpServletResponse response, String url, String ... paramValues) throws IOException { String redirectURL = prepareRedirectURL(request, url, paramValues); if (isFacesAjaxRequest(request)) { setNoCacheHeaders(response); response.setContentType("text/xml"); response.setCharacterEncoding(UTF_8.name()); response.getWriter().printf(FACES_AJAX_REDIRECT_XML, redirectURL); } else { response.sendRedirect(redirectURL); } } // Helpers -------------------------------------------------------------------------------------------------------- /** * Helper method to prepare redirect URL. Package-private so that {@link FacesLocal} can also use it. */ static String prepareRedirectURL(HttpServletRequest request, String url, String... paramValues) { String redirectURL = url; if (!startsWithOneOf(url, "http://", "https://", "/")) { redirectURL = request.getContextPath() + "/" + url; } if (isEmpty(paramValues)) { return redirectURL; } Object[] encodedParams = new Object[paramValues.length]; for (int i = 0; i < paramValues.length; i++) { encodedParams[i] = encodeURL(paramValues[i]); } return format(redirectURL, encodedParams); } }