Add support for partial JSR-303 bean validation
This change makes it possible to use validation hints in conjunction with declarative, JSR-303 validation. Such hints may be defined in an attribute of the view state where the view state id itself is also interpeted as a hint. A custom ValidationHintResolver can be plugged in if necessary. Issue: SWF-1453
This commit is contained in:
@@ -190,6 +190,7 @@ project('spring-webflow') {
|
||||
compile("org.springframework:spring-webmvc-portlet:$springVersion", optional)
|
||||
compile("org.springframework.security:spring-security-core:$springSecurityVersion", optional)
|
||||
|
||||
testCompile "javax.validation:validation-api:1.0.0.GA"
|
||||
testCompile "org.apache.openjpa:openjpa:1.1.0"
|
||||
testCompile "org.apache.openjpa:openjpa-lib:1.1.0"
|
||||
testCompile "org.apache.openjpa:openjpa-persistence:1.1.0"
|
||||
@@ -385,4 +386,4 @@ configure(rootProject) {
|
||||
"set GRADLE_OPTS=$gradleBatOpts %GRADLE_OPTS%\nset DEFAULT_JVM_OPTS=")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ 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;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* A {@link ViewFactoryCreator} implementation for creating instances of a JSF-specific {@link ViewFactory}.
|
||||
@@ -37,7 +38,8 @@ public class JsfViewFactoryCreator implements ViewFactoryCreator {
|
||||
private Lifecycle lifecycle;
|
||||
|
||||
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver resolver) {
|
||||
return new JsfViewFactory(viewIdExpression, getLifecycle());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.webflow.engine.builder.ViewFactoryCreator;
|
||||
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
|
||||
import org.springframework.webflow.execution.ViewFactory;
|
||||
import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase {
|
||||
|
||||
@@ -77,7 +78,8 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
|
||||
public static class TestViewFactoryCreator implements ViewFactoryCreator {
|
||||
|
||||
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver validationHintResolver) {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* {@link BeanDefinitionParser} for the <code><flow-builder-services></code> tag.
|
||||
*
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
|
||||
@@ -53,6 +53,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
|
||||
private static final String EXPRESSION_PARSER_PROPERTY = "expressionParser";
|
||||
private static final String VIEW_FACTORY_CREATOR_PROPERTY = "viewFactoryCreator";
|
||||
private static final String VALIDATOR_PROPERTY = "validator";
|
||||
private static final String VALIDATION_HINT_RESOLVER_PROPERTY = "validationHintResolver";
|
||||
|
||||
protected String getBeanClassName(Element element) {
|
||||
return FLOW_BUILDER_SERVICES_CLASS_NAME;
|
||||
@@ -68,6 +69,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
|
||||
parseExpressionParser(element, parserContext, builder);
|
||||
parseViewFactoryCreator(element, parserContext, builder);
|
||||
parseValidator(element, parserContext, builder);
|
||||
parseValidationHintResolver(element, parserContext, builder);
|
||||
parseDevelopment(element, builder);
|
||||
|
||||
parserContext.popAndRegisterContainingComponent();
|
||||
@@ -115,6 +117,13 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
|
||||
}
|
||||
}
|
||||
|
||||
private void parseValidationHintResolver(Element element, ParserContext context, BeanDefinitionBuilder definitionBuilder) {
|
||||
String resolver = element.getAttribute(VALIDATION_HINT_RESOLVER_PROPERTY);
|
||||
if (StringUtils.hasText(resolver)) {
|
||||
definitionBuilder.addPropertyReference(VALIDATION_HINT_RESOLVER_PROPERTY, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDevelopment(Element element, BeanDefinitionBuilder definitionBuilder) {
|
||||
String development = element.getAttribute(DEVELOPMENT_ATTR);
|
||||
if (StringUtils.hasText(development)) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* Provides services needed to a direct a flow builder through building a flow definition.
|
||||
@@ -77,6 +78,11 @@ public interface FlowBuilderContext {
|
||||
*/
|
||||
public Validator getValidator();
|
||||
|
||||
/**
|
||||
* Return the {@link ValidationHintResolver}.
|
||||
*/
|
||||
public ValidationHintResolver getValidationHintResolver();
|
||||
|
||||
/**
|
||||
* Returns the application context hosting the flow system.
|
||||
* @return the application context
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.binding.expression.ExpressionParser;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.webflow.execution.View;
|
||||
import org.springframework.webflow.execution.ViewFactory;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* A factory for ViewFactory objects. This is an SPI interface and conceals specific types of view factories from the
|
||||
@@ -36,10 +37,12 @@ public interface ViewFactoryCreator {
|
||||
* @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
|
||||
* @param validationHintResolver a custom ValidationHintResolver to use
|
||||
* @return the view factory
|
||||
*/
|
||||
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator);
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver validationHintResolver);
|
||||
|
||||
/**
|
||||
* Get the default id of the view to render in the provided view state by convention.
|
||||
|
||||
@@ -114,7 +114,7 @@ import org.springframework.webflow.security.SecurityRule;
|
||||
|
||||
/**
|
||||
* Builds a runtime {@link Flow} definition object from a {@link FlowModel}.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowModelFlowBuilder extends AbstractFlowBuilder {
|
||||
@@ -535,6 +535,11 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
|
||||
getLocalContext().getExpressionParser().parseExpression(state.getModel(),
|
||||
new FluentParserContext().evaluate(RequestContext.class)));
|
||||
}
|
||||
if (state.getValidationHints() != null) {
|
||||
attributes.put("validationHints",
|
||||
getLocalContext().getExpressionParser().parseExpression(state.getValidationHints(),
|
||||
new FluentParserContext().evaluate(RequestContext.class)));
|
||||
}
|
||||
parseAndPutSecured(state.getSecured(), attributes);
|
||||
getLocalContext().getFlowArtifactFactory().createViewState(state.getId(), flow,
|
||||
parseViewVariables(state.getVars()), parseActions(state.getOnEntryActions()), viewFactory, redirect,
|
||||
@@ -622,7 +627,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
|
||||
BinderConfiguration binderConfiguration = createBinderConfiguration(binderModel);
|
||||
return getLocalContext().getViewFactoryCreator().createViewFactory(viewId,
|
||||
getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderConfiguration,
|
||||
getLocalContext().getValidator());
|
||||
getLocalContext().getValidator(), getLocalContext().getValidationHintResolver());
|
||||
}
|
||||
|
||||
private BinderConfiguration createBinderConfiguration(BinderModel binderModel) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
|
||||
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
|
||||
import org.springframework.webflow.engine.builder.FlowBuilderContext;
|
||||
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* A builder context that delegates to a flow-local bean factory for builder services. Such builder services override
|
||||
@@ -102,4 +103,12 @@ class LocalFlowBuilderContext implements FlowBuilderContext {
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationHintResolver getValidationHintResolver() {
|
||||
if (localFlowContext.containsLocalBean("validationHintResolver")) {
|
||||
return localFlowContext.getBean("validationHintResolver", ValidationHintResolver.class);
|
||||
} else {
|
||||
return parent.getValidationHintResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
|
||||
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
|
||||
import org.springframework.webflow.engine.builder.FlowBuilderContext;
|
||||
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* Generic implementation of a flow builder context, suitable for use by most flow assembly systems.
|
||||
@@ -109,6 +110,10 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
|
||||
return flowBuilderServices.getValidator();
|
||||
}
|
||||
|
||||
public ValidationHintResolver getValidationHintResolver() {
|
||||
return flowBuilderServices.getValidationHintResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -28,17 +28,18 @@ import org.springframework.webflow.engine.State;
|
||||
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
|
||||
import org.springframework.webflow.engine.builder.FlowBuilderContext;
|
||||
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* A simple holder for configuring the services used by flow builders. These services are exposed to a builder in a
|
||||
* {@link FlowBuilderContext}.
|
||||
*
|
||||
*
|
||||
* Note this class does not attempt to default any service implementations other than the {@link FlowArtifactFactory},
|
||||
* which is more like builder helper objects than a service. It is expected clients inject non-null references to
|
||||
* concrete service implementations appropriate for their environment.
|
||||
*
|
||||
*
|
||||
* @see FlowBuilderContextImpl
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowBuilderServices implements ApplicationContextAware, InitializingBean {
|
||||
@@ -72,6 +73,11 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
|
||||
*/
|
||||
private Validator validator;
|
||||
|
||||
/**
|
||||
* A ValidationHintResolver for resolving string based validation hints.
|
||||
*/
|
||||
private ValidationHintResolver validationHintResolver;
|
||||
|
||||
/**
|
||||
* The Spring application context that provides access to the services of the application.
|
||||
*/
|
||||
@@ -122,6 +128,14 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public ValidationHintResolver getValidationHintResolver() {
|
||||
return validationHintResolver;
|
||||
}
|
||||
|
||||
public void setValidationHintResolver(ValidationHintResolver validationHintResolver) {
|
||||
this.validationHintResolver = validationHintResolver;
|
||||
}
|
||||
|
||||
public boolean getDevelopment() {
|
||||
return development;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package org.springframework.webflow.engine.model;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Contains basic merge functions that can be utilized by other models.
|
||||
*
|
||||
*
|
||||
* @author Scott Andrews
|
||||
*/
|
||||
public abstract class AbstractModel implements Model {
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Model support for view states.
|
||||
*
|
||||
*
|
||||
* @author Scott Andrews
|
||||
*/
|
||||
public class ViewStateModel extends AbstractTransitionableStateModel {
|
||||
@@ -35,6 +35,8 @@ public class ViewStateModel extends AbstractTransitionableStateModel {
|
||||
|
||||
private String model;
|
||||
|
||||
private String validationHints;
|
||||
|
||||
private LinkedList<VarModel> vars;
|
||||
|
||||
private BinderModel binder;
|
||||
@@ -70,11 +72,32 @@ public class ViewStateModel extends AbstractTransitionableStateModel {
|
||||
setRedirect(merge(getRedirect(), state.getRedirect()));
|
||||
setPopup(merge(getPopup(), state.getPopup()));
|
||||
setModel(merge(getModel(), state.getModel()));
|
||||
setValidationHints(mergeValidationHints(getValidationHints(), state.getValidationHints()));
|
||||
setVars(merge(getVars(), state.getVars(), false));
|
||||
setBinder((BinderModel) merge(getBinder(), state.getBinder()));
|
||||
setOnRenderActions(merge(getOnRenderActions(), state.getOnRenderActions(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge validation hints by taking the union of both with the child validation hints listed first.
|
||||
* @param child child validation hints
|
||||
* @param parent parent validation hints
|
||||
* @return the merged hints or {@code null}
|
||||
*/
|
||||
private String mergeValidationHints(String child, String parent) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (StringUtils.hasText(child)) {
|
||||
sb.append(child);
|
||||
}
|
||||
if (StringUtils.hasText(parent)) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(parent);
|
||||
}
|
||||
return (sb.length() > 0) ? sb.toString() : null;
|
||||
}
|
||||
|
||||
public Model createCopy() {
|
||||
ViewStateModel copy = new ViewStateModel(getId());
|
||||
super.fillCopy(copy);
|
||||
@@ -82,6 +105,7 @@ public class ViewStateModel extends AbstractTransitionableStateModel {
|
||||
copy.setRedirect(redirect);
|
||||
copy.setPopup(popup);
|
||||
copy.setModel(model);
|
||||
copy.setValidationHints(validationHints);
|
||||
copy.setVars(copyList(vars));
|
||||
copy.setBinder((BinderModel) copy(binder));
|
||||
copy.setOnRenderActions(copyList(onRenderActions));
|
||||
@@ -160,6 +184,19 @@ public class ViewStateModel extends AbstractTransitionableStateModel {
|
||||
}
|
||||
}
|
||||
|
||||
public String getValidationHints() {
|
||||
return validationHints;
|
||||
}
|
||||
|
||||
public void setValidationHints(String validationHints) {
|
||||
if (StringUtils.hasText(validationHints)) {
|
||||
this.validationHints = validationHints;
|
||||
}
|
||||
else {
|
||||
this.validationHints = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vars
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.xml.sax.SAXException;
|
||||
* EntityResolver implementation for the Spring Web Flow XML Schema. This will load the XSD from the classpath.
|
||||
* <p>
|
||||
* The xmlns of the XSD expected to be resolved:
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* <?xml version="1.0" encoding="UTF-8"?>
|
||||
* <flow xmlns="http://www.springframework.org/schema/webflow"
|
||||
@@ -35,13 +35,13 @@ import org.xml.sax.SAXException;
|
||||
* xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||
* http://www.springframework.org/schema/webflow/spring-webflow.xsd">
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author Erwin Vervaet
|
||||
* @author Ben Hale
|
||||
*/
|
||||
class WebFlowEntityResolver implements EntityResolver {
|
||||
|
||||
private static final String[] WEBFLOW_VERSIONS = new String[] { "spring-webflow-2.0" };
|
||||
private static final String[] WEBFLOW_VERSIONS = new String[] { "spring-webflow-2.4", "spring-webflow-2.0" };
|
||||
|
||||
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
|
||||
if (systemId != null && systemId.indexOf("spring-webflow.xsd") > -1) {
|
||||
|
||||
@@ -60,7 +60,7 @@ import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Builds a flow model from a XML-based flow definition resource.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Scott Andrews
|
||||
*/
|
||||
@@ -570,6 +570,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder {
|
||||
state.setRedirect(element.getAttribute("redirect"));
|
||||
state.setPopup(element.getAttribute("popup"));
|
||||
state.setModel(element.getAttribute("model"));
|
||||
state.setValidationHints(element.getAttribute("validation-hints"));
|
||||
state.setVars(parseVars(element));
|
||||
state.setBinder(parseBinder(element));
|
||||
state.setOnRenderActions(parseOnRenderActions(element));
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* Defines the XmlFlowModelBuilder, for building FlowModels from XML-based resources.
|
||||
*
|
||||
* <p>This package also contains the definition of the XML-based flow definition language, defined within
|
||||
* {@code spring-webflow-2.0.xsd}. See this schema for a detailed description of language elements.
|
||||
* {@code spring-webflow-2.4.xsd}. See this schema for a detailed description of language elements.
|
||||
*/
|
||||
package org.springframework.webflow.engine.model.builder.xml;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.webflow.mvc.portlet.PortletMvcViewFactory;
|
||||
import org.springframework.webflow.mvc.servlet.ServletMvcViewFactory;
|
||||
import org.springframework.webflow.mvc.view.AbstractMvcViewFactory;
|
||||
import org.springframework.webflow.mvc.view.FlowViewResolver;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
import org.springframework.webflow.validation.WebFlowMessageCodesResolver;
|
||||
|
||||
/**
|
||||
@@ -49,12 +50,12 @@ import org.springframework.webflow.validation.WebFlowMessageCodesResolver;
|
||||
* By default, this implementation creates view factories that resolve their views by loading flow-relative resources,
|
||||
* such as .jsp templates located in a flow working directory. This class also supports rendering views resolved by
|
||||
* pre-existing Spring MVC {@link ViewResolver view resolvers}.
|
||||
*
|
||||
*
|
||||
* @see ServletMvcViewFactory
|
||||
* @see PortletMvcViewFactory
|
||||
* @see FlowResourceFlowViewResolver
|
||||
* @see DelegatingFlowViewResolver
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Scott Andrews
|
||||
*/
|
||||
@@ -170,7 +171,8 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
|
||||
}
|
||||
|
||||
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver validationHintResolver) {
|
||||
if (useSpringBeanBinding) {
|
||||
expressionParser = new BeanWrapperExpressionParser(conversionService);
|
||||
}
|
||||
@@ -183,6 +185,7 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
|
||||
viewFactory.setFieldMarkerPrefix(fieldMarkerPrefix);
|
||||
}
|
||||
viewFactory.setValidator(validator);
|
||||
viewFactory.setValidationHintResolver(validationHintResolver);
|
||||
return viewFactory;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.binding.message.MessageBuilder;
|
||||
import org.springframework.binding.message.MessageResolver;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.Validator;
|
||||
@@ -52,14 +53,17 @@ import org.springframework.webflow.definition.TransitionDefinition;
|
||||
import org.springframework.webflow.engine.builder.BinderConfiguration;
|
||||
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
|
||||
import org.springframework.webflow.execution.Event;
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
import org.springframework.webflow.execution.FlowExecutionKey;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.View;
|
||||
import org.springframework.webflow.validation.BeanValidationHintResolver;
|
||||
import org.springframework.webflow.validation.ValidationHelper;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* Base view implementation for the Spring Web MVC Servlet and Spring Web MVC Portlet frameworks.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class AbstractMvcView implements View {
|
||||
@@ -94,6 +98,8 @@ public abstract class AbstractMvcView implements View {
|
||||
|
||||
private boolean userEventProcessed;
|
||||
|
||||
private ValidationHintResolver validationHintResolver = new BeanValidationHintResolver();
|
||||
|
||||
/**
|
||||
* Creates a new MVC view.
|
||||
* @param view the Spring MVC view to render
|
||||
@@ -124,6 +130,12 @@ public abstract class AbstractMvcView implements View {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public void setValidationHintResolver(ValidationHintResolver validationHintResolver) {
|
||||
if (validationHintResolver != null) {
|
||||
this.validationHintResolver = validationHintResolver;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configuration describing how this view should bind to its model to access data for rendering.
|
||||
* @param binderConfiguration the model binder configuration
|
||||
@@ -356,7 +368,7 @@ public abstract class AbstractMvcView implements View {
|
||||
* considered. In the absence of binding configuration all request parameters will be used to update matching fields
|
||||
* on the model.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param model the model to be updated
|
||||
* @return an instance of MappingResults with information about the results of the binding.
|
||||
*/
|
||||
@@ -380,7 +392,7 @@ public abstract class AbstractMvcView implements View {
|
||||
* parameter. If there is no matching incoming request parameter, a special mapping is created that will set the
|
||||
* target field on the model to an empty value (typically null).
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param mapper the mapper to which mappings will be added
|
||||
* @param parameterNames the request parameters
|
||||
* @param model the model
|
||||
@@ -410,7 +422,7 @@ public abstract class AbstractMvcView implements View {
|
||||
* converters are supported for backwards compatibility only and will not result in use of the Spring 3 type
|
||||
* conversion system at runtime.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param mapper the mapper to add the mapping to
|
||||
* @param binding the binding element
|
||||
* @param model the model
|
||||
@@ -437,7 +449,7 @@ public abstract class AbstractMvcView implements View {
|
||||
/**
|
||||
* Add a {@link DefaultMapping} instance for all incoming request parameters except those having a special field
|
||||
* marker prefix. This method is used when binding configuration was not specified on the view.
|
||||
*
|
||||
*
|
||||
* @param mapper the mapper to add mappings to
|
||||
* @param parameterNames the request parameter names
|
||||
* @param model the model
|
||||
@@ -458,7 +470,7 @@ public abstract class AbstractMvcView implements View {
|
||||
/**
|
||||
* Adds a special {@link DefaultMapping} that results in setting the target field on the model to an empty value
|
||||
* (typically null).
|
||||
*
|
||||
*
|
||||
* @param mapper the mapper to add the mapping to
|
||||
* @param field the field for which a mapping is to be added
|
||||
* @param model the model
|
||||
@@ -480,7 +492,7 @@ public abstract class AbstractMvcView implements View {
|
||||
|
||||
/**
|
||||
* Adds a {@link DefaultMapping} between the given request parameter name and a matching model field.
|
||||
*
|
||||
*
|
||||
* @param mapper the mapper to add the mapping to
|
||||
* @param parameter the request parameter name
|
||||
* @param model the model
|
||||
@@ -574,6 +586,33 @@ public abstract class AbstractMvcView implements View {
|
||||
return (Expression) requestContext.getCurrentState().getAttributes().get("model");
|
||||
}
|
||||
|
||||
private Object[] getValidationHints(Object model) {
|
||||
Expression expr = (Expression) requestContext.getCurrentState().getAttributes().get("validationHints");
|
||||
String flowId = requestContext.getActiveFlow().getId();
|
||||
String stateId = requestContext.getCurrentState().getId();
|
||||
if (expr != null) {
|
||||
try {
|
||||
Object hintsValue = expr.getValue(requestContext);
|
||||
if (hintsValue instanceof String) {
|
||||
String[] hints = StringUtils.commaDelimitedListToStringArray((String) hintsValue);
|
||||
return validationHintResolver.resolveValidationHints(model, flowId, stateId, hints);
|
||||
}
|
||||
else if (hintsValue instanceof Object[]) {
|
||||
return (Object[]) hintsValue;
|
||||
}
|
||||
else {
|
||||
throw new FlowExecutionException(flowId, stateId,
|
||||
"Failed to resolve validation hints [" + hintsValue + "]");
|
||||
}
|
||||
}
|
||||
catch (EvaluationException e) {
|
||||
throw new FlowExecutionException(flowId, stateId,
|
||||
"Failed to resolve validation hints expression [" + expr + "]", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getEmptyValue(Class<?> fieldType) {
|
||||
if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
|
||||
// Special handling of boolean property.
|
||||
@@ -625,7 +664,8 @@ public abstract class AbstractMvcView implements View {
|
||||
}
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression()
|
||||
.getExpressionString(), expressionParser, messageCodesResolver, mappingResults);
|
||||
helper.setValidator(validator);
|
||||
helper.setValidator(this.validator);
|
||||
helper.setValidationHints(getValidationHints(model));
|
||||
helper.validate();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ import org.springframework.webflow.engine.builder.BinderConfiguration;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.View;
|
||||
import org.springframework.webflow.execution.ViewFactory;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* Base class for mvc view factories.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class AbstractMvcViewFactory implements ViewFactory {
|
||||
@@ -43,6 +44,8 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
|
||||
|
||||
private Validator validator;
|
||||
|
||||
private ValidationHintResolver validationHintResolver;
|
||||
|
||||
private BinderConfiguration binderConfiguration;
|
||||
|
||||
private String eventIdParameterName;
|
||||
@@ -82,6 +85,11 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public void setValidationHintResolver(ValidationHintResolver validationHintResolver) {
|
||||
this.validationHintResolver = validationHintResolver;
|
||||
}
|
||||
|
||||
|
||||
public View getView(RequestContext context) {
|
||||
String viewId = (String) this.viewId.getValue(context);
|
||||
org.springframework.web.servlet.View view = viewResolver.resolveView(viewId, context);
|
||||
@@ -91,6 +99,7 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
|
||||
mvcView.setBinderConfiguration(binderConfiguration);
|
||||
mvcView.setMessageCodesResolver(messageCodesResolver);
|
||||
mvcView.setValidator(validator);
|
||||
mvcView.setValidationHintResolver(validationHintResolver);
|
||||
if (StringUtils.hasText(eventIdParameterName)) {
|
||||
mvcView.setEventIdParameterName(eventIdParameterName);
|
||||
}
|
||||
|
||||
@@ -29,17 +29,19 @@ import org.springframework.webflow.execution.Event;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.View;
|
||||
import org.springframework.webflow.execution.ViewFactory;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
/**
|
||||
* A view factory creator that returns view factories that produce Mock View implementations that can be used to assert
|
||||
* that the correct view id was selected as part of a flow execution test.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
class MockViewFactoryCreator implements ViewFactoryCreator {
|
||||
|
||||
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver resolver) {
|
||||
return new MockViewFactory(viewId);
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ class MockViewFactoryCreator implements ViewFactoryCreator {
|
||||
* A Mock view implementation that simply holds a reference to a identifier for a view that should be rendered.
|
||||
* Useful to assert that the right view was selected as part of a flow execution test, without actually exercising
|
||||
* any real rendering logic.
|
||||
*
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
static class MockView implements View {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
|
||||
/**
|
||||
* A JSR-303 (Bean Validation) implementation of {@link ValidationHintResolver}
|
||||
* that resolves String-based hints to a {@code Class<?>} array.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 2.4
|
||||
*/
|
||||
public class BeanValidationHintResolver implements ValidationHintResolver {
|
||||
|
||||
/**
|
||||
* Resolve each hint as a fully qualified class name or the name of an inner
|
||||
* {@code Class} in the model type or the model or its parent types.
|
||||
*
|
||||
* @param model the model object
|
||||
* @param flowId the current flow id
|
||||
* @param stateId the current view state id
|
||||
* @param hints the hints to resolve
|
||||
*
|
||||
* @return the resolved hints or {@code null}
|
||||
* @throws FlowExecutionException if a hint is unresolved
|
||||
*
|
||||
* @see #handleUnresolvedHint(Object, String, String, String)
|
||||
*/
|
||||
public Class<?>[] resolveValidationHints(Object model, String flowId, String stateId, String[] hints)
|
||||
throws FlowExecutionException {
|
||||
|
||||
if (ObjectUtils.isEmpty(hints)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Class<?>> result = new ArrayList<Class<?>>();
|
||||
for (String hint : hints) {
|
||||
if (hint.equalsIgnoreCase("Default")) {
|
||||
hint = "javax.validation.groups.Default";
|
||||
}
|
||||
Class<?> resolvedHint = toClass(hint);
|
||||
if ((resolvedHint == null) && (model != null)) {
|
||||
resolvedHint = findInnerClass(model.getClass(), StringUtils.capitalize(hint));
|
||||
}
|
||||
if (resolvedHint == null) {
|
||||
resolvedHint = handleUnresolvedHint(model, flowId, stateId, hint);
|
||||
}
|
||||
if (resolvedHint != null) {
|
||||
result.add(resolvedHint);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toArray(new Class<?>[result.size()]);
|
||||
}
|
||||
|
||||
private Class<?> toClass(String hint) {
|
||||
try {
|
||||
return Class.forName(hint);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class<?> findInnerClass(Class<?> targetClass, String hint) {
|
||||
try {
|
||||
return Class.forName(targetClass.getName() + "$" + hint);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
Class<?> superClass = targetClass.getSuperclass();
|
||||
if (superClass != null) {
|
||||
return findInnerClass(superClass, hint);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a hint could not be resolved. This implementation raises a
|
||||
* {@link FlowExecutionException}.
|
||||
*
|
||||
* @param model the model object that will be validated using the hints
|
||||
* @param flowId the current flow id
|
||||
* @param stateId the current state id
|
||||
* @param hint the hint
|
||||
* @return
|
||||
*
|
||||
* @throws FlowExecutionException
|
||||
*/
|
||||
protected Class<?> handleUnresolvedHint(Object model, String flowId, String stateId, String hint)
|
||||
throws FlowExecutionException {
|
||||
|
||||
throw new FlowExecutionException(flowId, stateId, "Failed to resolve validation hint [" + hint + "]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008 the original author or authors.
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,12 +35,13 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.SmartValidator;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
|
||||
/**
|
||||
* A helper class the encapsulates conventions to invoke validation logic.
|
||||
*
|
||||
*
|
||||
* @author Scott Andrews
|
||||
* @author Canny Duck
|
||||
* @author Jeremy Grelle
|
||||
@@ -65,6 +66,9 @@ public class ValidationHelper {
|
||||
|
||||
private Validator validator;
|
||||
|
||||
private Object[] validationHints;
|
||||
|
||||
|
||||
/**
|
||||
* Create a throwaway validation helper object. Validation is invoked by the {@link #validate()} method.
|
||||
* <p>
|
||||
@@ -74,7 +78,7 @@ public class ValidationHelper {
|
||||
* <p>
|
||||
* For example: <code>model.validateEnterBookingDetails(VaticationContext)</code> or
|
||||
* <code>context.getBean("modelValidator").validateEnterBookingDetails(model, VaticationContext)</code>
|
||||
*
|
||||
*
|
||||
* @param model the object to validate
|
||||
* @param requestContext the context for the request
|
||||
* @param eventId the event triggering validation
|
||||
@@ -102,17 +106,24 @@ public class ValidationHelper {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide validation hints such as validation groups to use against a JSR-303 provider.
|
||||
*/
|
||||
public void setValidationHints(Object[] validationHints) {
|
||||
this.validationHints = validationHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the validators available by convention.
|
||||
*/
|
||||
public void validate() {
|
||||
if (this.validator != null) {
|
||||
invokeValidatorDefaultValidateMethod(model, this.validator);
|
||||
invokeValidatorDefaultValidateMethod(this.validator);
|
||||
}
|
||||
invokeModelValidationMethod(model);
|
||||
Object modelValidator = getModelValidator();
|
||||
if (modelValidator != null) {
|
||||
invokeModelValidator(model, modelValidator);
|
||||
invokeModelValidator(modelValidator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,12 +202,12 @@ public class ValidationHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void invokeModelValidator(Object model, Object validator) {
|
||||
invokeValidatorValidateMethodForCurrentState(model, validator);
|
||||
invokeValidatorDefaultValidateMethod(model, validator);
|
||||
private void invokeModelValidator(Object validator) {
|
||||
invokeValidatorValidateMethodForCurrentState(validator);
|
||||
invokeValidatorDefaultValidateMethod(validator);
|
||||
}
|
||||
|
||||
private boolean invokeValidatorValidateMethodForCurrentState(Object model, Object validator) {
|
||||
private boolean invokeValidatorValidateMethodForCurrentState(Object validator) {
|
||||
String methodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
|
||||
// preferred
|
||||
Method validateMethod = findValidationMethod(model, validator, methodName, ValidationContext.class);
|
||||
@@ -233,7 +244,7 @@ public class ValidationHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean invokeValidatorDefaultValidateMethod(Object model, Object validator) {
|
||||
private boolean invokeValidatorDefaultValidateMethod(Object validator) {
|
||||
if (validator instanceof Validator) {
|
||||
// Spring Framework Validator type
|
||||
Validator springValidator = (Validator) validator;
|
||||
@@ -243,7 +254,19 @@ public class ValidationHelper {
|
||||
if (springValidator.supports(model.getClass())) {
|
||||
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName,
|
||||
model, expressionParser, messageCodesResolver, mappingResults);
|
||||
springValidator.validate(model, errors);
|
||||
|
||||
if (this.validationHints != null) {
|
||||
if (springValidator instanceof SmartValidator) {
|
||||
((SmartValidator) springValidator).validate(model, errors, this.validationHints);
|
||||
}
|
||||
else {
|
||||
logger.warn("Validation hints provided but validator not an instance of SmartValidator: ["
|
||||
+ springValidator.getClass().getName() + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
springValidator.validate(model, errors);
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Spring Validator '" + ClassUtils.getShortName(validator.getClass())
|
||||
@@ -295,4 +318,5 @@ public class ValidationHelper {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.validation;
|
||||
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
|
||||
/**
|
||||
* A strategy for resolving String-based hints to Objects such as validation
|
||||
* groups against a JSR-303 provider.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 2.4
|
||||
*/
|
||||
public interface ValidationHintResolver {
|
||||
|
||||
/**
|
||||
* Resolve the given String hints. Implementations may raise a
|
||||
* {@link FlowExecutionException} if a hint cannot be resolved.
|
||||
*
|
||||
* @param model the model object that will be validated using the hints
|
||||
* @param flowId the current flow id
|
||||
* @param stateId the current state id
|
||||
* @param hints the hints to resolve
|
||||
*
|
||||
* @return an array of resolved hints
|
||||
*/
|
||||
Object[] resolveValidationHints(Object model, String flowId, String stateId, String[] hints);
|
||||
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
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
|
||||
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.4.xsd=org/springframework/webflow/config/spring-webflow-config-2.4.xsd
|
||||
http\://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd=org/springframework/webflow/config/spring-webflow-config-2.4.xsd
|
||||
@@ -12,7 +12,7 @@
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
Spring Web Flow Configuration Schema
|
||||
Authors: Keith Donald, Jeremy Grelle, Scott Andrews
|
||||
Authors: Keith Donald, Jeremy Grelle, Scott Andrews, Rossen Stoyanchev
|
||||
<br>
|
||||
A XML-based DSL for configuring the Spring Web Flow 2.0 system.
|
||||
]]>
|
||||
@@ -232,6 +232,16 @@ The custom ViewFactoryCreator implementation to use produce ViewFactories capabl
|
||||
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="validationHintResolver">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.webflow.validation.ValidationHintResolver">
|
||||
<![CDATA[
|
||||
The bean name of a ValidationHintResolver used to resolve String-based validation hints.
|
||||
This attribute is not required. The default implementation used is BeanValidationHintResolver.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
Spring Web Flow Schema
|
||||
Authors: Keith Donald, Erwin Vervaet, Scott Andrews
|
||||
Authors: Keith Donald, Erwin Vervaet, Scott Andrews, Rossen Stoyanchev
|
||||
<br>
|
||||
This schema defines Spring Web Flow's XML-based flow definition language.
|
||||
<br>
|
||||
@@ -523,6 +523,21 @@ Displays the view in a popup dialog. Default is false.
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The model object this view is bound to. Typically used as the source of form field values or other data input controls.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="validation-hints" type="expression">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
A comma-separated list of validation hints such as validation groups against a JSR-303 provider.
|
||||
Each hint is used to find an inner Class either in the Class of the model or its parent classes.
|
||||
For example, given org.example.MyModel with inner type MyGroup, the hint "MyGroup" can be used.
|
||||
The hint "default" is reserved as the default validation group, i.e. "javax.validation.groups.Default".
|
||||
A hint can also be a fully qualified class name.
|
||||
The validation hints string is evaluated as an expression before hints are resolved.
|
||||
The result of the expression can be a String or an Object[] with Class instances.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.webflow.engine.builder.ViewFactoryCreator;
|
||||
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
|
||||
import org.springframework.webflow.execution.ViewFactory;
|
||||
import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator;
|
||||
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||
|
||||
public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
|
||||
|
||||
@@ -47,6 +48,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
|
||||
assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator);
|
||||
assertTrue(builderServices.getConversionService() instanceof TestConversionService);
|
||||
assertTrue(builderServices.getValidator() instanceof EmptySpringValidator);
|
||||
assertTrue(builderServices.getValidationHintResolver() instanceof MyBeanValidationHintResolver);
|
||||
assertTrue(builderServices.getDevelopment());
|
||||
}
|
||||
|
||||
@@ -58,13 +60,15 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
|
||||
assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService);
|
||||
assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator);
|
||||
assertNull(builderServices.getValidator());
|
||||
assertNull(builderServices.getValidationHintResolver());
|
||||
assertFalse(builderServices.getDevelopment());
|
||||
}
|
||||
|
||||
public static class TestViewFactoryCreator implements ViewFactoryCreator {
|
||||
|
||||
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration, Validator validator) {
|
||||
ConversionService conversionService, BinderConfiguration binderConfiguration,
|
||||
Validator validator, ValidationHintResolver validationHintResolver) {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springframework.webflow.config;
|
||||
|
||||
import org.springframework.webflow.validation.BeanValidationHintResolver;
|
||||
|
||||
public class MyBeanValidationHintResolver extends BeanValidationHintResolver {
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
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-2.3.xsd">
|
||||
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.4.xsd">
|
||||
|
||||
<webflow:flow-builder-services id="flowBuilderServicesDefault"/>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
view-factory-creator="customViewFactoryCreator"
|
||||
conversion-service="customConversionService"
|
||||
validator="customValidator"
|
||||
validationHintResolver="customValidationHintResolver"
|
||||
development="true" />
|
||||
|
||||
<webflow:flow-builder-services id="flowBuilderServicesConversionServiceCustom"
|
||||
@@ -31,5 +32,7 @@
|
||||
<bean id="customConversionService" class="org.springframework.webflow.config.FlowBuilderServicesBeanDefinitionParserTests$TestConversionService"/>
|
||||
|
||||
<bean id="customValidator" class="org.springframework.webflow.config.EmptySpringValidator" />
|
||||
|
||||
<bean id="customValidationHintResolver" class="org.springframework.webflow.config.MyBeanValidationHintResolver" />
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -59,6 +59,7 @@ public class ViewStateModelTests extends TestCase {
|
||||
parent.setRedirect("true");
|
||||
parent.setPopup("true");
|
||||
parent.setModel("fooModel");
|
||||
parent.setValidationHints("foo");
|
||||
parent.setView("fooView");
|
||||
|
||||
LinkedList<TransitionModel> transitions = new LinkedList<TransitionModel>();
|
||||
@@ -89,6 +90,7 @@ public class ViewStateModelTests extends TestCase {
|
||||
assertEquals("true", child.getRedirect());
|
||||
assertEquals("true", child.getPopup());
|
||||
assertEquals("fooModel", child.getModel());
|
||||
assertEquals("foo", child.getValidationHints());
|
||||
assertEquals("fooView", child.getView());
|
||||
assertEquals("bar", child.getAttributes().get(0).getValue());
|
||||
assertEquals("foo", child.getBinder().getBindings().get(0).getProperty());
|
||||
|
||||
@@ -8,6 +8,13 @@ public class WebFlowEntityResolverTests extends TestCase {
|
||||
|
||||
private static final String PUBLIC_ID = "http://www.springframework.org/schema/webflow";
|
||||
|
||||
public void testResolve24() throws Exception {
|
||||
WebFlowEntityResolver resolver = new WebFlowEntityResolver();
|
||||
InputSource source = resolver.resolveEntity(PUBLIC_ID,
|
||||
"http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd");
|
||||
assertNotNull(source);
|
||||
}
|
||||
|
||||
public void testResolve20() throws Exception {
|
||||
WebFlowEntityResolver resolver = new WebFlowEntityResolver();
|
||||
InputSource source = resolver.resolveEntity(PUBLIC_ID,
|
||||
|
||||
@@ -146,6 +146,7 @@ public class XmlFlowModelBuilderTests extends TestCase {
|
||||
FlowModel flow = builder.getFlowModel();
|
||||
ViewStateModel model = (ViewStateModel) flow.getStates().get(0);
|
||||
assertEquals("formObject", model.getModel());
|
||||
assertEquals("foo,bar", model.getValidationHints());
|
||||
assertEquals("objectProperty", model.getBinder().getBindings().get(0).getProperty());
|
||||
assertEquals("customConverter", model.getBinder().getBindings().get(0).getConverter());
|
||||
}
|
||||
@@ -325,4 +326,17 @@ public class XmlFlowModelBuilderTests extends TestCase {
|
||||
execution.resume(context);
|
||||
assertTrue(((TestBeanValidator) action.getValidator()).getInvoked());
|
||||
}
|
||||
|
||||
public void testParseFlowValidationHints() {
|
||||
ClassPathResource res = new ClassPathResource("flow-validation-hints.xml", getClass());
|
||||
XmlFlowModelBuilder builder = new XmlFlowModelBuilder(res);
|
||||
DefaultFlowModelHolder holder = new DefaultFlowModelHolder(builder);
|
||||
FlowModel model = holder.getFlowModel();
|
||||
|
||||
ViewStateModel state = (ViewStateModel) model.getStateById("state1");
|
||||
assertEquals("foo,bar", state.getValidationHints());
|
||||
|
||||
state = (ViewStateModel) model.getStateById("state2");
|
||||
assertNull(state.getValidationHints());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd">
|
||||
|
||||
<view-state id="state1" validation-hints="foo,bar" />
|
||||
|
||||
<view-state id="state2" />
|
||||
|
||||
</flow>
|
||||
@@ -1,8 +1,8 @@
|
||||
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd">
|
||||
|
||||
<view-state id="form" model="formObject">
|
||||
<view-state id="form" model="formObject" validation-hints="foo,bar">
|
||||
<binder>
|
||||
<binding property="objectProperty" converter="customConverter" required="true" />
|
||||
</binder>
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.SmartValidator;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.webflow.action.ViewFactoryActionAdapter;
|
||||
@@ -628,6 +630,66 @@ public class MvcViewTests extends TestCase {
|
||||
assertFalse(bindBean.validationMethodInvoked);
|
||||
}
|
||||
|
||||
public void testResumeEventStringValidationHint() throws Exception {
|
||||
StubSmartValidator validator = new StubSmartValidator();
|
||||
MockRequestContext context = new MockRequestContext();
|
||||
context.putRequestParameter("_eventId", "submit");
|
||||
TestModel testModel = new TestModel();
|
||||
StaticExpression validationHintsExpression = new StaticExpression("State1,AllStates");
|
||||
context.getCurrentState().getAttributes().put("validationHints", validationHintsExpression);
|
||||
StaticExpression modelExpression = new StaticExpression(testModel);
|
||||
modelExpression.setExpressionString("testModel");
|
||||
context.getCurrentState().getAttributes().put("model", modelExpression);
|
||||
context.getFlowScope().put("testModel", testModel);
|
||||
context.getMockExternalContext().setNativeContext(new MockServletContext());
|
||||
context.getMockExternalContext().setNativeRequest(new MockHttpServletRequest());
|
||||
context.getMockExternalContext().setNativeResponse(new MockHttpServletResponse());
|
||||
context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1"));
|
||||
org.springframework.web.servlet.View mvcView = new MockView();
|
||||
AbstractMvcView view = new MockMvcView(mvcView, context);
|
||||
view.setValidator(validator);
|
||||
view.setExpressionParser(createExpressionParser());
|
||||
|
||||
view.processUserEvent();
|
||||
|
||||
assertFalse(view.userEventQueued());
|
||||
assertTrue(view.hasFlowEvent());
|
||||
assertEquals("submit", view.getFlowEvent().getId());
|
||||
assertEquals(TestModel.State1.class, validator.hints[0]);
|
||||
assertEquals(TestModel.AllStates.class, validator.hints[1]);
|
||||
assertTrue(validator.invoked);
|
||||
}
|
||||
|
||||
public void testResumeEventObjectArrayValidationHint() throws Exception {
|
||||
StubSmartValidator validator = new StubSmartValidator();
|
||||
MockRequestContext context = new MockRequestContext();
|
||||
context.putRequestParameter("_eventId", "submit");
|
||||
TestModel testModel = new TestModel();
|
||||
Object[] validationHints = new Object[] { TestModel.State1.class };
|
||||
StaticExpression validationHintsExpression = new StaticExpression(validationHints);
|
||||
context.getCurrentState().getAttributes().put("validationHints", validationHintsExpression);
|
||||
StaticExpression modelExpression = new StaticExpression(testModel);
|
||||
modelExpression.setExpressionString("testModel");
|
||||
context.getCurrentState().getAttributes().put("model", modelExpression);
|
||||
context.getFlowScope().put("testModel", testModel);
|
||||
context.getMockExternalContext().setNativeContext(new MockServletContext());
|
||||
context.getMockExternalContext().setNativeRequest(new MockHttpServletRequest());
|
||||
context.getMockExternalContext().setNativeResponse(new MockHttpServletResponse());
|
||||
context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1"));
|
||||
org.springframework.web.servlet.View mvcView = new MockView();
|
||||
AbstractMvcView view = new MockMvcView(mvcView, context);
|
||||
view.setValidator(validator);
|
||||
view.setExpressionParser(createExpressionParser());
|
||||
|
||||
view.processUserEvent();
|
||||
|
||||
assertFalse(view.userEventQueued());
|
||||
assertTrue(view.hasFlowEvent());
|
||||
assertEquals("submit", view.getFlowEvent().getId());
|
||||
assertEquals(validationHints, validator.hints);
|
||||
assertTrue(validator.invoked);
|
||||
}
|
||||
|
||||
private SpringELExpressionParser createExpressionParser() {
|
||||
StringToDate c = new StringToDate();
|
||||
c.setPattern("yyyy-MM-dd");
|
||||
@@ -806,4 +868,31 @@ public class MvcViewTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static class StubSmartValidator implements SmartValidator {
|
||||
private boolean invoked;
|
||||
private Object[] hints;
|
||||
|
||||
public void validate(Object object, Errors errors) {
|
||||
invoked = true;
|
||||
}
|
||||
|
||||
public void validate(Object object, Errors errors, Object... hints) {
|
||||
invoked = true;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestModel {
|
||||
|
||||
public static class State1 {
|
||||
}
|
||||
|
||||
public static class AllStates {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.validation;
|
||||
|
||||
import javax.validation.groups.Default;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.webflow.execution.FlowExecutionException;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link BeanValidationHintResolver};
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class BeanValidationHintResolverTests extends TestCase {
|
||||
|
||||
private BeanValidationHintResolver resolver;
|
||||
|
||||
public void setUp() {
|
||||
this.resolver = new BeanValidationHintResolver();
|
||||
}
|
||||
|
||||
public void testResolveFullyQualifiedClassNameHint() {
|
||||
String[] hints = new String[] { this.getClass().getName() };
|
||||
Class<?>[] resolvedHints = this.resolver.resolveValidationHints(null, "flowId", "state1", hints);
|
||||
|
||||
assertNotNull(resolvedHints);
|
||||
assertEquals(1, resolvedHints.length);
|
||||
assertEquals(this.getClass(), resolvedHints[0]);
|
||||
}
|
||||
|
||||
public void testResolveInnterTypeHints() {
|
||||
String[] hints = new String[] {"default", "state1", "state2"};
|
||||
Class<?>[] resolvedHints = this.resolver.resolveValidationHints(new TestModel(), "flowId", "state1", hints);
|
||||
|
||||
assertNotNull(resolvedHints);
|
||||
assertEquals(3, resolvedHints.length);
|
||||
assertEquals(Default.class, resolvedHints[0]);
|
||||
assertEquals(BaseTestModel.State1.class, resolvedHints[1]);
|
||||
assertEquals(TestModel.State2.class, resolvedHints[2]);
|
||||
}
|
||||
|
||||
public void testResolveHintNoMatch() {
|
||||
try {
|
||||
this.resolver.resolveValidationHints(null, "flowId", "state1", new String[] { "foo" });
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (FlowExecutionException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public static class BaseTestModel {
|
||||
|
||||
private static class State1 {
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestModel extends BaseTestModel {
|
||||
|
||||
private static class State2 {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008 the original author or authors.
|
||||
* Copyright 2008-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,7 @@ import org.springframework.binding.validation.ValidationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.validation.DefaultMessageCodesResolver;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.SmartValidator;
|
||||
import org.springframework.webflow.engine.Flow;
|
||||
import org.springframework.webflow.engine.StubViewFactory;
|
||||
import org.springframework.webflow.engine.ViewState;
|
||||
@@ -39,10 +39,14 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
private String modelName;
|
||||
|
||||
private DefaultMessageCodesResolver codesResolver;
|
||||
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
requestContext = new MockRequestControlContext();
|
||||
eventId = "userEvent";
|
||||
modelName = "model";
|
||||
codesResolver = new DefaultMessageCodesResolver();
|
||||
}
|
||||
|
||||
public void testValidateWithMessageContext() {
|
||||
@@ -58,7 +62,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void testValidateWithValidationContext() {
|
||||
Object model = new StubModelValidationContext();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
helper.validate();
|
||||
MessageContext messages = requestContext.getMessageContext();
|
||||
assertEquals(1, messages.getAllMessages().length);
|
||||
@@ -70,7 +74,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
applicationContext.registerSingleton("modelValidator", StubModelMessageContext.class);
|
||||
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
|
||||
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
helper.validate();
|
||||
MessageContext messages = requestContext.getMessageContext();
|
||||
assertEquals(1, messages.getAllMessages().length);
|
||||
@@ -82,7 +86,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
applicationContext.registerSingleton("modelValidator", StubModelValidationContext.class);
|
||||
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
|
||||
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
helper.validate();
|
||||
MessageContext messages = requestContext.getMessageContext();
|
||||
assertEquals(1, messages.getAllMessages().length);
|
||||
@@ -94,7 +98,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
applicationContext.registerSingleton("modelValidator", StubModelErrors.class);
|
||||
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
|
||||
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
helper.validate();
|
||||
MessageContext messages = requestContext.getMessageContext();
|
||||
assertEquals(1, messages.getAllMessages().length);
|
||||
@@ -106,7 +110,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
applicationContext.registerSingleton("modelValidator", StubModelErrorsOverridden.class);
|
||||
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
|
||||
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
helper.validate();
|
||||
MessageContext messages = requestContext.getMessageContext();
|
||||
assertEquals(1, messages.getAllMessages().length);
|
||||
@@ -116,7 +120,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void testStateAndFallbackModelValidationMethodInvoked() {
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -127,7 +131,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void testFallbackModelValidationMethodInvoked() {
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -138,7 +142,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void testStateAndFallbackErrorsModelValidationMethodInvoked() {
|
||||
ErrorsModel model = new ErrorsModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -149,7 +153,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void testFallbackModelErrorsValidationMethodInvoked() {
|
||||
ErrorsModel model = new ErrorsModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -165,7 +169,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -181,7 +185,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
ExtendedModel model = new ExtendedModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -197,7 +201,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -213,7 +217,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
ExtendedModel model = new ExtendedModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -229,7 +233,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -245,7 +249,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
ExtendedModel model = new ExtendedModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -261,7 +265,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -277,7 +281,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -293,7 +297,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
ExtendedModel model = new ExtendedModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -309,7 +313,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -325,7 +329,7 @@ public class ValidationHelperTests extends TestCase {
|
||||
|
||||
Model model = new Model();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null,
|
||||
new DefaultMessageCodesResolver(), null);
|
||||
this.codesResolver, null);
|
||||
ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state1);
|
||||
helper.validate();
|
||||
@@ -333,6 +337,24 @@ public class ValidationHelperTests extends TestCase {
|
||||
assertTrue(validator.fallbackInvoked);
|
||||
}
|
||||
|
||||
public void testSmartValidatorWithStateIdHint() {
|
||||
LegacyModelValidator validator = new LegacyModelValidator();
|
||||
ExtendedModel model = new ExtendedModel();
|
||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null);
|
||||
helper.setValidator(validator);
|
||||
helper.setValidationHints(new Object[] { Model.State1.class });
|
||||
|
||||
ViewState state = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||
requestContext.setCurrentState(state);
|
||||
|
||||
helper.validate();
|
||||
|
||||
assertTrue(validator.fallbackInvoked);
|
||||
assertTrue(validator.hints.length > 0);
|
||||
assertEquals(Model.State1.class, validator.hints[0]);
|
||||
}
|
||||
|
||||
|
||||
public static class Model {
|
||||
private boolean state1Invoked;
|
||||
private boolean fallbackInvoked;
|
||||
@@ -344,6 +366,8 @@ public class ValidationHelperTests extends TestCase {
|
||||
public void validate(ValidationContext context) {
|
||||
fallbackInvoked = true;
|
||||
}
|
||||
|
||||
private static class State1 {}
|
||||
}
|
||||
|
||||
public static class ExtendedModel extends Model {
|
||||
@@ -362,9 +386,10 @@ public class ValidationHelperTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static class LegacyModelValidator implements Validator {
|
||||
public static class LegacyModelValidator implements SmartValidator {
|
||||
private boolean state1Invoked;
|
||||
private boolean fallbackInvoked;
|
||||
private Object[] hints;
|
||||
|
||||
public void validateState1(Model model, Errors errors) {
|
||||
state1Invoked = true;
|
||||
@@ -374,6 +399,11 @@ public class ValidationHelperTests extends TestCase {
|
||||
fallbackInvoked = true;
|
||||
}
|
||||
|
||||
public void validate(Object object, Errors errors, Object... hints) {
|
||||
fallbackInvoked = true;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
1
src/dist/changelog.txt
vendored
1
src/dist/changelog.txt
vendored
@@ -7,6 +7,7 @@ Changes in version 2.4.0.RELEASE
|
||||
Move samples to a separate repository https://github.com/SpringSource/spring-webflow-samples
|
||||
Upgrade spring-faces to JSF 2 as a minimum requirement
|
||||
Upgrade portlet/JSF support to function in JSF 2 environment
|
||||
Add partial validation support for JSR-303 bean validation
|
||||
|
||||
Changes in version 2.3.1.RELEASE (Mar 27, 2012)
|
||||
-----------------------------------------------
|
||||
|
||||
@@ -479,7 +479,45 @@ public class ApplicationConversionServiceFactoryBean extends FormattingConversio
|
||||
In other words Web Flow will apply all available validation
|
||||
mechanisms.
|
||||
</para>
|
||||
|
||||
|
||||
<sect3 id="view-validation-jsr303-partial">
|
||||
<title>Partial Validation</title>
|
||||
<para>
|
||||
JSR-303 Bean Validation supports partial validation via groups. For example:
|
||||
|
||||
<programlisting language="java">
|
||||
@NotNull
|
||||
@Size(min = 2, max = 30, groups = State1.class)
|
||||
private String name;
|
||||
</programlisting>
|
||||
You can specify validation hints in a view state:
|
||||
<programlisting language="xml"><![CDATA[
|
||||
<view-state id="state1" model="myModel" validation-hints="group1,group2">
|
||||
]]>
|
||||
</programlisting>
|
||||
Each hint can be an inner Class either in the model type or its parent types.
|
||||
For example, given <classname>org.example.MyModel</classname> with inner type
|
||||
<classname>Group1</classname> and <classname>Group2</classname> you can
|
||||
specify the hints "group1", "group2" or both "group1,group2".
|
||||
A hint can also be a fully qualified class name.
|
||||
The hint "default" indicates the default validation group, i.e.
|
||||
<classname>javax.validation.groups.Default</classname>.
|
||||
Also, the <emphasis>validation-hints</emphasis> property can be an expression that
|
||||
resolves to a String or an <classname>Object[]</classname>
|
||||
containing <classname>Class</classname> based hints.
|
||||
</para>
|
||||
<para>
|
||||
Note that a custom <classname>ValidationHintResolver</classname> can be plugged if
|
||||
necessary through the validationHintResolver property of the
|
||||
flow-builder-services element:
|
||||
<programlisting language="xml">
|
||||
<webflow:flow-registry flow-builder-services="flowBuilderServices" />
|
||||
|
||||
<webflow:flow-builder-services id="flowBuilderServices" validator=".." validationHintResolver=".." />
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect3>
|
||||
|
||||
</sect2>
|
||||
<sect2 id="view-validation-programmatic">
|
||||
<title>Programmatic validation</title>
|
||||
|
||||
@@ -29,6 +29,13 @@
|
||||
Flow output can now be saved to Spring MVC Flash Scope for any <code>end-state</code> that issues an internal redirect. To enable this feature set <code>FlowHandlerAdapter.saveOutputToFlashScopeOnRedirect</code>. See <xref linkend="spring-mvc-flash-output"/>.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2 id="whatsnew-partial-validation">
|
||||
<title>Partial JSR-303 Bean Validation</title>
|
||||
<para>
|
||||
Partial validation with JSR-303 Bean Validation groups is now supported through the validation-hints
|
||||
view-state attribute. See <xref linkend="view-validation-jsr303-partial" />.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
<sect1 id="whatsnew-swf-230">
|
||||
<title>Spring Web Flow 2.3</title>
|
||||
|
||||
Reference in New Issue
Block a user