How to start a Spring Web Flow from a URL like this /resource/edit/n - spring-webflow

I'd like to use Spring Web Flow mostly for its PRG abilities.
Let's say I have URLs like /widget/edit/99 or /widget/edit/123 , where the number represents the id of the widget.
How can I start the /widget/edit flow , passing in the id?
By default it looks like the flow URL must match the flow name folder structure.
I'd like to keep the url at /widget/edit/99 and not redirect.
(Using v 2.4)

You need to try with FlowController and DefaultFlowUrlHandler.
FlowController - It acts as gateway to Web Flow defined control logic and DispatcherServlet.
As per the documentation, in createFlowDefinitionUrl method in DefaultFlowUrlHandler:
The flow definition URL for the given flow id will be built by appending
the flow id to the base app context and servlet paths.
Example - given a request originating at:
http://someHost/someApp/someServlet/nestedPath/foo
and a request for the flow id "nestedPath/bar", the new flow definition URL
would be:
http://someHost/someApp/someServlet/nestedPath/bar
So if request is like: somehost/yourContextPath/yourServletContext/widget/edit/99 and flow id is widget/edit, new flow definition URL would be: somehost/yourContextPath/yourServletContext/widget/edit
Assuming certain configurations as:
The web.xml configuration maps "/widgetinventory/*" requests to the yourServletContext servlet:
<servlet>
<servlet-name>yourServletContext</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>yourServletContext</servlet-name>
<url-pattern>/widgetinventory/*</url-pattern>
</servlet-mapping>
yourServletContext-servlet.xml:
All requests with a servlet path matching "/widgetinventory//" are mapped to the "flowController" bean.
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="alwaysUseFullPath" value="true" />
<property name="mappings">
<value>/app/**/**=flowController</value>
</property>
</bean>
<bean name="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
<property name="flowUrlHandler" ref="customDefaultUrlhandler" />
</bean>
<bean id="customDefaultUrlhandler" class="package.CustomDefaultFlowUrlHandler"/>
<flow:flow-executor id="flowExecutor" flow-registry="flowRegistry"/>
<flow:flow-builder-services id="flowBuilderServices" view-factory-creator="viewFactoryCreator" validator="validator"/>
<!-- Create the registry of flow definitions -->
<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows/" flow-builder-services="flowBuilderServices">
<flow:flow-location-pattern value="**/*-flow.xml"/>
</flow:flow-registry>
See the flowUrlHandler being mapped to customDefaultUrlhandler which is an extension of DefaultFlowUrlHandler. The two methods which help to alter the flow url you specified in it are: getFlowId and createFlowDefinitionUrl.
Override the flowId and createFlowDefinitionUrl methods in DefaultFlowUrlHandler.
But this is all assuming your flow url is of type: somehost/{yourContextPath}/{yourServletContext}/widget/edit/99 where widget/edit is flowId and 99 is some widgetId.
public class CustomDefaultUrlhandler extends DefaultFlowUrlHandler{
You need to alter flowId and createFlowDefinitionUrl if request is something like this:
http://host/{yourContextPath}/{yourServletContext}/widget/edit/99 to direct to widget/edit flow
#Override
public String getFlowId(HttpServletRequest request) {
String pathInfo = request.getPathInfo(); // /widget/edit/99
if (pathInfo != null) {
String widgetId = pathInfo.substring(pathInfo.lastIndexOf("/") + 1);
if(widgetId != null){
return pathInfo.substring(1,pathInfo.lastIndexOf("/")); //return widget/edit by stripping /99
}else{
return pathInfo.substring(1); //return widget/edit
}
} else {
String servletPath = request.getServletPath();
if (StringUtils.hasText(servletPath)) {
int dotIndex = servletPath.lastIndexOf('.');
if (dotIndex != -1) {
return servletPath.substring(1, dotIndex);
} else {
return servletPath.substring(1);
}
} else {
String contextPath = request.getContextPath();
if (StringUtils.hasText(contextPath)) {
return request.getContextPath().substring(1);
} else {
return null;
}
}
}
}
#Override
public String createFlowDefinitionUrl(String flowId, AttributeMap input, HttpServletRequest request) {
//now flowId = "widget/edit"
StringBuffer url = new StringBuffer();
if (request.getPathInfo() != null) {
//for /{yourContextPath}/{yourServletContext}/widget/edit/99 - pathInfo is /widget/edit/99
url.append(request.getContextPath());
url.append(request.getServletPath());
url.append('/');
url.append(flowId);
} else {
String servletPath = request.getServletPath();
if (StringUtils.hasText(servletPath)) {
url.append(request.getContextPath());
url.append('/');
url.append(flowId);
int dotIndex = servletPath.lastIndexOf('.');
if (dotIndex != -1) {
url.append(servletPath.substring(dotIndex));
}
} else {
url.append('/');
url.append(flowId);
}
}
if (input != null && !input.isEmpty()) {
url.append('?');
if (request.getPathInfo() != null) {
//append your widget id here and retrieve this in flow by requestParameters el.
String widgetId = pathInfo.substring(pathInfo.lastIndexOf("/") + 1);
url.append("widgetId ="+widgetId);
}
appendQueryParameters(url, input.asMap(), getEncodingScheme(request));
}
return url.toString();
}
}
Basically we are customizing the flowid from your URL and passing the ids as request parameters.
Make sure your flow id is widget/edit in your scenario.
Check this link about how to get the flow id in desired format here : Spring Webflow - How to Get List of FLOW IDs

