From b70c76d459072c6d27ed75002001a03935e97efb Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Tue, 22 Jul 2008 03:21:30 +0000 Subject: [PATCH] binder addition --- .../service/GenericConversionService.java | 18 ++- .../faces/webflow/JsfViewFactoryCreator.java | 3 +- ...lderServicesBeanDefinitionParserTests.java | 3 +- .../faces/ui/DojoDecorationRendererTests.java | 4 +- .../booking/ApplicationConversionService.java | 16 +++ .../webapp/WEB-INF/config/webflow-config.xml | 2 +- .../webapp/WEB-INF/hotels/booking/booking.xml | 5 +- .../engine/builder/ViewFactoryCreator.java | 7 +- .../builder/model/FlowModelFlowBuilder.java | 15 +-- .../webflow/engine/model/BinderModel.java | 21 +++- .../webflow/engine/model/BindingModel.java | 18 ++- .../builder/xml/XmlFlowModelBuilder.java | 3 +- .../mvc/builder/MvcViewFactoryCreator.java | 7 +- .../mvc/portlet/PortletMvcViewFactory.java | 6 +- .../webflow/mvc/servlet/ServletMvcView.java | 1 + .../mvc/servlet/ServletMvcViewFactory.java | 6 +- .../webflow/mvc/view/AbstractMvcView.java | 116 +++++++++++------- .../mvc/view/AbstractMvcViewFactory.java | 10 +- .../webflow/mvc/view/BindingModel.java | 22 +++- .../webflow/test/MockViewFactoryCreator.java | 3 +- ...lderServicesBeanDefinitionParserTests.java | 3 +- .../webflow/mvc/view/MvcViewTests.java | 9 +- 22 files changed, 218 insertions(+), 80 deletions(-) create mode 100644 spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java index bc50561d..30e74db1 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java @@ -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)) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java index 2af0b28c..89f1eb72 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java @@ -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()); } diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java index 6e218245..55294ac2 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java @@ -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"); } diff --git a/spring-faces/src/test/java/org/springframework/faces/ui/DojoDecorationRendererTests.java b/spring-faces/src/test/java/org/springframework/faces/ui/DojoDecorationRendererTests.java index 3412a4e7..31061bf1 100644 --- a/spring-faces/src/test/java/org/springframework/faces/ui/DojoDecorationRendererTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/ui/DojoDecorationRendererTests.java @@ -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); diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java new file mode 100644 index 00000000..e1b3a897 --- /dev/null +++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java @@ -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()); + } + +} \ No newline at end of file diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml index c57cbfdc..055c9d51 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml @@ -22,7 +22,7 @@ - + diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking.xml index 2c938880..16b74d97 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking.xml @@ -17,11 +17,12 @@ + - - + + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java index 8860900c..4a8472c3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java @@ -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); -} + +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java index 8b08cbc5..bba820c9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java @@ -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) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BinderModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BinderModel.java index 78a69254..4b7775e7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BinderModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BinderModel.java @@ -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())); } -} +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BindingModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BindingModel.java index ae2be482..c7051dbf 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BindingModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BindingModel.java @@ -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; + } + } + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java index d51bdae6..a3c14a3c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java @@ -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) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcViewFactoryCreator.java index f234bcd5..0ad9895a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcViewFactoryCreator.java @@ -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 "); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java index c40af945..7ac3da89 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java @@ -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; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java index 0e52e948..4a669c7b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java @@ -49,4 +49,5 @@ public class ServletMvcView extends AbstractMvcView { context.getActiveFlow().getApplicationContext()); getView().render(model, request, response); } + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcViewFactory.java index 3d4cd3ba..94085036 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcViewFactory.java @@ -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; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java index 80659791..5ec586ce 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java @@ -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, diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcViewFactory.java index 5da93f11..23d2dd8d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcViewFactory.java @@ -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); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java index 0f01d7b5..b0224746 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java @@ -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; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java index b77c1cdc..d5949528 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java @@ -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); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java index f41b5545..831647ee 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java @@ -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"); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java index 7b74cdd0..8df7399a 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java @@ -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()); }