binder configuration

This commit is contained in:
Keith Donald
2008-07-29 14:54:56 +00:00
parent 9158483eb3
commit 4a57f3d1ff
15 changed files with 226 additions and 78 deletions

View File

@@ -0,0 +1,121 @@
package org.springframework.webflow.engine.builder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* Contains the information needed to bind model to a view. This information consists of one or more
* {@link Binding bindings} that connect properties of the model to UI elements of the view.
*
* @see ViewFactoryCreator
*
* @author Keith Donald
*/
public class BinderConfiguration {
private Set bindings = new HashSet();
/**
* Adds a new binding to this binding configuration.
* @param binding the binding
*/
public void addBinding(Binding binding) {
bindings.add(binding);
}
/**
* Returns the set of bindings associated with this binding configuration.
*/
public Set getBindings() {
return bindings;
}
/**
* Gets the binding with the specified name, or returns null if no such binding is found.
* @param name the name of the binding.
* @return the binding
*/
public Binding getBinding(String name) {
Iterator it = bindings.iterator();
while (it.hasNext()) {
Binding binding = (Binding) it.next();
if (name.equals(binding.getProperty())) {
return binding;
}
}
return null;
}
/**
* A binding that provides the information needed to connect an element of the view to a property of the model.
*
* @author Keith Donald
*/
public static final class Binding {
private String property;
private String converter;
private boolean required;
/**
* Creates a new view binding
* @param property the model property to bind to
* @param converter the id of a custom converter to apply type conversion during binding
* @param required whether this binding is required
*/
public Binding(String property, String converter, boolean required) {
Assert.hasText(property, "The property is required");
this.property = property;
this.converter = converter;
this.required = required;
}
public boolean equals(Object object) {
if (!(object instanceof Binding)) {
return false;
}
Binding binding = (Binding) object;
return property.equals(binding.property);
}
public int hashCode() {
return property.hashCode();
}
/**
* The name of the bound property.
* @return the property
*/
public String getProperty() {
return property;
}
/**
* The id of the custom converter to use to convert bound property values.
* @return the converter id, or null
*/
public String getConverter() {
return converter;
}
/**
* Whether a non-empty value is required for each binding attempt.
* @return the required status
*/
public boolean getRequired() {
return required;
}
public String toString() {
return new ToStringCreator(this).append("property", property).append("converter", converter).append(
"required", required).toString();
}
}
}

View File

