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.
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>
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".
I'm going to save my entity ID into the session in one of action states:
<on-exit>
<evaluate expression="persistantService.saveOrUpdate(flowScope.entity)"/>
<evaluate expression="externalContext.sessionMap.put('entityId', flowScope.entity.Id)"/>
</on-exit>
Actually, entity.Id field - is int.
On the start of the flow i'm trying to get entityId from the session, if it exists, load it from storage, else - create new one. Here how I suppose to do it:
<decision-state id="test">
<if test="externalContext.sessionMap.contains('entityId')"
then="findExistingEntity"
else="creteNewEntity"/>
</decision-state>
<action-state id="findExistingEntity">
<evaluate expression="persistantService.findEntityById(externalContext.sessionMap.entityId)"
result="flowScope.entity" />
</action-state>
The problem is that persistantService.findEntityById accepts int, but not Object or Integer that are taken from session.
How can I resolve it? How can I convert externalContext.sessionMap.entityId to int?
Or probably there is another way, to test that entity is saved and load it from persistant storage?
i'd use the conversationScope
<on-exit>
<evaluate expression="persistantService.saveOrUpdate(flowScope.entity)"/>
<set name="conversationScope.entityId" value="flowScope.entity.Id"/>
</on-exit>
...
<decision-state id="test">
<if test="conversationScope.entityId != null"
<!-- i'm not sure if that works you might put the test in a method in a bean and call that--!>
then="findExistingEntity"
else="creteNewEntity"/>
</decision-state>
<action-state id="findExistingEntity">
<evaluate expression="persistantService.findEntityById(conversationScope.entityId)"
result="flowScope.entity" />
</action-state>
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 am trying to follow the code for Spring Webflow from 'Spring in Action'. However, when I tried to run the application, I got the following error:
org.springframework.webflow.engine.FlowInputMappingException: Errors occurred during input mapping on startup of the 'pizza' flow; errors = [[RequiredError#13cb4078 mapping = order -> flowScope.order, code = 'required', error = true, errorCause = [null], originalValue = [null], mappedValue = [null]]]
I believe the line that instantiates the order object in the following flow xml is responsible for the exception:
<var name="order" class="com.mycompany.pizza.domain.Order" />
<subflow-state id="customer" subflow="customer-flow">
<input name="order" value="order"/>
<transition on="customerReady" to="buildOrder" />
</subflow-state>
My subflow xml looks like this:
<view-state id="welcome">
<transition on="phoneEntered" to="lookupCustomer" />
</view-state>
<action-state id="lookupCustomer">
<evaluate result="order.customer"
expression="pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" />
<transition to="registrationForm"
on-exception="com.mycompany.pizza.service.CustomerNotFoundException" />
<transition to="customerReady" />
</action-state>
Hope there's someone who could point me at the right direction. Thanks!
The error is saying you are REQUIRED to pass a NOT NULL input param/obj "order" to your subflow and you are passing a null value in the order input. So if it is not provided it will throw the exception you see. At the top of your subflow should look something like this:
<input name="order" required="true" type="com.mycompany.pizza.domain.Order"/>
That being said, generally I think when passing pojos between flows/subflows it is good practice to be very explicit and fill out the 'type' attribute in both input tags on the caller of the subflow and the subflow itself and to fill out the scope prefix for the value attribute (e.g flowScope.order)
Moreover, I think your problem is that the <var> tag is NOT initializing your Order pojo that is why it is null it is the equiv of:
Order order = null;
You should explicitly set flowScope.order via a new operator or a factory-method call using the 'set' tag inside an 'on-start' tag in beginning of your parent flow. Something like this:
<on-start>
<set name="flowScope.order" value="new com.mycompany.pizza.domain.Order()"/>
<!-- for development purposes... assuming you are using log4j grab the logger and check that order is in fact NOT null -->
<evaluate expression="T(org.apache.log4j.Logger).getLogger('someLogger').info(flowScope.order)"/>
</on-start>
and then (still inside your parent flow) change your subflow call to look like this:
<subflow-state id="customer" subflow="customer-flow">
<input name="order" value="flowScope.order" type="com.mycompany.pizza.domain.Order"/>
<transition on="customerReady" to="buildOrder" />
</subflow-state>
and... Make sure you also fill out the type attribute inside the input tag of your subflow.xml for order like so:
<input name="order" required="true" type="com.mycompany.pizza.domain.Order"/>