I have a global-transition declared and I would like the value of the "validate" attribute to be conditionned or set in an action-state.
Here is how I would like to do it :
<decision-state id="decision_view">
<if test="condition == true" then="actionState1" />
</decision-state>
<action-state id="actionState1">
<evaluate result="flowScope.validateGT1" expression="true"/>
</action-state>
<global-transitions>
<transition on="gtransition1" to="gtransition1"
validate="flowScope.validateGT1" /> // Does not work, syntax error
</global-transitions>
This syntax does not work at all. Is there a way to determine the validate boolean dynamically ?
The project I am working on is using a 2.3.1 version of Spring webflow.
Thanks.
I think you cannot use the expression for boolean as it expects only literals true or false:
http://www.w3.org/TR/xmlschema-2/#boolean
Check the attirbute type for "validate" in spring webflow xsd:
xsd:attribute name="validate" type="xsd:boolean"
Instead what you can do is retrieve the boolean value in validator itself and decide if validation should be done or not as:
public class YourValidator {
public void validateStateId(YourModel model, ValidationContext context) {
RequestContext requestContext = RequestContextHolder.getRequestContext();
boolean shouldValidate = (Boolean)requestContext.getFlowScope.get("validateGT1");
if(shouldValidate){
MessageContext messages = context.getMessageContext();
...
}
}
}
Try to use evaluate
<evaluate expression="flowScope.validateGT1" result="flag" />
<global-transitions>
<transition on="gtransition1" to="gtransition1"
validate="flag" />
</global-transitions>
Related
I need to achieve viewRoot because I add a new htmlPanelGroup dynamically using DB results but always return null.
How is possible throw a evaluate expression from SWF fragment which achieve viewRoot properly?
Thanks!
Flow
<on-entry>
<set name="viewScope.code" value="requestParameters.code" />
</on-entry>
<on-render>
<evaluate expression="eventProvider.createEvent(viewScope.code)" />
</on-render>
Method
public void createEvent(String idEvent){
logger.entry("EventProvider.createEvent()");
Page p=pageBo.getDao().get(Integer.valueOf(idEvent));
pageBo.getDao().initializeElements(p);
Set<Element> elements=p.getElements();
Application app=FacesContextWrapper.getCurrentInstance().getApplication();
UIComponent parent=FacesContextWrapper.getCurrentInstance().getViewRoot().findComponent("eventContainer");
....
....
....}
Finally, I've chosen to save requestParameter in a flowScope var and so I can use it when on-render execution is invoked
<on-entry>
<set name="flowScope.code" value="requestParameters.code" />
</on-entry>
<on-render>
<evaluate expression="eventProvider.loadPage(flowScope.code)" />
</on-render>
Every documentation and examples that I read to be about validation form fields but I wish to show messages for others cases, for example to warn the view or flow have changed. So, that messages must be added on action methods.
In my case the app send to users a confirmation email, on flow there is a decision-state which redirect to views depending on link got declared a request parameter or not. Then I define a action-state which evaluate a method to confirm the email or warning to user about account's state (deleted or already enabled)
<on-start>
<set name="requestScope.code" value="requestParameters.code" />
<set name="requestScope.ln" value="requestParameters.ln" />
</on-start>
<decision-state id="checkConfirmation">
<if test="requestScope.code==null" then="login" else="confirmation" />
</decision-state>
<action-state id="confirmation">
<evaluate expression="login.confirmation(requestScope.ln,requestScope.code)" />
<transition on="yes" to="confirmationOk" />
<transition on="noUserFound" to="noUserFound" />
<transition on="userEnabled" to="userEnabled" />
<transition on="error" to="error" />
</action-state>
<view-state id="userEnabled" view="confirmation.xhtml">
<on-entry>
<set name="viewScope.operation" value="'enabled'" />
</on-entry>
<transition on="login" to="login" />
</view-state>
confirmation method
public String confirmation(String language,String emailCode){
logger.entry("Login.confirmation()");
String emailDecode=new String(Base64.decode(emailCode.getBytes()));
User user=userBo.getDao().findNamedQueryUnique("getUserByEmail",emailDecode);
if(user!=null){
if(!user.isEnabled()){
user.setEnabled(true);
try{
userBo.getDao().merge(user);
if(!EmailProvider.sendEmailEnabled(language,emailDecode)){
return "error";
}
}catch(DataAccessException e){
e.printStackTrace();
FlashMessages.addMessage(language,"es.project.properties.message.msg","unexpected.exception");
return "error";
}
}else{
// JSF way - FlashMessages.addMessage(language,"es.project.properties.message.msg","user.enable.confirmation");
//Spring webflow way - MessageContext context=new DefaultMessageContext();
//MessageBuilder builder=new MessageBuilder();
//context.addMessage(builder.code("user.enable.confirmation").build());
**********************************************************
*** I wish to add messages here and show in next view ****
**********************************************************
return "userEnabled";
}
}else{
//FlashMessages.addMessage(language,"es.project.properties.message.msg","user.validation.norfound.exception");
return "noUserFound";
}
return "yes";
}
I tried to follow spring webflow examples (booking-faces and booking-mvc) adding messages.properties on flow folder or creating a MessageSource bean, but I'm really lost on this matter.
I believe you are not having the messages.properties in classpath properly.
Suppose your file is in folder structure as:
src
|
|-resources
|
|-messages.properties
i.e make sure its in classpath.
You need to declare the ResourceBundleMessageSource with properties file in your servlet context xml file as:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
</bean>
Then you can access the properties as:
<action-state id="confirmation">
...
<transition on="userEnabled" to="userEnabled">
<!--This is the property you want to set in else part of confirmation method - instead include here-->
<set name="flashScope.flashMessageKey" value="'es.project.properties.message.msg'" />
</transition>
</action-state>
Include this in view:
<%# taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>
Then you can view this message on view render by:
<fmt:message key="${flashMessageKey}"/>
I found a solution for Spring,JSF 2.2 and i18 in this post. Basically, you should define a spring message source and create a spring component which will be called for jsf outputtext tag. Then how #Prasad wrote it's necessary to set a key in a spring flow or programatically.
Properties resources are src/main/resources/error_en.properties and src/main/resources/error_es.properties
MessageSource Bean
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource msg=new ReloadableResourceBundleMessageSource();
msg.setBasename("classpath*:error");
return msg;
}
Spring component
#Component(value="message")
public class MessageSourceProvider extends HashMap {
private static final long serialVersionUID = 1L;
#Autowired
private MessageSource messageSource;
#Override
public String get(Object key) {
ServletRequest request = (ServletRequest) FacesContextWrapper.getCurrentInstance().getExternalContext().getRequest();
String message;
try {
message = messageSource.getMessage((String) key, null, request.getLocale());
}
catch (NoSuchMessageException e) {
message = "???" + key + "???";
}
return message;
}
}
JSF tag
<h:outputText value="#{message.unexpected}" />
I'm using Spring 3.2.0. I have registered a few custom property editors for some basic needs as follows.
import editors.DateTimeEditor;
import editors.StrictNumberFormatEditor;
import java.math.RoundingMode;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import org.joda.time.DateTime;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.beans.propertyeditors.URLEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;
#ControllerAdvice
public final class GlobalDataBinder
{
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request)
{
binder.setIgnoreInvalidFields(true);
binder.setIgnoreUnknownFields(true);
//binder.setAllowedFields(someArray);
NumberFormat numberFormat=DecimalFormat.getInstance();
numberFormat.setGroupingUsed(false);
numberFormat.setMaximumFractionDigits(2);
numberFormat.setRoundingMode(RoundingMode.HALF_UP);
binder.registerCustomEditor(DateTime.class, new DateTimeEditor("MM/dd/yyyy HH:mm:ss", true));
binder.registerCustomEditor(Double.class, new StrictNumberFormatEditor(Double.class, numberFormat, true));
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
binder.registerCustomEditor(URL.class, new URLEditor());
}
}
I have this many editors registered so far. Two of them DateTimeEditor and StrictNumberFormatEditor have been customized by overriding respective methods to fulfill custom needs of number format and Joda-Time.
Since I'm using Spring 3.2.0, I can take advantage of #ControllerAdvice.
Spring recommends to list a set of allowed fields with the setAllowedFields() method so that malicious users can not inject values into bound objects.
From the docs about DataBinder
Binder that allows for setting property values onto a target object,
including support for validation and binding result analysis. The
binding process can be customized through specifying allowed fields,
required fields, custom editors, etc.
Note that there are potential security implications in failing to set
an array of allowed fields. In the case of HTTP form POST data for
example, malicious clients can attempt to subvert an application by
supplying values for fields or properties that do not exist on the
form. In some cases this could lead to illegal data being set on
command objects or their nested objects. For this reason, it is highly
recommended to specify the allowedFields property on the DataBinder.
I have a big application and obviously there are thousands of fields. Specifying and listing all of them with the setAllowedFields() is a tedious job. Additionally, somehow I need to remember them.
Changing a web page to remove some fields or add additional fields as the need arises again requires to modify the parameter value of the setAllowedFields() method to reflect those changes.
Is there any alternative to this?
Instead of using setAllowedFields() to white-list, you can use setDisallowedFields() to black-list. For example, from the petclinic sample application:
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
From a pure security standpoint white-listing is preferred to black-listing, but it maybe help ease the burden some.
setAllowedFields() is very handy when using entity objects directly in web layer. Alternatively, one could use dedicated data transfer objects (DTO), from which entity objects are constructed in the service layer. Not only can the factories be re-used, but also used outside the web context, e.g. for asynchronous messages. Besides, DTO inheritance doesn't have to follow entity inheritance, so you are free to design the DTO hierarchy according to the needs of the use-cases.
from http://static.springsource.org/spring-webflow/docs/2.0.x/reference/htmlsingle/spring-webflow-reference.html#view-model
4.9. Specifying bindings explicitly
Use the binder element to configure the exact set of model bindings usable by the view. This is particularly useful in a Spring MVC environment for restricting the set of "allowed fields" per view.
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
If the binder element is not specified, all public properties of the model are eligible for binding by the view. With the binder element specified, only the explicitly configured bindings are allowed.
Each binding may also apply a converter to format the model property value for display in a custom manner. If no converter is specified, the default converter for the model property's type will be used.
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" />
<binding property="checkoutDate" converter="shortDate" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
In the example above, the shortDate converter is bound to the checkinDate and checkoutDate properties. Custom converters may be registered with the application's ConversionService.
Each binding may also apply a required check that will generate a validation error if the user provided value is null on form postback:
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" required="true" />
<binding property="checkoutDate" converter="shortDate" required="true" />
<binding property="creditCard" required="true" />
<binding property="creditCardName" required="true" />
<binding property="creditCardExpiryMonth" required="true" />
<binding property="creditCardExpiryYear" required="true" />
</binder>
<transition on="proceed" to="reviewBooking">
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
In the example above, all of the bindings are required. If one or more blank input values are bound, validation errors will be generated and the view will re-render with those errors.
A solution to use binder with DTO (companydata in example) in case most of the form input values should be converted to null if empty, but there is a need to add few exceptions (setDisallowedFields didn't work for me).
#InitBinder()
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
#InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(String.class, "companydata.companyName", new StringTrimmerEditor(false));
binder.registerCustomEditor(String.class, "companydata.companyNumber", new StringTrimmerEditor(false));
}
I am using spring webflow, this is my flow
<view-state id="welcome">
<transition on="emailEntered" to="checkEmail"></transition>
</view-state>
<decision-state id="checkEmail">
<if test="alta.checkEmail(requestParameters.email)"
then="okState"
else="errorState"/>
</decision-state>
<view-state id="okState"/>
<view-state id="errorState"/>
I have enabled auto-scanning in my servlet-context:
<context:component-scan base-package="com.me.myproj" />
I get a org.springframework.binding.expression.PropertyNotFoundException: Property not found error for state checkEmail. The problem is that it doesn't recognize my 'alta' bean, this is my Alta class (placed in com.me.myproj):
#Component
public class Alta {
public Alta(){
System.out.println("constructor ok");
}
public boolean checkEmail(String email){
return "my.name#email.com".equals(email);
}
}
If I explicitly create the bean:
<bean id="alta" class="com.me.myproj.Alta"/>
Then it works fine. So it seems that flow context doesn't recognize auto-scanned components, although alta is instanciated (as I saw when I debugged).
What can I do to avoid declaring explictly all beans involved in my flow?
Did you include
<context:annotation-config/>
in your servlet-context.xml?
When you explicitly create the bean in the XML you are naming the bean with name "alta" (id value). That is why you can execute methods from class Alta refering to "alta.checkEmail(...)".
<bean id="alta" class="com.me.myproj.Alta"/>
If you want to avoid XML declaration and use annotations only, you should specify that name in the annotation by just passing the name as argument [1]. For example:
#Component("alta")
public class Alta {
public Alta(){
System.out.println("constructor ok");
}
public boolean checkEmail(String email){
return "my.name#email.com".equals(email);
}
}
Hope this helps.
[1] http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/stereotype/Component.html
I am newbie to Spring Webflow, I have am using Custom FlowHandler
I can reach the respective Controller, but if set an attribute in a request Object I don't get it in JSP that is View state.
How can I proceed in this?
you shouldn't set anything to your request attributes in spring webflow - there is no need, besides webflow does some internal request redirecting and probably your attributes are los.
all the variables you create in the flow are available in view as if they were set by adding attribute to request. wether you create them via tag or create by executing some service calls via tag
<on-start>
<evaluate expression="XXXControllerwsf.firstHit(flowRequestContext)" result="flowScope.res"/>
</on-start>
<decision-state id="urlcheck">
<if test="flowScope.res.reurl== 'splash' " then="splash" else="change"/>
</decision-state>
<view-state id="change" view="${flowScope.res.reurl}">
</view-state>
<view-state id="splash" view="forward:/XXX/jsp1/XXXchange1.jsp">
<transition on="buy" to="SignInSignUp"/>
</view-state>
</flow>
Hey Miceuz thanks for Replying .....
<on-start>
<evaluate expression="XXXXControllerwsf.firstHit(flowRequestContext)" result="flowScope.res"/>
</on-start>
<decision-state id="urlcheck">
<if test="flowScope.res.reurl== 'splash' " then="splash" else="change"/>
</decision-state>
<view-state id="change" view="${flowScope.res.reurl}">
</view-state>
<view-state id="splash" view="forward:/Jahia/jsp1/XXXXchange1.jsp">
<transition on="buy" to="SignInSignUp"/>
</view-state>
</flow>
On start of the flow I am invoking a Contorller which extends FormAction
In firsthit Method I am setting a request Attribute as below
request.setAttribute("rajan", "rajanweww");
request.setAttribute("rajan", Object);
If i try to access in JSP , getting it as null... as you said Webflow has internal redirect.. Then how can I access the Value that has been set in Controller?