binder addition

This commit is contained in:
Keith Donald
2008-07-22 03:21:30 +00:00
parent 454ecc7222
commit b70c76d459
22 changed files with 218 additions and 80 deletions

View File

@@ -168,11 +168,23 @@ public class GenericConversionService implements ConversionService {
public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass)
throws ConversionExecutorNotFoundException {
Assert.hasText(id, "The id of the custom converter is required");
Assert.notNull(sourceClass, "The source class to convert from is required");
Assert.notNull(targetClass, "The target class to convert to is required");
sourceClass = convertToWrapperClassIfNecessary(sourceClass);
targetClass = convertToWrapperClassIfNecessary(targetClass);
if (targetClass.isAssignableFrom(sourceClass)) {
return new StaticConversionExecutor(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass));
}
Converter converter = (Converter) customConverters.get(id);
if (converter == null) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"No custom ConversionExecutor found with id '" + id + "' for converting from sourceClass ["
+ sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
if (parent != null) {
return parent.getConversionExecutor(id, sourceClass, targetClass);
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"No custom ConversionExecutor found with id '" + id + "' for converting from sourceClass ["
+ sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
}
}
if (converter.getSourceClass().isAssignableFrom(sourceClass)) {
if (!converter.getTargetClass().isAssignableFrom(targetClass)) {

View File

@@ -21,6 +21,7 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.ViewFactory;
/**
@@ -35,7 +36,7 @@ public class JsfViewFactoryCreator implements ViewFactoryCreator {
private Lifecycle lifecycle;
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
return new JsfViewFactory(viewIdExpression, getLifecycle());
}

View File

@@ -18,6 +18,7 @@ import org.springframework.faces.webflow.JsfManagedBeanAwareELExpressionParser;
import org.springframework.faces.webflow.JsfViewFactoryCreator;
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.expression.el.WebFlowELExpressionParser;
@@ -63,7 +64,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
public static class TestViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -80,9 +80,7 @@ public class DojoDecorationRendererTests extends TestCase {
childComponent.setConverter(converter);
childComponent.setId("foo");
Calendar cal = Calendar.getInstance(Locale.US);
cal.set(Calendar.MONTH, Calendar.NOVEMBER);
cal.set(Calendar.DAY_OF_MONTH, 21);
cal.set(Calendar.YEAR, 1977);
cal.set(1977, Calendar.NOVEMBER, 21, 12, 0);
childComponent.setValue(cal.getTime());
DojoDecorationRenderer renderer = new DojoDecorationRenderer();
String nodeAttributes = renderer.getNodeAttributesAsString(jsf.facesContext(), childComponent);

View File

@@ -0,0 +1,16 @@
package org.springframework.webflow.samples.booking;
import org.springframework.binding.convert.converters.StringToDate;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.stereotype.Component;
@Component("conversionService")
public class ApplicationConversionService extends DefaultConversionService {
@Override
protected void addDefaultConverters() {
super.addDefaultConverters();
addConverter("shortDate", new StringToDate());
}
}

View File

@@ -22,7 +22,7 @@
</webflow:flow-registry>
<!-- Plugs in a custom creator for Web Flow views -->
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/>
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService"/>
<!-- Configures Web Flow to use Tiles to create views for rendering; Tiles allows for applying consistent layouts to your views -->
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">

View File

@@ -17,11 +17,12 @@
<binder>
<binding property="checkinDate" converter="shortDate" required="true" />
<binding property="checkoutDate" converter="shortDate" required="true" />
<binding property="beds" required="true"/>
<binding property="smoking" required="true" />
<binding property="creditCard" required="true" />
<binding property="creditCardName" required="true" />
<binding property="creditCardExpirationMonth" required="true"/>
<binding property="creditCardExpirationYear" required="true"/>
<binding property="creditCardExpiryMonth" required="true"/>
<binding property="creditCardExpiryYear" required="true"/>
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />

View File

@@ -18,6 +18,7 @@ package org.springframework.webflow.engine.builder;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.webflow.engine.model.BinderModel;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
@@ -33,10 +34,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
* @return the view factory
*/
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService);
ConversionService conversionService, BinderModel binderModel);
/**
* Get the default id of the view to render in the provided view state by convention.
@@ -44,4 +46,5 @@ public interface ViewFactoryCreator {
* @return the default view id
*/
public String getViewIdByConvention(String viewStateId);
}
}

