From 808bdabe6f96baf6e506089d8b0adba6a3aa62c8 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Sun, 27 Jul 2008 04:10:54 +0000 Subject: [PATCH] made event id configurable; made requird a boolean --- .../webflow/engine/model/BindingModel.java | 14 +-- .../builder/xml/XmlFlowModelBuilder.java | 4 +- .../mvc/builder/MvcViewFactoryCreator.java | 101 +++++++++++------- .../mvc/portlet/PortletMvcViewFactory.java | 6 +- .../mvc/servlet/ServletMvcViewFactory.java | 6 +- .../webflow/mvc/view/AbstractMvcView.java | 24 +++-- .../mvc/view/AbstractMvcViewFactory.java | 29 +++-- .../webflow/mvc/view/MvcViewTests.java | 3 +- 8 files changed, 109 insertions(+), 78 deletions(-) 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 c7051dbf..c60b6c6f 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,7 +28,7 @@ public class BindingModel extends AbstractModel { private String converter; - private String required; + private boolean 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, String required) { + public BindingModel(String property, String converter, boolean required) { setProperty(property); setConverter(converter); setRequired(required); @@ -91,16 +91,12 @@ public class BindingModel extends AbstractModel { } } - public String getRequired() { + public boolean getRequired() { return required; } - public void setRequired(String required) { - if (StringUtils.hasText(required)) { - this.required = required; - } else { - this.required = null; - } + public void setRequired(boolean required) { + this.required = required; } } 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 a3c14a3c..67b098ab 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,8 +543,8 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { } private BindingModel parseBinding(Element element) { - return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), element - .getAttribute("required")); + boolean required = Boolean.valueOf(element.getAttribute("required")).booleanValue(); + return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), 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 0ad9895a..04366e31 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 @@ -24,6 +24,7 @@ import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.beanwrapper.BeanWrapperExpressionParser; import org.springframework.context.ApplicationContext; 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.ViewFactoryCreator; @@ -31,6 +32,7 @@ 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; +import org.springframework.webflow.mvc.view.AbstractMvcViewFactory; import org.springframework.webflow.mvc.view.FlowViewResolver; /** @@ -60,32 +62,23 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon private boolean useSpringBeanBinding; + private String eventIdParameterName; + + private String fieldMarkerPrefix; + /** * Create a new Spring MVC View Factory Creator. * @see #setDefaultViewSuffix(String) - * @see #setViewResolvers(List) + * @see #setEventIdParameterName(String) + * @see #setFieldMarkerPrefix(String) + * @see #setUseSpringBeanBinding(boolean) * @see #setFlowViewResolver(FlowViewResolver) + * @see #setViewResolvers(List) */ public MvcViewFactoryCreator() { } - /** - * Returns the configured mvc environment. - * @return the mvc environment - */ - public MvcEnvironment getEnvironment() { - return environment; - } - - /** - * Sets the configured mvc environment. - * @param environment the mvc environment. - */ - public void setEnvironment(MvcEnvironment environment) { - this.environment = environment; - } - /** * Configure an {@link FlowResourceFlowViewResolver} capable of resolving view resources by applying the specified * default resource suffix. Default is .jsp. @@ -98,13 +91,41 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon } /** - * Sets the chain of Spring MVC {@link ViewResolver view resolvers} to delegate to resolve views selected by flows. - * Allows for reuse of existing View Resolvers configured in a Spring application context. If multiple resolvers are - * to be used, the resolvers should be ordered in the manner they should be applied. - * @param viewResolvers the view resolver list + * Sets the name of the request parameter to use to lookup user events signaled by views created in this factory. If + * not specified, the default is _eventId + * @param eventIdParameterName the event id parameter name */ - public void setViewResolvers(List viewResolvers) { - this.flowViewResolver = new DelegatingFlowViewResolver(viewResolvers); + public void setEventIdParameterName(String eventIdParameterName) { + this.eventIdParameterName = eventIdParameterName; + } + + /** + * 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 + * is particularly useful for HTML checkboxes and select options. + *

+ * Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter"). Set this to null if you want to turn off + * the empty field check completely. + *

+ * HTML checkboxes only send a value when they're checked, so it is not possible to detect that a formerly checked + * box has just been unchecked, at least not with standard HTML means. + *

+ * This auto-reset mechanism addresses this deficiency, provided that a marker parameter is sent for each checkbox + * field, like "_subscribeToNewsletter" for a "subscribeToNewsletter" field. As the marker parameter is sent in any + * case, the data binder can detect an empty field and automatically reset its value. + */ + public void setFieldMarkerPrefix(String fieldMarkerPrefix) { + this.fieldMarkerPrefix = fieldMarkerPrefix; + } + + /** + * Sets whether to use data binding with Spring's {@link BeanWrapper} should be enabled. Set to 'true' to enable. + * 'false', disabled, is the default. With this enabled, the same binding system used by Spring MVC 2.x is also used + * in a Web Flow environment. + * @param useSpringBeanBinding the Spring bean binding flag + */ + public void setUseSpringBeanBinding(boolean useSpringBeanBinding) { + this.useSpringBeanBinding = useSpringBeanBinding; } /** @@ -116,22 +137,13 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon } /** - * Whether data binding with Spring's {@link BeanWrapper} should be enabled. Default is false. With this enabled, - * the same binding system used by Spring MVC 2.x is also used in a Web Flow environment. - * @return the use Spring bean binding flag + * Sets the chain of Spring MVC {@link ViewResolver view resolvers} to delegate to resolve views selected by flows. + * Allows for reuse of existing View Resolvers configured in a Spring application context. If multiple resolvers are + * to be used, the resolvers should be ordered in the manner they should be applied. + * @param viewResolvers the view resolver list */ - public boolean getUseSpringBeanBinding() { - return useSpringBeanBinding; - } - - /** - * Sets whether to use data binding with Spring's {@link BeanWrapper} should be enabled. Set to 'true' to enable. - * 'false', disabled, is the default. With this enabled, the same binding system used by Spring MVC 2.x is also used - * in a Web Flow environment. - * @param useSpringBeanBinding the Spring bean binding flag - */ - public void setUseSpringBeanBinding(boolean useSpringBeanBinding) { - this.useSpringBeanBinding = useSpringBeanBinding; + public void setViewResolvers(List viewResolvers) { + this.flowViewResolver = new DelegatingFlowViewResolver(viewResolvers); } // implementing ApplicationContextAware @@ -145,6 +157,19 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon if (useSpringBeanBinding) { expressionParser = new BeanWrapperExpressionParser(conversionService); } + AbstractMvcViewFactory viewFactory = createMvcViewFactory(viewId, expressionParser, conversionService, + binderModel); + if (StringUtils.hasText(eventIdParameterName)) { + viewFactory.setEventIdParameterName(eventIdParameterName); + } + if (StringUtils.hasText(fieldMarkerPrefix)) { + viewFactory.setFieldMarkerPrefix(fieldMarkerPrefix); + } + return viewFactory; + } + + private AbstractMvcViewFactory createMvcViewFactory(Expression viewId, ExpressionParser expressionParser, + ConversionService conversionService, BinderModel binderModel) { if (environment == MvcEnvironment.SERVLET) { return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService, binderModel); } else if (environment == MvcEnvironment.PORTLET) { 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 7ac3da89..890e5cfe 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 @@ -38,11 +38,7 @@ public class PortletMvcViewFactory extends AbstractMvcViewFactory { } protected AbstractMvcView createMvcView(View view, RequestContext context) { - PortletMvcView mvcView = new PortletMvcView(view, context); - mvcView.setExpressionParser(getExpressionParser()); - mvcView.setConversionService(getConversionService()); - mvcView.setBinderModel(getBinderModel()); - return mvcView; + return new PortletMvcView(view, context); } } 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 94085036..9c770948 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 @@ -38,11 +38,7 @@ public class ServletMvcViewFactory extends AbstractMvcViewFactory { } protected AbstractMvcView createMvcView(View view, RequestContext context) { - ServletMvcView mvcView = new ServletMvcView(view, context); - mvcView.setExpressionParser(getExpressionParser()); - mvcView.setConversionService(getConversionService()); - mvcView.setBinderModel(getBinderModel()); - return mvcView; + return new ServletMvcView(view, context); } } 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 5ec586ce..19dd7184 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 @@ -80,13 +80,15 @@ public abstract class AbstractMvcView implements View { private ConversionService conversionService; - private MappingResults mappingResults; + private String fieldMarkerPrefix = "_"; - private boolean viewErrors; + private String eventIdParameterName = "_eventId"; private String eventId; - private String fieldMarkerPrefix = "_"; + private MappingResults mappingResults; + + private boolean viewErrors; private BinderModel binderModel; @@ -143,6 +145,15 @@ public abstract class AbstractMvcView implements View { this.fieldMarkerPrefix = fieldMarkerPrefix; } + /** + * Sets the name of the request parameter to use to lookup user events signaled by this view. If not specified, the + * default is _eventId + * @param eventIdParameterName the event id parameter name + */ + public void setEventIdParameterName(String eventIdParameterName) { + this.eventIdParameterName = eventIdParameterName; + } + public void render() throws IOException { Map model = new HashMap(); model.putAll(flowScopes()); @@ -308,10 +319,7 @@ public abstract class AbstractMvcView implements View { 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()); - } + mapping.setRequired(binding.getRequired()); if (binding.getConverter() != null) { ConversionExecutor conversionExecutor = conversionService.getConversionExecutor(binding.getConverter(), String.class, target.getValueType(model)); @@ -441,7 +449,7 @@ public abstract class AbstractMvcView implements View { } private void determineEventId(RequestContext context) { - eventId = WebUtils.findParameterValue(context.getRequestParameters().asMap(), "_eventId"); + eventId = WebUtils.findParameterValue(context.getRequestParameters().asMap(), eventIdParameterName); } private static class PropertyNotFoundError implements MappingResultsCriteria { 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 23d2dd8d..9753c0f4 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.util.StringUtils; import org.springframework.webflow.engine.model.BinderModel; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.View; @@ -40,6 +41,10 @@ public abstract class AbstractMvcViewFactory implements ViewFactory { private BinderModel binderModel; + private String eventIdParameterName; + + private String fieldMarkerPrefix; + public AbstractMvcViewFactory(Expression viewId, FlowViewResolver viewResolver, ExpressionParser expressionParser, ConversionService conversionService, BinderModel binderModel) { this.viewId = viewId; @@ -49,22 +54,28 @@ public abstract class AbstractMvcViewFactory implements ViewFactory { this.binderModel = binderModel; } - protected ExpressionParser getExpressionParser() { - return expressionParser; + public void setEventIdParameterName(String eventIdParameterName) { + this.eventIdParameterName = eventIdParameterName; } - protected ConversionService getConversionService() { - return conversionService; - } - - protected BinderModel getBinderModel() { - return binderModel; + public void setFieldMarkerPrefix(String fieldMarkerPrefix) { + this.fieldMarkerPrefix = fieldMarkerPrefix; } public View getView(RequestContext context) { String viewId = (String) this.viewId.getValue(context); org.springframework.web.servlet.View view = viewResolver.resolveView(viewId, context); - return createMvcView(view, context); + AbstractMvcView mvcView = createMvcView(view, context); + mvcView.setExpressionParser(expressionParser); + mvcView.setConversionService(conversionService); + mvcView.setBinderModel(binderModel); + if (StringUtils.hasText(eventIdParameterName)) { + mvcView.setEventIdParameterName(eventIdParameterName); + } + if (StringUtils.hasText(fieldMarkerPrefix)) { + mvcView.setFieldMarkerPrefix(fieldMarkerPrefix); + } + return mvcView; } protected abstract AbstractMvcView createMvcView(org.springframework.web.servlet.View view, RequestContext context); 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 8df7399a..49069e81 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 @@ -216,8 +216,7 @@ public class MvcViewTests extends TestCase { 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")); + binderModel.addBinding(new org.springframework.webflow.engine.model.BindingModel("stringProperty", null, true)); view.setBinderModel(binderModel); view.processUserEvent(); assertTrue(view.hasFlowEvent());