diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java index 5f0aa7c1..32321cef 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java @@ -135,11 +135,11 @@ public class GenericConversionService implements ConversionService { if (this.sourceClassConverters == null || this.sourceClassConverters.isEmpty()) { throw new IllegalStateException("No converters have been added to this service's registry"); } + sourceClass = convertToWrapperClassIfNecessary(sourceClass); + targetClass = convertToWrapperClassIfNecessary(targetClass); if (targetClass.isAssignableFrom(sourceClass)) { return new ConversionExecutorImpl(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass)); } - sourceClass = convertToWrapperClassIfNecessary(sourceClass); - targetClass = convertToWrapperClassIfNecessary(targetClass); Map sourceTargetConverters = findConvertersForSource(sourceClass); Converter converter = findTargetConverter(sourceTargetConverters, targetClass); if (converter != null) { diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java index 11cb66e6..fa2ed9b1 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java @@ -56,16 +56,6 @@ public class Mapping { */ private boolean required; - /** - * Creates a new mapping. - * @param sourceExpression the source expression - * @param targetExpression the target expression - * @param typeConverter a type converter - */ - public Mapping(Expression sourceExpression, Expression targetExpression, ConversionExecutor typeConverter) { - this(sourceExpression, targetExpression, typeConverter, false); - } - /** * Creates a new mapping. * @param sourceExpression the source expression @@ -94,7 +84,7 @@ public class Mapping { Assert.notNull(target, "The target to map to is required"); Assert.notNull(context, "The mapping context is required"); Object sourceValue = sourceExpression.getValue(source); - if (required && sourceValue == null || isEmptyString(sourceValue)) { + if (required && (sourceValue == null || isEmptyString(sourceValue))) { String defaultText = "'" + targetExpression.getExpressionString() + "' is required"; MessageResolver message = new MessageBuilder().error().source(targetExpression.getExpressionString()) .codes(createMessageCodes("required", target, targetExpression)).defaultText(defaultText).build(); @@ -106,6 +96,7 @@ public class Mapping { try { targetValue = typeConverter.execute(sourceValue); } catch (ConversionException e) { + e.printStackTrace(); String defaultText = "The '" + targetExpression.getExpressionString() + "' value is the wrong type"; MessageResolver message = new MessageBuilder().error().source(targetExpression.getExpressionString()) .codes(createMessageCodes("typeMismatch", target, targetExpression)).defaultText(defaultText) diff --git a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/BookingService.java b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/BookingService.java index 37d2d134..854859b9 100755 --- a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/BookingService.java +++ b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/BookingService.java @@ -29,16 +29,18 @@ public interface BookingService { */ public Hotel findHotelById(Long id); + /** + * Create a new, transient hotel booking instance for the given user. + * @param hotelId the hotelId + * @param userName the user name + * @return the new transient booking instance + */ + public Booking createBooking(Long hotelId, String userName); + /** * Cancel an existing booking. * @param id the booking id */ public void cancelBooking(Booking booking); - /** - * Lookup a user based on their username - * @param username the user's username - * @return the user - */ - public User findUser(String username); } diff --git a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/JpaBookingService.java b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/JpaBookingService.java index 6dff554c..13d7618d 100755 --- a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/JpaBookingService.java +++ b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/JpaBookingService.java @@ -40,8 +40,7 @@ public class JpaBookingService implements BookingService { @Transactional(readOnly = true) @SuppressWarnings("unchecked") public List findHotels(SearchCriteria criteria) { - String pattern = !StringUtils.hasText(criteria.getSearchString()) ? "'%'" : "'%" - + criteria.getSearchString().toLowerCase().replace('*', '%') + "%'"; + String pattern = getSearchPattern(criteria); return em.createQuery( "select h from Hotel h where lower(h.name) like " + pattern + " or lower(h.city) like " + pattern + " or lower(h.zip) like " + pattern + " or lower(h.address) like " + pattern).setMaxResults( @@ -54,9 +53,10 @@ public class JpaBookingService implements BookingService { } @Transactional(readOnly = true) - public User findUser(String username) { - return (User) em.createQuery("select u from User u where u.username = :username").setParameter("username", - username).getSingleResult(); + public Booking createBooking(Long hotelId, String username) { + Hotel hotel = em.find(Hotel.class, hotelId); + User user = findUser(username); + return new Booking(hotel, user); } // read-write transactional methods @@ -71,11 +71,16 @@ public class JpaBookingService implements BookingService { // helpers private String getSearchPattern(SearchCriteria criteria) { - if (criteria.getSearchString().length() > 0) { - return "'%'" + criteria.getSearchString().toLowerCase().replace('*', '%') + "%'"; + if (StringUtils.hasText(criteria.getSearchString())) { + return "'%" + criteria.getSearchString().toLowerCase().replace('*', '%') + "%'"; } else { - return "'%"; + return "'%'"; } } + private User findUser(String username) { + return (User) em.createQuery("select u from User u where u.username = :username").setParameter("username", + username).getSingleResult(); + } + } \ No newline at end of file diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/booking.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/booking.xml index 5c3e776b..e90e23ac 100755 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/booking.xml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/booking.xml @@ -7,12 +7,10 @@ - + - - - + @@ -23,7 +21,9 @@ - + + + diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml index 41670e35..58cc488d 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml @@ -15,28 +15,28 @@
Name:
-
#{hotel.name}
+
#{booking.hotel.name}
Address:
-
#{hotel.address}
+
#{booking.hotel.address}
City, State:
-
#{hotel.city}, #{hotel.state}
+
#{booking.hotel.city}, #{booking.hotel.state}
Zip:
-
#{hotel.zip}
+
#{booking.hotel.zip}
Country:
-
#{hotel.country}
+
#{booking.hotel.country}
Nightly rate:
- +
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main.xml index 4c00cff1..8cbeb06c 100755 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main.xml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main.xml @@ -41,7 +41,7 @@ - + diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/PropertyEditors.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/PropertyEditors.java deleted file mode 100644 index a6b49c41..00000000 --- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/PropertyEditors.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.webflow.samples.booking; - -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; -import org.springframework.beans.propertyeditors.CustomDateEditor; - -public class PropertyEditors implements PropertyEditorRegistrar { - - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true)); - } - -} diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/booking.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/booking.xml index 50eb5268..b50ee9dd 100755 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/booking.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/booking.xml @@ -8,27 +8,20 @@ - + - - + - - - - + - - - diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.jsp b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.jsp index 6d400437..7b15abb6 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.jsp +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.jsp @@ -16,28 +16,28 @@
Name:
-
${hotel.name}
+
${booking.hotel.name}
Address:
-
${hotel.address}
+
${booking.hotel.address}
City, State:
-
${hotel.city}, ${hotel.state}
+
${booking.hotel.city}, ${booking.hotel.state}
Zip:
-
${hotel.zip}
+
${booking.hotel.zip}
Country:
-
${hotel.country}
+
${booking.hotel.country}
Nightly rate:
- ${status.value} + ${status.value}
diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/main/main.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/main/main.xml index 1b266a5a..eb6ebc78 100755 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/main/main.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/flows/main/main.xml @@ -10,37 +10,29 @@ - - + + + - - + + + + - - - - - - - + - - - - - \ No newline at end of file diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml index 57a3c565..12528766 100755 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml @@ -62,22 +62,6 @@ - - - - - - - - - - - - - - - - diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java index 3bcc7ca8..a84cb555 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java @@ -2,8 +2,11 @@ package org.springframework.webflow.action; import java.util.Iterator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor; +import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.support.ParserContextImpl; @@ -17,6 +20,8 @@ import org.springframework.webflow.execution.RequestContext; public class BindAction extends AbstractAction { + private static Log logger = LogFactory.getLog(BindAction.class); + private Expression target; private ExpressionParser expressionParser; @@ -37,15 +42,26 @@ public class BindAction extends AbstractAction { } DefaultAttributeMapper mapper = new DefaultAttributeMapper(); AttributeMap eventAttributes = context.getLastEvent().getAttributes(); + if (logger.isDebugEnabled()) { + logger.debug("Binding event '" + context.getLastEvent().getId() + "' attributes " + eventAttributes + + " to target " + target); + } for (Iterator it = eventAttributes.asMap().keySet().iterator(); it.hasNext();) { String name = (String) it.next(); Expression sourceAttribute = expressionParser.parseExpression(name, new ParserContextImpl() .eval(AttributeMap.class)); Expression targetAttribute = expressionParser.parseExpression(name, new ParserContextImpl().eval(target .getClass())); - Class targetType = targetAttribute.getValueType(target); - mapper.addMapping(new Mapping(sourceAttribute, targetAttribute, new RuntimeBindingConversionExecutor( - targetType, conversionService), false)); + Class targetType; + try { + targetType = targetAttribute.getValueType(target); + } catch (EvaluationException e) { + targetType = null; + } + if (targetType != null) { + mapper.addMapping(new Mapping(sourceAttribute, targetAttribute, new RuntimeBindingConversionExecutor( + targetType, conversionService), false)); + } } try { mapper.map(context.getLastEvent().getAttributes(), target, new MappingContextImpl(context diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java index 00c5b75e..d08bbc3d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java @@ -45,6 +45,7 @@ import org.springframework.binding.mapping.Mapping; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.JdkVersion; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.style.ToStringCreator; @@ -54,6 +55,7 @@ import org.springframework.util.xml.DomUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.webflow.action.ActionResultExposer; +import org.springframework.webflow.action.BindAction; import org.springframework.webflow.action.EvaluateAction; import org.springframework.webflow.action.ExternalRedirectAction; import org.springframework.webflow.action.FlowDefinitionRedirectAction; @@ -327,7 +329,9 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } flowContext.setResourceLoader(new FlowRelativeResourceLoader(resource)); - AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); + if (JdkVersion.isAtLeastJava15()) { + AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); + } new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); registerFlowBeans(flowContext.getDefaultListableBeanFactory()); flowContext.refresh(); @@ -667,7 +671,9 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde if (!(childNode instanceof Element)) { continue; } - if (DomUtils.nodeNameEquals(childNode, "evaluate")) { + if (DomUtils.nodeNameEquals(childNode, "bind")) { + actions.add(parseBindAction((Element) childNode)); + } else if (DomUtils.nodeNameEquals(childNode, "evaluate")) { actions.add(parseEvaluateAction((Element) childNode)); } else if (DomUtils.nodeNameEquals(childNode, "render")) { actions.add(parseRenderAction((Element) childNode)); @@ -678,6 +684,13 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde return (Action[]) actions.toArray(new Action[actions.size()]); } + private Action parseBindAction(Element element) { + String targetString = element.getAttribute("target"); + ExpressionParser parser = getExpressionParser(); + Expression target = parser.parseExpression(targetString, new ParserContextImpl().eval(RequestContext.class)); + return new BindAction(target, parser, getConversionService()); + } + private Action parseEvaluateAction(Element element) { String expressionString = element.getAttribute("expression"); Expression expression = getExpressionParser().parseExpression(expressionString, diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java index 5e20a041..18bb8baa 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java @@ -39,10 +39,34 @@ public class BindActionTests extends TestCase { BindBean bean = (BindBean) context.getFlowScope().get("bindTarget"); assertEquals("foo", bean.getStringProperty()); - assertEquals(3, bean.getIntegerProperty()); + assertEquals(new Integer(3), bean.getIntegerProperty()); } - public void testBindWithErrors() throws Exception { + public void testBindNonexistantProperties() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("bindTarget", new BindBean()); + + Expression target = expressionParser.parseExpression("bindTarget", new ParserContextImpl() + .eval(RequestContext.class)); + action = new BindAction(target, expressionParser, conversionService); + + LocalAttributeMap eventData = new LocalAttributeMap(); + eventData.put("stringProperty", "foo"); + eventData.put("bogusProperty", "bar"); + eventData.put("integerProperty", "3"); + Event event = new Event(this, "submit", eventData); + context.setLastEvent(event); + + Event result = action.execute(context); + assertEquals("success", result.getId()); + + BindBean bean = (BindBean) context.getFlowScope().get("bindTarget"); + assertEquals("foo", bean.getStringProperty()); + assertEquals(new Integer(3), bean.getIntegerProperty()); + assertEquals(0, context.getMessageContext().getMessages().length); + } + + public void testBindWithTypeConversionErrors() throws Exception { MockRequestContext context = new MockRequestContext(); context.getFlowScope().put("bindTarget", new BindBean()); @@ -61,7 +85,7 @@ public class BindActionTests extends TestCase { BindBean bean = (BindBean) context.getFlowScope().get("bindTarget"); assertEquals("foo", bean.getStringProperty()); - assertEquals(0, bean.getIntegerProperty()); + assertEquals(new Integer(3), bean.getIntegerProperty()); assertEquals(1, context.getMessageContext().getMessages().length); assertEquals("integerProperty", context.getMessageContext().getMessages()[0].getSource()); assertEquals(Severity.ERROR, context.getMessageContext().getMessages()[0].getSeverity()); @@ -69,9 +93,33 @@ public class BindActionTests extends TestCase { .getText()); } + public void testBindWithEmptyAttributes() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("bindTarget", new BindBean()); + + Expression target = expressionParser.parseExpression("bindTarget", new ParserContextImpl() + .eval(RequestContext.class)); + action = new BindAction(target, expressionParser, conversionService); + + LocalAttributeMap eventData = new LocalAttributeMap(); + eventData.put("stringProperty", ""); + eventData.put("integerProperty", null); + Event event = new Event(this, "submit", eventData); + context.setLastEvent(event); + + Event result = action.execute(context); + System.out.println(context.getMessageContext()); + assertEquals("success", result.getId()); + + BindBean bean = (BindBean) context.getFlowScope().get("bindTarget"); + assertEquals("", bean.getStringProperty()); + assertEquals(null, bean.getIntegerProperty()); + assertEquals(0, context.getMessageContext().getMessages().length); + } + public static class BindBean { private String stringProperty; - private int integerProperty; + private Integer integerProperty = new Integer(3); public String getStringProperty() { return stringProperty; @@ -81,11 +129,11 @@ public class BindActionTests extends TestCase { this.stringProperty = stringProperty; } - public int getIntegerProperty() { + public Integer getIntegerProperty() { return integerProperty; } - public void setIntegerProperty(int integerProperty) { + public void setIntegerProperty(Integer integerProperty) { this.integerProperty = integerProperty; } }