2.0.4 improvements: development mode, flow model parent registry fix, flow handler mapping, improved sample

This commit is contained in:
Keith Donald
2008-09-07 08:19:58 +00:00
parent 227b4a0276
commit e4b75c8228
34 changed files with 329 additions and 207 deletions

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.binding.mapping.results;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.PropertyNotFoundException;
import org.springframework.binding.mapping.Result;
@@ -68,11 +65,7 @@ public class SourceAccessError extends Result {
}
public String toString() {
ToStringCreator creator = new ToStringCreator(this).append("errorCode", getErrorCode());
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
error.printStackTrace(writer);
creator.append("exceptionStackTrace", stringWriter.toString());
return creator.toString();
return new ToStringCreator(this).append("errorCode", getErrorCode()).append("message", error.getMessage())
.toString();
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.binding.mapping.results;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.PropertyNotFoundException;
import org.springframework.binding.mapping.Result;
@@ -72,11 +69,7 @@ public class TargetAccessError extends Result {
}
public String toString() {
ToStringCreator creator = new ToStringCreator(this).append("errorCode", getErrorCode());
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
error.printStackTrace(writer);
creator.append("stackTrace", stringWriter.toString());
return creator.toString();
return new ToStringCreator(this).append("errorCode", getErrorCode()).append("message", error.getMessage())
.toString();
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.binding.mapping.results;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.binding.convert.ConversionExecutionException;
import org.springframework.binding.mapping.Result;
import org.springframework.core.style.ToStringCreator;
@@ -76,12 +73,7 @@ public class TypeConversionError extends Result {
}
public String toString() {
ToStringCreator creator = new ToStringCreator(this).append("originalValue", originalValue).append("targetType",
targetType);
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
exception.printStackTrace(writer);
creator.append("exceptionStackTrace", stringWriter.toString());
return creator.toString();
return new ToStringCreator(this).append("originalValue", originalValue).append("targetType", targetType)
.append("message", exception.getMessage()).toString();
}
}

View File

@@ -45,15 +45,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParser extends AbstractSingle
parseConversionService(element, definitionBuilder);
parseExpressionParser(element, definitionBuilder, parseEnableManagedBeans(element, definitionBuilder));
parseViewFactoryCreator(element, definitionBuilder);
}
private boolean parseEnableManagedBeans(Element element, BeanDefinitionBuilder definitionBuilder) {
String enableManagedBeans = element.getAttribute("enable-managed-beans");
if (StringUtils.hasText(enableManagedBeans)) {
return Boolean.valueOf(enableManagedBeans).booleanValue();
} else {
return false;
}
parseDevelopment(element, definitionBuilder);
}
private void parseConversionService(Element element, BeanDefinitionBuilder definitionBuilder) {
@@ -65,15 +57,6 @@ public class FacesFlowBuilderServicesBeanDefinitionParser extends AbstractSingle
}
}
private void parseViewFactoryCreator(Element element, BeanDefinitionBuilder definitionBuilder) {
String viewFactoryCreator = element.getAttribute("view-factory-creator");
if (StringUtils.hasText(viewFactoryCreator)) {
definitionBuilder.addPropertyReference("viewFactoryCreator", viewFactoryCreator);
} else {
definitionBuilder.addPropertyValue("viewFactoryCreator", new JsfViewFactoryCreator());
}
}
private void parseExpressionParser(Element element, BeanDefinitionBuilder definitionBuilder,
boolean enableManagedBeans) {
if (enableManagedBeans) {
@@ -122,4 +105,29 @@ public class FacesFlowBuilderServicesBeanDefinitionParser extends AbstractSingle
return definitionBuilder.getBeanDefinition().getPropertyValues().getPropertyValue("conversionService")
.getValue();
}
private boolean parseEnableManagedBeans(Element element, BeanDefinitionBuilder definitionBuilder) {
String enableManagedBeans = element.getAttribute("enable-managed-beans");
if (StringUtils.hasText(enableManagedBeans)) {
return Boolean.valueOf(enableManagedBeans).booleanValue();
} else {
return false;
}
}
private void parseViewFactoryCreator(Element element, BeanDefinitionBuilder definitionBuilder) {
String viewFactoryCreator = element.getAttribute("view-factory-creator");
if (StringUtils.hasText(viewFactoryCreator)) {
definitionBuilder.addPropertyReference("viewFactoryCreator", viewFactoryCreator);
} else {
definitionBuilder.addPropertyValue("viewFactoryCreator", new JsfViewFactoryCreator());
}
}
private void parseDevelopment(Element element, BeanDefinitionBuilder definitionBuilder) {
String development = element.getAttribute("development");
if (StringUtils.hasText(development)) {
definitionBuilder.addPropertyValue("development", development);
}
}
}

View File