See, this topic Consider using spring mvc instead, In Spring MVC you can use the #PathVariable annotation on a method argument to bind it to the value of a URI template variable:
e.g
#RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(#PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
The URI Template "/owners/{ownerId}" specifies the variable name ownerId. When the controller
handles this request, the value of ownerId is set to the value found in the appropriate part of the URI.
For example, when a request comes in for /owners/fred, the value of ownerId is fred.
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring
automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do
so. You can also register support for parsing additional data types. See the section called “Method
Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.
See Here documentation about PathVariable.

Related

Spring MVC default GET request parameter binding to command type

As I read explanation here, I found that Spring can automatically bind GET request parameter to a type. Below is the sample code from the link.
#Controller
#RequestMapping("/person")
public class PersonController {
...
#RequestMapping("/create")
public String create(Person p) {
//TODO: add Person to DAO
return "person/show";
}
}
Can someone tell me how spring do this? What bean that contains the logic to convert the parameter onto command type (Person type)?
The trick is done here: org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument()
This is the excerpt of code where it actually binds the class to the values:
String name = ModelFactory.getNameForParameter(parameter);
//Here it determines the type of the parameter and creates an instance
Object attribute = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
//Then it binds the parameters from the servlet to the previously created instance
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}

Controller Inheritance and Ambiguous Mappings with URL Versioning in Spring MVC

