SWF-583 - View-state programming model needs to be consistent between MVC and JSF
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,8 @@
|
||||
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)" result="flowScope.booking" />
|
||||
</on-start>
|
||||
|
||||
<view-state id="enterBookingDetails">
|
||||
<transition on="proceed" to="reviewBooking">
|
||||
<render fragments="messagesFragment"/>
|
||||
<evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
|
||||
</transition>
|
||||
<view-state id="enterBookingDetails" model="booking">
|
||||
<transition on="proceed" to="reviewBooking"/>
|
||||
<transition on="cancel" to="bookingCancelled" bind="false"/>
|
||||
</view-state>
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
|
||||
<div class="section">
|
||||
<h2>Book Hotel</h2>
|
||||
<ui:fragment id="messagesFragment">
|
||||
<div id="messages">
|
||||
<ui:fragment id="messages">
|
||||
<div id="messagesInsertionPoint">
|
||||
<h:messages errorClass="errors" />
|
||||
</div>
|
||||
</ui:fragment>
|
||||
<h:form id="booking">
|
||||
<h:form id="bookingForm">
|
||||
<fieldset>
|
||||
<div class="field">
|
||||
<div class="label">Name:</div>
|
||||
|
||||
Reference in New Issue
Block a user