FilenameFlowUrlHandler.java
/*
* Copyright 2004-2012 the original author or authors.
*
* 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
*
* https://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.springframework.webflow.context.servlet;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.webflow.core.collection.AttributeMap;
/**
* A file name based {@link FlowUrlHandler} implementation that is an alternative to the standard
* {@link DefaultFlowUrlHandler}. Treats the filename of a request without the URL suffix and/or prefix as the flow id.
* Used by the {@link org.springframework.webflow.mvc.servlet.FlowController} implementation as a default
* implementation to preserve compatibility with existing Web Flow 2 applications.
*
* <p>
* This implementation extracts the filename and removes the file extension from the request URL. The results will be
* used as the flow Id that must be unique throughout the application.
*
* <p>
* For example the URLs:
*
* <pre>
* https://someHost/someApp/someServlet/foo
* https://someHost/someApp/someServlet/nestedPath/foo
* https://someHost/someApp/someServlet/nestedPath/foo.html
* </pre>
*
* will all treat the filename "foo" as the flow id.
*
* <p>
* <strong>Note:</strong> Because this class only treats a filename as a flow id, clashes can result. For example:
*
* <pre>
* http://localhost/springtravel/app/hotel/booking
* http://localhost/springtravel/app/flight/booking
* </pre>
*
* would both map the same flow id "booking", instead of "hotel/booking" and "flight/booking". This is an limitation of
* this implementation. Consider using the standard {@link DefaultFlowUrlHandler} that uses the request URL prefix as
* well to avoid these clashes.
*
* @author Agim Emruli
* @author Jeremy Grelle
* @author Nazaret Kazarian
*/
public class FilenameFlowUrlHandler extends DefaultFlowUrlHandler {
private UrlPathHelper urlPathHelper;
public FilenameFlowUrlHandler() {
urlPathHelper = new UrlPathHelper();
}
public String getFlowId(HttpServletRequest request) {
return extractFilenameFromUrlPath(urlPathHelper.getLookupPathForRequest(request));
}
public static String extractFilenameFromUrlPath(String urlPath) {
String filename = extractFullFilenameFromUrlPath(urlPath);
int dotIndex = filename.lastIndexOf('.');
if (dotIndex != -1) {
filename = filename.substring(0, dotIndex);
}
return filename;
}
public static String extractFullFilenameFromUrlPath(String urlPath) {
int end = urlPath.indexOf('?');
if (end == -1) {
end = urlPath.indexOf('#');
if (end == -1) {
end = urlPath.length();
}
}
int begin = urlPath.lastIndexOf('/', end) + 1;
int paramIndex = urlPath.indexOf(';', begin);
end = (paramIndex != -1 && paramIndex < end ? paramIndex : end);
return urlPath.substring(begin, end);
}
/**
* The flow definition URL for the given flowId will be inferred from the URL of the current request, re-using the
* same path and file extension.
*
* <p>
* Example - given a request originating at:
*
* <pre>
* https://someHost/someApp/someServlet/nestedPath/foo.html
* </pre>
*
* and a request for the flow id "bar", the new flow definition URL would be:
*
* <pre>
* https://someHost/someApp/someServlet/nestedPath/bar.html
* </pre>
*/
public String createFlowDefinitionUrl(String flowId, AttributeMap<?> input, HttpServletRequest request) {
StringBuilder url = new StringBuilder();
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
url.append(request.getContextPath());
url.append(request.getServletPath());
// include the pathInfo part up until the filename
url.append(pathInfo.substring(0, pathInfo.lastIndexOf("/") + 1));
url.append(flowId);
int dotIndex = pathInfo.lastIndexOf('.');
if (dotIndex != -1) {
url.append(pathInfo.substring(dotIndex));
}
} else {
String servletPath = request.getServletPath();
if (StringUtils.hasText(servletPath)) {
url.append(request.getContextPath());
// include the servletPath part up to the filename
int slashIndex = servletPath.lastIndexOf("/");
if (slashIndex != -1) {
url.append(servletPath.substring(0, slashIndex));
}
url.append('/');
url.append(flowId);
int dotIndex = servletPath.lastIndexOf('.');
if (dotIndex != -1) {
url.append(servletPath.substring(dotIndex));
}
} else {
// Leaving this for now, as DefaultFlowUrlHandler does the same thing,
// but this should probably be an error case in the future.
url.append('/');
url.append(flowId);
}
}
if (input != null && !input.isEmpty()) {
url.append('?');
appendQueryParameters(url, input.asMap(), getEncodingScheme(request));
}
return url.toString();
}
}