View File

@@ -75,6 +75,7 @@ import org.springframework.webflow.engine.model.AbstractStateModel;
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.DecisionStateModel;
import org.springframework.webflow.engine.model.EndStateModel;
import org.springframework.webflow.engine.model.EvaluateModel;
@@ -513,7 +514,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
}
private void parseAndAddViewState(ViewStateModel state, Flow flow) {
ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), false);
ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), false, state.getBinder());
Boolean redirect = null;
if (StringUtils.hasText(state.getRedirect())) {
redirect = (Boolean) fromStringTo(Boolean.class).execute(state.getRedirect());
@@ -571,7 +572,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
}
parseAndPutSecured(state.getSecured(), attributes);
Action finalResponseAction;
ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), true);
ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), true, null);
if (viewFactory != null) {
finalResponseAction = new ViewFactoryActionAdapter(viewFactory);
} else {
@@ -583,7 +584,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
attributes);
}
private ViewFactory parseViewFactory(String view, String stateId, boolean endState) {
private ViewFactory parseViewFactory(String view, String stateId, boolean endState, BinderModel binderModel) {
if (!StringUtils.hasText(view)) {
if (endState) {
return null;
@@ -591,7 +592,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
view = getLocalContext().getViewFactoryCreator().getViewIdByConvention(stateId);
Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
return createViewFactory(viewId);
return createViewFactory(viewId, binderModel);
}
} else if (view.startsWith("externalRedirect:")) {
String encodedUrl = view.substring("externalRedirect:".length());
@@ -606,13 +607,13 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
} else {
Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
return createViewFactory(viewId);
return createViewFactory(viewId, binderModel);
}
}
private ViewFactory createViewFactory(Expression viewId) {
private ViewFactory createViewFactory(Expression viewId, BinderModel binderModel) {
return getLocalContext().getViewFactoryCreator().createViewFactory(viewId,
getLocalContext().getExpressionParser(), getLocalContext().getConversionService());
getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderModel);
}
private ViewVariable[] parseViewVariables(List vars) {

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.webflow.engine.model;
import java.util.Iterator;
import java.util.LinkedList;
/**
@@ -25,10 +26,28 @@ public class BinderModel extends AbstractModel {
private LinkedList bindings;
public void addBinding(BindingModel bindingModel) {
if (bindings == null) {
bindings = new LinkedList();
}
bindings.add(bindingModel);
}
public LinkedList getBindings() {
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;
}
@@ -41,4 +60,4 @@ public class BinderModel extends AbstractModel {
BinderModel binder = (BinderModel) model;
setBindings(merge(getBindings(), binder.getBindings()));
}
}
}

View File

@@ -28,14 +28,18 @@ public class BindingModel extends AbstractModel {
private String converter;
private String required;
/**
* Create a binding model
* @param property the name of the bound property
* @param converter the converter
* @param required required status
*/
public BindingModel(String property, String converter) {
public BindingModel(String property, String converter, String required) {
setProperty(property);
setConverter(converter);
setRequired(required);
}
public boolean isMergeableWith(Model model) {
@@ -87,4 +91,16 @@ public class BindingModel extends AbstractModel {
}
}
public String getRequired() {
return required;
}
public void setRequired(String required) {
if (StringUtils.hasText(required)) {
this.required = required;
} else {
this.required = null;
}
}
}

View File

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

View File

@@ -27,6 +27,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
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;
@@ -140,14 +141,14 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
}
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
if (useSpringBeanBinding) {
expressionParser = new BeanWrapperExpressionParser(conversionService);
}
if (environment == MvcEnvironment.SERVLET) {
return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService);
return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService, binderModel);
} else if (environment == MvcEnvironment.PORTLET) {
return new PortletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService);
return new PortletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService, binderModel);
} else {
throw new IllegalStateException("Web MVC Environment " + environment + " not supported ");
}

View File

