diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java
index 7c91a505..04c102d5 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java
@@ -16,6 +16,9 @@
package org.springframework.faces.webflow;
import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
import javax.faces.FacesException;
import javax.faces.component.UIViewRoot;
@@ -25,9 +28,18 @@ import javax.faces.lifecycle.Lifecycle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.binding.expression.Expression;
+import org.springframework.binding.message.MessageContext;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.webflow.definition.TransitionDefinition;
+import org.springframework.webflow.definition.TransitionableStateDefinition;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
+import org.springframework.webflow.mvc.view.MessageContextErrors;
/**
* JSF-specific {@link View} implementation.
@@ -40,6 +52,8 @@ public class JsfView implements View {
public static final String EVENT_KEY = "org.springframework.webflow.FacesEvent";
+ private static final String MESSAGES_ID = "messages";
+
/**
* The root of the JSF component tree managed by this view
*/
@@ -47,24 +61,26 @@ public class JsfView implements View {
private Lifecycle facesLifecycle;
- private RequestContext context;
+ private RequestContext requestContext;
private String viewId;
private boolean restored = false;
+ private boolean viewErrors = false;
+
public JsfView(UIViewRoot viewRoot, Lifecycle facesLifecycle, RequestContext context) {
this.viewRoot = viewRoot;
this.viewId = viewRoot.getViewId();
this.facesLifecycle = facesLifecycle;
- this.context = context;
+ this.requestContext = context;
}
/**
* This implementation performs the standard duties of the JSF RENDER_RESPONSE phase.
*/
public void render() throws IOException {
- FacesContext facesContext = FlowFacesContext.newInstance(context, facesLifecycle);
+ FacesContext facesContext = FlowFacesContext.newInstance(requestContext, facesLifecycle);
facesContext.setViewRoot(viewRoot);
facesContext.renderResponse();
try {
@@ -82,26 +98,25 @@ public class JsfView implements View {
}
public void processUserEvent() {
- FacesContext facesContext = FlowFacesContext.newInstance(context, facesLifecycle);
+ FacesContext facesContext = FlowFacesContext.newInstance(requestContext, facesLifecycle);
facesContext.setViewRoot(viewRoot);
try {
if (restored && !facesContext.getResponseComplete() && !facesContext.getRenderResponse()) {
facesLifecycle.execute(facesContext);
+ validateModel(facesContext);
facesContext.renderResponse();
}
} finally {
facesContext.release();
}
-
}
public boolean hasFlowEvent() {
- return context.getExternalContext().getRequestMap().contains(EVENT_KEY);
+ return requestContext.getExternalContext().getRequestMap().contains(EVENT_KEY) && !viewErrors;
}
public Event getFlowEvent() {
- String eventId = (String) context.getExternalContext().getRequestMap().get(EVENT_KEY);
- return new Event(this, eventId);
+ return new Event(this, getEventId());
}
public UIViewRoot getViewRoot() {
@@ -115,4 +130,91 @@ public class JsfView implements View {
public void setRestored(boolean restored) {
this.restored = restored;
}
+
+ private void validateModel(FacesContext facesContext) {
+ Object model = getModelObject();
+ if (shouldValidate(model)) {
+ validate(model);
+ if (requestContext.getMessageContext().hasErrorMessages()) {
+ viewErrors = true;
+ if (requestContext.getExternalContext().isAjaxRequest()) {
+ List fragments = new ArrayList();
+ String formId = getModelExpression().getExpressionString();
+ if (viewRoot.findComponent(formId) != null) {
+ fragments.add(formId);
+ }
+ if (viewRoot.findComponent(MESSAGES_ID) != null) {
+ fragments.add(MESSAGES_ID);
+ }
+ if (fragments.size() > 0) {
+ String[] fragmentsArray = new String[fragments.size()];
+ for (int i = 0; i < fragments.size(); i++) {
+ fragmentsArray[i] = (String) fragments.get(i);
+ }
+ requestContext.getFlashScope().put(View.RENDER_FRAGMENTS_ATTRIBUTE, fragmentsArray);
+ }
+ }
+ }
+ }
+ }
+
+ private Object getModelObject() {
+ Expression model = getModelExpression();
+ if (model != null) {
+ return model.getValue(requestContext);
+ } else {
+ return null;
+ }
+ }
+
+ private Expression getModelExpression() {
+ return (Expression) requestContext.getCurrentState().getAttributes().get("model");
+ }
+
+ private boolean shouldValidate(Object model) {
+ if (model == null) {
+ return false;
+ }
+ TransitionableStateDefinition currentState = (TransitionableStateDefinition) requestContext.getCurrentState();
+ TransitionDefinition transition = currentState.getTransition(getEventId());
+ if (transition != null) {
+ if (transition.getAttributes().contains("bind")) {
+ return transition.getAttributes().getBoolean("bind").booleanValue();
+ }
+ }
+ return true;
+ }
+
+ private void validate(Object model) {
+ String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
+ Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,
+ new Class[] { MessageContext.class });
+ if (validateMethod != null) {
+ ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() });
+ }
+ BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext();
+ if (beanFactory != null) {
+ String validatorName = getModelExpression().getExpressionString() + "Validator";
+ if (beanFactory.containsBean(validatorName)) {
+ Object validator = beanFactory.getBean(validatorName);
+ validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
+ model.getClass(), MessageContext.class });
+ if (validateMethod != null) {
+ ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
+ requestContext.getMessageContext() });
+ } else {
+ validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
+ model.getClass(), Errors.class });
+ if (validateMethod != null) {
+ ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
+ new MessageContextErrors(requestContext.getMessageContext()) });
+ }
+ }
+ }
+ }
+ }
+
+ private String getEventId() {
+ return (String) requestContext.getExternalContext().getRequestMap().get(EVENT_KEY);
+ }
}
\ No newline at end of file
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java
index dba4fa9e..486bca63 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewTests.java
@@ -22,10 +22,14 @@ import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
+import org.springframework.webflow.engine.Flow;
+import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
+import org.springframework.webflow.execution.View;
+import org.springframework.webflow.execution.ViewFactory;
import org.springframework.webflow.test.MockExternalContext;
public class JsfViewTests extends TestCase {
@@ -141,6 +145,7 @@ public class JsfViewTests extends TestCase {
Boolean.FALSE);
EasyMock.expect(flashMap.put(EasyMock.matches(FlowFacesContext.RENDER_RESPONSE_KEY), EasyMock.anyObject()))
.andStubReturn(null);
+ EasyMock.expect(context.getCurrentState()).andStubReturn(new MockViewState());
Lifecycle lifecycle = new NoEventLifecycle(jsfMock.lifecycle());
@@ -170,6 +175,7 @@ public class JsfViewTests extends TestCase {
Boolean.FALSE);
EasyMock.expect(flashMap.put(EasyMock.matches(FlowFacesContext.RENDER_RESPONSE_KEY), EasyMock.anyObject()))
.andStubReturn(null);
+ EasyMock.expect(context.getCurrentState()).andStubReturn(new MockViewState());
Lifecycle lifecycle = new NoEventLifecycle(jsfMock.lifecycle());
@@ -199,6 +205,7 @@ public class JsfViewTests extends TestCase {
Boolean.FALSE);
EasyMock.expect(flashMap.put(EasyMock.matches(FlowFacesContext.RENDER_RESPONSE_KEY), EasyMock.anyObject()))
.andStubReturn(null);
+ EasyMock.expect(context.getCurrentState()).andStubReturn(new MockViewState());
Lifecycle lifecycle = new EventSignalingLifecycle(jsfMock.lifecycle());
@@ -293,4 +300,17 @@ public class JsfViewTests extends TestCase {
throw new UnsupportedOperationException("Auto-generated method stub");
}
}
+
+ private class MockViewState extends ViewState {
+
+ public MockViewState() {
+ super(new Flow("mockFlow"), VIEW_ID, new ViewFactory() {
+
+ public View getView(RequestContext context) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Auto-generated method stub");
+ }
+ });
+ }
+ }
}
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 66093b27..ab3094d5 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
@@ -13,11 +13,8 @@
-
-
-
-
-
+
+
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 e6062e0b..2e3ebfda 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
@@ -10,12 +10,12 @@