SWF-1367 Add support for JSF 2 Ajax requests, update booking-faces sample (JSF 2 based Ajax, jsr-303 validation), add chained delegation to AjaxHandlers, other miscellaneous improvements

This commit is contained in:
Rossen Stoyanchev
2010-07-28 12:56:24 +00:00
parent 507fec3edf
commit fe9229cf15
36 changed files with 722 additions and 494 deletions

View File

@@ -17,47 +17,39 @@ package org.springframework.faces.richfaces;
import java.io.IOException;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ajax4jsf.context.AjaxContext;
import org.springframework.faces.webflow.FlowLifecycle;
import org.springframework.faces.webflow.FacesContextHelper;
import org.springframework.js.ajax.AbstractAjaxHandler;
import org.springframework.js.ajax.AjaxHandler;
import org.springframework.js.ajax.SpringJavascriptAjaxHandler;
import org.springframework.util.Assert;
import org.springframework.web.context.support.WebApplicationObjectSupport;
/**
* Ajax handler that works with Rich Faces, allowing support for Web Flow Ajax features with the Rich Faces toolkit.
*
* @see AbstractAjaxHandler
*
* @author Jeremy Grelle
*/
public class RichFacesAjaxHandler extends WebApplicationObjectSupport implements AjaxHandler {
public class RichFacesAjaxHandler extends AbstractAjaxHandler implements AjaxHandler {
private AjaxHandler delegate = new SpringJavascriptAjaxHandler();
public boolean isAjaxRequest(HttpServletRequest request, HttpServletResponse response) {
if (isRichFacesAjaxRequest(request, response)) {
return true;
} else {
return delegate.isAjaxRequest(request, response);
}
/**
* Create a RichFacesAjaxHandler that is not part of a chain of AjaxHandler's.
*/
public RichFacesAjaxHandler() {
this(null);
}
public void sendAjaxRedirect(String targetUrl, HttpServletRequest request, HttpServletResponse response,
boolean popup) throws IOException {
if (isRichFacesAjaxRequest(request, response)) {
response.sendRedirect(response.encodeRedirectURL(targetUrl));
} else {
delegate.sendAjaxRedirect(targetUrl, request, response, popup);
}
/**
* Create a RichFacesAjaxHandler as part of a chain of AjaxHandler's.
*/
public RichFacesAjaxHandler(AbstractAjaxHandler delegate) {
super(delegate);
}
protected boolean isRichFacesAjaxRequest(HttpServletRequest request, HttpServletResponse response) {
protected boolean isAjaxRequestInternal(HttpServletRequest request, HttpServletResponse response) {
FacesContextHelper helper = new FacesContextHelper();
try {
FacesContext facesContext = helper.getFacesContext(getServletContext(), request, response);
@@ -68,33 +60,13 @@ public class RichFacesAjaxHandler extends WebApplicationObjectSupport implements
return false;
}
} finally {
helper.cleanup();
helper.releaseIfNecessary();
}
}
private static class FacesContextHelper {
private boolean created = false;
protected FacesContext getFacesContext(ServletContext context, HttpServletRequest request,
HttpServletResponse response) {
if (FacesContext.getCurrentInstance() != null) {
return FacesContext.getCurrentInstance();
} else {
FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder
.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
FacesContext defaultFacesContext = facesContextFactory.getFacesContext(context, request, response,
FlowLifecycle.newInstance());
Assert.notNull(defaultFacesContext, "Creation of the default FacesContext failed.");
created = true;
return defaultFacesContext;
}
}
protected void cleanup() {
if (created) {
FacesContext.getCurrentInstance().release();
}
}
protected void sendAjaxRedirectInternal(String targetUrl, HttpServletRequest request, HttpServletResponse response,
boolean popup) throws IOException {
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
}

View File

@@ -1,173 +0,0 @@
/*
* Copyright 2004-2010 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.faces.ui;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ComponentSystemEventListener;
import javax.faces.event.PhaseId;
import javax.faces.event.SystemEventListener;
/**
* <p>
* A subclass of AjaxViewRoot for use with JSF 2.0.
* </p>
*
* <p>
* Note that these methods should ideally be in DelegatingViewRoot.java. However, due to some introspection done by the
* JSF runtime that would make it impossible to use in JSF 1.2 because some of the delegate methods contain JSF 2.0
* specific types and cause ClassNotFoundExceptions.
* </p>
*
* @author Phil Webb
*/
public class Jsf2AjaxViewRoot extends AjaxViewRoot {
public Jsf2AjaxViewRoot(UIViewRoot original) {
super(original);
}
public void addClientBehavior(String eventName, ClientBehavior behavior) {
getOriginalViewRoot().addClientBehavior(eventName, behavior);
}
public void addComponentResource(FacesContext context, UIComponent componentResource) {
getOriginalViewRoot().addComponentResource(context, componentResource);
}
public void addComponentResource(FacesContext context, UIComponent componentResource, String target) {
getOriginalViewRoot().addComponentResource(context, componentResource, target);
}
public void broadcastEvents(FacesContext context, PhaseId phaseId) {
getOriginalViewRoot().broadcastEvents(context, phaseId);
}
public void clearInitialState() {
getOriginalViewRoot().clearInitialState();
}
public String createUniqueId(FacesContext context, String seed) {
return getOriginalViewRoot().createUniqueId(context, seed);
}
public Map getClientBehaviors() {
return getOriginalViewRoot().getClientBehaviors();
}
public String getClientId() {
return getOriginalViewRoot().getClientId();
}
public List getComponentResources(FacesContext context, String target) {
return getOriginalViewRoot().getComponentResources(context, target);
}
public String getDefaultEventName() {
return getOriginalViewRoot().getDefaultEventName();
}
public Collection getEventNames() {
return getOriginalViewRoot().getEventNames();
}
public List getListenersForEventClass(Class eventClass) {
return getOriginalViewRoot().getListenersForEventClass(eventClass);
}
public UIComponent getNamingContainer() {
return getOriginalViewRoot().getNamingContainer();
}
public List getPhaseListeners() {
return getOriginalViewRoot().getPhaseListeners();
}
public Map getResourceBundleMap() {
return getOriginalViewRoot().getResourceBundleMap();
}
public List getViewListenersForEventClass(Class systemEvent) {
return getOriginalViewRoot().getViewListenersForEventClass(systemEvent);
}
public Map getViewMap() {
return getOriginalViewRoot().getViewMap();
}
public Map getViewMap(boolean create) {
return getOriginalViewRoot().getViewMap(create);
}
public boolean initialStateMarked() {
return getOriginalViewRoot().initialStateMarked();
}
public void markInitialState() {
getOriginalViewRoot().markInitialState();
}
public boolean isInView() {
return getOriginalViewRoot().isInView();
}
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
getOriginalViewRoot().processEvent(event);
}
public void removeComponentResource(FacesContext context, UIComponent componentResource) {
getOriginalViewRoot().removeComponentResource(context, componentResource);
}
public void removeComponentResource(FacesContext context, UIComponent componentResource, String target) {
getOriginalViewRoot().removeComponentResource(context, componentResource, target);
}
public void setInView(boolean isInView) {
getOriginalViewRoot().setInView(isInView);
}
public void subscribeToEvent(Class eventClass, ComponentSystemEventListener componentListener) {
getOriginalViewRoot().subscribeToEvent(eventClass, componentListener);
}
public void subscribeToViewEvent(Class systemEvent, SystemEventListener listener) {
getOriginalViewRoot().subscribeToViewEvent(systemEvent, listener);
}
public void unsubscribeFromEvent(Class eventClass, ComponentSystemEventListener componentListener) {
getOriginalViewRoot().unsubscribeFromEvent(eventClass, componentListener);
}
public void unsubscribeFromViewEvent(Class systemEvent, SystemEventListener listener) {
getOriginalViewRoot().unsubscribeFromViewEvent(systemEvent, listener);
}
public boolean visitTree(VisitContext context, VisitCallback callback) {
return getOriginalViewRoot().visitTree(context, callback);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2004-2010 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.faces.webflow;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* Provides helper methods for getting a FacesContext that is suitable for use outside of Web Flow. Inside a running
* Flow session {@link FlowFacesContext} is typically used instead.
* <p>
*
* @author Rossen Stoyanchev
* @since 2.2.0
*/
public class FacesContextHelper {
private boolean release = false;
public FacesContext getFacesContext(ServletContext servletContext, HttpServletRequest request,
HttpServletResponse response) {
FacesContext facesContext = null;
if (FacesContext.getCurrentInstance() != null) {
facesContext = FacesContext.getCurrentInstance();
} else {
FacesContextFactory factory = (FacesContextFactory) FactoryFinder
.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
facesContext = factory.getFacesContext(servletContext, request, response, FlowLifecycle.newInstance());
release = true;
}
return facesContext;
}
public void releaseIfNecessary() {
if (release) {
FacesContext.getCurrentInstance().release();
}
}
}

View File

@@ -17,12 +17,8 @@ package org.springframework.faces.webflow;
import java.io.IOException;
import javax.faces.FactoryFinder;
import javax.faces.application.ResourceHandler;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -42,18 +38,14 @@ public class FacesJsfResourceRequestHandler extends WebApplicationObjectSupport
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
FacesContext context = getFacesContext(request, response);
ResourceHandler resourceHandler = context.getApplication().getResourceHandler();
resourceHandler.handleResourceRequest(context);
}
private FacesContext getFacesContext(HttpServletRequest request, HttpServletResponse response) {
FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder
.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
return facesContextFactory.getFacesContext(getServletContext(), request, response, lifecycle);
FacesContextHelper helper = new FacesContextHelper();
try {
FacesContext facesContext = helper.getFacesContext(getServletContext(), request, response);
ResourceHandler resourceHandler = facesContext.getApplication().getResourceHandler();
resourceHandler.handleResourceRequest(facesContext);
} finally {
helper.releaseIfNecessary();
}
}
}

View File

@@ -81,16 +81,16 @@ public class FlowFacesContext extends FacesContext {
}
/**
* Translates a FacesMessage to an SWF Message and adds it to the current MessageContext
* Translates a FacesMessage to a Spring Web Flow message and adds it to the current MessageContext
*/
public void addMessage(String clientId, FacesMessage message) {
messageDelegate.addMessage(clientId, message);
messageDelegate.addToFlowMessageContext(clientId, message);
}
/**
* Returns an Iterator for all component clientId's for which messages have been added.
*/
public Iterator getClientIdsWithMessages() {
public Iterator<String> getClientIdsWithMessages() {
return messageDelegate.getClientIdsWithMessages();
}
@@ -120,7 +120,7 @@ public class FlowFacesContext extends FacesContext {
/**
* Returns an Iterator for all Messages in the current MessageContext that does translation to FacesMessages.
*/
public Iterator getMessages() {
public Iterator<FacesMessage> getMessages() {
return messageDelegate.getMessages();
}
@@ -128,7 +128,7 @@ public class FlowFacesContext extends FacesContext {
* Returns an Iterator for all Messages with the given clientId in the current MessageContext that does translation
* to FacesMessages.
*/
public Iterator getMessages(String clientId) {
public Iterator<FacesMessage> getMessages(String clientId) {
return messageDelegate.getMessages(clientId);
}
@@ -200,6 +200,10 @@ public class FlowFacesContext extends FacesContext {
return delegate;
}
protected FlowFacesContextMessageDelegate getMessageDelegate() {
return messageDelegate;
}
protected class FlowExternalContext extends ExternalContextWrapper {
private static final String CUSTOM_RESPONSE = "customResponse";

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -63,7 +64,7 @@ public class FlowFacesContextMessageDelegate {
/**
* @see FlowFacesContext#addMessage(String, FacesMessage)
*/
public void addMessage(String clientId, FacesMessage message) {
public void addToFlowMessageContext(String clientId, FacesMessage message) {
String source = null;
if (StringUtils.hasText(clientId)) {
source = clientId;
@@ -75,7 +76,7 @@ public class FlowFacesContextMessageDelegate {
/**
* @see FlowFacesContext#getClientIdsWithMessages
*/
public Iterator getClientIdsWithMessages() {
public Iterator<String> getClientIdsWithMessages() {
return new ClientIdIterator();
}
@@ -102,19 +103,61 @@ public class FlowFacesContextMessageDelegate {
/**
* @see FlowFacesContext#getMessages()
*/
public Iterator getMessages() {
public Iterator<FacesMessage> getMessages() {
return new FacesMessageIterator();
}
/**
* @see FlowFacesContext#getMessages(String)
*/
public Iterator getMessages(String clientId) {
public Iterator<FacesMessage> getMessages(String clientId) {
return new FacesMessageIterator(clientId);
}
/**
* @see Jsf2FlowFacesContext#getMessageList()
*/
public List<FacesMessage> getMessageList() {
return getMessageList(null);
}
/**
* @see Jsf2FlowFacesContext#getMessageList(String)
*/
public List<FacesMessage> getMessageList(String clientId) {
List<FacesMessage> messages = getFacesMessages(clientId);
if (null == messages) {
return Collections.unmodifiableList(Collections.<FacesMessage> emptyList());
} else {
return Collections.unmodifiableList(messages);
}
}
// ------------------ Private helper methods ----------------------//
private List<FacesMessage> getFacesMessages() {
return getFacesMessages(null);
}
private List<FacesMessage> getFacesMessages(String clientId) {
List<FacesMessage> translatedMessages = new ArrayList<FacesMessage>();
Message[] summaryMessages = context.getMessageContext().getMessagesByCriteria(
new MatchBySuffixCriteria(clientId, SUMMARY_MESSAGE_KEY));
Message[] detailMessages = context.getMessageContext().getMessagesByCriteria(
new MatchBySuffixCriteria(clientId, DETAIL_MESSAGE_KEY));
for (int i = 0; i < summaryMessages.length; i++) {
translatedMessages.add(toFacesMessage(summaryMessages[i], detailMessages[i]));
}
Message[] userMessages = context.getMessageContext().getMessagesByCriteria(new UserMessageCriteria(clientId));
for (int z = 0; z < userMessages.length; z++) {
translatedMessages.add(toFacesMessage(userMessages[z], userMessages[z]));
}
return translatedMessages;
}
private FacesMessage toFacesMessage(Message summaryMessage, Message detailMessage) {
// If we can return the actual message instance.
@@ -137,49 +180,25 @@ public class FlowFacesContextMessageDelegate {
}
}
private class FacesMessageIterator implements Iterator {
private class FacesMessageIterator implements Iterator<FacesMessage> {
private Object[] messages;
private FacesMessage[] messages;
private int currentIndex = -1;
protected FacesMessageIterator() {
Message[] summaryMessages = context.getMessageContext().getMessagesByCriteria(new SummaryMessageCriteria());
Message[] detailMessages = context.getMessageContext().getMessagesByCriteria(new DetailMessageCriteria());
Message[] userMessages = context.getMessageContext().getMessagesByCriteria(new UserMessageCriteria());
List translatedMessages = new ArrayList();
for (int i = 0; i < summaryMessages.length; i++) {
translatedMessages.add(toFacesMessage(summaryMessages[i], detailMessages[i]));
}
for (int z = 0; z < userMessages.length; z++) {
translatedMessages.add(toFacesMessage(userMessages[z], userMessages[z]));
}
this.messages = translatedMessages.toArray();
this.messages = getFacesMessages().toArray(new FacesMessage[] {});
}
protected FacesMessageIterator(String clientId) {
Message[] summaryMessages = context.getMessageContext().getMessagesBySource(clientId + SUMMARY_MESSAGE_KEY);
Message[] detailMessages = context.getMessageContext().getMessagesBySource(clientId + DETAIL_MESSAGE_KEY);
Message[] userMessages = context.getMessageContext().getMessagesBySource(clientId);
List translatedMessages = new ArrayList();
for (int i = 0; i < summaryMessages.length; i++) {
translatedMessages.add(toFacesMessage(summaryMessages[i], detailMessages[i]));
}
for (int z = 0; z < userMessages.length; z++) {
translatedMessages.add(toFacesMessage(userMessages[z], userMessages[z]));
}
this.messages = translatedMessages.toArray();
this.messages = getFacesMessages(clientId).toArray(new FacesMessage[] {});
}
public boolean hasNext() {
return messages.length > currentIndex + 1;
}
public Object next() {
public FacesMessage next() {
currentIndex++;
return messages[currentIndex];
}
@@ -190,7 +209,7 @@ public class FlowFacesContextMessageDelegate {
}
private class ClientIdIterator implements Iterator {
private class ClientIdIterator implements Iterator<String> {
private Message[] messages;
@@ -204,7 +223,7 @@ public class FlowFacesContextMessageDelegate {
return messages.length > currentIndex + 1;
}
public Object next() {
public String next() {
Message next = messages[++currentIndex];
if (next.getSource() == null) {
return null;
@@ -221,34 +240,46 @@ public class FlowFacesContextMessageDelegate {
}
private class SummaryMessageCriteria implements MessageCriteria {
private class MatchBySuffixCriteria implements MessageCriteria {
public boolean test(Message message) {
if (message.getSource() == null) {
return false;
}
return message.getSource().toString().endsWith(SUMMARY_MESSAGE_KEY);
private String clientId;
private String suffix;
public MatchBySuffixCriteria(String clientId, String suffix) {
this.clientId = clientId;
this.suffix = suffix;
}
}
private class DetailMessageCriteria implements MessageCriteria {
public boolean test(Message message) {
if (message.getSource() == null) {
return false;
boolean result = false;
if (message.getSource() != null) {
if (clientId != null) {
result = message.getSource().toString().equals(clientId + suffix);
} else {
result = message.getSource().toString().endsWith(suffix);
}
}
return message.getSource().toString().endsWith(DETAIL_MESSAGE_KEY);
return result;
}
}
private class UserMessageCriteria implements MessageCriteria {
private String clientId;
public UserMessageCriteria(String clientId) {
this.clientId = clientId;
}
public boolean test(Message message) {
if (message.getSource() == null) {
return true;
boolean result = false;
if (clientId != null) {
result = new MatchBySuffixCriteria(clientId, "").test(message);
} else {
result = (!new MatchBySuffixCriteria(clientId, SUMMARY_MESSAGE_KEY).test(message))
&& (!new MatchBySuffixCriteria(clientId, DETAIL_MESSAGE_KEY).test(message));
}
return !message.getSource().toString().endsWith(SUMMARY_MESSAGE_KEY)
&& !message.getSource().toString().endsWith(DETAIL_MESSAGE_KEY);
return result;
}
}
@@ -256,7 +287,7 @@ public class FlowFacesContextMessageDelegate {
String nullSummaryId = null + SUMMARY_MESSAGE_KEY;
private Set identifiedMessageSources = new HashSet();
private Set<String> identifiedMessageSources = new HashSet<String>();
// From getClientIdsWithMessages docs: If any messages have been queued that were not associated with
// any specific client identifier, a null value will be included in the iterated values.
@@ -267,7 +298,7 @@ public class FlowFacesContextMessageDelegate {
|| message.getSource().equals(nullSummaryId)) {
return identifiedMessageSources.add(null);
}
return identifiedMessageSources.add(message.getSource());
return identifiedMessageSources.add((String) message.getSource());
}
}

View File

@@ -41,6 +41,10 @@ public class FlowSerializedView implements Serializable {
return this.componentState;
}
public Object[] asTreeStructAndCompStateArray() {
return new Object[] { this.treeStructure, this.componentState };
}
public String toString() {
return new ToStringCreator(this).append("viewId", viewId).toString();
}

View File

@@ -112,7 +112,7 @@ public class FlowViewStateManager extends StateManager {
delegate.writeState(context, state); // MyFaces
} else if (state instanceof FlowSerializedView) { // Mojarra
FlowSerializedView view = (FlowSerializedView) state;
delegate.writeState(context, new Object[] { view.getTreeStructure(), view.getComponentState() });
delegate.writeState(context, view.asTreeStructAndCompStateArray());
} else {
super.writeState(context, state);
}
@@ -173,4 +173,22 @@ public class FlowViewStateManager extends StateManager {
return viewRoot;
}
@Override
public String getViewState(FacesContext context) {
/*
* Mojarra 2: PartialRequestContextImpl.renderState() invokes this method during Ajax request rendering. It is
* overridden to convert FlowSerializedView to an array containing tree structure and component state. The
* ResponseStateManager.getViewState() calls the ServerSideStateHelper, which expects the array.
*/
Object state = saveView(context);
if (state != null) {
if (state instanceof FlowSerializedView) {
FlowSerializedView view = (FlowSerializedView) state;
state = view.asTreeStructAndCompStateArray();
}
return context.getRenderKit().getResponseStateManager().getViewState(context, state);
}
return null;
}
}

View File

@@ -21,6 +21,7 @@ import java.io.Writer;
import java.util.List;
import java.util.Map;
import javax.faces.FactoryFinder;
import javax.faces.application.FacesMessage;
import javax.faces.application.ProjectStage;
import javax.faces.context.ExceptionHandler;
@@ -28,20 +29,32 @@ import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.context.PartialViewContext;
import javax.faces.context.PartialViewContextFactory;
import javax.faces.event.PhaseId;
import org.springframework.webflow.execution.RequestContext;
/**
* Extends FlowFacesContext in order to provide JSF 2 delegation method. This is necessary because some of the methods
* use JSF 2 specific types as input or output parameters.
* Extends FlowFacesContext in order to provide JSF 2 delegation method.
*
* @author Rossen Stoyanchev
*/
public class Jsf2FlowFacesContext extends FlowFacesContext {
/*
* This partialViewContext duplicates the one FacesContextImpl because the constructor of FacesContextImpl calls
* getPartialViewContext(), which causes it to be instantiated with an instance of FacesContextImpl. This leads to
* issues with adding and showing validation messages during Ajax requests because the FlowFacesContext is
* effectively bypassed.
*/
private PartialViewContext partialViewContext;
public Jsf2FlowFacesContext(RequestContext context, FacesContext delegate) {
super(context, delegate);
PartialViewContextFactory f = (PartialViewContextFactory) FactoryFinder
.getFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY);
partialViewContext = f.getPartialViewContext(this);
}
public ExternalContext getExternalContext() {
@@ -55,15 +68,22 @@ public class Jsf2FlowFacesContext extends FlowFacesContext {
}
public PartialViewContext getPartialViewContext() {
return getDelegate().getPartialViewContext();
return partialViewContext;
}
/**
* Returns a List for all Messages in the current MessageContext that does translation to FacesMessages.
*/
public List<FacesMessage> getMessageList() {
return getDelegate().getMessageList();
return getMessageDelegate().getMessageList();
}
/**
* Returns a List for all Messages with the given clientId in the current MessageContext that does translation to
* FacesMessages.
*/
public List<FacesMessage> getMessageList(String clientId) {
return getDelegate().getMessageList(clientId);
return getMessageDelegate().getMessageList(clientId);
}
public boolean isPostback() {

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2004-2010 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.faces.webflow;
import java.io.IOException;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextWrapper;
import javax.faces.context.PartialResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.js.ajax.AbstractAjaxHandler;
import com.sun.faces.context.PartialViewContextImpl;
/**
* Ajax handler for JSF 2 requests that can identify JSF 2 Ajax requests and send redirect instructions back to the
* client by including a redirect instruction in the content of the response.
*
* @see AbstractAjaxHandler
*
* @author Rossen Stoyanchev
* @since 2.2.0
*/
public class JsfAjaxHandler extends AbstractAjaxHandler {
public JsfAjaxHandler() {
this(null);
}
public JsfAjaxHandler(AbstractAjaxHandler delegate) {
super(delegate);
}
protected boolean isAjaxRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String facesRequestHeader = request.getHeader("Faces-Request");
return ("partial/ajax".equals(facesRequestHeader)) ? true : false;
}
protected void sendAjaxRedirectInternal(final String targetUrl, final HttpServletRequest request,
final HttpServletResponse response, boolean popup) throws IOException {
// Ideally facesContext.getExternalContext().redirect() should be used instead of the code in this method.
// However PartialViewContextImpl.createPartialResponseWriter() calls cxt.getRenderKit(), which in turn
// tries to get the UIViewRoot's renderKitId. That results in NPE when JsfAjaxHandler is called
// outside of flow execution. The code below wraps the FacesContext to override getRenderKit() and provide a
// default render kit id.
FacesContextHelper helper = new FacesContextHelper();
try {
FacesContext facesContext = helper.getFacesContext(getServletContext(), request, response);
facesContext = new FixedRenderKitFacesContext(facesContext, determineRenderKitId(request, response));
PartialViewContextImpl partialViewContext = new PartialViewContextImpl(facesContext);
PartialResponseWriter writer = partialViewContext.getPartialResponseWriter();
writer.startDocument();
writer.redirect(targetUrl);
writer.endDocument();
} finally {
helper.releaseIfNecessary();
}
}
protected String determineRenderKitId(HttpServletRequest request, HttpServletResponse response) {
return RenderKitFactory.HTML_BASIC_RENDER_KIT;
}
private class FixedRenderKitFacesContext extends FacesContextWrapper {
private FacesContext delegate;
private String renderKitId;
public FixedRenderKitFacesContext(FacesContext delegate, String renderKitId) {
this.delegate = delegate;
this.renderKitId = renderKitId;
}
@Override
public FacesContext getWrapped() {
return this.delegate;
}
@Override
public RenderKit getRenderKit() {
RenderKitFactory factory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
return factory.getRenderKit(this, renderKitId);
}
}
}

View File

@@ -59,6 +59,10 @@ public class JsfRuntimeInformation {
return jsfVersion >= JSF_12;
}
public static boolean isLessThanJsf20() {
return jsfVersion < JSF_20;
}
protected static boolean isMyFacesPresent() {
return myFacesPresent;
}

View File

@@ -17,6 +17,7 @@ package org.springframework.faces.webflow;
import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12;
import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20;
import static org.springframework.faces.webflow.JsfRuntimeInformation.isLessThanJsf20;
import static org.springframework.faces.webflow.JsfRuntimeInformation.isPortletRequest;
import java.util.Iterator;
@@ -37,7 +38,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.binding.expression.Expression;
import org.springframework.faces.ui.AjaxViewRoot;
import org.springframework.faces.ui.Jsf2AjaxViewRoot;
import org.springframework.js.ajax.SpringJavascriptAjaxHandler;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.execution.RequestContext;
@@ -143,12 +143,10 @@ public class JsfViewFactory implements ViewFactory {
}
private JsfView createJsfView(UIViewRoot root, Lifecycle lifecycle, RequestContext context) {
if (isSpringJavascriptAjaxRequest(context.getExternalContext())) {
AjaxViewRoot viewRoot = (isAtLeastJsf20()) ? new Jsf2AjaxViewRoot(root) : new AjaxViewRoot(root);
return new JsfView(viewRoot, lifecycle, context);
} else {
return new JsfView(root, lifecycle, context);
if (isLessThanJsf20() && isSpringJavascriptAjaxRequest(context.getExternalContext())) {
root = new AjaxViewRoot(root);
}
return new JsfView(root, lifecycle, context);
}
private boolean isSpringJavascriptAjaxRequest(ExternalContext context) {

View File

@@ -153,12 +153,8 @@ public class FlowFacesContextTests extends TestCase {
EasyMock.expect(requestContext.getMessageContext()).andStubReturn(messageContext);
EasyMock.replay(new Object[] { requestContext });
int iterationCount = 0;
Iterator i = facesContext.getMessages("unknown");
while (i.hasNext()) {
iterationCount++;
}
assertEquals(0, iterationCount);
assertFalse(i.hasNext());
}
public final void testGetClientIdsWithMessages() {

View File

@@ -127,7 +127,6 @@ public class JSFMockHelper {
FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY, MockRenderKitFactory.class.getName());
FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY, MockPartialViewContextFactory.class
.getName());
externalContext = new MockExternalContext(servletContext, request, response);
lifecycleFactory = (MockLifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
lifecycle = (MockLifecycle) lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);

View File

@@ -0,0 +1,39 @@
package org.springframework.faces.webflow;
import java.io.IOException;
import junit.framework.TestCase;
import org.apache.myfaces.test.mock.MockPrintWriter;
import org.springframework.web.context.support.StaticWebApplicationContext;
public class JsfAjaxHandlerTests extends TestCase {
private JSFMockHelper jsfMock = new JSFMockHelper();
private JsfAjaxHandler ajaxHandler;
protected void setUp() throws Exception {
jsfMock.setUp();
StaticWebApplicationContext webappContext = new StaticWebApplicationContext();
webappContext.setServletContext(jsfMock.servletContext());
ajaxHandler = new JsfAjaxHandler();
ajaxHandler.setApplicationContext(webappContext);
}
protected void tearDown() throws Exception {
jsfMock.tearDown();
}
public void testSendAjaxRedirect() throws Exception {
ajaxHandler.sendAjaxRedirectInternal("/target", jsfMock.request(), jsfMock.response(), false);
assertEquals(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<partial-response><redirect url=\"/target\"/></partial-response>",
extractResponseContent());
}
private String extractResponseContent() throws IOException {
MockPrintWriter writer = (MockPrintWriter) jsfMock.response().getWriter();
return new String(writer.content());
}
}

View File

@@ -22,7 +22,6 @@ import org.easymock.EasyMock;
import org.jboss.el.ExpressionFactoryImpl;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.support.FluentParserContext;
import org.springframework.faces.ui.AjaxViewRoot;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
@@ -218,7 +217,7 @@ public class JsfViewFactoryTests extends TestCase {
assertNotNull("A View was not restored", restoredView);
assertTrue("A JsfView was expected", restoredView instanceof JsfView);
assertTrue("An AjaxViewRoot was not set", ((JsfView) restoredView).getViewRoot() instanceof AjaxViewRoot);
assertTrue("An ViewRoot was not set", ((JsfView) restoredView).getViewRoot() instanceof UIViewRoot);
assertEquals("View name did not match", VIEW_ID, ((JsfView) restoredView).getViewRoot().getViewId());
assertFalse("An unexpected event was signaled,", restoredView.hasFlowEvent());
}

View File

@@ -9,7 +9,7 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.myfaces.test.mock.MockExternalContext;
import org.apache.myfaces.test.mock.MockExternalContext20;
public class MockBaseFacesContextFactory extends FacesContextFactory {
@@ -21,7 +21,7 @@ public class MockBaseFacesContextFactory extends FacesContextFactory {
return FacesContext.getCurrentInstance();
} else {
ExternalContext ext = new MockExternalContext((ServletContext) context, (HttpServletRequest) request,
ExternalContext ext = new MockExternalContext20((ServletContext) context, (HttpServletRequest) request,
(HttpServletResponse) response);
return new MockBaseFacesContext(ext, lifecycle);