FacesViews is a mechanism to use SEO-friendly extensionless URLs in a Faces application without the need to enlist individual Facelet source files in some configuration file.
All files found in the scanned directories are automatically mapped as Facelets files and made available using both their original extension as well as
without an extension. By default, all URLs generated by ViewHandler.getActionURL(FacesContext, String), which is used by among others
<h:form>, <h:link>, <h:button> and all extended tags, will also be extensionless. And, URLs with an
extension will be 301-redirected to the extensionless one.
Scanning is done automatically and the feature is compatible with applications that have no web.xml or faces-config.xml. As such it
can be used as an alternative to declaring the FacesServlet in web.xml for the .xhtml to .xhtml mapping, which
additionally prevents exposing the source code of those Facelets that happens with the default Faces mapping.
Usage
Zero configuration
Put Facelets source files into the /WEB-INF/faces-views directory. All Facelets files in this special directory will be automatically scanned as
extensionless URLs. Given the following file structure:
/WEB-INF/faces-views/index.xhtml /WEB-INF/faces-views/users/add.xhtml /normal.xhtml
the Facelets are available via the following URLs, given a root deployment on domain example.com:
example.com/index example.com/users/add example.com/index.xhtml (301-redirects to /index by default) example.com/users/add.xhtml (301-redirects to /users/add by default) example.com/normal.xhtml
Note that although the directory outside /WEB-INF/faces-views is not scanned, the FacesServlet is mapped on all extensions
found in /WEB-INF/faces-views, so this also affects files outside this directory. In the above example /normal.xhtml is thus also
available via the .xhtml extension. Whether the extension variant of a scanned view redirects to the extensionless one, is configurable via
FACES_VIEWS_EXTENSION_ACTION_PARAM_NAME.
Minimal configuration
Below is the minimal web.xml configuration to make all Facelets source files found in the root folder and all subdirectories of the public web
content (excluding /WEB-INF, /META-INF and /resources) available as extensionless URLs:
<context-param>
<param-name>org.omnifaces.FACES_VIEWS_SCAN_PATHS</param-name>
<param-value>/*.xhtml</param-value>
</context-param>
The path pattern /*.xhtml basically means that all files with the .xhtml extension from the directory / must be
scanned, including all sub directories. In case you want to scan only .xhtml files in the directory /foo, then use path pattern of
/foo/*.xhtml instead. In case you want to scan all files in the directory /foo, then use path pattern of
/foo. You can specify multiple values separated by a comma. Given the following file structure:
/page1.xhtml /foo/page2.xhtml /WEB-INF/resources/template.xhtml /script.js
the Facelets are available via the following URLs:
example.com/page1 example.com/foo/page2 example.com/page1.xhtml (301-redirects to /page1 by default) example.com/foo/page2.xhtml (301-redirects to /foo/page2 by default)
Note that /WEB-INF is not scanned and thus template.xhtml is not made publicly available. Likewise /script.js is not
scanned either as it doesn't have the configured extension. Finally, although a web.xml is used, there does not need to be a mapping for the
FacesServlet in it.
MultiViews configuration
A MultiViews view swallows any trailing path segments and exposes them as positional path parameters. Enabling it is a matter of suffixing the path pattern
with /*. The support was added in OmniFaces 2.5. Below is the web.xml configuration which extends the above minimal configuration
with MultiViews support:
<context-param>
<param-name>org.omnifaces.FACES_VIEWS_SCAN_PATHS</param-name>
<param-value>/*.xhtml/*</param-value>
</context-param>
On an example URL of https://example.com/context/foo/bar/baz when neither /foo/bar/baz.xhtml nor /foo/bar.xhtml exist,
but /foo.xhtml does exist, then the request will forward to /foo.xhtml and make the values bar and baz
available as injectable path parameters via @Param in the managed bean associated with /foo.xhtml.
@Inject @Param(pathIndex = 0) private String bar; @Inject @Param(pathIndex = 1) private String baz;
MultiViews can be enabled on a subset of the scanned views by suffixing only the path pattern concerned. A path pattern of /*.xhtml, /foo/* thus
scans every .xhtml file but enables MultiViews only on the files in /foo, so that example.com/foo/page2/bar forwards
to /foo/page2.xhtml while example.com/page1/bar does not resolve at all.
Dynamic routes configuration
A directory whose name is wrapped in square brackets is a dynamic route segment. It matches exactly one path segment and exposes it under the bracketed name. The support was added in OmniFaces 5.5 and needs no configuration beyond the scan path above. Given the following file structure:
/organizations/[id]/index.xhtml /organizations/[id]/members.xhtml /organizations/settings/members.xhtml
the Facelets are available via the following URLs:
example.com/organizations/123 (forwards to /organizations/[id]/index.xhtml with segment "id" being "123") example.com/organizations/123/members (forwards to /organizations/[id]/members.xhtml with segment "id" being "123") example.com/organizations/settings/members (forwards to /organizations/settings/members.xhtml without any segment)
The segment value is available as an injectable path parameter via @Param in the managed bean associated with the forwarded view.
@Inject @Param(pathName = "id") private String id;
A dynamic route directory needs no welcome file, in which case only the views inside it answer and the bare /organizations/123 does not resolve.
The first URL above thus additionally requires an extensionless welcome file.
A request path is resolved by first looking for an exact match among the scanned views, then walking the dynamic route segments, and only then falling back
to MultiViews. A literal directory is always preferred over a dynamic one at the same level, which means a literal sibling is a value the dynamic segment can
never take: in the above example an organization whose id is settings is unreachable. An application without any bracketed directory never
reaches the dynamic route resolution at all and therefore behaves exactly as before.
Dynamic route segments nest and compose with MultiViews, so /[locale]/products/[sku]/reviews.xhtml answers to
/nl/products/12345/reviews/2 with nl and 12345 available by name and 2 available as
@Param(pathIndex = 0).
Note that only a directory name is interpreted this way. A bracketed file name such as [id].xhtml is scanned literally and
logged as a warning, as square brackets are gen-delims per RFC 3986 which containers may refuse in a URL outright. For the same reason, a link to a dynamic
route is rendered with every segment substituted, which is why <o:pathParam name> must supply them, see PathParam.
Three configurations have no correct interpretation and fail the deployment:
- Two differently named dynamic route segments under the same parent, e.g.
/organizations/[id]next to/organizations/[slug], as there is no defensible way to choose between them. - The same segment name twice in one path, e.g.
/[id]/foo/[id]/bar, as a path parameter is addressed by name and could then not address both. - An unbalanced or empty square bracket pair in a directory name, e.g.
[idor[].
Welcome files
If a <welcome-file> is defined in web.xml that's scanned by FacesViews and the default
REDIRECT_TO_EXTENSIONLESS extension action is used, then it's necessary to define an extensionless welcome file to prevent a request to
/ being redirected to /[welcome file]. E.g. without this https://example.com will redirect to
https://example.com/index.
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
This is also what makes the bare URL of a directory answer to the welcome file inside it, which includes a dynamic route directory, as in
/organizations/[id]/index.xhtml answering to /organizations/123.
If you're using MultiViews on a site-wide basis and have a welcome file configured for it, then basically any request which doesn't match any physical file
will end up in that welcome file. In case this is undesirable, because you're having e.g. a REST API listening on /api/* or a websocket endpoint
listening on /push/*, then you can configure them as an exclude pattern as below:
<context-param>
<param-name>org.omnifaces.FACES_VIEWS_SCAN_PATHS</param-name>
<param-value>/*.xhtml/*, !/api, !/push</param-value>
</context-param>
Dispatch methods
Faces normally inspects the request URI to derive a logical view id from it. It assumes the FacesServlet is either mapped on a prefix path or an
extension, and will get confused when an extensionless "exactly mapped" request is encountered. To counter this, FacesViews makes use of
FacesViewsForwardingFilter which intercepts each request and makes it appear to Faces that the request was a normal extension mapped one.
Two dispatch methods are used to do this: forwarding, and wrapping the request and continuing the filter chain. For the latter the FacesServlet is
programmatically mapped to every individual scanned view. A dynamic route is not expressible as a servlet URL pattern, as the specification has only exact,
prefix, extension and default mappings, and is therefore always resolved by forwarding.
These internal details are important to be aware of, since they greatly influence how extensionless requests interact with other filter based functionality
such as security filters, compression filters, file upload filters, etcetera. With the forwarding method, such filters typically have to be set to dispatch
type FORWARD as well. The FacesViews filter is by default the first in the chain, so other filters which are set to dispatch type
REQUEST are then not invoked at all, as the chain is ended. Set FACES_VIEWS_FILTER_AFTER_DECLARED_FILTERS_PARAM_NAME to place it after
the filters declared in web.xml, in which case those are invoked, but they should not modify the response, as a forward clears the response
buffer so far when it is not yet committed.
Configuration
The following context parameters are available.
"org.omnifaces.FACES_VIEWS_ENABLED" |
Used to completely switch scanning off. Allowed values: { true,false} Default value: true (note that if no /WEB-INF/faces-views directory is present and no explicit paths have been configured, no scanning will be done either) |
"org.omnifaces.FACES_VIEWS_SCAN_PATHS" |
A comma separated list of paths that are to be scanned in addition to /WEB-INF/faces-views. Allowed values: any path relative to the web root, including the root path ( /) and /WEB-INF. A wildcard can be added to the path,
which will cause only files with the given extension te be scanned. Examples: - Scan all files in both folder1 and folder2: /folder1, /folder2 - Scan only .xhtml files in the root: /*.xhtml Note that when the root path is given, all its sub paths are also scanned EXCEPT WEB-INF, META-INF and resources. If
those have to be scanned as well, they can be added to the list of paths explicitly. Default value: /WEB-INF/faces-views (note when this value is set, those paths will be in addition to the default
/WEB-INF/faces-views) |
"org.omnifaces.FACES_VIEWS_SCANNED_VIEWS_ALWAYS_EXTENSIONLESS" |
Used to set how scanned views should be rendered in Faces controlled links. With this setting set to false, it depends on whether the
request URI uses an extension or not. If it doesn't, links are also rendered without one, otherwise they are rendered with an extension. When set to
true links are always rendered without an extension. Default value: true |
"org.omnifaces.FACES_VIEWS_EXTENSION_ACTION" |
Determines the action that is performed whenever a resource is requested WITH extension that's also available without an extension. Allowed values are enumerated in ExtensionAction, which have the following meaning: - REDIRECT_TO_EXTENSIONLESS: Send a 301 (permanent) redirect to the same URL, but with the extension removed. E.g. /foo.xhtml
redirects to /foo. - SEND_404: Send a 404 (not found), makes it look like e.g. /foo.xhtml never existed and there's only /foo. - PROCEED: No special action is taken. Both /foo.xhtml and /foo are processed as-if they were separate views (with
same content). Default value: REDIRECT_TO_EXTENSIONLESS |
"org.omnifaces.FACES_VIEWS_PATH_ACTION" |
Determines the action that is performed whenever a resource is requested in a public path that has been used for scanning views by faces views (e.g. the
paths set by "org.omnifaces.FACES_VIEWS_SCAN_PATHS", but excluding the root path /). Allowed values are enumerated in PathAction, which have the following meaning: - SEND_404: Send a 404 (not found), makes it look like e.g. /path/foo.xhtml never existed and there's only /foo and
optionally /foo.xhtml. - REDIRECT_TO_SCANNED_EXTENSIONLESS: Send a 301 (permanent) redirect to the resource corresponding with the one that was scanned. E.g.
/path/foo.xml redirects to /foo. - PROCEED: No special action is taken. /path/foo.xml and /foo (and optionally /foo.xhtml) will be
accessible. Default value: SEND_404 |
"org.omnifaces.FACES_VIEWS_FILTER_AFTER_DECLARED_FILTERS" |
Used to set whether the FacesViewsForwardingFilter should match before declared filters (false) or after declared filters
(true). Default value: false (the FacesViews forwarding filter is the first in the filter chain) |
"org.omnifaces.FACES_VIEWS_LOWERCASED_REQUEST_URI" |
Used to set whether the request URI should only match the lowercased form of the file name. By default, a scanned view of for example
/TitleCasedFileName.xhtml will listen to a request URI of /TitleCasedFileName, but when this setting is set to true, then it will
instead listen to a lowercased request URI of /titlecasedfilename. Default value: false (the request URI must exactly match the letter case of the file name) |
PrettyFaces
Note that there is some overlap between this feature and PrettyFaces. The difference is that FacesViews has a
focus on zero- or very minimal config, where PrettyFaces has a focus on very powerful mapping mechanisms, which of course need some level of configuration.
As such FacesViews will only focus on auto discovering views and mapping them to both .xhtml and to no-extension without needing to explicitly
declare the FacesServlet in web.xml.
Specifically, FacesViews will thus not become a general URL rewriting tool (e.g. one that maps path segments to parameters, or that totally changes the name of the URL). For this the user is advised to look at the aforementioned PrettyFaces.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe name of the request attribute under which the resolved dynamic route segment values are stored, as an unmodifiable map of segment name to segment value.static final StringThe name of the boolean context parameter to switch auto-scanning completely off for Servlet 3.0 containers.static final StringThe name of the enum context parameter that determines the action that is performed whenever a resource is requested WITH extension that's also available without an extension.static final StringThe name of the boolean context parameter via which the user can set whether theFacesViewsForwardingFiltershould match before declared filters (false) or after declared filters (true).static final StringThe name of the boolean context parameter via which the user can set whether the request URI should only match the lowercased form of the file name.static final StringThe name of the request attribute under which the original request path info is stored.static final StringThe name of the request attribute under which the original request servlet path is stored.static final StringThe name of the enum context parameter that determines the action that is performed whenever a resource is requested in a public path that has been used for scanning views by faces views.static final StringThe name of the commaseparated context parameter where the value holds a comma separated list of paths that are to be scanned by faces views.static final StringThe name of the boolean context parameter via which the user can set scanned views to be always rendered extensionless.static final StringA special dedicated "well-known" directory where facelets implementing views can be placed. -
Method Summary
Modifier and TypeMethodDescriptionstatic voidaddFacesServletMappings(ServletContext servletContext) This will map theFacesServletto extensions found during scanning inApplicationInitializer.static booleanisFacesViewsEnabled(ServletContext servletContext) Returns whether FacesViews feature is enabled.static booleanisMultiViewsEnabled(HttpServletRequest request) Returns whether MultiViews feature is enabled on given request.static booleanisMultiViewsEnabled(ServletContext servletContext) Returns whether MultiViews feature is enabled.static booleanisMultiViewsEnabled(ServletContext servletContext, String resource) Returns whether MultiViews feature is enabled on the given resource.static voidregisterForwardingFilter(ServletContext servletContext) This will register theFacesViewsForwardingFilter.static voidregisterViewHandler(ServletContext servletContext, Application application) Register a view handler that transforms a view id with extension back to an extensionless one.static StringstripFacesViewsPrefix(String resource) Strips any special '/WEB-INF/faces-views' prefix path from the given resource.static StringstripWelcomeFilePrefix(ServletContext servletContext, String resource) Strips any mapped welcome file prefix path from the given resource.
-
Field Details
-
WEB_INF_VIEWS
A special dedicated "well-known" directory where facelets implementing views can be placed. This directory is scanned by convention so that no explicit configuration is needed.- See Also:
-
FACES_VIEWS_ENABLED_PARAM_NAME
The name of the boolean context parameter to switch auto-scanning completely off for Servlet 3.0 containers.- See Also:
-
FACES_VIEWS_SCAN_PATHS_PARAM_NAME
The name of the commaseparated context parameter where the value holds a comma separated list of paths that are to be scanned by faces views.- See Also:
-
FACES_VIEWS_SCANNED_VIEWS_EXTENSIONLESS_PARAM_NAME
The name of the boolean context parameter via which the user can set scanned views to be always rendered extensionless. Without this setting (or it being set to false), it depends on whether the request URI uses an extension or not. If it doesn't, links are also rendered without one, otherwise are rendered with an extension.- See Also:
-
FACES_VIEWS_EXTENSION_ACTION_PARAM_NAME
The name of the enum context parameter that determines the action that is performed whenever a resource is requested WITH extension that's also available without an extension. SeeExtensionAction- See Also:
-
FACES_VIEWS_PATH_ACTION_PARAM_NAME
The name of the enum context parameter that determines the action that is performed whenever a resource is requested in a public path that has been used for scanning views by faces views. SeePathAction- See Also:
-
FACES_VIEWS_FILTER_AFTER_DECLARED_FILTERS_PARAM_NAME
The name of the boolean context parameter via which the user can set whether theFacesViewsForwardingFiltershould match before declared filters (false) or after declared filters (true).- See Also:
-
FACES_VIEWS_LOWERCASED_REQUEST_URI_PARAM_NAME
The name of the boolean context parameter via which the user can set whether the request URI should only match the lowercased form of the file name. By default, a scanned view of for example/TitleCasedFileName.xhtmlwill listen to a request URI of/TitleCasedFileName, but when this setting is set to true, then it will instead listen to a lowercased request URI of/titlecasedfilename.- Since:
- 3.14
- See Also:
-
FACES_VIEWS_ORIGINAL_SERVLET_PATH
The name of the request attribute under which the original request servlet path is stored.- See Also:
-
FACES_VIEWS_ORIGINAL_PATH_INFO
The name of the request attribute under which the original request path info is stored.- See Also:
-
FACES_VIEWS_DYNAMIC_ROUTE_PARAMS
The name of the request attribute under which the resolved dynamic route segment values are stored, as an unmodifiable map of segment name to segment value. It is only present on a request which resolved to a dynamic route.- Since:
- 5.5
- See Also:
-
-
Method Details
-
registerForwardingFilter
This will register theFacesViewsForwardingFilter. This is invoked byApplicationInitializer.- Parameters:
servletContext- The involved servlet context.
-
addFacesServletMappings
This will map theFacesServletto extensions found during scanning inApplicationInitializer. This is invoked byApplicationListener, because theFacesServlethas to be available.- Parameters:
servletContext- The involved servlet context.
-
registerViewHandler
Register a view handler that transforms a view id with extension back to an extensionless one. This is invoked byApplicationProcessor, because theApplicationhas to be available.- Parameters:
servletContext- The involved servlet context.application- The involved faces application.
-
isFacesViewsEnabled
Returns whether FacesViews feature is enabled. That is, when theorg.omnifaces.FACES_VIEWS_ENABLEDcontext parameter value does not equalfalse.- Parameters:
servletContext- The involved servlet context.- Returns:
- Whether FacesViews feature is enabled.
- Since:
- 2.5
-
isMultiViewsEnabled
Returns whether MultiViews feature is enabled. This is implicitly enabled whenorg.omnifaces.FACES_VIEWS_SCAN_PATHScontext parameter value is suffixed with/*.- Parameters:
servletContext- The involved servlet context.- Returns:
- Whether MultiViews feature is enabled.
- Since:
- 2.5
-
isMultiViewsEnabled
Returns whether MultiViews feature is enabled on given request.- Parameters:
request- The involved HTTP servlet request.- Returns:
- Whether MultiViews feature is enabled on given request.
- Since:
- 2.6
-
isMultiViewsEnabled
Returns whether MultiViews feature is enabled on the given resource.- Parameters:
servletContext- The involved servlet context.resource- The resource.- Returns:
- Whether MultiViews feature is enabled on the given resource.
- Since:
- 3.6
-
stripWelcomeFilePrefix
Strips any mapped welcome file prefix path from the given resource.- Parameters:
servletContext- The involved servlet context.resource- The resource.- Returns:
- The resource without the welcome file prefix path, or as-is if it didn't start with this prefix.
- Since:
- 2.5
-
stripFacesViewsPrefix
Strips any special '/WEB-INF/faces-views' prefix path from the given resource.- Parameters:
resource- The resource.- Returns:
- The resource without the special prefix path, or as-is if it didn't start with this prefix.
-