numerous binding fixes, see changelog

This commit is contained in:
Keith Donald
2009-02-25 01:32:06 +00:00
parent c8d4d6d97c
commit 16e88dcb6f
19 changed files with 510 additions and 64 deletions

View File

@@ -11,6 +11,12 @@ Bug Fixes
* Fixed bug where Web Flow upgrader tool did not properly output end-state 'output' elements (SWF-969)
* Fixed bug where String to Enum conversions failed to work in conjunction with BeanWrapperExpressionParser, enabled when setting "useSpringBinding" flag on a MvcViewFactoryCreator to true (SWF-1005).
* Fixed bug where Collection and Array property bindings did not work with custom converters (SWF-984)
* Fixed bug where form:checkbox and form:checkboxes tags did not apply type conversion properly in views rendered by Web Flows (SWF-986)
* Fixed bug where Errors nestedPath attribute was not respected in views rendered by Web Flow (SWF-973)
* Fixed bug where Errors nestedPath attribute was not respected when using Errors API programatically within a Validator called by Web Flow (SWF-973)
Improvements
* Improved FlowExecution test documentation (SWF-???, SWF-???)
Changes in version 2.0.5 (14.11.2008)
-------------------------------------

View File

@@ -36,6 +36,17 @@ public interface ConversionService {
*/
public Object executeConversion(Object source, Class targetClass) throws ConversionException;
/**
* Execute a conversion using the custom converter with the provided id.
* @param converterId the id of the custom converter, which must be registered with this conversion service and
* capable of converting to the target class
* @param source the source to convert from (may be null)
* @param targetClass the target class to convert to
* @return the converted object, an instance of the <code>targetClass</code>
* @throws ConversionException if an exception occurred during the conversion process
*/
public Object executeConversion(String converterId, Object source, Class targetClass);
/**
* Return the default conversion executor capable of converting source objects of the specified
* <code>sourceClass</code> to instances of the <code>targetClass</code>.
@@ -65,8 +76,8 @@ public interface ConversionService {
/**
* Return all conversion executors capable of converting <i>from</i> the provided <code>sourceClass</code>. For
* example, <code>getConversionExecutor(String.class)</code> would return all converters that convert from String
* to some other Object. Mainly useful for adapting a set of converters to some other environment.
* example, <code>getConversionExecutor(String.class)</code> would return all converters that convert from String to
* some other Object. Mainly useful for adapting a set of converters to some other environment.
* @param sourceClass the source class converting from
* @return the conversion executors that can convert from that source class
*/
@@ -78,4 +89,5 @@ public interface ConversionService {
* @return the class, or <code>null</code> if no alias exists
*/
public Class getClassForAlias(String alias);
}

View File

@@ -386,6 +386,15 @@ public class GenericConversionService implements ConversionService {
}
}
public Object executeConversion(String converterId, Object source, Class targetClass) throws ConversionException {
if (source != null) {
ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass);
return conversionExecutor.execute(source);
} else {
return null;
}
}
public Class getClassForAlias(String name) throws IllegalArgumentException {
Class clazz = (Class) aliasMap.get(name);
if (clazz != null) {

View File

@@ -29,8 +29,7 @@ public class PropertyNotFoundException extends EvaluationException {
* @param cause root cause of the failure
*/
public PropertyNotFoundException(Class contextClass, String property, Throwable cause) {
super(contextClass, property, "Property '" + property + "' not found on context of class ["
+ contextClass.getName() + "]", cause);
super(contextClass, property, "Property not found", cause);
}
}

View File

@@ -48,8 +48,8 @@ public class ValueCoercionException extends EvaluationException {
* @param cause root cause of the failure
*/
public ValueCoercionException(Class contextClass, String property, Object value, Class targetClass, Throwable cause) {
super(contextClass, property, "Value [" + value + "] could not be coerced to type [" + targetClass.getName()
+ "]", cause);
super(contextClass, property,
"Value could not be converted to target class; is a suitable type converter registered?", cause);
this.value = value;
this.targetClass = targetClass;
}

View File

@@ -72,20 +72,21 @@ public class MessageContextErrors extends AbstractErrors {
}
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
messageContext.addMessage(new MessageBuilder().error().source(field).code(errorCode).args(errorArgs)
.defaultText(defaultMessage).build());
messageContext.addMessage(new MessageBuilder().error().source(fixedField(field)).code(errorCode)
.args(errorArgs).defaultText(defaultMessage).build());
}
public void addAllErrors(Errors errors) {
Iterator it = errors.getAllErrors().iterator();
while (it.hasNext()) {
ObjectError error = (ObjectError) it.next();
MessageBuilder builder = new MessageBuilder().error().codes(error.getCodes()).args(error.getArguments())
.defaultText(error.getDefaultMessage());
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
rejectValue(fieldError.getField(), error.getCode(), error.getArguments(), error.getDefaultMessage());
} else {
reject(error.getCode(), error.getArguments(), error.getDefaultMessage());
builder.source(fieldError.getField());
}
messageContext.addMessage(builder.build());
}
}
@@ -103,7 +104,7 @@ public class MessageContextErrors extends AbstractErrors {
Message message = messages[i];
errors.add(new ObjectError(objectName, message.getText()));
}
return errors;
return Collections.unmodifiableList(errors);
}
public List getFieldErrors() {
@@ -116,11 +117,12 @@ public class MessageContextErrors extends AbstractErrors {
Message message = messages[i];
errors.add(new FieldError(objectName, (String) message.getSource(), message.getText()));
}
return errors;
return Collections.unmodifiableList(errors);
}
public Object getFieldValue(String field) {
// requires boundObject, and expressionParser to work
field = fixedField(field);
// requires boundObject and expressionParser to be set to work
if (mappingResults != null) {
List results = mappingResults.getResults(new PropertyErrorMappingResult(field));
if (!results.isEmpty()) {
@@ -131,13 +133,15 @@ public class MessageContextErrors extends AbstractErrors {
return parseFieldExpression(field).getValue(boundObject);
}
// internal helpers
private Expression parseFieldExpression(String field) {
return expressionParser.parseExpression(field, new FluentParserContext().evaluate(boundObject.getClass()));
}
private static MessageCriteria GLOBAL_ERROR = new MessageCriteria() {
public boolean test(Message message) {
if (message.getSource() == null && message.getSeverity().equals(Severity.ERROR)) {
if (message.getSeverity() == Severity.ERROR && message.getSource() == null) {
return true;
} else {
return false;
@@ -147,7 +151,7 @@ public class MessageContextErrors extends AbstractErrors {
private static MessageCriteria FIELD_ERROR = new MessageCriteria() {
public boolean test(Message message) {
if (message.getSource() != null && message.getSeverity().equals(Severity.ERROR)) {
if (message.getSeverity() == Severity.ERROR && message.getSource() instanceof String) {
return true;
} else {
return false;

View File

@@ -93,6 +93,10 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
throw new UnsupportedOperationException("Auto-generated method stub");
}
public Object executeConversion(String converterId, Object source, Class targetClass) {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutionException {
throw new UnsupportedOperationException("Auto-generated method stub");

View File

@@ -1,5 +1,31 @@
package org.springframework.webflow.samples.booking;
public enum Amenity {
OCEAN_VIEW, LATE_CHECKOUT, MINIBAR;
}
import java.io.Serializable;
public class Amenity implements Serializable {
private String name;
public Amenity(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Amenity)) {
return false;
}
Amenity a = (Amenity) obj;
return name.equals(a.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}

View File

@@ -13,6 +13,7 @@ public class ApplicationConversionService extends DefaultConversionService {
StringToDate dateConverter = new StringToDate();
dateConverter.setPattern("MM-dd-yyyy");
addConverter("shortDate", dateConverter);
addConverter(new StringToAmenity());
}
}

View File

@@ -5,6 +5,7 @@ import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.Basic;
@@ -186,6 +187,12 @@ public class Booking implements Serializable {
}
public void setAmenities(Set<Amenity> amenities) {
Iterator<Amenity> it = amenities.iterator();
while (it.hasNext()) {
Object a = it.next();
System.out.println(a);
System.out.println(a.getClass());
}
this.amenities = amenities;
}

View File

@@ -0,0 +1,21 @@
package org.springframework.webflow.samples.booking;
import org.springframework.binding.convert.converters.StringToObject;
public class StringToAmenity extends StringToObject {
public StringToAmenity() {
super(Amenity.class);
}
@Override
protected Object toObject(String string, Class tagetClass) throws Exception {
return new Amenity(string);
}
@Override
protected String toString(Object value) throws Exception {
return ((Amenity) value).getName();
}
}

View File

@@ -26,7 +26,8 @@
<!-- Configures Web Flow to use Tiles to create views for rendering; Tiles allows for applying consistent layouts to your views -->
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers" ref="tilesViewResolver"/>
<property name="viewResolvers" ref="tilesViewResolver"/>
<property name="useSpringBeanBinding" value="true" />
</bean>
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->

View File

@@ -127,7 +127,7 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
this.flowAttributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP;
}
}
/**
* A little proxy that refreshes the externally configured conversion service reference on each invocation.
*/
@@ -136,6 +136,10 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
return getFlowBuilderServices().getConversionService().executeConversion(source, targetClass);
}
public Object executeConversion(String converterId, Object source, Class targetClass) {
return getFlowBuilderServices().getConversionService().executeConversion(converterId, source, targetClass);
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutionException {
return getFlowBuilderServices().getConversionService().getConversionExecutor(sourceClass, targetClass);

View File

@@ -15,12 +15,14 @@
*/
package org.springframework.webflow.mvc.view;
import java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
@@ -35,6 +37,7 @@ import org.springframework.binding.message.MessageCriteria;
import org.springframework.binding.message.Severity;
import org.springframework.util.Assert;
import org.springframework.validation.AbstractErrors;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
@@ -51,7 +54,7 @@ import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
*
* @author Keith Donald
*/
public class BindingModel extends AbstractErrors {
public class BindingModel extends AbstractErrors implements BindingResult {
private String objectName;
@@ -110,14 +113,23 @@ public class BindingModel extends AbstractErrors {
}
public List getFieldErrors(String field) {
return toErrors(messageContext.getMessagesByCriteria(new FieldErrorMessage(field)));
field = fixedField(field);
MessageCriteria messageCriteria;
if (field.endsWith("*")) {
String prefix = field.substring(0, field.length() - 1);
messageCriteria = new FieldPrefixErrorMessage(prefix);
} else {
messageCriteria = new FieldErrorMessage(field);
}
return toErrors(messageContext.getMessagesByCriteria(messageCriteria));
}
public Class getFieldType(String field) {
return parseFieldExpression(field).getValueType(boundObject);
return parseFieldExpression(fixedField(field)).getValueType(boundObject);
}
public Object getFieldValue(String field) {
field = fixedField(field);
if (mappingResults != null) {
List results = mappingResults.getResults(new FieldErrorResult(field));
if (!results.isEmpty()) {
@@ -131,7 +143,82 @@ public class BindingModel extends AbstractErrors {
// not typically used by mvc views, but implemented to be on the safe side
public List getFieldErrors() {
return toErrors(messageContext.getMessagesByCriteria(new FieldErrorMessage()));
return toErrors(messageContext.getMessagesByCriteria(ERRORS_FIELD_SOURCE));
}
public String getObjectName() {
return objectName;
}
// never expected to be called by mvc views
public void addAllErrors(Errors errors) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
// implementing BindingResult
public Object getTarget() {
return boundObject;
}
public Object getRawFieldValue(String field) {
return parseFieldExpression(fixedField(field)).getValue(boundObject);
}
public PropertyEditor findEditor(String field, Class valueType) {
if (conversionService != null) {
String converterId = null;
if (field != null) {
field = fixedField(field);
if (binderConfiguration != null) {
Binding binding = binderConfiguration.getBinding(field);
if (binding != null) {
converterId = binding.getConverter();
}
}
if (valueType == null) {
valueType = parseFieldExpression(field).getValueType(boundObject);
}
}
return new ConversionExecutorPropertyEditor(conversionService, valueType, converterId);
} else {
return null;
}
}
// never expected to be called by mvc views
public void addError(ObjectError error) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public Map getModel() {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public PropertyEditorRegistry getPropertyEditorRegistry() {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public String[] getSuppressedFields() {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public void recordSuppressedField(String field) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public String[] resolveMessageCodes(String errorCode, String field) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
// internal helpers
@@ -187,7 +274,7 @@ public class BindingModel extends AbstractErrors {
errors.add(new FieldError(objectName, (String) message.getSource(), message.getText()));
}
}
return errors;
return Collections.unmodifiableList(errors);
}
private static class FieldErrorResult implements MappingResultsCriteria {
@@ -219,39 +306,37 @@ public class BindingModel extends AbstractErrors {
}
};
private static final MessageCriteria ERRORS_FIELD_SOURCE = new MessageCriteria() {
public boolean test(Message message) {
return message.getSeverity() == Severity.ERROR && message.getSource() instanceof String;
}
};
private static class FieldErrorMessage implements MessageCriteria {
private String field;
public FieldErrorMessage() {
}
public FieldErrorMessage(String field) {
Assert.hasText(field, "The field name is required");
this.field = field;
}
public boolean test(Message message) {
if (field != null) {
return field.equals(message.getSource()) && message.getSeverity() == Severity.ERROR;
} else {
return message.getSource() != null && message.getSeverity() == Severity.ERROR;
}
return message.getSeverity() == Severity.ERROR && field.equals(message.getSource());
}
}
public String getObjectName() {
return objectName;
private static class FieldPrefixErrorMessage implements MessageCriteria {
private String fieldPrefix;
public FieldPrefixErrorMessage(String fieldPrefix) {
Assert.hasText(fieldPrefix, "The fieldPrefix is required");
this.fieldPrefix = fieldPrefix;
}
public boolean test(Message message) {
return message.getSeverity() == Severity.ERROR && message.getSource() instanceof String
&& ((String) message.getSource()).startsWith(fieldPrefix);
}
}
public void addAllErrors(Errors errors) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
throw new UnsupportedOperationException("Should not be called during view rendering");
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2004-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.mvc.view;
import java.beans.PropertyEditorSupport;
import org.springframework.binding.convert.ConversionService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
class ConversionExecutorPropertyEditor extends PropertyEditorSupport {
private ConversionService conversionService;
private Class fieldType;
private String converterId;
public ConversionExecutorPropertyEditor(ConversionService conversionService, Class fieldType, String converterId) {
Assert.notNull(fieldType, "The field type is required");
this.conversionService = conversionService;
this.fieldType = fieldType;
this.converterId = converterId;
}
public String getAsText() {
if (StringUtils.hasText(converterId)) {
return (String) conversionService.executeConversion(converterId, getValue(), String.class);
} else {
return (String) conversionService.executeConversion(getValue(), String.class);
}
}
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(converterId)) {
setValue(conversionService.executeConversion(converterId, text, fieldType));
} else {
setValue(conversionService.executeConversion(text, fieldType));
}
}
}

View File

@@ -30,28 +30,26 @@ import org.springframework.webflow.execution.FlowExecutionOutcome;
import org.springframework.webflow.test.MockExternalContext;
/**
* Base class for integration tests that verify a flow executes as expected. Flow execution tests captured by subclasses
* should test that a flow responds to all supported transition criteria correctly, transitioning to the correct states
* and producing the expected results on the occurrence of possible external (user) events.
* Base class for tests that verify a flow executes as expected. Flow execution tests authored by subclasses should test
* that a flow responds to all supported transition criteria correctly, transitioning to the correct states and
* producing the expected results on the occurrence of external events.
* <p>
* More specifically, a typical flow execution test case will test:
* A typical flow execution test case will test:
* <ul>
* <li>That the flow execution starts as expected (see {@link #startFlow(MutableAttributeMap, ExternalContext)}).
* <li>That given the set of supported state transition criteria, a state executes the appropriate transition when a
* matching event is signaled (with potential input request parameters, see the {@link #resumeFlow(ExternalContext)}
* variants). A test case should be coded for each logical event that can occur, where an event drives a possible path
* through the flow. The goal should be to exercise all possible paths of the flow. Use a test coverage tool like Clover
* or Emma to assist with measuring your test's effectiveness.
* <li>That given a transition that leads to an interactive state type (a view state or an end state) that the view
* selection returned to the client matches what was expected and the current state of the flow matches what is
* expected.
* <li>That a state executes the appropriate transition when an event is signaled. A test case should be authored for
* each logical event that can occur, where an event triggers a transition representing a path through the flow. The
* goal should be to exercise all state transitions (see the {@link #resumeFlow(ExternalContext)} variants and the
* {@link #setCurrentState(String)} for more information).
* <li>That given a transition that leads to an interactive state type (such as a view state or an end state), the view
* selected matches what was expected and the current state of the flow matches what is expected.
* </ul>
* <p>
* A flow execution test can effectively automate and validate the orchestration required to drive an end-to-end
* business task that spans several steps involving the user to complete. Such tests are a good way to test your system
* top-down starting at the web-tier and pushing through all the way to the DB without having to deploy to a servlet or
* portlet container. In addition, they can be used to effectively test a flow's execution (the web layer) standalone,
* typically with a mock service layer. Both styles of testing are valuable and supported.
* typically with a mock service layer.
*
* @author Keith Donald
*/
@@ -311,14 +309,15 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
}
/**
* Assert that the entire flow execution has ended; that is, it is no longer active.
* Assert that the flow execution has ended; that is, it is no longer active.
*/
protected void assertFlowExecutionEnded() {
assertTrue("The flow execution is still active but it should have ended", getFlowExecution().hasEnded());
}
/**
* Assert that the entire flow execution has ended; that is, it is no longer active.
* Assert that the flow execution has ended with the outcome specified.
* @param outcome the name of the flow execution outcome
*/
protected void assertFlowExecutionOutcomeEquals(String outcome) {
assertNotNull("There has been no flow execution outcome", flowExecutionOutcome);

View File

@@ -78,4 +78,5 @@ public class TestBean implements Serializable {
public int hashCode() {
return (datum1.hashCode() + datum2 + (executed ? 1 : 0)) * 29;
}
}

View File

@@ -76,6 +76,10 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public Object executeConversion(String converterId, Object source, Class targetClass) {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutionException {
throw new UnsupportedOperationException("Auto-generated method stub");

View File

@@ -0,0 +1,209 @@
package org.springframework.webflow.mvc.view;
import java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.springframework.binding.convert.converters.StringToObject;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.mapping.Mapping;
import org.springframework.binding.mapping.impl.DefaultMappingResults;
import org.springframework.binding.mapping.results.TypeConversionError;
import org.springframework.binding.message.DefaultMessageContext;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.validation.FieldError;
import org.springframework.webflow.TestBean;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
import org.springframework.webflow.expression.DefaultExpressionParserFactory;
public class BindingModelTests extends TestCase {
BindingModel model;
DefaultMessageContext messages;
DefaultConversionService conversionService;
TestBean testBean;
ExpressionParser expressionParser;
public void setUp() {
testBean = new TestBean();
messages = new DefaultMessageContext();
conversionService = new DefaultConversionService();
expressionParser = DefaultExpressionParserFactory.getExpressionParser();
model = new BindingModel("testBean", testBean, expressionParser, conversionService, messages);
}
public void testInitialState() {
assertEquals(0, model.getErrorCount());
assertEquals(0, model.getFieldErrorCount());
assertEquals(0, model.getFieldErrorCount("datum1"));
assertEquals(0, model.getGlobalErrorCount());
assertEquals(0, model.getAllErrors().size());
assertEquals(0, model.getFieldErrors().size());
assertNull(model.getFieldError("datum1"));
assertEquals(String.class, model.getFieldType("datum1"));
}
public void testGetValue() {
testBean.datum1 = "test";
assertEquals("test", model.getFieldValue("datum1"));
}
public void testGetConvertedValue() {
testBean.datum2 = 3;
assertEquals("3", model.getFieldValue("datum2"));
}
public void testGetRawValue() {
testBean.datum2 = 3;
assertEquals(new Integer(3), model.getRawFieldValue("datum2"));
}
public void testGetFieldValueNonStringNoConversionService() {
model = new BindingModel("testBean", testBean, DefaultExpressionParserFactory.getExpressionParser(), null,
messages);
testBean.datum2 = 3;
assertEquals(new Integer(3), model.getFieldValue("datum2"));
}
public void testGetFieldValueConvertedWithCustomConverter() {
testBean.datum2 = 3;
conversionService.addConverter("customConverter", new StringToObject(Integer.class) {
protected Object toObject(String string, Class targetClass) throws Exception {
return Integer.valueOf(string);
}
protected String toString(Object object) throws Exception {
return "$" + object;
}
});
BinderConfiguration binder = new BinderConfiguration();
binder.addBinding(new Binding("datum2", "customConverter", true));
model.setBinderConfiguration(binder);
assertEquals("$3", model.getFieldValue("datum2"));
}
public void testGetFieldValueError() {
Map source = new HashMap();
source.put("datum2", "bogus");
List mappingResults = new ArrayList();
Mapping mapping = new Mapping() {
public Expression getSourceExpression() {
return expressionParser.parseExpression("datum2", null);
}
public Expression getTargetExpression() {
return expressionParser.parseExpression("datum2", null);
}
public boolean isRequired() {
return true;
}
};
mappingResults.add(new TypeConversionError(mapping, "bogus", null));
DefaultMappingResults results = new DefaultMappingResults(source, testBean, mappingResults);
model.setMappingResults(results);
assertEquals("bogus", model.getFieldValue("datum2"));
// not offically an error until an actual error message is associated with field
assertEquals(0, model.getErrorCount());
assertEquals(0, model.getFieldErrorCount());
}
public void testGetFieldError() {
messages.addMessage(new MessageBuilder().source("datum2").error().defaultText("Error").build());
assertEquals(1, model.getErrorCount());
assertEquals(1, model.getFieldErrorCount());
assertEquals(0, model.getGlobalErrorCount());
FieldError error = model.getFieldError("datum2");
assertEquals(null, error.getCode());
assertEquals(null, error.getCodes());
assertEquals(null, error.getArguments());
assertEquals("Error", error.getDefaultMessage());
// we dont track this
assertEquals(null, error.getRejectedValue());
assertTrue(!error.isBindingFailure());
FieldError error2 = (FieldError) model.getFieldErrors().get(0);
assertEquals(error, error2);
}
public void testGetFieldErrorsWildcard() {
messages.addMessage(new MessageBuilder().source("datum2").error().defaultText("Error").build());
assertEquals(1, model.getFieldErrorCount("da*"));
FieldError error = model.getFieldError("da*");
assertEquals(null, error.getCode());
assertEquals(null, error.getCodes());
assertEquals(null, error.getArguments());
assertEquals("Error", error.getDefaultMessage());
}
public void testFindPropertyEditor() {
PropertyEditor editor = model.findEditor("datum2", Integer.class);
assertNotNull(editor);
editor.setAsText((String) model.getFieldValue("datum2"));
assertEquals("0", editor.getAsText());
}
public void testNestedPath() {
model = new BindingModel("nestedPathBean", new NestedPathBean(), expressionParser, conversionService, messages);
model.pushNestedPath("nestedBean");
assertEquals("test", model.getFieldValue("datum1"));
assertEquals("0", model.getFieldValue("datum2"));
assertEquals(int.class, model.getFieldType("datum2"));
messages.addMessage(new MessageBuilder().source("nestedBean.datum2").error().defaultText("Error").build());
assertNotNull(model.getFieldErrors("datum2").get(0));
model.popNestedPath();
assertEquals("", model.getFieldValue("datum1"));
}
public static class NestedPathBean {
private String datum1 = "";
private NestedBean nestedBean = new NestedBean();
public String getDatum1() {
return datum1;
}
public void setDatum1(String datum1) {
this.datum1 = datum1;
}
public NestedBean getNestedBean() {
return nestedBean;
}
public void setNestedBean(NestedBean nestedBean) {
this.nestedBean = nestedBean;
}
public static class NestedBean {
private String datum1 = "test";
private int datum2;
public int getDatum2() {
return datum2;
}
public void setDatum2(int datum2) {
this.datum2 = datum2;
}
public String getDatum1() {
return datum1;
}
public void setDatum1(String datum1) {
this.datum1 = datum1;
}
}
}
}