I am trying to setup versioned services with Spring MVC, using inheritance to extend older controllers to avoid rewriting unchanged controller methods. I've based my solution on a previous question about versioning services, however I've run into a problem with ambiguous mappings.
#Controller
#RequestMapping({"/rest/v1/bookmark"})
public class BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public Map<String, String> writeBookmark(#RequestParam String parameter) {
// Perform some operations and return String
}
}
#Controller
#RequestMapping({"/rest/v2/bookmark"})
public class BookmarkJsonControllerV2 extends BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public BookmarkJsonModel writeBookmark(#RequestBody #Valid BookmarkJsonModel bookmark) {
// Perform some operations and return BookmarkJsonModel
}
}
With this setup I get IllegalStateException: Ambiguous mapping found. My thought regarding this is that because I have two methods with different return/argument types I have two methods in BookmarkJsonControllerV2 with the same mapping. As a workaround I attempted to override writeBookmark in BookmarkJsonControllerV2 without any request mapping:
#Override
public Map<String, String> writeBookmark(#RequestParam String parameter) {
return null; // Shouldn't actually be used
}
However, when I compiled and ran this code I still got the exception for an ambiguous mapping. However, when I hit the URL /rest/v2/bookmark/write I got back an empty/null response. Upon changing return null to:
return new HashMap<String, String>() {{
put("This is called from /rest/v2/bookmark/write", "?!");
}};
I would receive JSON with that map, indicating that despite not having any request mapping annotation, it is apparently "inheriting" the annotation from the super class. At this point, my only "solution" to future-proofing the extension of the controllers is to make every controller return Object and only have the HttpServletRequest and HttpServletResponse objects as arguments. This seems like a total hack and I would rather never do this.
So is there a better approach to achieve URL versioning using Spring MVC that allows me to only override updated methods in subsequent versions or is my only real option to completely rewrite each controller?
For whatever reason, using the #RequestMapping annotation was causing the ambiguous mapping exceptions. As a workaround I decided to try using springmvc-router for my REST services which would allow me to leverage inheritance on my controller classes so I would not have to reimplement endpoints that did not change between versions as desired. My solution also allowed me to continue using annotation mappings for my non-REST controllers.
Note: I am using Spring 3.1, which has different classes for the handler mappings than previous versions.
The springmvc-router project brings the router system from the Play framework over to Spring MVC. Inside of my application-context.xml, the relevant setup looks like:
<mvc:annotation-driven/>
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
<bean class="org.resthub.web.springmvc.router.RouterHandlerMapping">
<property name="routeFiles">
<list>
<value>routes/routes.conf</value>
</list>
</property>
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="1" />
</bean>
This allows me to continue using my annotated controllers alongside the router. Spring uses a chain-of-responsibility system, so we can assign multiple mapping handlers. From here, I have a router configuration like so:
# Original Services
POST /rest/bookmark/write bookmarkJsonController.write
POST /rest/bookmark/delete bookmarkJsonController.delete
# Version 2 Services
POST /rest/v2/bookmark/write bookmarkJsonControllerV2.write
POST /rest/v2/bookmark/delete bookmarkJsonControllerV2.delete
Alongside controllers looking like:
#Controller
public class BookmarkJsonController {
#ResponseBody
public Map<String, Boolean> write(#RequestParam String param) { /* Actions go here */ }
#ResponseBody
public Map<String, Boolean> delete(#RequestParam String param) { /* Actions go here */ }
}
#Controller
public class BookmarkJsonControllerV2 extends BoomarkJsonController {
#ResponseBody
public Model write(#RequestBody Model model) { /* Actions go here */ }
}
With a configuration like this, the URL /rest/v2/bookmark/write will hit the method BookmarkJsonControllerV2.write(Model model) and the URL /rest/v2/bookmark/delete will hit the inherited method BookmarkJsonController.delete(String param).
The only disadvantage from this comes from having to redefine entire routes for new versions, as opposed to changing the #RequestMapping(value = "/rest/bookmark") to #RequestMapping(value = "/rest/v2/bookmark") on the class.

How do I get the user when logs incorrectly with Spring Security?

I am using Spring security for interceptor to user wrong logged, but I can not find the way to do.
I specifically want to save the user logged in last, but I can't figure out how to achieve this.
Please help me.
Yes you can do that. You can define following tag in tag in your configuration xml file.
<security:form-login login-page="/sessionexpired"
login-processing-url="/j_spring_security_check"
default-target-url="/submitLogin"
always-use-default-target="true"
authentication-failure-handler-ref="customAuthenticationFailureHandler"/>
You can see the last parameter set authentication-failure-handler-ref its value is a refenrece to following bean defined in the same xml file.
<bean id="customAuthenticationFailureHandler" class="com.xxx.xxx.xxx.CustomFilter">
<constructor-arg type="String" value="loginfailed"></constructor-arg>
<constructor-arg type="org.hibernate.SessionFactory" ref="sessionFactory"></constructor-arg>
</bean>
The class defined in this bean is your own class that will get the information about the failed login details.
public class CustomFilter extends SimpleUrlAuthenticationFailureHandler {
private String defaultFailureUrl;
private SessionFactory sessionFactory;
public CustomFilter(String defaultFailureUrl,SessionFactory sessionFactory) {
super();
this.defaultFailureUrl = defaultFailureUrl;
this.sessionFactory = sessionFactory;
}
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
// TODO Auto-generated method stub
String userName = request.getParameter("j_username");;
/*System.out.println("CustomFilter Begins");
System.out.println("CustomeFilter.username :: " + userName);
System.out.println("getMessage :: " + exception.getMessage());
System.out.println("exception :: " + exception.getClass().getSimpleName());
System.out.println("RemoteAddr :: " + request.getRemoteAddr()); */
}
}
When the Authentication will be failed then method onAuthenticationFailure of this class will be called and you can get the user details there to log in database or log file.
Hope this helps you. Cheers.

Spring MVC controller - getPathInfo() is null

I have worked servlet that need to convert to Spring MVC controller to have access spring beans etc. Why in normal servlet request.getPathInfo() return not null, but in Spring Controller i get null value ? I know i can use #PathVariable, but wonder why the results of this method is the difference?
#RequestMapping(value = {"/test", "/test/*"})
public void test(HttpServletRequest req, HttpServletResponse res) {
log.info(req.getPathInfo() == null); // true!
if (req.getMethod().equalsIgnoreCase("get")) {
// analogue to doGet...
} else {
// analogue to doPost...
}
}
I think the solution is in the javadoc of getPathInfo()
The extra path information follows the servlet path but precedes the
query string and will start with a "/" character.
In case of Spring the servlet path is the full path hence if you call getServletPath() it will always return the full URI and getPathInfo() will return nothing.

Map url with language identifier

Is there a nice way to resolve locale based on the URL and in the other hand map requests without any additional requirement ?
For example
http://example.com/ru/news
http://example.com/iw/news
and in the controller still use the standard mappings
#Controller
#RequestMapping(value = "/news")
public class NewsController {
// Controller methods ...
}
You could write a custom interceptor that works like LocaleChangeInterceptor
Here's a sample implementation that uses a regex pattern (most of the code is copied from LocaleChangeInterceptor):
public class CustomLocaleChangeInterceptor extends HandlerInterceptorAdapter {
private Pattern localePattern;
public void setLocalePattern(final String localePattern) {
Assert.isTrue(localePattern.matches(".*\\(.*\\).*"), "Your pattern needs to define a match group");
this.localePattern = Pattern.compile(localePattern);
}
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws ServletException {
final String pathTranslated = request.getPathTranslated();
if (pathTranslated != null) {
final Matcher matcher = localePattern.matcher(pathTranslated);
if (matcher.find()) {
final String newLocale = matcher.group(1);
if (newLocale != null) {
final LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
}
final LocaleEditor localeEditor = new LocaleEditor();
localeEditor.setAsText(newLocale);
localeResolver.setLocale(request, response, (Locale) localeEditor.getValue());
}
}
}
// Proceed in any case.
return true;
}
}
Wire it like this:
<bean id="localeChangeInterceptor"
class="foo.bar.CustomLocaleChangeInterceptor">
<property name="localePattern" value="\b([a-z]{2})\b"/>
</bean
I'm not aware of an out-of-the-box solution for this, but it's easy enough to implement using a custom interceptor and some wisely chosen mappings.
Write an implementation of HandlerInterceptor which implements preHandle so that the locale string is extracted from the request URI, and then tag the request with that locale (see the source code for the similar LocalChangeInterceptor, which does a similar thing to what you need, but uses a request parameter instead of a path variable).
Then wire it up using <mvc:interceptor> e.g.
<mvc:interceptors>
<mvc:interceptor>
<mapping path="/*"/>
<bean class="x.y.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
You can then loosen up the request mapping on your controller to tolerate (and ignore) the locale part of the URI:
#Controller
#RequestMapping(value = "*/news")
public class NewsController {
// Controller methods ...
}
Take a look at http://lrkwz.github.com/URLManagedLocale/ you can simply drop the dependency in your pom file and configure the interceptor and localeresolver.

Resources