numerous binding fixes, see changelog
This commit is contained in:
@@ -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)
|
||||
-------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public class ApplicationConversionService extends DefaultConversionService {
|
||||
StringToDate dateConverter = new StringToDate();
|
||||
dateConverter.setPattern("MM-dd-yyyy");
|
||||
addConverter("shortDate", dateConverter);
|
||||
addConverter(new StringToAmenity());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -78,4 +78,5 @@ public class TestBean implements Serializable {
|
||||
public int hashCode() {
|
||||
return (datum1.hashCode() + datum2 + (executed ? 1 : 0)) * 29;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user