SWF-1419 Add ability to plug in custom Spring Validator via FlowBuilderServices to support JSR-303 validation

This commit is contained in:
Rossen Stoyanchev
2010-12-02 13:54:00 +00:00
parent 8928b6d7ff
commit df7f3406a0
57 changed files with 981 additions and 142 deletions

View File

@@ -73,25 +73,4 @@ When set to true, changes to a flow definition will be auto-detected and will re
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="resources">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.faces.webflow.JsfResourceRequestHandler"><![CDATA[
Configures a handler that uses the JSF ResourceHandler API introduced in JSF 2 to serve web application and
classpath resources such as images, CSS and JavaScript files from well-known locations.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="order" type="xsd:int">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies the order of the HandlerMapping for the resource handler. The default order is 0.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema
xmlns="http://www.springframework.org/schema/faces"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.springframework.org/schema/faces"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="2.2">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" />
<xsd:element name="flow-builder-services">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers custom implementations of services needed to build flow definitions in a JSF environment.
With this tag, you may configure a custom ConversionService, FormatterFactory, ExpressionParser, and ViewFactoryCreator implementation.
This tag is only needed when you wish to plugin custom implementations.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="conversion-service">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ConversionService implementation to use to convert from one type to another.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expression-parser">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ExpressionParser implementation to use to compile expression strings into Expressions.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="view-factory-creator">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ViewFactoryCreator implementation to use produce ViewFactories capable of rendering Views.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="enable-managed-beans" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
When this attribute is set to true, a special EL expression parser will be enabled that allows access to JSF-managed beans
from EL expressions in flow definitions.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="development" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Puts all flows in development mode.
When set to true, changes to a flow definition will be auto-detected and will result in a flow refresh.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="resources">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.faces.webflow.JsfResourceRequestHandler"><![CDATA[
Configures a handler that uses the JSF ResourceHandler API introduced in JSF 2 to serve web application and
classpath resources such as images, CSS and JavaScript files from well-known locations.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="order" type="xsd:int">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies the order of the HandlerMapping for the resource handler. The default order is 0.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -15,7 +15,11 @@
*/
package org.springframework.faces.webflow;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.js.ajax.AjaxHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.webflow.mvc.servlet.FlowHandlerAdapter;
/**
@@ -39,4 +43,9 @@ public class JsfFlowHandlerAdapter extends FlowHandlerAdapter {
}
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return super.handle(request, response, handler);
}
}

View File

@@ -20,6 +20,7 @@ import javax.faces.lifecycle.Lifecycle;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.execution.ViewFactory;
@@ -36,7 +37,7 @@ public class JsfViewFactoryCreator implements ViewFactoryCreator {
private Lifecycle lifecycle;
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
return new JsfViewFactory(viewIdExpression, getLifecycle());
}

View File

@@ -1 +1,3 @@
http\://www.springframework.org/schema/faces/spring-faces-2.0.xsd=org/springframework/faces/config/spring-faces-2.0.xsd
http\://www.springframework.org/schema/faces/spring-faces-2.0.xsd=org/springframework/faces/config/spring-faces-2.0.xsd
http\://www.springframework.org/schema/faces/spring-faces-2.2.xsd=org/springframework/faces/config/spring-faces-2.2.xsd
http\://www.springframework.org/schema/faces/spring-faces.xsd=org/springframework/faces/config/spring-faces-2.2.xsd

View File

@@ -17,6 +17,7 @@ import org.springframework.faces.model.converter.FacesConversionService;
import org.springframework.faces.webflow.FacesSpringELExpressionParser;
import org.springframework.faces.webflow.JSFMockHelper;
import org.springframework.faces.webflow.JsfViewFactoryCreator;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
@@ -78,7 +79,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
public static class TestViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -4,9 +4,9 @@
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/faces
http://www.springframework.org/schema/faces/spring-faces-2.0.xsd">
http://www.springframework.org/schema/faces/spring-faces.xsd">
<faces:flow-builder-services id="flowBuilderServicesDefault"/>

View File

@@ -3,8 +3,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.0.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces.xsd">
<faces:resources />

View File

@@ -21,12 +21,12 @@
<dependencies>
<!-- core dependencies -->
<dependency org="javax.xml.bind" name="com.springsource.javax.xml.bind" rev="2.2.0" conf="compile->runtime" />
<dependency org="com.sun.xml" name="com.springsource.com.sun.xml.bind" rev="2.2.0" conf="compile->runtime" />
<dependency org="javax.persistence" name="com.springsource.javax.persistence" rev="1.0.0" conf="compile->runtime"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.4.0" conf="provided->runtime"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.2.0" conf="compile->runtime"/>
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="compile->runtime"/>
<dependency org="javax.xml.bind" name="com.springsource.javax.xml.bind" rev="2.2.0" conf="compile->runtime" />
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="compile->runtime"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate" rev="3.2.6.ga" conf="compile->runtime"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.annotations" rev="3.3.0.ga" conf="compile->runtime"/>

View File

@@ -38,17 +38,12 @@ public class Booking implements Serializable {
private Hotel hotel;
@NotNull
private Date checkinDate;
@Future
@NotNull
private Date checkoutDate;
@Pattern(regexp = "[0-9]{16}", message = "is not a 16 digit card number")
private String creditCard;
@NotEmpty
private String creditCardName;
private int creditCardExpiryMonth;
@@ -68,6 +63,7 @@ public class Booking implements Serializable {
this.hotel = hotel;
this.user = user;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckinDate(calendar.getTime());
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckoutDate(calendar.getTime());
@@ -99,6 +95,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@Future
@NotNull
public Date getCheckinDate() {
return checkinDate;
}
@@ -127,6 +125,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@Future
@NotNull
public Date getCheckoutDate() {
return checkoutDate;
}
@@ -135,6 +135,7 @@ public class Booking implements Serializable {
this.checkoutDate = checkoutDate;
}
@Pattern(regexp = "[0-9]{16}", message = "{invalidCreditCardPattern}")
public String getCreditCard() {
return creditCard;
}
@@ -166,6 +167,7 @@ public class Booking implements Serializable {
this.beds = beds;
}
@NotEmpty
public String getCreditCardName() {
return creditCardName;
}
@@ -202,11 +204,11 @@ public class Booking implements Serializable {
public void validateEnterBookingDetails(ValidationContext context) {
MessageContext messages = context.getMessageContext();
if (checkinDate.before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").code(
"booking.checkinDate.beforeToday").build());
messages.addMessage(new MessageBuilder().error().source("checkinDate")
.code("booking.checkinDate.beforeToday").build());
} else if (checkoutDate.before(checkinDate)) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").code(
"booking.checkoutDate.beforeCheckinDate").build());
messages.addMessage(new MessageBuilder().error().source("checkoutDate")
.code("booking.checkoutDate.beforeCheckinDate").build());
}
}

View File

@@ -0,0 +1,2 @@
# Custom messages for JSR-303 validation
invalidCreditCardPattern=is not a valid 16 digit card number.

View File

@@ -4,9 +4,9 @@
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- Instructs Spring to perfrom declarative transaction management on annotated classes -->
<tx:annotation-driven />

View File

@@ -4,9 +4,9 @@
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- Configure Spring Security -->
<security:http auto-config="true" use-expressions="true">

View File

@@ -4,9 +4,9 @@
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Scans for application @Components to deploy -->
<context:component-scan base-package="org.springframework.webflow.samples.booking" />

View File

@@ -4,12 +4,12 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.0.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces.xsd">
<!-- Executes flows: the central entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="facesContextListener"/>
<webflow:listener ref="jpaFlowExecutionListener" />

View File

@@ -3,8 +3,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.0.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces.xsd">
<faces:resources />

View File

@@ -27,7 +27,7 @@
<h:selectOneMenu id="pageSize" value="#{searchCriteria.pageSize}">
<f:selectItems value="#{referenceData.pageSizeOptions}" />
</h:selectOneMenu>
<p:commandButton id="findHotels" value="Find Hotels" action="search" update="@form" />
<p:commandButton id="findHotels" value="Find Hotels" action="search" update="@form" ajax="false" />
</h:panelGrid>
<p:tooltip for="searchString" targetPosition="topRight" position="bottomLeft"
value="Search hotels by name, address, city, or zip." style="cream" />

View File

@@ -26,6 +26,7 @@
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp" rev="2.1.0" conf="provided->runtime"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.1.2" conf="compile->runtime"/>
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="compile->runtime"/>
<dependency org="javax.xml.bind" name="com.springsource.javax.xml.bind" rev="2.2.0" conf="compile->runtime" />
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="compile->runtime"/>
<dependency org="org.apache.taglibs" name="com.springsource.org.apache.taglibs.standard" rev="1.1.2" conf="compile->runtime"/>
<dependency org="org.apache.tiles" name="com.springsource.org.apache.tiles" rev="2.1.2.osgi" conf="compile->runtime"/>
@@ -35,8 +36,10 @@
<dependency org="org.hibernate" name="com.springsource.org.hibernate" rev="3.2.6.ga" conf="compile->runtime"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.annotations" rev="3.3.0.ga" conf="compile->runtime"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.ejb" rev="3.3.1.ga" conf="compile->runtime"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.validator" rev="4.1.0.GA" conf="compile->runtime" />
<dependency org="org.hsqldb" name="com.springsource.org.hsqldb" rev="1.8.0.9" conf="compile->runtime"/>
<dependency org="org.joda" name="com.springsource.org.joda.time" rev="1.6.0" conf="compile->runtime"/>
<dependency org="org.slf4j" name="com.springsource.slf4j.log4j" rev="1.5.6" />
<dependency org="org.springframework" name="org.springframework.aop" rev="3.0.4.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework" name="org.springframework.beans" rev="3.0.4.RELEASE" conf="compile->runtime"/>
<dependency org="org.springframework" name="org.springframework.context" rev="3.0.4.RELEASE" conf="compile->runtime"/>

View File

@@ -157,6 +157,27 @@
<artifactId>org.springframework.webflow</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Bean Validation -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>com.springsource.org.hibernate.validator</artifactId>
<version>4.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>com.springsource.slf4j.log4j</artifactId>
<version>1.5.6</version>
</dependency>
<!--
JAXB is needed when running on Java 5. In this environment these dependencies have to be added
(unless xml configuration is explicitly disabled via Configuration.ignoreXmlConfiguration)
On Java 6 jaxb is part of the runtime environment.
-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>com.springsource.javax.xml.bind</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Build time only dependencies -->
<dependency>
<groupId>javax.servlet</groupId>

View File

@@ -16,17 +16,19 @@ import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;
/**
* A Hotel Booking made by a User.
*/
@Entity
@BookingDateRange
public class Booking implements Serializable {
private Long id;
private User user;
@@ -55,6 +57,7 @@ public class Booking implements Serializable {
public Booking() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckinDate(calendar.getTime());
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckoutDate(calendar.getTime());
@@ -92,6 +95,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@NotNull
@Future
public Date getCheckinDate() {
return checkinDate;
}
@@ -120,6 +125,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@NotNull
@Future
public Date getCheckoutDate() {
return checkoutDate;
}
@@ -128,6 +135,7 @@ public class Booking implements Serializable {
this.checkoutDate = checkoutDate;
}
@NotEmpty
public String getCreditCard() {
return creditCard;
}
@@ -159,6 +167,7 @@ public class Booking implements Serializable {
this.beds = beds;
}
@NotEmpty
public String getCreditCardName() {
return creditCardName;
}
@@ -192,17 +201,6 @@ public class Booking implements Serializable {
this.amenities = amenities;
}
public void validateEnterBookingDetails(ValidationContext context) {
MessageContext messages = context.getMessageContext();
if (checkinDate.before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").code(
"booking.checkinDate.beforeToday").build());
} else if (checkoutDate.before(checkinDate)) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").code(
"booking.checkoutDate.beforeCheckinDate").build());
}
}
private Date today() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1);

