Does anybody know if it's possible to define dynamic transitions in a Spring Web Flow definition?
Example 1 - using a properties file:
<action-state id="createSubscription" >
<evaluate expression="myvar" />
<transition on="$[test.result.valid]" to="subscribeUser-successResponse" />
<transition to="subscribeUser-exceptionResponse" />
</action-state>
Example 2 - using the value of the variable itself:
<action-state id="createSubscription" >
<evaluate expression="myvar" />
<transition to="$[myvar]" />
</action-state>
It's not mandatory, but could help to design more generic flows.
Thanks in advance to everyone.
You can do definitely do for transition "to".
Suppose flow xml has some action and view state as:
<action-state id="createSubscription">
<evaluate expression="myAction.prepareNextState(flowScope.formBean)"/>
<transition to="${flowScope.formBean.displayNextState}">
</action-state>
<view-state id="someView" view="someView" model="formBean">
...
</view-state>
and myAction class with prepareNextState method is as:
public class MyAction implements Serializable{
....
public void prepareNextState(FormBean formBean){
//displayNextState is a String field in FormBean
formBean.setDisplayNextState("someView");
}
....
}
This way we can define generic transitions for transition "to".
Related
I am using hibernate 4.3 and SWF 2.4 and encountered a strange behavior in a particular case which leads to a LazyInitializedException.
Here is the configuration of the flow :
<persistence-context />
<on-start>
<!-- attach enterprise to session if not provided -->
<evaluate expression="enterpriseInput ?: enterpriseDaoImpl.merge(externalContext.sessionMap.workingEnterprise)" result="flowScope.enterprise"/>
</on-start>
<view-state id="start" view="filiation/list">
<transition to="showBoardOrGoToList" on="save" />
</view-state>
<action-state id="showBoardOrGoToList">
<evaluate expression="flowController.displayBoard(event)" />
<transition on="yes" to="boardSubflow"/>
<transition on="no" to="something" />
</action-state>
<subflow-state id="boardSubflow" subflow="board">
<on-entry>
<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
</on-entry>
<input name="admins" value="flowScope.admins"/>
<output name="enterprise" value="flowScope.enterprise"/>
<transition on="save" to="end">
<evaluate expression="enterpriseDaoImpl.merge(enterprise)" result="flowScope.enterprise"/>
</transition>
<transition on="cancel" to="cancel"/>
</subflow-state>
...
When the flow reaches the following line :
<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
It will trigger a LazyInitializedException, the method initAdmins() basically does a Hibernate.initialize() on the elements of a list.
for (Administrator administrator : enterprise.getAdministrators()) {
Hibernate.initialize(administrator);
}
If just before that line a method on a DAO is called it will perform the DB query without issue, so this means that the hibernate session is available at this point but the lazyInit is not working.
BUT, if i call initAdmins() in the <on-start> tag of the flow, it works...
THe flow config that works :
<persistence-context />
<on-start>
<!-- attach enterprise to session if not provided -->
<evaluate expression="enterpriseInput ?: enterpriseDaoImpl.merge(externalContext.sessionMap.workingEnterprise)" result="flowScope.enterprise"/>
<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
</on-start>
<view-state id="start" view="filiation/list">
<transition to="showBoardOrGoToList" on="save" />
</view-state>
<action-state id="showBoardOrGoToList">
<evaluate expression="flowController.displayBoard(event)" />
<transition on="yes" to="boardSubflow"/>
<transition on="no" to="something" />
</action-state>
<subflow-state id="boardSubflow" subflow="board">
<input name="admins" value="flowScope.admins"/>
<output name="enterprise" value="flowScope.enterprise"/>
<transition on="save" to="end">
<evaluate expression="enterpriseDaoImpl.merge(enterprise)" result="flowScope.enterprise"/>
</transition>
<transition on="cancel" to="cancel"/>
</subflow-state>
...
The problem is that i want to initialize the admins only when required as this is a costly operation.
There is very little information on the mechanism of the persistence-context so i couldn't find an explanation on this.
For me it really looks like a bug.
Can someone highlight me or explain why the lazyInit is broken outside of the on-start tag?
Thx
Not sure what is causing the issue. I've experienced this before and I agree I think it is a bug as well however I don't think it's worth the effort to troubleshoot because we'd have to step into the WebFlow code (done this a few times... not fun takes hours) and I've always found a simple and unintrusive work around (5 minutes or less).
Try to initialize the admins in the following transition
<transition on="yes" to="boardSubflow"/>
see if it works now. (would be the same effect as on-start inside the subflow in terms of your goals of efficiency).
<transition on="yes" to="boardSubflow">
<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
</transition>
If that doesn't work try to create an action-state who's sole purpose is to init admins. Place this action-state in the middle the transition -> subflow call. Make sure to evaluate the expression in either the on-start or on-exit of this action-state.
<action-state id="initAdminsActionState">
<transition to="boardSubflow"/>
<on-exit>
<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
</on-exit>
</action-state>
I would like to integrate a javascript dialog into the start of a web flow where the option selected determines if an existing object is added to the flow or a new one is.
<on-entry>
<evaluate expression="appService.checkMembershipStatus(memberId)"/>
// this will check if the state is 'RENEW' and return boolean
// If returns true, then show javascript dialog to say "Renew existing?".
//If they select 'Yes', the existing membership is loaded into the flowScope.
//If they select 'No', then a new membership (object) is loaded into the flowscope
// else
// A new memebership (object) is loaded into the flowscope
</on-entry>
<view-state id="begin">
// continue as normal
</view-state>
Thanks
You can achieve this using <decision-state>. A sample flow is as below
<view-state id="screen1">
<transition to="checkMembershipStatus" />
</view-state>
<decision-state id="checkMembershipStatus">
<if test="appService.checkMembershipStatus(memberId)"
then="renewMembership"
else="loadNewMember" />
</decision-state>
<!--In this page show a javascript dialog (or custom JSP page) on load to get the answer [YES/NO] -->
<view-state id="renewMembership">
<transition on="Yes" to="loadExistingMember" />
<transition on="No" to="loadNewMember" />
</view-state>
<action-state id="loadExistingMember">
<evaluate expression="loadExistingMember()" result="member" />
<transition to="begin" />
</action-state>
<action-state id="loadNewMember">
<evaluate expression="loadnewMember()" result="member" />
<transition to="begin" />
</action-state>
<view-state id="begin">
// continue as normal
</view-state>
I'm trying to add a Forgot Password path to an existing view. I created a new view, action, model bean, and some states in my webflow. Instead of seeing the view, I keep getting the error java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'forgotPassword' available as request attribute. I know the bean exists, and it should be visible. I think I set up the webflow properly, but I'm not 100% sure. Does anybody know what I might be doing wrong?
casLoginView.jsp:
Forgot Password
login-webflow.xml:
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<var name="forgotPasswordBean" class="com.mycompany.authentication.ForgotPasswordBean" />
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="realSubmit">
<evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
<transition on="forgotPassword" bind="false" validate="false" to="forgotPasswordView"/>
</view-state>
<view-state id="forgotPasswordView" view="myForgotPasswordView.jsp" model="forgotPasswordBean">
<binder>
<binding property="username" required="true"/>
</binder>
<transition on="submit" to="forgotPassword"/>
</view-state>
<action-state id="forgotPassword">
<evaluate expression="forgotPasswordAction.submit(flowScope.forgotPasswordBean)" />
<transition on="success" to="newPasswordSentView"/>
<transition on="forbidden" to="forgotPasswordForbiddenView"/>
<transition on="error" to="forgotPasswordView"/>
</action-state>
<end-state id="newPasswordSentView" view="myNewPasswordSentView" />
<end-state id="forgotPasswordForbiddenView" view="forgotPasswordForbiddenView" />
Your <form:form ... > tag should reference the correct bean. Your configuration mentions a forgotPasswordBean and not a forgotPassword one.
Either your form object should reference the correct bean
<form:form modelAttribute="forgotPasswordBean" ... >
Or you should rename the bean in your webflow configuration (including all the references to it).
<var name="forgotPassword" class="com.mycompany.authentication.ForgotPasswordBean" />
I've configured the backtracking in my flow in the following way:
<view-state id="step1" model="step1Model" >
<transition on="next" to="step2" history="invalidate">
</transition>
</view-state>
<view-state id="step2" model="step2Model" >
<transition on="next" to="xxxx" history="invalidate">
</transition>
</view-state>
Also, I'm using the programatic validation implementing a model validate method in the same way you can read in the documentation (link).
The backtracking configuration is working properly when the model validation doesn't have any errors, in this case if you press the browser back button the flow is reloaded how it's expected.
The problem is when the model has some errors the validation doesn't let you go to the next step, in this case if you press the browser back button you can see a browser error page "Document Expired. This document is no longer available".
Any idea about this?
Thanks, Gerardo.
Problem is that you invalidate history on each step.
It would be better if you use end-state to clean history.
<view-state id="step1" model="step1Model" >
<transition on="next" to="step2">
</transition>
</view-state>
<view-state id="step2" model="step2Model" >
<transition on="next" to="end">
</transition>
</view-state>
<end-state id="end" />
I Have problem with Spring Webflow. My flow XML definition is:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd" parent="changeLang">
<input name="hash" required="true"/>
<action-state id="decideAction">
<set name="flowScope.goTo" value ="verifyActionService.verifyHash(hash)" />
<transition to="${goTo}" ></transition>
</action-state>
<view-state id="correctVerify" view="registered" model="userAddressesForm">
<transition on="addPhoneNumber" to="correctVerify">
<evaluate expression="verifyActionService.addPhoneNumber(userAddressesForm)" />
</transition>
<transition on="deletePhoneNumber" to="correctVerify">
<evaluate expression="verifyActionService.deletePhoneNumber(userAddressesForm, requestParameters.deleteNumber)" />
</transition>
</view-state>
<view-state id="notCorrectVerify" view="register"></view-state>
</flow>
The method verifyHash return a state id equal "correctVerify" like this:
public String verifyHash(String hash) {
return "correctVerify";
}
When I run it, a get an error like this:
at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.IllegalArgumentException: Cannot find state with id '${goTo}' in flow 'verify' -- Known state ids are 'array<String>['decideAction', 'correctVerify', 'notCorrectVerify', 'start']'
at org.springframework.webflow.engine.Flow.getStateInstance(Flow.java:348)
at org.springframework.webflow.engine.support.DefaultTargetStateResolver.resolveTargetState(DefaultTargetStateResolver.java:60)
at org.springframework.webflow.engine.Transition.execute(Transition.java:217)
at org.springframework.webflow.engine.impl.FlowExecutionImpl.execute(FlowExecutionImpl.java:391)
at org.springframework.webflow.engine.impl.RequestControlContextImpl.execute(RequestControlContextImpl.java:214)
at org.springframework.webflow.engine.TransitionableState.handleEvent(TransitionableState.java:119)
Can anybody help me?
The to attribute of transition takes a string literal. If you want to combine string literals and EL, you need to use a template expression:
<transition to="#{goTo}"/>
Information about the two different types of expression can be found in this section of the documentation.
Also, are you sure you need to be returning a view-state name from your service layer? The general pattern for <action-state> is you call a method using <evaluate> and then define different transitions to different states based on the result of the <evaluate>... similar to a switch statement. Take a look at this section on action states.