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());
}