View File

@@ -0,0 +1,22 @@
package org.springframework.webflow.samples.booking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = BookingDateRangeValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface BookingDateRange {
String message() default "Invalid check-in and check-out date range";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,19 @@
package org.springframework.webflow.samples.booking;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class BookingDateRangeValidator implements ConstraintValidator<BookingDateRange, Booking> {
public void initialize(BookingDateRange bookingDateRange) {
}
public boolean isValid(Booking booking, ConstraintValidatorContext context) {
if ((booking.getCheckinDate() != null) && (booking.getCheckoutDate() != null)
&& booking.getCheckoutDate().before(booking.getCheckinDate())) {
return false;
}
return true;
}
}

View File

@@ -21,9 +21,14 @@
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<value>
hibernate.session_factory_name=mySessionFactory
</value>
</property>
</bean>
</bean>
<!-- Deploys a in-memory "booking" datasource populated -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />

View File

@@ -4,9 +4,9 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<!-- Executes flows: the entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
@@ -22,7 +22,8 @@
</webflow:flow-registry>
<!-- Plugs in a custom creator for Web Flow views -->
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" development="true" />
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"
development="true" validator="validator" />
<!-- Configures Web Flow to use Tiles to create views for rendering; Tiles allows for applying consistent layouts to your views -->
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
@@ -38,5 +39,8 @@
<!-- Installs a listener to apply Spring Security authorities -->
<bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
<!-- Bootstraps JSR-303 validation and adapts it to Spring's Validator interface -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</beans>

View File

@@ -15,15 +15,15 @@
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" required="true" />
<binding property="checkoutDate" required="true" />
<binding property="beds" required="true" />
<binding property="smoking" required="true" />
<binding property="creditCard" required="true" />
<binding property="creditCardName" required="true" />
<binding property="creditCardExpiryMonth" required="true" />
<binding property="creditCardExpiryYear" required="true" />
<binding property="amenities" required="false" />
<binding property="checkinDate" />
<binding property="checkoutDate" />
<binding property="beds" />
<binding property="smoking" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
<binding property="amenities" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />

View File

@@ -22,7 +22,7 @@
<div class="error">
<spring:bind path="booking.*">
<c:forEach items="${status.errorMessages}" var="error">
<c:out value="${error}"/><br>
<span><c:out value="${error}"/></span><br>
</c:forEach>
</spring:bind>
</div>

View File

@@ -1,12 +1,16 @@
booking.checkinDate.required=The check in date is required
booking.checkinDate.typeMismatch=The check in date must be in the format mm-dd-yyyy
booking.checkinDate.NotNull=The check in date is required
booking.checkinDate.Future=The check in date must be in the future
booking.checkinDate.beforeToday=The check in date must be a future date
booking.checkinDate.typeMismatch=The check in date must be in the format mm-dd-yyyy
booking.checkoutDate.required=The check out date is required
booking.checkoutDate.Future=The check out date must be in the future
booking.checkoutDate.NotNull=The check out date is required
booking.checkoutDate.typeMismatch=The check out date must be in the format mm-dd-yyyy
booking.checkoutDate.beforeCheckinDate=The check out date must be later than the check in date
booking.BookingDateRange=The check out date must be later than the check in date
booking.creditCard.required=The credit card must be a valid 16 digit number
booking.creditCardName.required=The name on the credit card is required
booking.creditCard.NotEmpty=The credit card must be a valid 16 digit number
booking.creditCardName.NotEmpty=The name on the credit card is required
required=The {0} field is required
NotEmpty=The {0} field is required
NotNull=The {0} field is required
Future=The {0} date must be in the future

View File

@@ -5,11 +5,11 @@
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd
http://www.springframework.org/schema/faces
http://www.springframework.org/schema/faces/spring-faces-2.0.xsd">
http://www.springframework.org/schema/faces/spring-faces.xsd">
<!-- Maps portlet modes to handlers -->
<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">

View File

@@ -26,14 +26,17 @@
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.4.0" conf="provided->compile"/>
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.1.2" conf="compile->compile"/>
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="compile->compile"/>
<dependency org="javax.xml.bind" name="com.springsource.javax.xml.bind" rev="2.2.0" conf="compile->runtime" />
<dependency org="org.aopalliance" name="com.springsource.org.aopalliance" rev="1.0.0" conf="compile->compile"/>
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="compile->compile"/>
<dependency org="org.apache.taglibs" name="com.springsource.org.apache.taglibs.standard" rev="1.1.2" conf="compile->compile"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate" rev="3.2.6.ga" conf="compile->compile"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.annotations" rev="3.3.0.ga" conf="compile->compile"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.ejb" rev="3.3.1.ga" conf="compile->compile"/>
<dependency org="org.hibernate" name="com.springsource.org.hibernate.validator" rev="4.1.0.GA" conf="compile->runtime" />
<dependency org="org.hsqldb" name="com.springsource.org.hsqldb" rev="1.8.0.9" conf="compile->compile"/>
<dependency org="org.joda" name="com.springsource.org.joda.time" rev="1.6.0" conf="compile->runtime"/>
<dependency org="org.slf4j" name="com.springsource.slf4j.log4j" rev="1.5.6" />
<dependency org="org.springframework" name="org.springframework.aop" rev="3.0.4.RELEASE" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.beans" rev="3.0.4.RELEASE" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.context" rev="3.0.4.RELEASE" conf="compile->compile"/>

View File

@@ -117,6 +117,27 @@
<artifactId>org.springframework.webflow</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Bean Validation -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>com.springsource.org.hibernate.validator</artifactId>
<version>4.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>com.springsource.slf4j.log4j</artifactId>
<version>1.5.6</version>
</dependency>
<!--
JAXB is needed when running on Java 5. In this environment these dependencies have to be added
(unless xml configuration is explicitly disabled via Configuration.ignoreXmlConfiguration)
On Java 6 jaxb is part of the runtime environment.
-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>com.springsource.javax.xml.bind</artifactId>
<version>2.2.0</version>
</dependency>
<!-- build time only dependencies -->
<dependency>
<groupId>javax.portlet</groupId>

View File

@@ -15,13 +15,17 @@ import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;
/**
* A Hotel Booking made by a User.
*/
@Entity
@BookingDateRange
public class Booking implements Serializable {
private Long id;
@@ -49,6 +53,7 @@ public class Booking implements Serializable {
public Booking() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckinDate(calendar.getTime());
calendar.add(Calendar.DAY_OF_MONTH, 1);
setCheckoutDate(calendar.getTime());
@@ -82,6 +87,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@NotNull
@Future
public Date getCheckinDate() {
return checkinDate;
}
@@ -110,6 +117,8 @@ public class Booking implements Serializable {
@Basic
@Temporal(TemporalType.DATE)
@NotNull
@Future
public Date getCheckoutDate() {
return checkoutDate;
}
@@ -118,6 +127,7 @@ public class Booking implements Serializable {
this.checkoutDate = checkoutDate;
}
@NotEmpty
public String getCreditCard() {
return creditCard;
}
@@ -149,6 +159,7 @@ public class Booking implements Serializable {
this.beds = beds;
}
@NotEmpty
public String getCreditCardName() {
return creditCardName;
}

View File

@@ -0,0 +1,22 @@
package org.springframework.webflow.samples.booking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = BookingDateRangeValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface BookingDateRange {
String message() default "Invalid check-in and check-out date range";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,19 @@
package org.springframework.webflow.samples.booking;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class BookingDateRangeValidator implements ConstraintValidator<BookingDateRange, Booking> {
public void initialize(BookingDateRange bookingDateRange) {
}
public boolean isValid(Booking booking, ConstraintValidatorContext context) {
if ((booking.getCheckinDate() != null) && (booking.getCheckoutDate() != null)
&& booking.getCheckoutDate().before(booking.getCheckinDate())) {
return false;
}
return true;
}
}

View File

@@ -5,11 +5,11 @@
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- Activates annotation-based bean configuration -->
<context:annotation-config />

View File

@@ -4,9 +4,9 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<!-- Maps portlet modes to handlers -->
<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
@@ -38,11 +38,14 @@
</webflow:flow-executor>
<!-- The registry of executable flow definitions -->
<webflow:flow-registry id="flowRegistry">
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/flows/view/view.xml" />
<webflow:flow-location path="/WEB-INF/flows/main/main.xml" />
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>
<!-- Plugs in Spring's JSR-303 validator adapter -->
<webflow:flow-builder-services id="flowBuilderServices" development="true" validator="validator" />
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
<bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
@@ -50,4 +53,7 @@
<constructor-arg ref="transactionManager" />
</bean>
<!-- Bootstraps JSR-303 validation and adapts it to Spring's Validator interface -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</beans>

View File

@@ -9,8 +9,16 @@
<portlet:param name="execution" value="${flowExecutionKey}" />
</portlet:actionURL>
<form:form id="booking" modelAttribute="booking" action="${actionUrl}">
<form:errors path="*" cssClass="errors" />
<fieldset>
<spring:hasBindErrors name="booking">
<div class="error">
<spring:bind path="booking.*">
<c:forEach items="${status.errorMessages}" var="error">
<span><c:out value="${error}"/></span><br>
</c:forEach>
</spring:bind>
</div>
</spring:hasBindErrors>
<fieldset>
<table>
<tr class="field">
<td class="label">Name:</td>

View File

@@ -0,0 +1,16 @@
booking.checkinDate.NotNull=The check in date is required
booking.checkinDate.Future=The check in date must be in the future
booking.checkinDate.beforeToday=The check in date must be a future date
booking.checkinDate.typeMismatch=The check in date must be in the format mm-dd-yyyy
booking.checkoutDate.Future=The check out date must be in the future
booking.checkoutDate.NotNull=The check out date is required
booking.checkoutDate.typeMismatch=The check out date must be in the format mm-dd-yyyy
booking.BookingDateRange=The check out date must be later than the check in date
booking.creditCard.NotEmpty=The credit card must be a valid 16 digit number
booking.creditCardName.NotEmpty=The name on the credit card is required
NotEmpty=The {0} field is required
NotNull=The {0} field is required
Future=The {0} date must be in the future

View File

@@ -45,12 +45,14 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
private static final String DEVELOPMENT_ATTR = "development";
private static final String EXPRESSION_PARSER_ATTR = "expression-parser";
private static final String VIEW_FACTORY_CREATOR_ATTR = "view-factory-creator";
private static final String VALIDATOR_ATTR = "validator";
// --------------------------- Bean Configuration Properties --------------------- //
private static final String CONVERSION_SERVICE_PROPERTY = "conversionService";
private static final String DEVELOPMENT_PROPERTY = "development";
private static final String EXPRESSION_PARSER_PROPERTY = "expressionParser";
private static final String VIEW_FACTORY_CREATOR_PROPERTY = "viewFactoryCreator";
private static final String VALIDATOR_PROPERTY = "validator";
protected String getBeanClassName(Element element) {
return FLOW_BUILDER_SERVICES_CLASS_NAME;
@@ -65,6 +67,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
parseConversionService(element, parserContext, builder);
parseExpressionParser(element, parserContext, builder);
parseViewFactoryCreator(element, parserContext, builder);
parseValidator(element, parserContext, builder);
parseDevelopment(element, builder);
parserContext.popAndRegisterContainingComponent();
@@ -105,6 +108,13 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
definitionBuilder.addPropertyReference(VIEW_FACTORY_CREATOR_PROPERTY, viewFactoryCreator);
}
private void parseValidator(Element element, ParserContext context, BeanDefinitionBuilder definitionBuilder) {
String validator = element.getAttribute(VALIDATOR_ATTR);
if (StringUtils.hasText(validator)) {
definitionBuilder.addPropertyReference(VALIDATOR_PROPERTY, validator);
}
}
private void parseDevelopment(Element element, BeanDefinitionBuilder definitionBuilder) {
String development = element.getAttribute(DEVELOPMENT_ATTR);
if (StringUtils.hasText(development)) {

View File

@@ -5,7 +5,7 @@
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.springframework.org/schema/webflow-config"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="2.0.3">
version="2.0">
<xsd:annotation>
<xsd:documentation>

View File

@@ -0,0 +1,448 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema
xmlns="http://www.springframework.org/schema/webflow-config"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.springframework.org/schema/webflow-config"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="2.3">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Spring Web Flow Configuration Schema
Authors: Keith Donald, Jeremy Grelle, Scott Andrews
<br>
A XML-based DSL for configuring the Spring Web Flow 2.0 system.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" />
<xsd:element name="flow-registry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a registry of flow definitions. A flow registry is used by a flow executor to locate flows to execute.
<br>
Each flow definition registered in this registry is assigned a unique identifier. By default,
this identifier is the name of the externalized resource minus its file extension. For example,
a registry containing flow definitions built from the files "orderitem-flow.xml" and "shipping-flow.xml"
would index those definitions by "orderitem-flow" and "shipping-flow" by default.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="flow-location" type="flowLocationType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers a flow defined at an external resource such as a .xml file.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="flow-location-pattern">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers a set of flows resolved from a resource location pattern (e.g. /WEB-INF/flows/**/*-flow.xml).
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="value" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The location pattern to resolve (e.g. /WEB-INF/flows/**/*-flow.xml)
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="flow-builder" type="flowBuilderType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers a custom FlowBuilder implementation.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
<xsd:attribute name="flow-builder-services" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A reference to a FlowBuilderServices implementation defining custom services needed to build the flows registered in this registry.
Optional. For use when one or more custom builder services are required.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="parent" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A reference to this registry's parent. Registries can be organized in a hierarchy. If a child registry does not contain a flow,
its parent registry will be queried.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="base-path" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The base path where flow definitions are found. When specified, all flow locations are relative to this path.
Also when specified, by default flows are assigned ids equal to the the path segment between their base path and file name.
For example, if a flow definition is located at '/WEB-INF/hotels/booking/booking-flow.xml' and the base path is '/WEB-INF',
the remaining path to this flow is 'hotels/booking' which then becomes the flow id.
If a flow definition is found directly on the base path, the file name minus its extension is used as the flow id.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="flowLocationType">
<xsd:sequence>
<xsd:element name="flow-definition-attributes" type="flowDefinitionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Meta-attributes to assign to the flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The id assign the flow definition in this registry. Optional.
Specify when you wish to provide a custom flow definition identifier.
When specified, the id must be unique among all flows in this registry.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="path" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The resource path to the externalized flow definition resource. May be a path to a single resource or a ANT-style path expression
that matches multiple resources.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowBuilderType">
<xsd:sequence>
<xsd:element name="flow-definition-attributes" type="flowDefinitionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Attributes to assign to the flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The id assign the flow definition in this registry. Optional.
Specify when you wish to provide a custom flow definition identifier.
When specified, the id must be unique among all flows in this registry.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="class" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The fully qualified class name of the FlowBuilder implementation.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="flow-builder-services">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers custom implementations of services needed to build flow definitions.
With this tag, you may configure a custom ConversionService, FormatterFactory, ExpressionParser, and ViewFactoryCreator implementation.
This tag is only needed when you wish to plugin custom implementations.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="conversion-service">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ConversionService implementation to use to convert from one type to another.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expression-parser">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ExpressionParser implementation to use to compile expression strings into Expressions.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="view-factory-creator">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The custom ViewFactoryCreator implementation to use produce ViewFactories capable of rendering Views.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="validator">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.validation.Validator">
<![CDATA[
The bean name of the Validator that is to be used for validating a model declared on a view state.
This attribute is not required, and only needs to be specified explicitly if a custom Validator needs to be configured.
If not specified, JSR-303 validation will be installed if a JSR-303 provider is present on the classpath.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="development" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Puts all flows in development mode.
When set to true, changes to a flow definition will be auto-detected and will result in a flow refresh.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="flow-executor">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a flow executor. A FlowExecutor executes flow definitions and acts as the entry-point into the Web Flow system.
A flow executor launches new flow executions and resumes paused flow executions.
Paused flow executions are stored between requests in a FlowExecutionRepository.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="flow-execution-repository" type="flowExecutionRepositoryType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Configures the FlowExecutionRepository used by this executor to persist flow executions.
Specify when you want to tune the attributes of the default repository implementation.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="flow-execution-attributes" type="flowExecutionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Meta attributes to assign new flow executions launched by this executor. These attributes will be associated with all flow executions,
regardless of their underlying flow definition. Such attributes may influence flow execution behavior at runtime.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="flow-execution-listeners" type="flowExecutionListenersType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers listeners that observe the lifecycle of flow executions launched by this executor.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
<xsd:attribute name="flow-registry" type="xsd:string" default="flowRegistry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A reference to the FlowRegistry bean containing the flows eligible for execution by this executor.
The default value 'flowRegistry'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="flowExecutionRepositoryType">
<xsd:attribute name="max-executions" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of persistent flow executions allowed per user session. The default is 5.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-execution-snapshots" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of history snapshots allowed per flow execution. The default is 30.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowExecutionListenersType">
<xsd:sequence>
<xsd:element name="listener" type="flowExecutionListenerType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Registers a listener to observe the execution lifecycle of one or more flows.
The flow definitions this listener observes may be restricted by specifying listener criteria.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="flowExecutionListenerType">
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A reference to the flow execution listener implementation to register.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="criteria" type="xsd:string" default="*">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The criteria that determines the flow definitions your listener should observe, delimited by commas or '*' for "all".
Example: 'flow1,flow2,flow3'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowExecutionAttributesType">
<xsd:sequence>
<xsd:element name="always-redirect-on-pause" type="alwaysRedirectOnPauseType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Determines if flow executions always redirect after they pause.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A single flow execution meta attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="alwaysRedirectOnPauseType">
<xsd:attribute name="value" type="xsd:boolean" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
true = always redirect on pause; false = do not, only redirect when explicitly instructed by the flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowDefinitionAttributesType">
<xsd:sequence>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A single flow definition meta attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="attributeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute's type, used to perform a from-string type conversion if specified.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="value" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute value, subject to a type conversion if the 'type' attribute is defined.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -18,6 +18,7 @@ package org.springframework.webflow.engine.builder;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.Validator;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
@@ -70,6 +71,12 @@ public interface FlowBuilderContext {
*/
public ExpressionParser getExpressionParser();
/**
* Returns the Validator instance to use for validating a model.
* @return the validator
*/
public Validator getValidator();
/**
* Returns the application context hosting the flow system.
* @return the application context

View File

@@ -18,6 +18,7 @@ package org.springframework.webflow.engine.builder;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.validation.Validator;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
@@ -34,10 +35,11 @@ public interface ViewFactoryCreator {
* @param expressionParser an optional expression parser to use to resolve view expressions
* @param conversionService an optional conversion service to use to format text values
* @param binderConfiguration information on how the rendered view binds to a model that provides its data
* @param validator a global validator to invoke
* @return the view factory
*/
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration);
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator);
/**
* Get the default id of the view to render in the provided view state by convention.

View File

@@ -68,9 +68,9 @@ import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.VariableValueFactory;
import org.springframework.webflow.engine.ViewVariable;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.FlowBuilderException;
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder;
import org.springframework.webflow.engine.model.AbstractActionModel;
import org.springframework.webflow.engine.model.AbstractMappingModel;
@@ -372,8 +372,8 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
if (isFlowInDevelopment()) {
builder.addPropertyValue("cacheSeconds", "0");
}
flowContext.registerBeanDefinition(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, builder
.getBeanDefinition());
flowContext.registerBeanDefinition(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME,
builder.getBeanDefinition());
}
}
}
@@ -537,8 +537,10 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
}
MutableAttributeMap attributes = parseMetaAttributes(state.getAttributes());
if (state.getModel() != null) {
attributes.put("model", getLocalContext().getExpressionParser().parseExpression(state.getModel(),
new FluentParserContext().evaluate(RequestContext.class)));
attributes.put(
"model",
getLocalContext().getExpressionParser().parseExpression(state.getModel(),
new FluentParserContext().evaluate(RequestContext.class)));
}
parseAndPutSecured(state.getSecured(), attributes);
getLocalContext().getFlowArtifactFactory().createViewState(state.getId(), flow,
@@ -626,7 +628,8 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
private ViewFactory createViewFactory(Expression viewId, BinderModel binderModel) {
BinderConfiguration binderConfiguration = createBinderConfiguration(binderModel);
return getLocalContext().getViewFactoryCreator().createViewFactory(viewId,
getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderConfiguration);
getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderConfiguration,
getLocalContext().getValidator());
}
private BinderConfiguration createBinderConfiguration(BinderModel binderModel) {
@@ -868,8 +871,8 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
private Action parseRenderAction(RenderModel render) {
String[] fragmentExpressionStrings = StringUtils.commaDelimitedListToStringArray(render.getFragments());
fragmentExpressionStrings = StringUtils.trimArrayElements(fragmentExpressionStrings);
ParserContext context = new FluentParserContext().template().evaluate(RequestContext.class).expectResult(
String.class);
ParserContext context = new FluentParserContext().template().evaluate(RequestContext.class)
.expectResult(String.class);
Expression[] fragments = new Expression[fragmentExpressionStrings.length];
for (int i = 0; i < fragmentExpressionStrings.length; i++) {
String fragment = fragmentExpressionStrings[i];

View File

@@ -19,6 +19,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.validation.Validator;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
@@ -55,7 +56,7 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
public FlowDefinitionLocator getFlowDefinitionLocator() {
if (localFlowContext.containsLocalBean("flowRegistry")) {
return (FlowDefinitionLocator) localFlowContext.getBean("flowRegistry", FlowDefinitionLocator.class);
return localFlowContext.getBean("flowRegistry", FlowDefinitionLocator.class);
} else {
return parent.getFlowDefinitionLocator();
}
@@ -63,7 +64,7 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
public FlowArtifactFactory getFlowArtifactFactory() {
if (localFlowContext.containsLocalBean("flowArtifactFactory")) {
return (FlowArtifactFactory) localFlowContext.getBean("flowArtifactFactory", FlowArtifactFactory.class);
return localFlowContext.getBean("flowArtifactFactory", FlowArtifactFactory.class);
} else {
return parent.getFlowArtifactFactory();
}
@@ -71,7 +72,7 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
public ConversionService getConversionService() {
if (localFlowContext.containsLocalBean("conversionService")) {
return (ConversionService) localFlowContext.getBean("conversionService", ConversionService.class);
return localFlowContext.getBean("conversionService", ConversionService.class);
} else {
return parent.getConversionService();
}
@@ -79,7 +80,7 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
public ViewFactoryCreator getViewFactoryCreator() {
if (localFlowContext.containsLocalBean("viewFactoryCreator")) {
return (ViewFactoryCreator) localFlowContext.getBean("viewFactoryCreator", ViewFactoryCreator.class);
return localFlowContext.getBean("viewFactoryCreator", ViewFactoryCreator.class);
} else {
return parent.getViewFactoryCreator();
}
@@ -87,10 +88,18 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
public ExpressionParser getExpressionParser() {
if (localFlowContext.containsLocalBean("expressionParser")) {
return (ExpressionParser) localFlowContext.getBean("expressionParser", ExpressionParser.class);
return localFlowContext.getBean("expressionParser", ExpressionParser.class);
} else {
return parent.getExpressionParser();
}
}
public Validator getValidator() {
if (localFlowContext.containsLocalBean("validator")) {
return localFlowContext.getBean("validator", Validator.class);
} else {
return parent.getValidator();
}
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.binding.convert.service.GenericConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
@@ -104,6 +105,10 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
return flowBuilderServices.getApplicationContext();
}
public Validator getValidator() {
return flowBuilderServices.getValidator();
}
/**
* Factory method that creates the conversion service the flow builder will use. Subclasses may override. The
* default implementation registers Web Flow-specific converters thought to be useful for most builder

View File

@@ -22,6 +22,7 @@ import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.State;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
@@ -65,6 +66,12 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
*/
private ExpressionParser expressionParser;
/**
* A Validator instance to use for validating a model declared on a view state. A JSR-303 validation adapter is
* installed by default if a JSR-303 provider is present on the classpath.
*/
private Validator validator;
/**
* The Spring application context that provides access to the services of the application.
*/
@@ -107,6 +114,14 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
this.expressionParser = expressionParser;
}
public Validator getValidator() {
return validator;
}
public void setValidator(Validator validator) {
this.validator = validator;
}
public boolean getDevelopment() {
return development;
}

View File

@@ -27,6 +27,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.webflow.engine.builder.BinderConfiguration;
@@ -169,7 +170,7 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
}
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
if (useSpringBeanBinding) {
expressionParser = new BeanWrapperExpressionParser(conversionService);
}
@@ -181,6 +182,7 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
if (StringUtils.hasText(fieldMarkerPrefix)) {
viewFactory.setFieldMarkerPrefix(fieldMarkerPrefix);
}
viewFactory.setValidator(validator);
return viewFactory;
}

View File

@@ -45,6 +45,7 @@ import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.util.WebUtils;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.ParameterMap;
@@ -78,6 +79,8 @@ public abstract class AbstractMvcView implements View {
private ConversionService conversionService;
private Validator validator;
private String fieldMarkerPrefix = "_";
private String eventIdParameterName = "_eventId";
@@ -118,6 +121,10 @@ public abstract class AbstractMvcView implements View {
this.conversionService = conversionService;
}
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Sets the configuration describing how this view should bind to its model to access data for rendering.
* @param binderConfiguration the model binder configuration
@@ -534,12 +541,12 @@ public abstract class AbstractMvcView implements View {
private Map flowScopes() {
if (requestContext.getCurrentState().isViewState()) {
return requestContext.getConversationScope().union(requestContext.getFlowScope()).union(
requestContext.getViewScope()).union(requestContext.getFlashScope()).union(
requestContext.getRequestScope()).asMap();
return requestContext.getConversationScope().union(requestContext.getFlowScope())
.union(requestContext.getViewScope()).union(requestContext.getFlashScope())
.union(requestContext.getRequestScope()).asMap();
} else {
return requestContext.getConversationScope().union(requestContext.getFlowScope()).union(
requestContext.getFlashScope()).union(requestContext.getRequestScope()).asMap();
return requestContext.getConversationScope().union(requestContext.getFlowScope())
.union(requestContext.getFlashScope()).union(requestContext.getRequestScope()).asMap();
}
}
@@ -605,8 +612,8 @@ public abstract class AbstractMvcView implements View {
String field = error.getMapping().getTargetExpression().getExpressionString();
Class fieldType = error.getMapping().getTargetExpression().getValueType(getModelObject());
String[] messageCodes = messageCodesResolver.resolveMessageCodes(error.getCode(), model, field, fieldType);
return new MessageBuilder().error().source(field).codes(messageCodes).resolvableArg(field).defaultText(
error.getCode() + " on " + field).build();
return new MessageBuilder().error().source(field).codes(messageCodes).resolvableArg(field)
.defaultText(error.getCode() + " on " + field).build();
}
private Boolean getValidateAttribute(TransitionDefinition transition) {
@@ -621,8 +628,10 @@ public abstract class AbstractMvcView implements View {
if (logger.isDebugEnabled()) {
logger.debug("Validating model");
}
new ValidationHelper(model, requestContext, eventId, getModelExpression().getExpressionString(),
expressionParser, messageCodesResolver, mappingResults).validate();
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression()
.getExpressionString(), expressionParser, messageCodesResolver, mappingResults);
helper.setValidator(validator);
helper.validate();
}
private static class PropertyNotFoundError implements MappingResultsCriteria {

View File

@@ -20,6 +20,7 @@ import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.util.StringUtils;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
@@ -40,6 +41,8 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
private ConversionService conversionService;
private Validator validator;
private BinderConfiguration binderConfiguration;
private String eventIdParameterName;
@@ -75,6 +78,10 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
this.fieldMarkerPrefix = fieldMarkerPrefix;
}
public void setValidator(Validator validator) {
this.validator = validator;
}
public View getView(RequestContext context) {
String viewId = (String) this.viewId.getValue(context);
org.springframework.web.servlet.View view = viewResolver.resolveView(viewId, context);
@@ -83,6 +90,7 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
mvcView.setConversionService(conversionService);
mvcView.setBinderConfiguration(binderConfiguration);
mvcView.setMessageCodesResolver(messageCodesResolver);
mvcView.setValidator(validator);
if (StringUtils.hasText(eventIdParameterName)) {
mvcView.setEventIdParameterName(eventIdParameterName);
}

View File

@@ -22,6 +22,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.execution.Event;
@@ -38,7 +39,7 @@ import org.springframework.webflow.execution.ViewFactory;
class MockViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
return new MockViewFactory(viewId);
}

View File

@@ -64,6 +64,8 @@ public class ValidationHelper {
private final MappingResults mappingResults;
private Validator validator;
/**
* Create a throwaway validation helper object. Validation is invoked by the {@link #validate()} method.
* <p>
@@ -94,14 +96,24 @@ public class ValidationHelper {
this.mappingResults = mappingResults;
}
/**
* Configure a {@link Validator} to apply to the model.
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Invoke the validators available by convention.
*/
public void validate() {
if (this.validator != null) {
invokeValidatorDefaultValidateMethod(model, this.validator);
}
invokeModelValidationMethod(model);
Object validator = getModelValidator();
if (validator != null) {
invokeModelValidator(model, validator);
Object modelValidator = getModelValidator();
if (modelValidator != null) {
invokeModelValidator(model, modelValidator);
}
}
@@ -215,8 +227,8 @@ public class ValidationHelper {
// web flow 2.0.0 to 2.0.3 compatibility only [to remove in web flow 3]
validateMethod = findValidationMethod(model, validator, methodName, MessageContext.class);
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
requestContext.getMessageContext() });
ReflectionUtils.invokeMethod(validateMethod, validator,
new Object[] { model, requestContext.getMessageContext() });
return true;
}
return false;
@@ -224,13 +236,21 @@ public class ValidationHelper {
private boolean invokeValidatorDefaultValidateMethod(Object model, Object validator) {
if (validator instanceof Validator) {
// supports existing validators
// Spring Framework Validator type
Validator springValidator = (Validator) validator;
if (logger.isDebugEnabled()) {
logger.debug("Invoking Spring Validator '" + ClassUtils.getShortName(validator.getClass()) + "'");
}
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName,
model, expressionParser, messageCodesResolver, mappingResults);
((Validator) validator).validate(model, errors);
if (springValidator.supports(model.getClass())) {
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName,
model, expressionParser, messageCodesResolver, mappingResults);
springValidator.validate(model, errors);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Spring Validator '" + ClassUtils.getShortName(validator.getClass())
+ "' doesn't support model class " + model.getClass());
}
}
return true;
}
// preferred

View File

@@ -1 +1,3 @@
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd=org/springframework/webflow/config/spring-webflow-config-2.0.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd=org/springframework/webflow/config/spring-webflow-config-2.0.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd=org/springframework/webflow/config/spring-webflow-config-2.3.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd=org/springframework/webflow/config/spring-webflow-config-2.3.xsd

View File

@@ -0,0 +1,15 @@
package org.springframework.webflow.config;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class EmptySpringValidator implements Validator {
public boolean supports(Class<?> clazz) {
return false;
}
public void validate(Object target, Errors errors) {
}
}

View File

@@ -14,6 +14,7 @@ import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.spel.SpringELExpressionParser;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.validation.Validator;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
@@ -35,6 +36,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof DefaultConversionService);
assertNull(builderServices.getValidator());
assertFalse(builderServices.getDevelopment());
}
@@ -44,6 +46,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getValidator() instanceof EmptySpringValidator);
assertTrue(builderServices.getDevelopment());
}
@@ -54,13 +57,14 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser);
assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator);
assertNull(builderServices.getValidator());
assertFalse(builderServices.getDevelopment());
}
public static class TestViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -4,16 +4,18 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<webflow:flow-builder-services id="flowBuilderServicesDefault"/>
<webflow:flow-builder-services id="flowBuilderServicesAllCustom"
expression-parser="customExpressionParser"
view-factory-creator="customViewFactoryCreator"
conversion-service="customConversionService" development="true" />
conversion-service="customConversionService"
validator="customValidator"
development="true" />
<webflow:flow-builder-services id="flowBuilderServicesConversionServiceCustom"
conversion-service="customConversionService" />
@@ -28,4 +30,6 @@
<bean id="customConversionService" class="org.springframework.webflow.config.FlowBuilderServicesBeanDefinitionParserTests$TestConversionService"/>
<bean id="customValidator" class="org.springframework.webflow.config.EmptySpringValidator" />
</beans>

View File

@@ -4,9 +4,9 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-repository max-executions="1" max-execution-snapshots="2"/>

View File

@@ -4,9 +4,9 @@
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<webflow:flow-registry id="flowRegistry" parent="parentRegistry">
<webflow:flow-location id="flow" path="org/springframework/webflow/config/flow.xml">