@@ -19,6 +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.execution.RequestContext;
import org.springframework.webflow.mvc.view.AbstractMvcView;
import org.springframework.webflow.mvc.view.AbstractMvcViewFactory;
@@ -32,14 +33,15 @@ import org.springframework.webflow.mvc.view.FlowViewResolver;
public class PortletMvcViewFactory extends AbstractMvcViewFactory {
public PortletMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService) {
super(viewId, viewResolver, expressionParser, conversionService);
ConversionService conversionService, BinderModel binderModel) {
super(viewId, viewResolver, expressionParser, conversionService, binderModel);
}
protected AbstractMvcView createMvcView(View view, RequestContext context) {
PortletMvcView mvcView = new PortletMvcView(view, context);
mvcView.setExpressionParser(getExpressionParser());
mvcView.setConversionService(getConversionService());
mvcView.setBinderModel(getBinderModel());
return mvcView;
}

View File

@@ -49,4 +49,5 @@ public class ServletMvcView extends AbstractMvcView {
context.getActiveFlow().getApplicationContext());
getView().render(model, request, response);
}
}

View File

@@ -19,6 +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.execution.RequestContext;
import org.springframework.webflow.mvc.view.AbstractMvcView;
import org.springframework.webflow.mvc.view.AbstractMvcViewFactory;
@@ -32,14 +33,15 @@ import org.springframework.webflow.mvc.view.FlowViewResolver;
public class ServletMvcViewFactory extends AbstractMvcViewFactory {
public ServletMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService) {
super(viewId, viewResolver, expressionParser, conversionService);
ConversionService conversionService, BinderModel binderModel) {
super(viewId, viewResolver, expressionParser, conversionService, binderModel);
}
protected AbstractMvcView createMvcView(View view, RequestContext context) {
ServletMvcView mvcView = new ServletMvcView(view, context);
mvcView.setExpressionParser(getExpressionParser());
mvcView.setConversionService(getConversionService());
mvcView.setBinderModel(getBinderModel());
return mvcView;
}

View File