@@ -18,7 +18,6 @@ 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.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
@@ -34,11 +33,11 @@ public interface ViewFactoryCreator {
* @param viewId an expression that resolves the id of the view to render
* @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 binderModel an optional configuration for how the rendered view binds to a model that provides its data
* @param binderConfiguration information on how the rendered view binds to a model that provides its data
* @return the view factory
*/
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel);
ConversionService conversionService, BinderConfiguration binderConfiguration);
/**
* Get the default id of the view to render in the provided view state by convention.

View File

@@ -68,6 +68,8 @@ import org.springframework.webflow.engine.VariableValueFactory;
import org.springframework.webflow.engine.ViewVariable;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.FlowBuilderException;
import org.springframework.webflow.engine.builder.BinderConfiguration;
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;
@@ -76,6 +78,7 @@ import org.springframework.webflow.engine.model.ActionStateModel;
import org.springframework.webflow.engine.model.AttributeModel;
import org.springframework.webflow.engine.model.BeanImportModel;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.model.BindingModel;
import org.springframework.webflow.engine.model.DecisionStateModel;
import org.springframework.webflow.engine.model.EndStateModel;
import org.springframework.webflow.engine.model.EvaluateModel;
@@ -612,8 +615,25 @@ 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(), binderModel);
getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderConfiguration);
}
private BinderConfiguration createBinderConfiguration(BinderModel binderModel) {
if (binderModel == null) {
return null;
}
BinderConfiguration binderConfiguration = new BinderConfiguration();
List bindings = binderModel.getBindings();
for (Iterator it = bindings.iterator(); it.hasNext();) {
BindingModel bindingModel = (BindingModel) it.next();
boolean required = ((Boolean) fromStringTo(Boolean.class).execute(bindingModel.getRequired()))
.booleanValue();
Binding binding = new Binding(bindingModel.getProperty(), bindingModel.getConverter(), required);
binderConfiguration.addBinding(binding);
}
return binderConfiguration;
}
private ViewVariable[] parseViewVariables(List vars) {

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.webflow.engine.model;
import java.util.Iterator;
import java.util.LinkedList;
/**
@@ -37,17 +36,6 @@ public class BinderModel extends AbstractModel {
return bindings;
}
public BindingModel getBinding(String name) {
Iterator it = bindings.iterator();
while (it.hasNext()) {
BindingModel binding = (BindingModel) it.next();
if (name.equals(binding.getProperty())) {
return binding;
}
}
return null;
}
public void setBindings(LinkedList bindings) {
this.bindings = bindings;
}
@@ -60,4 +48,5 @@ public class BinderModel extends AbstractModel {
BinderModel binder = (BinderModel) model;
setBindings(merge(getBindings(), binder.getBindings()));
}
}

View File

@@ -28,7 +28,7 @@ public class BindingModel extends AbstractModel {
private String converter;
private boolean required;
private String required;
/**
* Create a binding model
@@ -36,7 +36,7 @@ public class BindingModel extends AbstractModel {
* @param converter the converter
* @param required required status
*/
public BindingModel(String property, String converter, boolean required) {
public BindingModel(String property, String converter, String required) {
setProperty(property);
setConverter(converter);
setRequired(required);
@@ -53,18 +53,13 @@ public class BindingModel extends AbstractModel {
public void merge(Model model) {
BindingModel binding = (BindingModel) model;
setConverter(merge(getConverter(), binding.getConverter()));
setRequired(merge(getRequired(), binding.getRequired()));
}
/**
* @return the property
*/
public String getProperty() {
return property;
}
/**
* @param property the property to set
*/
public void setProperty(String property) {
if (StringUtils.hasText(property)) {
this.property = property;
@@ -73,16 +68,10 @@ public class BindingModel extends AbstractModel {
}
}
/**
* @return the class property
*/
public String getConverter() {
return converter;
}
/**
* @param converter the class property to set
*/
public void setConverter(String converter) {
if (StringUtils.hasText(converter)) {
this.converter = converter;
@@ -91,12 +80,16 @@ public class BindingModel extends AbstractModel {
}
}
public boolean getRequired() {
public String getRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
public void setRequired(String required) {
if (StringUtils.hasText(required)) {
this.required = required;
} else {
this.required = null;
}
}
}

View File

@@ -543,8 +543,8 @@ public class XmlFlowModelBuilder implements FlowModelBuilder {
}
private BindingModel parseBinding(Element element) {
boolean required = Boolean.valueOf(element.getAttribute("required")).booleanValue();
return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), required);
return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), element
.getAttribute("required"));
}
private LinkedList parseOnExitActions(Element element) {

View File

@@ -27,8 +27,8 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.ViewFactory;
import org.springframework.webflow.mvc.portlet.PortletMvcViewFactory;
import org.springframework.webflow.mvc.servlet.ServletMvcViewFactory;
@@ -153,12 +153,12 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
}
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
ConversionService conversionService, BinderConfiguration binderConfiguration) {
if (useSpringBeanBinding) {
expressionParser = new BeanWrapperExpressionParser(conversionService);
}
AbstractMvcViewFactory viewFactory = createMvcViewFactory(viewId, expressionParser, conversionService,
binderModel);
binderConfiguration);
if (StringUtils.hasText(eventIdParameterName)) {
viewFactory.setEventIdParameterName(eventIdParameterName);
}
@@ -169,11 +169,13 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
}
private AbstractMvcViewFactory createMvcViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
ConversionService conversionService, BinderConfiguration binderConfiguration) {
if (environment == MvcEnvironment.SERVLET) {
return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService, binderModel);
return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService,
binderConfiguration);
} else if (environment == MvcEnvironment.PORTLET) {
return new PortletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService, binderModel);
return new PortletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService,
binderConfiguration);
} else {
throw new IllegalStateException("Web MVC Environment " + environment + " not supported ");
}

View File

