Permalink
Browse files

Fix multiviews fail when configured on subfolder without welcome file

  • Loading branch information...
BalusC committed Nov 18, 2016
1 parent f261525 commit 82ba9720de8c5416cbf635952816b141a3523bc4
@@ -37,7 +37,6 @@
import static org.omnifaces.util.ResourcePaths.stripPrefixPath;
import static org.omnifaces.util.Servlets.getApplicationAttribute;
import static org.omnifaces.util.Servlets.getRequestBaseURL;
import static org.omnifaces.util.Servlets.isFacesDevelopment;
import static org.omnifaces.util.Utils.csvToList;
import static org.omnifaces.util.Utils.isEmpty;
import static org.omnifaces.util.Utils.reverse;
@@ -200,7 +199,9 @@
* The name of the enum context parameter that determines the method used by FacesViews to invoke the FacesServlet.
* See {@link FacesServletDispatchMethod}.
* @see FacesServletDispatchMethod
* @deprecated This will be determined automatically.
*/
@Deprecated
public static final String FACES_VIEWS_DISPATCH_METHOD_PARAM_NAME = "org.omnifaces.FACES_VIEWS_DISPATCH_METHOD";
/**
@@ -268,7 +269,6 @@ public static void registerForwardingFilter(ServletContext servletContext) {
Map<String, String> collectedViews = scanAndStoreViews(servletContext, true);
if (!collectedViews.isEmpty()) {
FacesServletDispatchMethod dispatchMethod = getFacesServletDispatchMethod(servletContext);
boolean filterAfterDeclaredFilters = parseBoolean(servletContext.getInitParameter(FACES_VIEWS_FILTER_AFTER_DECLARED_FILTERS_PARAM_NAME));
// Register a Filter that forwards extensionless requests to an extension mapped request, e.g. /index to /index.xhtml
@@ -279,33 +279,22 @@ public static void registerForwardingFilter(ServletContext servletContext) {
// TODO: Migrate ResourceResolver to ResourceHandler.
servletContext.setInitParameter(FACELETS_RESOURCE_RESOLVER_PARAM_NAME, FacesViewsResolver.class.getName());
if (isFacesDevelopment(servletContext) && dispatchMethod != DO_FILTER) {
boolean multiViewsWelcomeFiles = isMultiViewsEnabled(servletContext) && !getMappedWelcomeFiles(servletContext).isEmpty();
// In development mode map this Filter to "/*", so we can catch requests to extensionless resources that have been dynamically added.
// Note that resources with mapped extensions are already handled by the FacesViewsResolver.
// Adding resources with new extensions still requires a restart.
// Development mode only works when the dispatch mode is not DO_FILTER,
// since DO_FILTER mode depends on the Faces Servlet being "exact"-mapped on the view resources.
filterRegistration.addMappingForUrlPatterns(null, filterAfterDeclaredFilters, "/*");
if (multiViewsWelcomeFiles) {
// When MultiViews is enabled and there are mapped welcome files, we need to filter on /* otherwise path params won't work on root.
filterRegistration.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD), filterAfterDeclaredFilters, "/*");
}
else {
if (isMultiViewsEnabled(servletContext) && !getMappedWelcomeFiles(servletContext).isEmpty()) {
// When MultiViews is enabled and there are mapped welcome files, we need to filter on /* otherwise path params won't work on root.
filterRegistration.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD), filterAfterDeclaredFilters, "/*");
// Map the forwarding filter to all the resources we found.
for (String mapping : collectedViews.keySet()) {
filterRegistration.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD), filterAfterDeclaredFilters, mapping);
}
else {
// Map the forwarding filter to all the resources we found.
for (String mapping : collectedViews.keySet()) {
filterRegistration.addMappingForUrlPatterns(EnumSet.of(REQUEST, FORWARD), filterAfterDeclaredFilters, mapping);
}
// Additionally map the filter to all paths that were scanned and which are also directly accessible.
// This is to give the filter an opportunity to block these.
for (String path : getPublicRootPaths(servletContext)) {
filterRegistration.addMappingForUrlPatterns(null, false, path + "*");
}
// Additionally map the filter to all paths that were scanned and which are also directly accessible.
// This is to give the filter an opportunity to block these.
for (String path : getPublicRootPaths(servletContext)) {
filterRegistration.addMappingForUrlPatterns(null, false, path + "*");
}
}
@@ -373,6 +362,24 @@ public static void registerViewHander(ServletContext servletContext) {
* @return The views found during scanning, or an empty map if no views encountered.
*/
static Map<String, String> scanAndStoreViews(ServletContext servletContext, boolean collectExtensions) {
Set<String> mappedWelcomeFiles = new HashSet<>();
for (String welcomeFile : WebXml.INSTANCE.init(servletContext).getWelcomeFiles()) {
if (isExtensionless(welcomeFile)) {
if (!welcomeFile.startsWith("/")) {
welcomeFile = "/" + welcomeFile;
}
if (welcomeFile.endsWith("/")) {
welcomeFile = welcomeFile.substring(0, welcomeFile.length() - 1);
}
mappedWelcomeFiles.add(welcomeFile);
}
}
servletContext.setAttribute(MAPPED_WELCOME_FILES, unmodifiableSet(mappedWelcomeFiles));
Map<String, String> collectedViews = new HashMap<>();
Set<String> collectedExtensions = new HashSet<>();
@@ -388,25 +395,14 @@ public static void registerViewHander(ServletContext servletContext) {
if (collectExtensions) {
servletContext.setAttribute(ENCOUNTERED_EXTENSIONS, unmodifiableSet(collectedExtensions));
Set<String> mappedWelcomeFiles = new HashSet<>();
if (!collectedExtensions.isEmpty()) {
for (String welcomeFile : WebXml.INSTANCE.init(servletContext).getWelcomeFiles()) {
if (isExtensionless(welcomeFile)) {
if (!welcomeFile.startsWith("/")) {
welcomeFile = "/" + welcomeFile;
}
mappedWelcomeFiles.add(welcomeFile);
if (isMultiViewsEnabled(servletContext) && collectedViews.containsKey(welcomeFile + "/*")) {
servletContext.setAttribute(MULTIVIEWS_WELCOME_FILE, welcomeFile);
}
for (String welcomeFile : getMappedWelcomeFiles(servletContext)) {
if (isMultiViewsEnabled(servletContext) && collectedViews.containsKey(welcomeFile + "/*")) {
servletContext.setAttribute(MULTIVIEWS_WELCOME_FILE, welcomeFile);
}
}
}
servletContext.setAttribute(MAPPED_WELCOME_FILES, unmodifiableSet(mappedWelcomeFiles));
}
}
@@ -488,6 +484,8 @@ private static void scanViews(ServletContext servletContext, String rootPath, Se
Map<String, String> collectedViews, String extensionToScan, Set<String> collectedExtensions)
{
if (!isEmpty(resourcePaths)) {
boolean multiViewsWelcomeFiles = isMultiViewsEnabled(servletContext) && !getMappedWelcomeFiles(servletContext).isEmpty();
for (String resourcePath : resourcePaths) {
if (isDirectory(resourcePath)) {
if (canScanDirectory(rootPath, resourcePath)) {
@@ -503,7 +501,17 @@ else if (canScanResource(resourcePath, extensionToScan)) {
// Store the resource with and without an extension, e.g. store both foo.xhtml and foo
collectedViews.put(resource, resourcePath);
String extensionlessResource = stripExtension(resource);
collectedViews.put(extensionlessResource + (isMultiViewsEnabled(servletContext, extensionlessResource) ? "/*" : ""), resourcePath);
if (isMultiViewsEnabled(servletContext, extensionlessResource)) {
collectedViews.put(extensionlessResource + "/*", resourcePath);
}
else {
if (multiViewsWelcomeFiles) { // This will install forwarding filter on /* and therefore we need to cover / ourselves.
collectedViews.put(extensionlessResource + "/", resourcePath);
}
collectedViews.put(extensionlessResource, resourcePath);
}
// Optionally, collect all unique extensions that we have encountered.
if (collectedExtensions != null) {
@@ -15,6 +15,7 @@
import static javax.faces.application.ProjectStage.Development;
import static javax.servlet.http.HttpServletResponse.SC_MOVED_PERMANENTLY;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static org.omnifaces.facesviews.FacesServletDispatchMethod.DO_FILTER;
import static org.omnifaces.facesviews.FacesViews.FACES_VIEWS_ORIGINAL_PATH_INFO;
import static org.omnifaces.facesviews.FacesViews.FACES_VIEWS_ORIGINAL_SERVLET_PATH;
import static org.omnifaces.facesviews.FacesViews.getExtensionAction;
@@ -93,10 +94,6 @@ public void init() throws ServletException {
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
String servletPath = request.getServletPath();
if (servletPath.endsWith("/")) {
servletPath = servletPath.substring(0, servletPath.length() - 1);
}
if (filterExtensionLess(request, response, chain, servletPath)) {
return;
}
@@ -122,15 +119,16 @@ private boolean filterExtensionLess(HttpServletRequest request, HttpServletRespo
ServletContext servletContext = getServletContext();
boolean multiViews = isMultiViewsEnabled(request);
Map<String, String> resources = getMappedResources(servletContext);
String resource = servletPath + (multiViews ? "/*" : "");
String normalizedServletPath = servletPath.endsWith("/") ? servletPath.substring(0, servletPath.length() - 1) : servletPath;
String resource = normalizedServletPath + (multiViews ? "/*" : "");
String pathInfo = coalesce(request.getPathInfo(), (String) request.getAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO));
if (getApplicationFromFactory().getProjectStage() == Development && !resources.containsKey(resource)) {
// Check if the resource was dynamically added by scanning the faces-views location(s) again.
resources = scanAndStoreViews(servletContext, false);
}
if (!resources.containsKey(resource) && multiViews) {
if (multiViews && !resources.containsKey(resource)) {
resource = getMultiViewsWelcomeFile(servletContext);
if (resource != null) {
@@ -141,48 +139,49 @@ private boolean filterExtensionLess(HttpServletRequest request, HttpServletRespo
}
if (resources.containsKey(resource)) {
String normalizedResource = servletPath;
// Check if a welcome file was explicitly requested.
if ((getRequestRelativeURI(request) + "/").startsWith(servletPath + "/")) {
normalizedResource = stripWelcomeFilePrefix(servletContext, servletPath);
}
if ((getRequestRelativeURI(request) + "/").startsWith(normalizedServletPath + "/")) {
String normalizedResource = stripWelcomeFilePrefix(servletContext, servletPath);
if (!request.getServletPath().equals(normalizedResource)) {
String uri = request.getContextPath() + normalizedResource;
String queryString = request.getQueryString();
redirectPermanent(response, uri + ((queryString != null) ? "?" + queryString : ""));
return true;
if (!servletPath.equals(normalizedResource)) {
// If so, redirect back to parent folder.
String uri = request.getContextPath() + normalizedResource;
String queryString = request.getQueryString();
redirectPermanent(response, uri + ((queryString != null) ? "?" + queryString : ""));
return true;
}
}
String extension = getExtension(resources.get(resource));
switch (dispatchMethod) {
case DO_FILTER:
// Continue the chain, but make the request appear to be to the resource with an extension.
// This assumes that the FacesServlet has been mapped to something that includes the extensionless
// request.
try {
request.setAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH, servletPath);
request.setAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO, pathInfo);
chain.doFilter(new UriExtensionRequestWrapper(request, servletPath, extension), response);
}
finally {
request.removeAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH);
request.removeAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO);
}
String servletPathWithExtension = normalizedServletPath + getExtension(resources.get(resource));
if (dispatchMethod == DO_FILTER && resources.containsKey(servletPathWithExtension)) {
// Continue the chain, but make the request appear to be to the resource with an extension.
// This assumes that the FacesServlet has been mapped to something that includes the extensionless
// request.
try {
request.setAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH, servletPath);
request.setAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO, pathInfo);
chain.doFilter(new UriExtensionRequestWrapper(request, servletPathWithExtension), response);
}
finally {
request.removeAttribute(FACES_VIEWS_ORIGINAL_SERVLET_PATH);
request.removeAttribute(FACES_VIEWS_ORIGINAL_PATH_INFO);
}
return true;
}
else {
// Forward the resource (view) using its original extension, on which the Facelets Servlet
// is mapped. Technically it matters most that the Facelets Servlet picks up the
// request, and the exact extension or even prefix is perhaps less relevant.
RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(servletPathWithExtension);
if (requestDispatcher != null) {
requestDispatcher.forward(request, response);
return true;
case FORWARD:
// Forward the resource (view) using its original extension, on which the Facelets Servlet
// is mapped. Technically it matters most that the Facelets Servlet picks up the
// request, and the exact extension or even prefix is perhaps less relevant.
RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(servletPath + extension);
if (requestDispatcher != null) {
requestDispatcher.forward(request, response);
return true;
}
}
}
}
@@ -35,9 +35,9 @@
private final String servletPath;
public UriExtensionRequestWrapper(HttpServletRequest request, String servletPath, String extension) {
public UriExtensionRequestWrapper(HttpServletRequest request, String servletPath) {
super(request);
this.servletPath = servletPath.endsWith(extension) ? servletPath : servletPath + extension;
this.servletPath = servletPath;
}
@Override
@@ -277,16 +277,6 @@
* </tr>
*
* <tr>
* <td class="colFirst"><code>{@value org.omnifaces.facesviews.FacesViews#FACES_VIEWS_DISPATCH_METHOD_PARAM_NAME}</code></td>
* <td>Determines the method used by FacesViews to invoke the FacesServlet.
* <br>Allowed values are enumerated in {@link org.omnifaces.facesviews.FacesServletDispatchMethod}, which have the following meaning:
* <br>- <code>DO_FILTER</code>: Use a plain {@link javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} to invoke the {@link javax.faces.webapp.FacesServlet}. Using this method necessitates the FacesServlet to be mapped to the (extensionless) requested resource or to everything (/*) when manually mapping.
* <br>- <code>FORWARD</code>: Use a forward to invoke the {@link javax.faces.webapp.FacesServlet}. Using this method the {@link javax.faces.webapp.FacesServlet} does not have to be mapped to the (extensionless) requested resource or to everything (/*) when manually mapping.
* <br>Default value: <code>DO_FILTER</code>
* </td>
* </tr>
*
* <tr>
* <td class="colFirst"><code>{@value org.omnifaces.facesviews.FacesViews#FACES_VIEWS_VIEW_HANDLER_MODE_PARAM_NAME}</code></td>
* <td>Determines how the {@link org.omnifaces.facesviews.FacesViewsViewHandler} should build the action URL that's used in e.g. forms and links.
* <br>Allowed values are enumerated in {@link org.omnifaces.facesviews.ViewHandlerMode}, which have the following meaning:

0 comments on commit 82ba972

Please sign in to comment.