@@ -27,6 +27,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
@@ -51,6 +52,7 @@ 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.execution.Event;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.RequestContext;
@@ -84,10 +86,10 @@ public abstract class AbstractMvcView implements View {
private String eventId;
private Set allowedBindFields;
private String fieldMarkerPrefix = "_";
private BinderModel binderModel;
/**
* Creates a new MVC view.
* @param view the Spring MVC view to render
@@ -114,6 +116,14 @@ public abstract class AbstractMvcView implements View {
this.conversionService = conversionService;
}
/**
* 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;
}
/**
* Specify a prefix that can be used for parameters that mark potentially empty fields, having "prefix + field" as
* name. Such a marker parameter is checked by existence: You can send any value for it, for example "visible". This
@@ -133,14 +143,6 @@ public abstract class AbstractMvcView implements View {
this.fieldMarkerPrefix = fieldMarkerPrefix;
}
/**
* Register fields that should be allowed for binding. Default is all fields.
* @param allowedBindFields the set of field name strings to allow
*/
public void setAllowedBindFields(Set allowedBindFields) {
this.allowedBindFields = allowedBindFields;
}
public void render() throws IOException {
Map model = new HashMap();
model.putAll(flowScopes());
@@ -178,9 +180,11 @@ public abstract class AbstractMvcView implements View {
viewErrors = true;
addErrorMessages(mappingResults);
} else {
validate(model);
if (requestContext.getMessageContext().hasErrorMessages()) {
viewErrors = true;
if (shouldValidate(model)) {
validate(model);
if (requestContext.getMessageContext().hasErrorMessages()) {
viewErrors = true;
}
}
}
}
@@ -240,6 +244,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.setMappingResults(mappingResults);
model.put(BindingResult.MODEL_KEY_PREFIX + getModelExpression().getExpressionString(), bindingModel);
}
@@ -264,7 +269,7 @@ public abstract class AbstractMvcView implements View {
if (transition == null) {
return true;
}
return transition.getAttributes().getBoolean("bind", Boolean.FALSE).booleanValue();
return transition.getAttributes().getBoolean("bind", Boolean.TRUE).booleanValue();
}
private MappingResults bind(Object model) {
@@ -273,45 +278,65 @@ public abstract class AbstractMvcView implements View {
}
DefaultMapper mapper = new DefaultMapper();
ParameterMap requestParameters = requestContext.getRequestParameters();
addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
if (binderModel != null) {
addModelBindingMappings(mapper, requestParameters.asMap().keySet(), model);
} else {
addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
}
return mapper.map(requestParameters, model);
}
private void addModelBindingMappings(DefaultMapper mapper, Set parameterNames, Object model) {
Iterator it = binderModel.getBindings().iterator();
while (it.hasNext()) {
org.springframework.webflow.engine.model.BindingModel binding = (org.springframework.webflow.engine.model.BindingModel) it
.next();
String parameterName = binding.getProperty();
if (parameterNames.contains(parameterName)) {
addMapping(mapper, binding, model);
} else {
if (fieldMarkerPrefix != null && parameterNames.contains(fieldMarkerPrefix + parameterName)) {
addEmptyValueMapping(mapper, parameterName, model);
}
}
}
}
private void addMapping(DefaultMapper mapper, org.springframework.webflow.engine.model.BindingModel binding,
Object model) {
Expression source = new RequestParameterExpression(binding.getProperty());
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(binding.getProperty(), parserContext);
DefaultMapping mapping = new DefaultMapping(source, target);
// TODO - this is inefficient - consider introducing a typed Binding object
if (binding.getRequired() != null) {
mapping.setRequired(Boolean.valueOf(binding.getRequired()).booleanValue());
}
if (binding.getConverter() != null) {
ConversionExecutor conversionExecutor = conversionService.getConversionExecutor(binding.getConverter(),
String.class, target.getValueType(model));
mapping.setTypeConverter(conversionExecutor);
}
if (logger.isDebugEnabled()) {
logger.debug("Adding mapping for parameter '" + binding.getProperty() + "'");
}
mapper.addMapping(mapping);
}
private void addDefaultMappings(DefaultMapper mapper, Set parameterNames, Object model) {
for (Iterator it = parameterNames.iterator(); it.hasNext();) {
String parameterName = (String) it.next();
if (fieldMarkerPrefix != null && parameterName.startsWith(fieldMarkerPrefix)) {
String field = parameterName.substring(fieldMarkerPrefix.length());
if (isAllowedField(field)) {
if (!parameterNames.contains(field)) {
addEmptyValueMapping(mapper, field, model);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Will not map field marker parameter '" + parameterName + "'");
}
if (!parameterNames.contains(field)) {
addEmptyValueMapping(mapper, field, model);
}
} else {
if (isAllowedField(parameterName)) {
addDefaultMapping(mapper, parameterName, model);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Will not map parameter '" + parameterName + "'");
}
}
addDefaultMapping(mapper, parameterName, model);
}
}
}
private boolean isAllowedField(String field) {
if (allowedBindFields == null) {
// always allow
return true;
} else {
return allowedBindFields.contains(field);
}
}
private void addEmptyValueMapping(DefaultMapper mapper, String field, Object model) {
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(field, parserContext);
@@ -320,7 +345,7 @@ public abstract class AbstractMvcView implements View {
Expression source = new StaticExpression(getEmptyValue(propertyType));
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding empty parameter value mapping for field '" + field + "'");
logger.debug("Adding empty value mapping for parameter '" + field + "'");
}
mapper.addMapping(mapping);
} catch (EvaluationException e) {
@@ -347,7 +372,7 @@ public abstract class AbstractMvcView implements View {
Expression target = expressionParser.parseExpression(parameter, parserContext);
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding mapping for parameter '" + parameter + "'");
logger.debug("Adding default mapping for parameter '" + parameter + "'");
}
mapper.addMapping(mapping);
}
@@ -377,6 +402,15 @@ public abstract class AbstractMvcView implements View {
.defaultText(errorCode + " on " + field).build();
}
private boolean shouldValidate(Object model) {
TransitionableStateDefinition currentState = (TransitionableStateDefinition) requestContext.getCurrentState();
TransitionDefinition transition = currentState.getTransition(eventId);
if (transition == null) {
return true;
}
return transition.getAttributes().getBoolean("validate", Boolean.TRUE).booleanValue();
}
private void validate(Object model) {
String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,

View File

@@ -18,6 +18,7 @@ package org.springframework.webflow.mvc.view;
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.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
@@ -37,12 +38,15 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
private ConversionService conversionService;
private BinderModel binderModel;
public AbstractMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
this.viewId = viewId;
this.viewResolver = viewResolver;
this.expressionParser = expressionParser;
this.conversionService = conversionService;
this.binderModel = binderModel;
}
protected ExpressionParser getExpressionParser() {
@@ -53,6 +57,10 @@ public abstract class AbstractMvcViewFactory implements ViewFactory {
return conversionService;
}
protected BinderModel getBinderModel() {
return binderModel;
}
public View getView(RequestContext context) {
String viewId = (String) this.viewId.getValue(context);
org.springframework.web.servlet.View view = viewResolver.resolveView(viewId, context);

View File

@@ -38,6 +38,7 @@ 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;
/**
* Makes the properties of the "model" object available to Spring views during rendering. Also makes data binding (aka
@@ -63,6 +64,8 @@ public class BindingModel extends AbstractErrors {
private MessageContext messageContext;
private BinderModel binderModel;
/**
* Creates a new Spring Binding model.
* @param objectName the name of the bound model object
@@ -91,6 +94,10 @@ public class BindingModel extends AbstractErrors {
this.mappingResults = results;
}
public void setBinderModel(BinderModel binderModel) {
this.binderModel = binderModel;
}
// implementing Errors
public List getAllErrors() {
@@ -144,12 +151,23 @@ public class BindingModel extends AbstractErrors {
private ConversionExecutor getConverter(Expression fieldExpression) {
if (conversionService != null) {
Class valueType = fieldExpression.getValueType(boundObject);
// TODO -- this really is not accurate for Collection or Array or Map types
// This needs to be cleaned up
// special handling for array, collection, map types
// necessary as getFieldValue is called by form tags for non-formattable properties, too
// TODO - investigate how to improve this in Spring MVC
if (valueType.isArray() || Collection.class.isAssignableFrom(valueType)
|| Map.class.isAssignableFrom(valueType)) {
return null;
}
if (binderModel != null) {
org.springframework.webflow.engine.model.BindingModel binding = binderModel.getBinding(fieldExpression
.getExpressionString());
if (binding != null) {
String converterId = binding.getConverter();
if (converterId != null) {
return conversionService.getConversionExecutor(converterId, valueType, String.class);
}
}
}
return conversionService.getConversionExecutor(valueType, String.class);
} else {
return null;

View File

@@ -22,6 +22,7 @@ import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.style.ToStringCreator;
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;
@@ -36,7 +37,7 @@ import org.springframework.webflow.execution.ViewFactory;
class MockViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
return new MockViewFactory(viewId);
}

View File

@@ -15,6 +15,7 @@ import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.support.ClassPathXmlApplicationContext;
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;
@@ -46,7 +47,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
public static class TestViewFactoryCreator implements ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser,
ConversionService conversionService) {
ConversionService conversionService, BinderModel binderModel) {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -25,6 +25,7 @@ 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.execution.RequestContext;
import org.springframework.webflow.test.MockFlowExecutionKey;
import org.springframework.webflow.test.MockRequestContext;
@@ -214,9 +215,10 @@ 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);
HashSet allowedBindFields = new HashSet();
allowedBindFields.add("stringProperty");
view.setAllowedBindFields(allowedBindFields);
BinderModel binderModel = new BinderModel();
binderModel
.addBinding(new org.springframework.webflow.engine.model.BindingModel("stringProperty", null, "true"));
view.setBinderModel(binderModel);
view.processUserEvent();
assertTrue(view.hasFlowEvent());
assertEquals("submit", view.getFlowEvent().getId());
@@ -246,7 +248,6 @@ public class MvcViewTests extends TestCase {
AbstractMvcView view = new MockMvcView(mvcView, context);
HashSet allowedBindFields = new HashSet();
allowedBindFields.add("booleanProperty");
view.setAllowedBindFields(allowedBindFields);
view.processUserEvent();
assertEquals(false, bindBean.getBooleanProperty());
}