jakarta.faces.application.ResourceHandler Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.faces.application;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import jakarta.faces.context.FacesContext;
/**
*
* ResourceHandler is the
* run-time API by which {@link jakarta.faces.component.UIComponent} and {@link jakarta.faces.render.Renderer}
* instances, and the {@link jakarta.faces.view.ViewDeclarationLanguage} can reference
* {@link Resource} instances. An implementation of this class must be thread-safe.
*
*
*
*
*
* Packaging Resources
*
*
*
*
*
* ResourceHandler defines a path based packaging convention for resources. The default implementation of
* ResourceHandler
must support packaging resources in the classpath or in the web application root. See
* section 2.6.1 of the spec prose document linked in the
* overview summary for the normative specification of packaging resources.
*
*
*
* Briefly, The default implementation must support packaging resources in the web application root under the path
*
*
*
* resources/<resourceIdentifier>
*
*
*
* relative to the web app root. "resources" is the default location, but this location
* can be changed by the value of the {@link #WEBAPP_RESOURCES_DIRECTORY_PARAM_NAME}
* <context-param>
.
*
*
*
* For the default implementation, resources packaged in the classpath must reside under the JAR entry name
*
*
*
* META-INF/resources/<resourceIdentifier>
*
*
*
*
*
* In the case of Faces Flows packaged within jar files, resources packaged in the classpath must reside under the jar
* entry name
*
*
*
* META-INF/flows/<resourceIdentifier>
*
*
*
*
*
* <resourceIdentifier>
consists of several segments, specified as follows.
*
*
*
* [localePrefix/][libraryName/][libraryVersion/]resourceName[/resourceVersion]
*
*
*
* None of the segments in the resourceIdentifier may be relative paths, such as ‘../otherLibraryName’. The
* implementation is not required to support the libraryVersion
and resourceVersion
segments
* for the JAR packaging case.
*
*
*
* Note that resourceName is the only required segment.
*
*
*
*
*
* Encoding Resources
*
*
*
*
*
* During the handling of view requests, the Jakarta Server Face run-time may be called upon to encode a resource in
* such a way as to instruct the user-agent to make a subsequent resource request. This behavior is orchestrated by one
* of the resource renderers (ScriptRenderer
, StylesheetRenderer
, ImageRenderer
),
* which all call {@link Resource#getRequestPath} to obtain the encoded URI for the resource. See
* {@link Resource#getRequestPath} and the Standard HTML RenderKit specification for the complete specification.
*
*
*
* This usage of resources does not apply for resources that correspond to VDL resources.
*
*
*
*
*
* Decoding Resources
*
*
*
*
*
* During the handling of resource requests, the Jakarta Server Faces run-time will be called upon to decode a resource
* in such a way as to serve up the bytes of the resource to the user-agent. This behavior is orchestrated by
* {@link #handleResourceRequest}, which calls {@link Resource#getInputStream} to obtain bytes of the resource. See
* {@link #handleResourceRequest} for the complete specification.
*
*
*
* This usage of resources does not apply for resources that correspond to VDL resources.
*
*
*
*
*
*
* @since 2.0
*/
public abstract class ResourceHandler {
/**
*
* {@link Resource#getRequestPath} returns the value of this constant as the prefix of the URI.
* {@link #handleResourceRequest(jakarta.faces.context.FacesContext)} looks for the value of this constant within the
* request URI to determine if the request is a resource request or a view request.
*
*/
public static final String RESOURCE_IDENTIFIER = "/jakarta.faces.resource";
/**
*
* Resource name of Jakarta Server Faces script resource.
*
*
* @since 2.3
*/
public static final String JSF_SCRIPT_RESOURCE_NAME = "jsf.js";
/**
*
* Library name of Jakarta Server Faces script resource.
*
*
* @since 2.3
*/
public static final String JSF_SCRIPT_LIBRARY_NAME = "jakarta.faces";
/**
*
* This file must be located in META-INF/contracts/<contractName>/
in a jar file that contains a
* resource library contract, where <contractName>
is the name of the contract. If the jar file
* contains multiple contracts, the marker file must be present in each one. See “constant field values” for
* the name of the file that must be placed at that location.
*
*
* @since 2.2
*/
public static final String RESOURCE_CONTRACT_XML = "jakarta.faces.contract.xml";
/**
*
* If a <context-param>
with the param name equal to the value of
* {@link #WEBAPP_RESOURCES_DIRECTORY_PARAM_NAME} exists, the runtime must interpret its value as a path, relative to
* the web app root, where resources are to be located. This param value must not start with a "/", though it may
* contain "/" characters. If no such <context-param>
exists, or its value is invalid, the value
* "resources", without the quotes, must be used by the runtime as the value.
*
*
* @since 2.2
*/
public static final String WEBAPP_RESOURCES_DIRECTORY_PARAM_NAME = "jakarta.faces.WEBAPP_RESOURCES_DIRECTORY";
/**
*
* If a <context-param>
with the param name equal to the value of
* {@link #WEBAPP_CONTRACTS_DIRECTORY_PARAM_NAME} exists, the runtime must interpret its value as a path, relative to
* the web app root, where resource library contracts are to be located. This param value must not start with a "/",
* though it may contain "/" characters. If no such <context-param>
exists, or its value is invalid,
* the value "contracts", without the quotes, must be used by the runtime as the value.
*
*
* @since 2.2
*/
public static final String WEBAPP_CONTRACTS_DIRECTORY_PARAM_NAME = "jakarta.faces.WEBAPP_CONTRACTS_DIRECTORY";
/**
*
* The name of a key within the application message bundle named by the return from {@link Application#getMessageBundle}
* whose value is the locale prefix used to find a packaged resource to return from {@link #createResource} (or one of
* its variants).
*/
public static final String LOCALE_PREFIX = "jakarta.faces.resource.localePrefix";
/**
*
* The ServletContext
init parameter consulted by the {@link #handleResourceRequest} to tell which kinds of
* resources must never be served up in response to a resource request. The value of this parameter is a single space
* separated list of file extensions, including the leading '.' character (without the quotes). If not specified, the
* default value given in the value of the {@link #RESOURCE_EXCLUDES_DEFAULT_VALUE} constant is used. If manually
* specified, the given value entirely overrides the default one and does not supplement it.
*
*/
public static final String RESOURCE_EXCLUDES_PARAM_NAME = "jakarta.faces.RESOURCE_EXCLUDES";
/**
*
* The default value for the {@link #RESOURCE_EXCLUDES_PARAM_NAME} init param.
*
*/
public static final String RESOURCE_EXCLUDES_DEFAULT_VALUE = ".class .jsp .jspx .properties .xhtml .groovy";
// ---------------------------------------------------------- Public Methods
/**
*
* Create an instance of ViewResource
given the argument
* resourceName
. The content-type of the resource is derived by passing the resourceName to
* {@link jakarta.faces.context.ExternalContext#getMimeType}
*
*
*
*
*
* The algorithm specified in section 2.6.1.4 of the spec prose document
* linked in the overview summary must be executed to create
* the Resource
. New requirements were introduced in version 2.2 of the
* specification. For historical reasons, this method operate correctly when the argument {@code resourceName} is of the
* form {@code libraryName/resourceName}, even when {@code resourceName} contains '/' characters.
*
*
*
*
* @param resourceName the name of the resource.
*
* @throws NullPointerException if resourceName
is null
.
*
* @return a newly created Resource
instance, suitable for use in encoding or decoding the named resource.
*/
public abstract Resource createResource(String resourceName);
/**
*
* Create an instance of Resource
given the argument resourceName
, which may contain "/"
* characters. The {@link jakarta.faces.view.ViewDeclarationLanguage} calls this method when it needs to load a view
* from a persistent store, such as a filesystem. This method is functionality equivalent to
* {@link #createResource(java.lang.String)}, but all callsites that need to load VDL views must use this method so that
* classes that want to decorate the ResourceHandler
in order to only affect the loading of views may do so
* without affecting the processing of other kinds of resources, such as scripts and stylesheets. A
* {@link jakarta.faces.context.FacesContext} must be present before calling this method. To preserve compatibility with
* prior revisions of the specification, a default implementation must be provided that calls
* {@link #createResource(java.lang.String)}.
*
*
*
*
*
* The default implementation must look for the resource in the following places, in this order.
*
*
*
*
* -
*
* Considering resource library contracts (at the locations specified in the spec prose document section Resource
* Library Contracts in the Request Processing Lifecycle chapter).
*
*
*
* -
*
* Considering the web app root.
*
*
*
* -
*
* Considering faces flows (at the locations specified in the spec prose document section Faces Flows in the
* Using Jakarta Server Faces in Web Applications chapter).
*
*
*
*
*
*
* Call {@link FacesContext#getResourceLibraryContracts}. If the result is non-{@code null} and not empty, for each
* value in the list, treat the value as the name of a resource library contract. If the argument {@code resoureName}
* exists as a resource in the resource library contract, return it. Otherwise, return the resource (not in the resource
* library contract), if found. Otherwise, return {@code null}.
*
*
*
* @param context the {@link FacesContext} for this request.
* @param resourceName the name of the resource to be interpreted as a view by the
* {@link jakarta.faces.view.ViewDeclarationLanguage}.
*
* @throws NullPointerException if resourceName
is {@code null}.
*
* @return a newly created {@link ViewResource} instance, suitable for use by the
* {@link jakarta.faces.view.ViewDeclarationLanguage}.
*
* @since 2.2
*/
public ViewResource createViewResource(FacesContext context, String resourceName) {
return context.getApplication().getResourceHandler().createResource(resourceName);
}
/**
*
* Return a {@code Stream} possibly lazily populated by walking the resource tree rooted at a given initial path. The
* resource tree is traversed breadth-first, the elements in the stream are view resource names that would
* yield a {@code ViewResource} when passed into {@link ResourceHandler#createViewResource} as the {@code resourceName}
* parameter.
*
*
*
* The {@code maxDepth} parameter is the maximum depth of directory levels to visit beyond the initial path,
* which is always visited. The value is relative to the root ({@code /}), not to the given initial path. E.g. given
* {@code maxDepth} = {@code 3} and initial path {@code /foo/}, visiting will proceed up to {@code /foo/bar/}, where
* {@code /} counts as depth {@code 1}, {@code /foo/} as depth {@code 2} and {@code /foo/bar/} as depth {@code 3}. A
* value lower or equal to the depth of the initial path means that only the initial path is visited. A value of
* {@link Integer#MAX_VALUE MAX_VALUE} may be used to indicate that all levels should be visited.
*
* @param facesContext The {@link FacesContext} for this request.
* @param path The initial path from which to start looking for view resources
* @param maxDepth The absolute maximum depth of nested directories to visit counted from the root ({@code /}).
* @param options The options to influence the traversal. See {@link ResourceVisitOption} for details on those.
*
* @return the {@link Stream} of view resource names
*
* @since 2.3
*/
public Stream getViewResources(FacesContext facesContext, String path, int maxDepth, ResourceVisitOption... options) {
return Stream.empty();
}
/**
*
* Return a {@code Stream} possibly lazily populated by walking the resource tree rooted at a given initial path. The
* resource tree is traversed breadth-first, the elements in the stream are view resource names that would
* yield a {@code ViewResource} when passed into {@link ResourceHandler#createViewResource} as the {@code resourceName}
* parameter.
*
*
*
* This method works as if invoking it were equivalent to evaluating the expression:
*
*
* getViewResources(facesContext, start, Integer.MAX_VALUE, options)
*
*
*
Put differently, it visits all levels of the resource tree.
*
* @param facesContext The {@link FacesContext} for this request.
* @param path The initial path from which to start looking for view resources
* @param options The options to influence the traversal. See {@link ResourceVisitOption} for details on those.
*
* @return the {@link Stream} of view resource names
*
* @since 2.3
*/
public Stream getViewResources(FacesContext facesContext, String path, ResourceVisitOption... options) {
return Stream.empty();
}
/**
*
* Create an instance of Resource
given the argument resourceId
. The content-type of the
* resource is derived by passing the resourceName to {@link jakarta.faces.context.ExternalContext#getMimeType}
*
*
*
*
*
* The resource must be identified according to the specification in 2.6.1.3 of the spec prose document
* linked in the overview summary. New requirements were
* introduced in version 2.2 of the specification.
*
*
*
*
* @param resourceId the resource identifier of the resource.
*
* @throws NullPointerException if resourceId
is null
.
*
* @return a newly created Resource
instance, suitable for use in encoding or decoding the named resource.
*
* @since 2.2
*/
public Resource createResourceFromId(String resourceId) {
return null;
}
/**
*
* Create an instance of Resource
with a resourceName given by
* the value of the argument resourceName
that is a member of the library named by the argument
* libraryName
. The content-type of the resource is derived by passing the resourceName to
* {@link jakarta.faces.context.ExternalContext#getMimeType}.
*
*
*
*
*
* The algorithm specified in section 2.6.1.4 of the spec prose document
* linked in the overview summary must be executed to create
* the Resource
. New requirements were introduced in version 2.2 of the
* specification.
*
*
*
*
* @param resourceName the name of the resource.
*
* @param libraryOrContractName the name of the library (or contract) in which this
* resource resides, may be null
. If there is a conflict between the name of a resource library and a
* resource library contract, the resource library takes precedence. May not
* include relative paths, such as "../".
*
* @throws NullPointerException if resourceName
is null
*
* @return a newly created Resource
instance, suitable for use in encoding or decoding the named resource.
*/
public abstract Resource createResource(String resourceName, String libraryOrContractName);
/**
*
* Create an instance of Resource
with a resourceName
* given by the value of the argument resourceName
that is a member of the library named by the argument
* libraryName
that claims to have the content-type given by the argument content-type
.
*
*
*
*
*
* The algorithm specified in section 2.6.1.4 of the spec prose document
* linked in the overview summary must be executed to create
* the Resource
. New requirements were introduced in version 2.2 of the
* specification.
*
*
*
*
* @param resourceName the name of the resource.
*
* @param libraryName the name of the library in which this resource resides, may be null
.
* May not include relative paths, such as "../".
*
* @param contentType the mime content that this Resource
instance will return from
* {@link Resource#getContentType}. If the value is null
, The content-type of the resource is derived by
* passing the resourceName to {@link jakarta.faces.context.ExternalContext#getMimeType}
*
* @throws NullPointerException if resourceName
is null
.
*
* @return a newly created Resource
instance, suitable for use in encoding or decoding the named resource.
*/
public abstract Resource createResource(String resourceName, String libraryName, String contentType);
/**
*
* Return true
if the resource library named by the argument
* libraryName
can be found. If there is a localePrefix
for
* this application, as defined in {@link #LOCALE_PREFIX}, first look for the library with the prefix. If no such
* library is found, look for the library without the prefix. This allows developers to avoid duplication of files. For
* example, consider the case where the developer wants to have a resource library containing a localized image resource
* and a non-localized script resource. By checking both locations for the existence of the library, along with other
* spec changes in section 2.6.1.4, this scenario is enabled.
*
*
* @param libraryName the library name.
* @return true
if the library exists, false
otherwise.
* @since 2.0
*
*/
public abstract boolean libraryExists(String libraryName);
/**
*
* This method specifies the contract for satisfying resource requests. This method is called from
* {@link jakarta.faces.webapp.FacesServlet#service} after that method determines the current request is a resource
* request by calling {@link #isResourceRequest}. Thus, handleResourceRequest
may assume that the current
* request is a resource request.
*
*
*
*
*
* The default implementation must implement an algorithm semantically identical to the following algorithm.
*
*
* For discussion, in all cases when a status code is to be set, this spec talks only using the Jakarta Servlet API, but
* it is understood that in a portlet environment the appropriate equivalent API must be used.
*
*
*
* -
*
* If the resourceIdentifier ends with any of the extensions listed in the value of the
* {@link #RESOURCE_EXCLUDES_PARAM_NAME} init parameter, HttpServletRequest.SC_NOT_FOUND
must be passed to
* HttpServletResponse.setStatus()
, then handleResourceRequest
must immediately return.
*
*
*
* -
*
* Extract the resourceName from the resourceIdentifier by taking the substring of
* resourceIdentifier that starts at {@link
* #RESOURCE_IDENTIFIER}.length() + 1
and goes to the end of resourceIdentifier. If no
* resourceName can be extracted, HttpServletRequest.SC_NOT_FOUND
must be passed to
* HttpServletResponse.setStatus()
, then handleResourceRequest
must immediately return.
*
*
*
* -
*
* Extract the libraryName from the request by looking in the request parameter map for an entry under the key
* "ln", without the quotes. If found, use its value as the libraryName.
*
*
*
* -
*
* If resourceName and libraryName are present, call {@link #createResource(String, String)} to create
* the Resource
. If only resourceName is present, call {@link #createResource(String)} to create
* the Resource
. If the Resource
cannot be successfully created,
* HttpServletRequest.SC_NOT_FOUND
must be passed to HttpServletResponse.setStatus()
, then
* handleResourceRequest
must immediately return.
*
*
*
* -
*
* Call {@link Resource#userAgentNeedsUpdate}. If this method returns false,
* HttpServletRequest.SC_NOT_MODIFIED
must be passed to HttpServletResponse.setStatus()
, then
* handleResourceRequest
must immediately return.
*
*
*
* -
*
* Pass the result of {@link Resource#getContentType} to HttpServletResponse.setContentType.
*
*
*
* -
*
* Call {@link Resource#getResponseHeaders}. For each entry in this Map
, call
* HttpServletResponse.setHeader()
, passing the key as the first argument and the value as the second
* argument.
*
*
*
* -
*
* Call {@link Resource#getInputStream} and serve up the bytes of the resource to the response.
*
*
*
* -
*
* Call HttpServletResponse.setContentLength()
passing the byte count of the resource.
*
*
*
* -
*
* If an IOException
is thrown during any of the previous steps, log a descriptive, localized message,
* including the resourceName and libraryName (if present). Then,
* HttpServletRequest.SC_NOT_FOUND
must be passed to HttpServletResponse.setStatus()
, then
* handleResourceRequest
must immediately return.
*
*
*
* -
*
* In all cases in this method, any streams, channels, sockets, or any other IO resources must be closed before this
* method returns.
*
*
*
*
*
*
*
* @param context the {@link jakarta.faces.context.FacesContext} for this request
* @throws IOException when an I/O error occurs.
*/
public abstract void handleResourceRequest(FacesContext context) throws IOException;
/**
*
* Return true
if the current request is a resource request. This method is called by
* {@link jakarta.faces.webapp.FacesServlet#service} to determine if this request is a view request or a
* resource request.
*
*
* @param context the {@link jakarta.faces.context.FacesContext} for this request
* @return true
if the current request is a resource request, false
otherwise.
*/
public abstract boolean isResourceRequest(FacesContext context);
/**
*
* Return {@code true} if the argument {@code url} contains the string given by the value of the constant
* {@link ResourceHandler#RESOURCE_IDENTIFIER}, false otherwise.
*
*
* @param url the url to inspect for the presence of {@link ResourceHandler#RESOURCE_IDENTIFIER}.
* @return true
if this is a resource URL, false
otherwise.
* @throws NullPointerException if the argument url is {@code null}.
*/
public boolean isResourceURL(String url) {
if (url == null) {
throw new NullPointerException("null url");
}
return url.contains(RESOURCE_IDENTIFIER);
}
/**
*
* Return the renderer-type
for a {@link jakarta.faces.render.Renderer} that is capable of rendering this
* resource. The default implementation must return values according to the following table. If no
* renderer-type
can be determined, null
must be returned.
*
*
*
* resource name to renderer-type mapping
*
*
*
* example resource name
*
* renderer-type
*
*
*
*
*
* mycomponent.js
*
* jakarta.faces.resource.Script
*
*
*
*
*
* mystyle.css
*
* jakarta.faces.resource.Stylesheet
*
*
*
*
*
* @param resourceName the resource name.
* @return the renderer type.
*/
public abstract String getRendererTypeForResourceName(String resourceName);
/**
*
* Mark the resource as identified by given resource and library name as rendered. The default implementation must
* ensure that {@link #isResourceRendered(FacesContext, String, String)} will return true
when the resource
* has already been rendered during the render response phase of the current view.
*
*
* @param context The {@link FacesContext} for this request.
* @param resourceName The name of the resource.
* @param libraryName The name of the library in which the resource resides, may be null
.
* @since 2.3
*/
@SuppressWarnings("unchecked")
public void markResourceRendered(FacesContext context, String resourceName, String libraryName) {
String resourceIdentifier = libraryName + ":" + resourceName;
Set resourceIdentifiers = (Set) context.getAttributes().computeIfAbsent(RESOURCE_IDENTIFIER, k -> new HashSet<>());
resourceIdentifiers.add(resourceIdentifier);
}
/**
*
* Returns whether the resource as identified by given resource and library name has been rendered. The default
* implementation must during the render response phase of the current view return true
when the resource
* has been marked as rendered via {@link #markResourceRendered(FacesContext, String, String)}.
*
*
* @param context The {@link FacesContext} for this request.
* @param resourceName The name of the resource.
* @param libraryName The name of the library in which this resource resides, may be null
.
* @return Whether the resource as identified by given resource and library name has been rendered.
* @since 2.3
*/
@SuppressWarnings("unchecked")
public boolean isResourceRendered(FacesContext context, String resourceName, String libraryName) {
String resourceIdentifier = libraryName + ":" + resourceName;
Set resourceIdentifiers = (Set) context.getAttributes().get(RESOURCE_IDENTIFIER);
return resourceIdentifiers != null && resourceIdentifiers.contains(resourceIdentifier);
}
}