@@ -55,6 +55,16 @@ The custom ViewFactoryCreator implementation to use produce ViewFactories capabl
<![CDATA[
When this attribute is set to true, a special EL expression parser will be enabled that allows access to JSF-managed beans
from EL expressions in flow definitions.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="development" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Puts all flows in development mode.
When set to true, changes to a flow definition will be auto-detected and will result in a flow refresh.
]]>
</xsd:documentation>
</xsd:annotation>

View File

@@ -43,6 +43,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof FacesConversionService);
assertFalse(builderServices.getDevelopment());
}
public void testEnableManagedBeans() {
@@ -51,6 +52,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
assertTrue(builderServices.getExpressionParser() instanceof JsfManagedBeanAwareELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof FacesConversionService);
assertFalse(builderServices.getDevelopment());
}
public void testFlowBuilderServicesAllCustomized() {
@@ -59,6 +61,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getDevelopment());
}
public void testFlowBuilderServicesConversionServiceCustomized() {
@@ -68,6 +71,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(((WebFlowELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator);
assertFalse(builderServices.getDevelopment());
}
public static class TestViewFactoryCreator implements ViewFactoryCreator {

View File

@@ -15,7 +15,7 @@
<faces:flow-builder-services id="flowBuilderServicesAllCustom"
expression-parser="customExpressionParser"
view-factory-creator="customViewFactoryCreator"
conversion-service="customConversionService"/>
conversion-service="customConversionService" development="true" />
<faces:flow-builder-services id="flowBuilderServicesConversionServiceCustom"
conversion-service="customConversionService"/>

View File

@@ -26,7 +26,7 @@
</webflow:flow-registry>
<!-- Configures the Spring Web Flow JSF integration -->
<faces:flow-builder-services id="facesFlowBuilderServices" />
<faces:flow-builder-services id="facesFlowBuilderServices" development="true" />
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
<bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener">

View File

@@ -5,7 +5,7 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -21,29 +21,27 @@ public class HotelsController {
}
@RequestMapping(method = RequestMethod.GET)
public List<Booking> index(SearchCriteria searchCriteria, Principal currentUser) {
public void index(SearchCriteria searchCriteria, Principal currentUser, Model model) {
if (currentUser != null) {
return bookingService.findBookings(currentUser.getName());
} else {
return null;
List<Booking> booking = bookingService.findBookings(currentUser.getName());
model.addAttribute(booking);
}
}
@RequestMapping(method = RequestMethod.GET)
public @ModelAttribute("hotels")
List<Hotel> search(SearchCriteria criteria) {
return bookingService.findHotels(criteria);
public String search(SearchCriteria criteria, Model model) {
List<Hotel> hotels = bookingService.findHotels(criteria);
model.addAttribute(hotels);
return "hotels/search";
}
@RequestMapping(method = RequestMethod.GET)
public Hotel show(@RequestParam("id")
Long id) {
public Hotel show(@RequestParam("id") Long id) {
return bookingService.findHotelById(id);
}
@RequestMapping(method = RequestMethod.GET)
public String deleteBooking(@RequestParam("id")
Long id) {
public String deleteBooking(@RequestParam("id") Long id) {
bookingService.cancelBooking(id);
return "redirect:index";
}

View File

@@ -18,11 +18,11 @@
<!-- The registry of executable flow definitions -->
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/hotels/booking/booking.xml" />
<webflow:flow-location id="hotels/booking" path="/WEB-INF/hotels/booking/booking.xml" />
</webflow:flow-registry>
<!-- Plugs in a custom creator for Web Flow views -->
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService"/>
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService" development="true" />
<!-- 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">

View File

@@ -4,58 +4,45 @@
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- URL to flow mapping rules -->
<bean id="flowMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/hotels/booking=bookingFlowHandler
</value>
</property>
<property name="order" value="0"/>
<!-- Maps request paths to flows in the flowRegistry; e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="0" />
</bean>
<!-- Maps request paths to @Controller classes; e.g. a path of /hotels looks for a controller named HotelsController -->
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<property name="order" value="1" />
<property name="defaultHandler">
<!-- If no @Controller match, map path to a view to render; e.g. the "/intro" path would map to the view named "intro" -->
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
</property>
</bean>
<!-- Controls access to the hotel booking flow -->
<bean id="bookingFlowHandler" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
<!-- Enables convention-based request URL mapping to @Controllers e.g. /hotels/* maps to HotelsController -->
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<property name="order" value="1"/>
</bean>
<!-- Maps all other request URLs to views -->
<bean id="viewMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="defaultHandler">
<!-- Selects view names to render based on the request URI: e.g. the "/intro" URL would map to the view named "intro" -->
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
</property>
<property name="order" value="2"/>
</bean>
<!-- Configures the Tiles layout system -->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts/layouts.xml</value>
<value>/WEB-INF/views.xml</value>
<value>/WEB-INF/hotels/views.xml</value>
<value>/WEB-INF/hotels/booking/views.xml</value>
</list>
</property>
</bean>
<!-- Resolves views by delegating to the Tiles layout system; a view name to resolve is treated as the name of a tiles definition -->
<!-- Resolves logical view names returned by Controllers to Tiles; a view name to resolve is treated as the name of a tiles definition -->
<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
</bean>
<!-- Enables annotated POJO @Controllers -->
<!-- Configures the Tiles layout system -->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts/layouts.xml</value>
<value>/WEB-INF/views.xml</value>
<value>/WEB-INF/hotels/views.xml</value>
<value>/WEB-INF/hotels/booking/views.xml</value>
</list>
</property>
</bean>
<!-- Dispatches requests mapped to POJO @Controller implementations -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- Enables plain Controllers -->
<!-- Dispatches requests mapped to org.springframework.web.servlet.mvc.Controller implementations -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- Enables FlowHandlers -->
<!-- Dispatches requests mapped to flows -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>

View File

@@ -35,7 +35,7 @@
<transition on="cancel" to="cancel" />
</view-state>
<end-state id="bookingConfirmed" commit="true" />
<end-state id="bookingConfirmed" commit="true" view="externalRedirect:servletRelative:/hotels/index"/>
<end-state id="cancel" />

View File

@@ -1,5 +1,12 @@
booking.checkinDate.typeMismatch=The Check In Date must be in the format dd/mm/yy
booking.checkinDate.beforeToday=The Check In Date must be a future date
booking.checkinDate.required=The check in date is required
booking.checkinDate.typeMismatch=The check in date must be in the format dd/mm/yy
booking.checkinDate.beforeToday=The check in date must be a future date
booking.checkoutDate.typeMismatch=The Check Out Date must be in the format dd/mm/yy
booking.checkoutDate.beforeCheckinDate=The Check Out Date must be later than the Check In Date
booking.checkoutDate.required=The check out date is required
booking.checkoutDate.typeMismatch=The check out date must be in the format dd/mm/yy
booking.checkoutDate.beforeCheckinDate=The check out date must be later than the check in date
booking.creditCard.required=The credit card must be a valid 16 digit number
booking.creditCardName.required=The name on the credit card is required
required=The {0} field is required

View File

@@ -17,7 +17,7 @@
</script>
</p>
<div id="hotelResults" class="section">
<c:if test="${not empty hotels}">
<c:if test="${not empty hotelList}">
<table class="summary">
<thead>
<tr>
@@ -29,7 +29,7 @@
</tr>
</thead>
<tbody>
<c:forEach var="hotel" items="${hotels}">
<c:forEach var="hotel" items="${hotelList}">
<tr>
<td>${hotel.name}</td>
<td>${hotel.address}</td>
@@ -38,7 +38,7 @@
<td><a href="show?id=${hotel.id}">View Hotel</a></td>
</tr>
</c:forEach>
<c:if test="${empty hotels}">
<c:if test="${empty hotelList}">
<tr>
<td colspan="5">No hotels found</td>
</tr>
@@ -56,7 +56,7 @@
}));
</script>
</c:if>
<c:if test="${not empty hotels && fn:length(hotels) == searchCriteria.pageSize}">
<c:if test="${not empty hotelList && fn:length(hotelList) == searchCriteria.pageSize}">
<a id="moreResultsLink" href="search?searchString=${searchCriteria.searchString}&pageSize=${searchCriteria.pageSize}&page=${searchCriteria.page + 1}">More Results</a>
<script type="text/javascript">
Spring.addDecoration(new Spring.AjaxEventDecoration({

View File

@@ -19,7 +19,6 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.util.StringUtils;
@@ -39,13 +38,14 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
return FlowBuilderServices.class;
}
protected void doParse(Element element, ParserContext context, BeanDefinitionBuilder builder) {
parseConversionService(element, builder, context);
parseExpressionParser(element, builder, context);
parseViewFactoryCreator(element, builder, context);
protected void doParse(Element element, BeanDefinitionBuilder builder) {
parseConversionService(element, builder);
parseExpressionParser(element, builder);
parseViewFactoryCreator(element, builder);
parseDevelopment(element, builder);
}
private void parseConversionService(Element element, BeanDefinitionBuilder definitionBuilder, ParserContext context) {
private void parseConversionService(Element element, BeanDefinitionBuilder definitionBuilder) {
String conversionService = element.getAttribute("conversion-service");
if (StringUtils.hasText(conversionService)) {
definitionBuilder.addPropertyReference("conversionService", conversionService);
@@ -54,7 +54,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
}
}
private void parseExpressionParser(Element element, BeanDefinitionBuilder definitionBuilder, ParserContext context) {
private void parseExpressionParser(Element element, BeanDefinitionBuilder definitionBuilder) {
String expressionParser = element.getAttribute("expression-parser");
if (StringUtils.hasText(expressionParser)) {
definitionBuilder.addPropertyReference("expressionParser", expressionParser);
@@ -74,7 +74,12 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
}
}
private void parseViewFactoryCreator(Element element, BeanDefinitionBuilder definitionBuilder, ParserContext context) {
private Object getConversionServiceValue(BeanDefinitionBuilder definitionBuilder) {
return definitionBuilder.getBeanDefinition().getPropertyValues().getPropertyValue("conversionService")
.getValue();
}
private void parseViewFactoryCreator(Element element, BeanDefinitionBuilder definitionBuilder) {
String viewFactoryCreator = element.getAttribute("view-factory-creator");
if (StringUtils.hasText(viewFactoryCreator)) {
definitionBuilder.addPropertyReference("viewFactoryCreator", viewFactoryCreator);
@@ -84,8 +89,10 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
}
}
private Object getConversionServiceValue(BeanDefinitionBuilder definitionBuilder) {
return definitionBuilder.getBeanDefinition().getPropertyValues().getPropertyValue("conversionService")
.getValue();
private void parseDevelopment(Element element, BeanDefinitionBuilder definitionBuilder) {
String development = element.getAttribute("development");
if (StringUtils.hasText(development)) {
definitionBuilder.addPropertyValue("development", development);
}
}
}

View File

@@ -44,6 +44,7 @@ import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder;
import org.springframework.webflow.engine.model.builder.FlowModelBuilder;
import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder;
import org.springframework.webflow.engine.model.registry.FlowModelHolder;
import org.springframework.webflow.engine.model.registry.FlowModelRegistry;
import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl;
/**
@@ -71,12 +72,7 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init
/**
* The definition registry produced by this factory bean.
*/
private FlowDefinitionRegistryImpl flowRegistry;
/**
* The model registry used to build flow models that can be assembled into registerable Flows.
*/
private FlowModelRegistryImpl flowModelRegistry;
private DefaultFlowRegistry flowRegistry;
/**
* A helper for creating abstract representation of externalized flow definition resources.
@@ -128,9 +124,8 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init
public void afterPropertiesSet() throws Exception {
flowResourceFactory = new FlowDefinitionResourceFactory(flowBuilderServices.getApplicationContext());
flowRegistry = new FlowDefinitionRegistryImpl();
flowRegistry = new DefaultFlowRegistry();
flowRegistry.setParent(parent);
flowModelRegistry = new FlowModelRegistryImpl();
registerFlowLocations();
registerFlowLocationPatterns();
registerFlowBuilders();
@@ -201,8 +196,14 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init
private AttributeMap getFlowAttributes(Set attributes) {
MutableAttributeMap flowAttributes = null;
if (flowBuilderServices.getDevelopment()) {
flowAttributes = new LocalAttributeMap(1 + attributes.size(), 1);
flowAttributes.put("development", Boolean.TRUE);
}
if (!attributes.isEmpty()) {
flowAttributes = new LocalAttributeMap();
if (flowAttributes == null) {
flowAttributes = new LocalAttributeMap(attributes.size(), 1);
}
for (Iterator it = attributes.iterator(); it.hasNext();) {
FlowElementAttribute attribute = (FlowElementAttribute) it.next();
flowAttributes.put(attribute.getName(), getConvertedValue(attribute));
@@ -217,13 +218,13 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init
private FlowModelHolder createFlowModelHolder(FlowDefinitionResource resource) {
FlowModelHolder modelHolder = new DefaultFlowModelHolder(createFlowModelBuilder(resource));
flowModelRegistry.registerFlowModel(resource.getId(), modelHolder);
flowRegistry.getFlowModelRegistry().registerFlowModel(resource.getId(), modelHolder);
return modelHolder;
}
private FlowModelBuilder createFlowModelBuilder(FlowDefinitionResource resource) {
if (isXml(resource.getPath())) {
return new XmlFlowModelBuilder(resource.getPath(), flowModelRegistry);
return new XmlFlowModelBuilder(resource.getPath(), flowRegistry.getFlowModelRegistry());
} else {
throw new IllegalArgumentException(resource
+ " is not a supported resource type; supported types are [.xml]");
@@ -280,4 +281,20 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init
}
}
public static class DefaultFlowRegistry extends FlowDefinitionRegistryImpl {
private FlowModelRegistry flowModelRegistry = new FlowModelRegistryImpl();
public FlowModelRegistry getFlowModelRegistry() {
return flowModelRegistry;
}
public void setParent(FlowDefinitionRegistry parent) {
super.setParent(parent);
if (parent instanceof DefaultFlowRegistry) {
DefaultFlowRegistry parentFlowRegistry = (DefaultFlowRegistry) parent;
flowModelRegistry.setParent(parentFlowRegistry.getFlowModelRegistry());
}
}
}
}

View File

@@ -11,7 +11,7 @@
<xsd:documentation>
<![CDATA[
Spring Web Flow Configuration Schema
Authors: Keith Donald, Ben Hale, Jeremy Grelle
Authors: Keith Donald, Jeremy Grelle
<br>
A XML-based DSL for configuring the Spring Web Flow 2.0 system.
]]>
@@ -206,6 +206,16 @@ The custom ExpressionParser implementation to use to compile expression strings
<xsd:documentation>
<![CDATA[
The custom ViewFactoryCreator implementation to use produce ViewFactories capable of rendering Views.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="development" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Puts all flows in development mode.
When set to true, changes to a flow definition will be auto-detected and will result in a flow refresh.
]]>
</xsd:documentation>
</xsd:annotation>

View File

@@ -23,7 +23,6 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
import org.springframework.webflow.core.collection.AttributeMap;
/**
@@ -32,7 +31,7 @@ import org.springframework.webflow.core.collection.AttributeMap;
* Expects URLs to launch flow to be of this pattern:
*
* <pre>
* http://&lt;host&gt;/[app context path]/[app servlet path]/[namespace]/&lt;flow id&gt;
* http://&lt;host&gt;/[app context path]/[app servlet path]/&lt;flow id&gt;
* </pre>
*
* For example:
@@ -44,7 +43,7 @@ import org.springframework.webflow.core.collection.AttributeMap;
* Expects URLs to resume flows to be of this pattern:
*
* <pre>
* http://&lt;host&gt;/[app context path]/[app servlet path]/[namespace]/&lt;flow id&gt;?execution=&lt;flow execution key&gt;
* http://&lt;host&gt;/[app context path]/[app servlet path]/&lt;flow id&gt;?execution=&lt;flow execution key&gt;
* </pre>
*
* For example:
@@ -73,7 +72,7 @@ public class DefaultFlowUrlHandler implements FlowUrlHandler {
}
public String getFlowId(HttpServletRequest request) {
return WebUtils.extractFilenameFromUrlPath(urlPathHelper.getLookupPathForRequest(request));
return request.getPathInfo().substring(0);
}
public String createFlowExecutionUrl(String flowId, String flowExecutionKey, HttpServletRequest request) {
@@ -86,9 +85,12 @@ public class DefaultFlowUrlHandler implements FlowUrlHandler {
public String createFlowDefinitionUrl(String flowId, AttributeMap input, HttpServletRequest request) {
StringBuffer url = new StringBuffer();
url.append(getFlowHandlerUri(request));
url.append('/');
url.append(encode(flowId));
url.append(request.getContextPath());
url.append(request.getServletPath());
if (!flowId.startsWith("/")) {
url.append('/');
}
url.append(flowId);
if (input != null && !input.isEmpty()) {
url.append('?');
appendQueryParameters(url, input.asMap());
@@ -125,10 +127,4 @@ public class DefaultFlowUrlHandler implements FlowUrlHandler {
}
}
private String getFlowHandlerUri(HttpServletRequest request) {
String flowRequestUri = request.getRequestURI();
int lastSlash = flowRequestUri.lastIndexOf('/');
return flowRequestUri.substring(0, lastSlash);
}
}
}

View File

@@ -81,4 +81,10 @@ public interface FlowDefinition extends Annotated {
*/
public ApplicationContext getApplicationContext();
/**
* Returns true if this flow definition is currently in development (running in development mode).
* @return the development flag
*/
public boolean inDevelopment();
}

View File

@@ -67,4 +67,6 @@ public interface FlowDefinitionRegistry extends FlowDefinitionLocator {
*/
public void registerFlowDefinition(FlowDefinition definition);
public boolean containsFlowDefinition(String flowId);
}

View File

@@ -69,6 +69,10 @@ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry {
// implementing FlowDefinitionRegistry
public boolean containsFlowDefinition(String flowId) {
return flowDefinitions.containsKey(flowId);
}
public int getFlowDefinitionCount() {
return flowDefinitions.size();
}

View File

@@ -91,10 +91,10 @@ import org.springframework.webflow.execution.RequestContext;
* {@link #start(RequestControlContext, MutableAttributeMap) start}, {@link #resume(RequestControlContext)},
* {@link #handleEvent(RequestControlContext) on event},
* {@link #end(RequestControlContext, String, MutableAttributeMap) end}, and
* {@link #handleException(FlowExecutionException, RequestControlContext)}. Each method accepts a
* {@link RequestContext request context} that allows for this flow to access execution state in a thread safe manner. A
* flow execution is what models a running instance of this flow definition, somewhat analogous to a java object that is
* an instance of a class.
* {@link #handleException(FlowExecutionException, RequestControlContext)}. Each method accepts a {@link RequestContext
* request context} that allows for this flow to access execution state in a thread safe manner. A flow execution is
* what models a running instance of this flow definition, somewhat analogous to a java object that is an instance of a
* class.
*
* @see org.springframework.webflow.engine.State
* @see org.springframework.webflow.engine.ActionState
@@ -239,6 +239,10 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
return applicationContext;
}
public boolean inDevelopment() {
return getAttributes().getBoolean("development", Boolean.FALSE).booleanValue();
}
/**
* Add given state definition to this flow definition. Marked protected, as this method is to be called by the
* (privileged) state definition classes themselves during state construction as part of a FlowBuilder invocation.
@@ -288,8 +292,8 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
}
/**
* Set the start state for this flow to the state with the provided <code>stateId</code>; a state must exist by
* the provided <code>stateId</code>.
* Set the start state for this flow to the state with the provided <code>stateId</code>; a state must exist by the
* provided <code>stateId</code>.
* @param stateId the id of the new start state
* @throws IllegalArgumentException when no state exists with the id you provided
*/
@@ -423,8 +427,8 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
}
/**
* Returns the list of actions executed by this flow when an execution of the flow <i>ends</i>. The returned list
* is mutable.
* Returns the list of actions executed by this flow when an execution of the flow <i>ends</i>. The returned list is
* mutable.
* @return the end action list
*/
public ActionList getEndActionList() {
@@ -518,11 +522,10 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
/**
* Start a new session for this flow in its start state. This boils down to the following:
* <ol>
* <li>Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.</li>
* <li>Map provided input data into the flow. Typically data will be mapped into flow scope using the registered
* input mapper ({@link #setInputMapper(Mapper)}).</li>
* <li>Execute all registered start actions ({@link #getStartActionList()}).</li>
* <li>Enter the configured start state ({@link #setStartState(State)})</li>
* <li>Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.</li> <li>Map
* provided input data into the flow. Typically data will be mapped into flow scope using the registered input
* mapper ({@link #setInputMapper(Mapper)}).</li> <li>Execute all registered start actions (
* {@link #getStartActionList()}).</li> <li>Enter the configured start state ({@link #setStartState(State)})</li>
* </ol>
* @param context the flow execution control context
* @param input eligible input into the session
@@ -576,9 +579,9 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
* Inform this flow definition that an execution session of itself has ended. As a result, the flow will do the
* following:
* <ol>
* <li>Execute all registered end actions ({@link #getEndActionList()}).</li>
* <li>Map data available in the flow execution control context into provided output map using a registered output
* mapper ({@link #setOutputMapper(Mapper)}).</li>
* <li>Execute all registered end actions ({@link #getEndActionList()}).</li> <li>Map data available in the flow
* execution control context into provided output map using a registered output mapper (
* {@link #setOutputMapper(Mapper)}).</li>
* </ol>
* @param context the flow execution control context
* @param outcome the logical flow outcome that will be returned by the session, generally the id of the terminating

View File

@@ -1,15 +1,15 @@
package org.springframework.webflow.engine.builder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* Contains the information needed to bind model to a view. This information consists of one or more
* {@link Binding bindings} that connect properties of the model to UI elements of the view.
* Contains the information needed to bind model to a view. This information consists of one or more {@link Binding
* bindings} that connect properties of the model to UI elements of the view.
*
* @see ViewFactoryCreator
*
@@ -17,7 +17,7 @@ import org.springframework.util.Assert;
*/
public class BinderConfiguration {
private Set bindings = new HashSet();
private Set bindings = new LinkedHashSet();
/**
* Adds a new binding to this binding configuration.

View File

@@ -76,8 +76,8 @@ public class DefaultFlowHolder implements FlowDefinitionHolder {
logger.debug("Assembling the flow for the first time");
assembleFlow();
} else {
if (getFlowBuilder().hasFlowChanged()) {
logger.debug("The flow has changed; reassembling...");
if (flowDefinition.inDevelopment() && getFlowBuilder().hasFlowChanged()) {
logger.debug("The flow under development has changed; reassembling...");
assembleFlow();
}
}

View File

@@ -142,13 +142,22 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
protected void doInit() throws FlowBuilderException {
try {
flowModel = flowModelHolder.getFlowModel();
initLocalFlowContext();
} catch (FlowModelBuilderException e) {
throw new FlowBuilderException("Unable to get the model for this flow", e);
}
if ("true".equals(flowModel.getAbstract())) {
throw new FlowBuilderException("Abstract flow models cannot be instantiated.");
}
initLocalFlowContext();
}
protected Flow createFlow() {
String flowId = getContext().getFlowId();
AttributeMap flowAttributes = parseFlowMetaAttributes(flowModel);
flowAttributes = getContext().getFlowAttributes().union(flowAttributes);
Flow flow = getLocalContext().getFlowArtifactFactory().createFlow(flowId, flowAttributes);
flow.setApplicationContext(getLocalContext().getApplicationContext());
return flow;
}
/**
@@ -257,12 +266,6 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
// subclassing hooks
protected Flow createFlow() {
Flow flow = parseFlow(flowModel);
flow.setApplicationContext(getLocalContext().getApplicationContext());
return flow;
}
protected FlowModel getFlowModel() {
return flowModel;
}
@@ -346,6 +349,10 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
return flowContext;
}
private boolean isFlowInDevelopment() {
return getContext().getFlowAttributes().getBoolean("development", Boolean.FALSE).booleanValue();
}
private void registerMessageSource(GenericApplicationContext flowContext, Resource flowResource) {
boolean localMessageSourcePresent = flowContext
.containsLocalBean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME);
@@ -360,20 +367,20 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(ReloadableResourceBundleMessageSource.class);
builder.addPropertyValue("basename", "messages");
if (isFlowInDevelopment()) {
builder.addPropertyValue("cacheSeconds", "0");
}
flowContext.registerBeanDefinition(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, builder
.getBeanDefinition());
}
}
}
private Flow parseFlow(FlowModel flow) {
String flowId = getLocalContext().getFlowId();
AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes();
private AttributeMap parseFlowMetaAttributes(FlowModel flow) {
MutableAttributeMap flowAttributes = parseMetaAttributes(flow.getAttributes());
parseAndPutPersistenceContext(flow.getPersistenceContext(), flowAttributes);
parseAndPutSecured(flow.getSecured(), flowAttributes);
return this.getLocalContext().getFlowArtifactFactory().createFlow(flowId,
flowAttributes.union(externallyAssignedAttributes));
return flowAttributes;
}
private FlowVariable parseFlowVariable(VarModel var) {

View File

@@ -41,18 +41,10 @@ public abstract class AbstractFlowBuilder implements FlowBuilder {
*/
private FlowBuilderContext context;
/**
* Returns this flow builder's context.
* @return the flow builder context
*/
protected FlowBuilderContext getContext() {
return context;
}
public void init(FlowBuilderContext context) throws FlowBuilderException {
this.context = context;
doInit();
this.flow = createFlow();
flow = createFlow();
}
/**
@@ -73,6 +65,14 @@ public abstract class AbstractFlowBuilder implements FlowBuilder {
return getContext().getFlowArtifactFactory().createFlow(id, attributes);
}
/**
* Returns this flow builder's context.
* @return the flow builder context
*/
protected FlowBuilderContext getContext() {
return context;
}
public void buildVariables() throws FlowBuilderException {
}

View File

@@ -27,6 +27,7 @@ import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
@@ -61,7 +62,7 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
Assert.notNull(flowDefinitionLocator, "The flow definition locator is required");
Assert.notNull(flowBuilderServices, "The flow builder services holder is required");
this.flowId = flowId;
this.flowAttributes = flowAttributes;
initFlowAttributes(flowAttributes);
this.flowDefinitionLocator = flowDefinitionLocator;
this.flowBuilderServices = flowBuilderServices;
this.conversionService = createConversionService();
@@ -119,6 +120,14 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
return service;
}
private void initFlowAttributes(AttributeMap flowAttributes) {
if (flowAttributes != null) {
this.flowAttributes = flowAttributes;
} else {
this.flowAttributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP;
}
}
/**
* A little proxy that refreshes the externally configured conversion service reference on each invocation.
*/

View File

@@ -43,14 +43,14 @@ import org.springframework.webflow.engine.builder.ViewFactoryCreator;
public class FlowBuilderServices implements ApplicationContextAware, InitializingBean {
/**
* The factory encapsulating the creation of central Flow artifacts such as {@link Flow flows} and
* {@link State states}.
* The factory encapsulating the creation of central Flow artifacts such as {@link Flow flows} and {@link State
* states}.
*/
private FlowArtifactFactory flowArtifactFactory = new FlowArtifactFactory();
/**
* The view factory creator for creating views to render during flow execution. The default is <code>null</code>
* and this service must be configured externally.
* The view factory creator for creating views to render during flow execution. The default is <code>null</code> and
* this service must be configured externally.
*/
private ViewFactoryCreator viewFactoryCreator;
@@ -70,6 +70,11 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
*/
private ApplicationContext applicationContext;
/**
* Whether or not the flow system is in development mode. In development mode, flows auto-refresh on change.
*/
private boolean development;
public FlowArtifactFactory getFlowArtifactFactory() {
return flowArtifactFactory;
}
@@ -102,6 +107,14 @@ public class FlowBuilderServices implements ApplicationContextAware, Initializin
this.expressionParser = expressionParser;
}
public boolean getDevelopment() {
return development;
}
public void setDevelopment(boolean development) {
this.development = development;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}

View File

@@ -190,8 +190,7 @@ public class FlowHandlerAdapter extends PortletContentGenerator implements Handl
RenderResponse response) {
if (e instanceof NoSuchFlowExecutionException) {
if (logger.isDebugEnabled()) {
logger.debug("Restarting a new execution of previously expired/ended flow '" + flowHandler.getFlowId()
+ "'");
logger.debug("Restarting a new execution of previously ended flow '" + flowHandler.getFlowId() + "'");
}
// by default, attempt to restart the flow
startFlow(flowHandler, null, request, response);

View File

@@ -137,8 +137,8 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd
* Set whether redirects sent by this handler adapter should be compatible with HTTP 1.0 clients.
* <p>
* By default, this will enforce a redirect HTTP status code of 302 by delegating to
* <code>HttpServletResponse.sendRedirect</code>. Setting this to false will send HTTP status code 303, which is
* the correct code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
* <code>HttpServletResponse.sendRedirect</code>. Setting this to false will send HTTP status code 303, which is the
* correct code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
* <p>
* Many HTTP 1.1 clients treat 302 just like 303, not making any difference. However, some clients depend on 303
* when redirecting after a POST request; turn this flag off in such a scenario.
@@ -278,7 +278,7 @@ public class FlowHandlerAdapter extends WebContentGenerator implements HandlerAd
if (e instanceof NoSuchFlowExecutionException && flowId != null) {
if (!response.isCommitted()) {
if (logger.isDebugEnabled()) {
logger.debug("Restarting a new execution of previously expired/ended flow '" + flowId + "'");
logger.debug("Restarting a new execution of previously ended flow '" + flowId + "'");
}
// by default, attempt to restart the flow
response.sendRedirect(flowUrlHandler.createFlowDefinitionUrl(flowId, null, request));

View File

@@ -0,0 +1,46 @@
package org.springframework.webflow.mvc.servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
public class FlowHandlerMapping extends AbstractHandlerMapping {
private FlowDefinitionRegistry flowRegistry;
protected void initServletContext(ServletContext servletContext) {
if (flowRegistry == null) {
flowRegistry = (FlowDefinitionRegistry) getApplicationContext().getBean("flowRegistry",
FlowDefinitionRegistry.class);
}
}
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String flowId = request.getPathInfo().substring(1);
if (getApplicationContext().containsBean(flowId)) {
Object handler = getApplicationContext().getBean(flowId);
if (handler instanceof FlowHandler) {
return handler;
}
}
if (flowRegistry.containsFlowDefinition(flowId)) {
return new DefaultFlowHandler(flowId);
}
return null;
}
private static class DefaultFlowHandler extends AbstractFlowHandler {
private String flowId;
public DefaultFlowHandler(String flowId) {
this.flowId = flowId;
}
public String getFlowId() {
return flowId;
}
}
}

View File

@@ -35,6 +35,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof DefaultConversionService);
assertFalse(builderServices.getDevelopment());
}
public void testFlowBuilderServicesAllCustomized() {
@@ -43,6 +44,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator);
assertTrue(builderServices.getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getDevelopment());
}
public void testFlowBuilderServicesConversionServiceCustomized() {
@@ -52,6 +54,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
assertTrue(builderServices.getExpressionParser() instanceof WebFlowELExpressionParser);
assertTrue(((WebFlowELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService);
assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator);
assertFalse(builderServices.getDevelopment());
}
public static class TestViewFactoryCreator implements ViewFactoryCreator {

View File

@@ -13,7 +13,7 @@
<webflow:flow-builder-services id="flowBuilderServicesAllCustom"
expression-parser="customExpressionParser"
view-factory-creator="customViewFactoryCreator"
conversion-service="customConversionService" />
conversion-service="customConversionService" development="true" />
<webflow:flow-builder-services id="flowBuilderServicesConversionServiceCustom"
conversion-service="customConversionService" />

View File

@@ -140,6 +140,9 @@ public class FlowDefinitionRegistryImplTests extends TestCase {
return null;
}
public boolean inDevelopment() {
return false;
}
}
private static class BarFlow implements FlowDefinition {
@@ -180,5 +183,10 @@ public class FlowDefinitionRegistryImplTests extends TestCase {
public ApplicationContext getApplicationContext() {
return null;
}
public boolean inDevelopment() {
return false;
}
}
}