SWF-1218 Add protected methods in AbstractMvcView and MvcViewFactoryCreator.

This commit is contained in:
Rossen Stoyanchev
2010-05-18 08:25:30 +00:00
parent 0f84b153b5
commit e1e3e7ccc7
2 changed files with 181 additions and 90 deletions

View File

@@ -184,7 +184,11 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
return viewFactory;
}
private AbstractMvcViewFactory createMvcViewFactory(Expression viewId, ExpressionParser expressionParser,
/**
* Creates a concrete instance of an AbstractMvcViewFactory according to the runtime environment (Servlet or
* Portlet).
*/
protected AbstractMvcViewFactory createMvcViewFactory(Expression viewId, ExpressionParser expressionParser,
ConversionService conversionService, BinderConfiguration binderConfiguration) {
if (environment == MvcEnvironment.SERVLET) {
return new ServletMvcViewFactory(viewId, flowViewResolver, expressionParser, conversionService,

View File

@@ -307,6 +307,30 @@ public abstract class AbstractMvcView implements View {
return mappingResults;
}
/**
* Returns the binding configuration that defines how to connect properties of the model to UI elements.
* @return an instance of {@link BinderConfiguration} or null.
*/
protected BinderConfiguration getBinderConfiguration() {
return binderConfiguration;
}
/**
* Returns the EL parser to be used for data binding purposes.
* @return an instance of {@link ExpressionParser}.
*/
protected ExpressionParser getExpressionParser() {
return expressionParser;
}
/**
* Returns the prefix that can be used for parameters that mark potentially empty fields.
* @return the prefix value.
*/
protected String getFieldMarkerPrefix() {
return fieldMarkerPrefix;
}
/**
* Obtain the user event from the current flow request. The default implementation returns the value of the request
* parameter with name {@link #setEventIdParameterName(String) eventIdParameterName}. Subclasses may override.
@@ -317,6 +341,158 @@ public abstract class AbstractMvcView implements View {
return WebUtils.findParameterValue(context.getRequestParameters().asMap(), eventIdParameterName);
}
/**
* <p>
* Causes the model to be populated from information contained in request parameters.
* </p>
* <p>
* If a view has binding configuration then only model fields specified in the binding configuration will be
* considered. In the absence of binding configuration all request parameters will be used to update matching fields
* on the model.
* </p>
*
* @param model the model to be updated
* @return an instance of MappingResults with information about the results of the binding.
*/
protected MappingResults bind(Object model) {
if (logger.isDebugEnabled()) {
logger.debug("Binding to model");
}
DefaultMapper mapper = new DefaultMapper();
ParameterMap requestParameters = requestContext.getRequestParameters();
if (binderConfiguration != null) {
addModelBindings(mapper, requestParameters.asMap().keySet(), model);
} else {
addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
}
return mapper.map(requestParameters, model);
}
/**
* <p>
* Adds a {@link DefaultMapping} for every configured view {@link Binding} for which there is an incoming request
* parameter. If there is no matching incoming request parameter, a special mapping is created that will set the
* target field on the model to an empty value (typically null).
* </p>
*
* @param mapper the mapper to which mappings will be added
* @param parameterNames the request parameters
* @param model the model
*/
protected void addModelBindings(DefaultMapper mapper, Set parameterNames, Object model) {
Iterator it = binderConfiguration.getBindings().iterator();
while (it.hasNext()) {
Binding binding = (Binding) 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);
}
}
}
}
/**
* <p>
* Creates and adds a {@link DefaultMapping} for the given {@link Binding}. Information such as the model field
* name, if the field is required, and whether type conversion is needed will be passed on from the binding to the
* mapping.
* </p>
* <p>
* <b>Note:</b> with Spring 3 type conversion and formatting now in use in Web Flow, it is no longer necessary to
* use named converters on binding elements. The preferred approach is to register Spring 3 formatters. Named
* converters are supported for backwards compatibility only and will not result in use of the Spring 3 type
* conversion system at runtime.
* </p>
*
* @param mapper the mapper to add the mapping to
* @param binding the binding element
* @param model the model
*/
protected void addMapping(DefaultMapper mapper, Binding binding, Object model) {
Expression source = new RequestParameterExpression(binding.getProperty());
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(binding.getProperty(), parserContext);
DefaultMapping mapping = new DefaultMapping(source, target);
mapping.setRequired(binding.getRequired());
if (binding.getConverter() != null) {
Assert.notNull(conversionService,
"A ConversionService must be configured to use resolve custom converters to use during binding");
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);
}
/**
* Add a {@link DefaultMapping} instance for all incoming request parameters except those having a special field
* marker prefix. This method is used when binding configuration was not specified on the view.
*
* @param mapper the mapper to add mappings to
* @param parameterNames the request parameter names
* @param model the model
*/
protected 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 (!parameterNames.contains(field)) {
addEmptyValueMapping(mapper, field, model);
}
} else {
addDefaultMapping(mapper, parameterName, model);
}
}
}
/**
* Adds a special {@link DefaultMapping} that results in setting the target field on the model to an empty value
* (typically null).
*
* @param mapper the mapper to add the mapping to
* @param field the field for which a mapping is to be added
* @param model the model
*/
protected void addEmptyValueMapping(DefaultMapper mapper, String field, Object model) {
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(field, parserContext);
try {
Class propertyType = target.getValueType(model);
Expression source = new StaticExpression(getEmptyValue(propertyType));
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding empty value mapping for parameter '" + field + "'");
}
mapper.addMapping(mapping);
} catch (EvaluationException e) {
}
}
/**
* Adds a {@link DefaultMapping} between the given request parameter name and a matching model field.
*
* @param mapper the mapper to add the mapping to
* @param parameter the request parameter name
* @param model the model
*/
protected void addDefaultMapping(DefaultMapper mapper, String parameter, Object model) {
Expression source = new RequestParameterExpression(parameter);
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(parameter, parserContext);
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding default mapping for parameter '" + parameter + "'");
}
mapper.addMapping(mapping);
}
// package private
/**
@@ -395,84 +571,6 @@ public abstract class AbstractMvcView implements View {
return (Expression) requestContext.getCurrentState().getAttributes().get("model");
}
private MappingResults bind(Object model) {
if (logger.isDebugEnabled()) {
logger.debug("Binding to model");
}
DefaultMapper mapper = new DefaultMapper();
ParameterMap requestParameters = requestContext.getRequestParameters();
if (binderConfiguration != null) {
addModelBindings(mapper, requestParameters.asMap().keySet(), model);
} else {
addDefaultMappings(mapper, requestParameters.asMap().keySet(), model);
}
return mapper.map(requestParameters, model);
}
private void addModelBindings(DefaultMapper mapper, Set parameterNames, Object model) {
Iterator it = binderConfiguration.getBindings().iterator();
while (it.hasNext()) {
Binding binding = (Binding) 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, Binding 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);
mapping.setRequired(binding.getRequired());
if (binding.getConverter() != null) {
Assert.notNull(conversionService,
"A ConversionService must be configured to use resolve custom converters to use during binding");
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 (!parameterNames.contains(field)) {
addEmptyValueMapping(mapper, field, model);
}
} else {
addDefaultMapping(mapper, parameterName, model);
}
}
}
private void addEmptyValueMapping(DefaultMapper mapper, String field, Object model) {
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(field, parserContext);
try {
Class propertyType = target.getValueType(model);
Expression source = new StaticExpression(getEmptyValue(propertyType));
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding empty value mapping for parameter '" + field + "'");
}
mapper.addMapping(mapping);
} catch (EvaluationException e) {
}
}
private Object getEmptyValue(Class fieldType) {
if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
// Special handling of boolean property.
@@ -486,17 +584,6 @@ public abstract class AbstractMvcView implements View {
}
}
private void addDefaultMapping(DefaultMapper mapper, String parameter, Object model) {
Expression source = new RequestParameterExpression(parameter);
ParserContext parserContext = new FluentParserContext().evaluate(model.getClass());
Expression target = expressionParser.parseExpression(parameter, parserContext);
DefaultMapping mapping = new DefaultMapping(source, target);
if (logger.isDebugEnabled()) {
logger.debug("Adding default mapping for parameter '" + parameter + "'");
}
mapper.addMapping(mapping);
}
private boolean hasErrors(MappingResults results) {
return results.hasErrorResults() && !onlyPropertyNotFoundErrorsPresent(results);
}