@@ -19,7 +19,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.web.servlet.View;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.mvc.view.AbstractMvcView;
import org.springframework.webflow.mvc.view.AbstractMvcViewFactory;
@@ -32,9 +32,17 @@ import org.springframework.webflow.mvc.view.FlowViewResolver;
*/
public class PortletMvcViewFactory extends AbstractMvcViewFactory {
/**
* Creates a new portlet-based MVC view factory.
* @param viewId the id of the view as an expression
* @param viewResolver the resolver to resolve the View implementation
* @param expressionParser the expression parser
* @param conversionService the conversion service
* @param binderConfiguration the model binding configuration
*/
public PortletMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
super(viewId, viewResolver, expressionParser, conversionService, binderModel);
ConversionService conversionService, BinderConfiguration binderConfiguration) {
super(viewId, viewResolver, expressionParser, conversionService, binderConfiguration);
}
protected AbstractMvcView createMvcView(View view, RequestContext context) {

View File

@@ -19,7 +19,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.web.servlet.View;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.mvc.view.AbstractMvcView;
import org.springframework.webflow.mvc.view.AbstractMvcViewFactory;
@@ -32,9 +32,17 @@ import org.springframework.webflow.mvc.view.FlowViewResolver;
*/
public class ServletMvcViewFactory extends AbstractMvcViewFactory {
/**
* Creates a new servlet-based MVC view factory.
* @param viewId the id of the view as an expression
* @param viewResolver the resolver to resolve the View implementation
* @param expressionParser the expression parser
* @param conversionService the conversion service
* @param binderConfiguration the model binding configuration
*/
public ServletMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
super(viewId, viewResolver, expressionParser, conversionService, binderModel);
ConversionService conversionService, BinderConfiguration binderConfiguration) {
super(viewId, viewResolver, expressionParser, conversionService, binderConfiguration);
}
protected AbstractMvcView createMvcView(View view, RequestContext context) {

View File

@@ -52,7 +52,8 @@ import org.springframework.web.util.WebUtils;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.definition.TransitionableStateDefinition;
import org.springframework.webflow.engine.model.BinderModel;
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.FlowExecutionKey;
import org.springframework.webflow.execution.RequestContext;
@@ -90,7 +91,7 @@ public abstract class AbstractMvcView implements View {
private boolean viewErrors;
private BinderModel binderModel;
private BinderConfiguration binderConfiguration;
/**
* Creates a new MVC view.
@@ -122,8 +123,8 @@ public abstract class AbstractMvcView implements View {
* Sets the configuration describing how this view should bind to its model to access data for rendering.
* @param binderModel the model binder configuratio
*/
public void setBinderModel(BinderModel binderModel) {
this.binderModel = binderModel;
public void setBinderConfiguration(BinderConfiguration binderConfiguration) {
this.binderConfiguration = binderConfiguration;
}
/**
@@ -255,7 +256,7 @@ public abstract class AbstractMvcView implements View {
if (modelObject != null) {
BindingModel bindingModel = new BindingModel(getModelExpression().getExpressionString(), modelObject,
expressionParser, conversionService, requestContext.getMessageContext());
bindingModel.setBinderModel(binderModel);
bindingModel.setBinderConfiguration(binderConfiguration);
bindingModel.setMappingResults(mappingResults);
model.put(BindingResult.MODEL_KEY_PREFIX + getModelExpression().getExpressionString(), bindingModel);
}
@@ -289,7 +290,7 @@ public abstract class AbstractMvcView implements View {
}
DefaultMapper mapper = new DefaultMapper();
ParameterMap requestParameters = requestContext.getRequestParameters();
if (binderModel != null) {
if (binderConfiguration != null) {
addModelBindingMappings(mapper, requestParameters.asMap().keySet(), model);
} else {
addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
@@ -298,10 +299,9 @@ public abstract class AbstractMvcView implements View {
}
private void addModelBindingMappings(DefaultMapper mapper, Set parameterNames, Object model) {
Iterator it = binderModel.getBindings().iterator();
Iterator it = binderConfiguration.getBindings().iterator();
while (it.hasNext()) {
org.springframework.webflow.engine.model.BindingModel binding = (org.springframework.webflow.engine.model.BindingModel) it
.next();
Binding binding = (Binding) it.next();
String parameterName = binding.getProperty();
if (parameterNames.contains(parameterName)) {
addMapping(mapper, binding, model);
@@ -313,8 +313,7 @@ public abstract class AbstractMvcView implements View {
}
}
private void addMapping(DefaultMapper mapper, org.springframework.webflow.engine.model.BindingModel binding,
Object model) {
private void addMapping(DefaultMapper mapper, Binding binding, Object model) {
Expression source = new RequestParameterExpression(binding.getProperty());
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(binding.getProperty(), parserContext);

View File

@@ -19,7 +19,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.util.StringUtils;
import org.springframework.webflow.engine.model.BinderModel;
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;
@@ -39,19 +39,27 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
private ConversionService conversionService;
private BinderModel binderModel;
private BinderConfiguration binderConfiguration;
private String eventIdParameterName;
private String fieldMarkerPrefix;
/**
* Creates a new MVC view factory.
* @param viewId the id of the view as an expression
* @param viewResolver the resolver to resolve the View implementation
* @param expressionParser the expression parser
* @param conversionService the conversion service
* @param binderConfiguration the model binding configuration
*/
public AbstractMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
ConversionService conversionService, BinderConfiguration binderConfiguration) {
this.viewId = viewId;
this.viewResolver = viewResolver;
this.expressionParser = expressionParser;
this.conversionService = conversionService;
this.binderModel = binderModel;
this.binderConfiguration = binderConfiguration;
}
public void setEventIdParameterName(String eventIdParameterName) {
@@ -68,7 +76,7 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
AbstractMvcView mvcView = createMvcView(view, context);
mvcView.setExpressionParser(expressionParser);
mvcView.setConversionService(conversionService);
mvcView.setBinderModel(binderModel);
mvcView.setBinderConfiguration(binderConfiguration);
if (StringUtils.hasText(eventIdParameterName)) {
mvcView.setEventIdParameterName(eventIdParameterName);
}

View File

@@ -38,7 +38,8 @@ import org.springframework.validation.AbstractErrors;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
/**
* Makes the properties of the "model" object available to Spring views during rendering. Also makes data binding (aka
@@ -64,7 +65,7 @@ public class BindingModel extends AbstractErrors {
private MessageContext messageContext;
private BinderModel binderModel;
private BinderConfiguration binderConfiguration;
/**
* Creates a new Spring Binding model.
@@ -94,8 +95,8 @@ public class BindingModel extends AbstractErrors {
this.mappingResults = results;
}
public void setBinderModel(BinderModel binderModel) {
this.binderModel = binderModel;
public void setBinderConfiguration(BinderConfiguration binderConfiguration) {
this.binderConfiguration = binderConfiguration;
}
// implementing Errors
@@ -158,9 +159,8 @@ public class BindingModel extends AbstractErrors {
|| Map.class.isAssignableFrom(valueType)) {
return null;
}
if (binderModel != null) {
org.springframework.webflow.engine.model.BindingModel binding = binderModel.getBinding(fieldExpression
.getExpressionString());
if (binderConfiguration != null) {
Binding binding = binderConfiguration.getBinding(fieldExpression.getExpressionString());
if (binding != null) {
String converterId = binding.getConverter();
if (converterId != null) {

View File

@@ -21,8 +21,8 @@ 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.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
@@ -37,7 +37,7 @@ import org.springframework.webflow.execution.ViewFactory;
class MockViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
ConversionService conversionService, BinderConfiguration binderConfiguration) {
return new MockViewFactory(viewId);
}

View File

@@ -13,9 +13,9 @@ import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.ViewFactory;
import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator;
@@ -47,7 +47,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
public static class TestViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService, BinderModel binderModel) {
ConversionService conversionService, BinderConfiguration binderConfiguration) {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -25,7 +25,8 @@ import org.springframework.webflow.action.ViewFactoryActionAdapter;
import org.springframework.webflow.engine.EndState;
import org.springframework.webflow.engine.StubViewFactory;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.test.MockFlowExecutionKey;
import org.springframework.webflow.test.MockRequestContext;
@@ -215,9 +216,9 @@ public class MvcViewTests extends TestCase {
context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1"));
org.springframework.web.servlet.View mvcView = new MockView();
AbstractMvcView view = new MockMvcView(mvcView, context);
BinderModel binderModel = new BinderModel();
binderModel.addBinding(new org.springframework.webflow.engine.model.BindingModel("stringProperty", null, true));
view.setBinderModel(binderModel);
BinderConfiguration binderConfiguration = new BinderConfiguration();
binderConfiguration.addBinding(new Binding("stringProperty", null, true));
view.setBinderConfiguration(binderConfiguration);
view.processUserEvent();
assertTrue(view.hasFlowEvent());
assertEquals("submit", view.getFlowEvent().getId());