diff --git a/build-spring-webflow/resources/changelog.txt b/build-spring-webflow/resources/changelog.txt
index 0050bbb0..6f2bebc5 100644
--- a/build-spring-webflow/resources/changelog.txt
+++ b/build-spring-webflow/resources/changelog.txt
@@ -4,9 +4,11 @@ http://www.springframework.org/webflow
Changes in version 2.2.0.RELEASE ()
-----------------------------------
+* Add support for handling JSF 2 resource requests (SWF-1366)
+* Add support for JSF 2 Ajax requests (SWF-1367)
-Changes in version 2.1.0.RELEASE (July 19, 2010)
+Changes in version 2.1.1.RELEASE (July 19, 2010)
------------------------------------------------
* Extract JSF 2 methods from FlowApplication into Jsf2FlowApplication (SWF-1261).
* Fix handling of cascaded attributes in AjaxTilesView (SWF-1053).
diff --git a/spring-faces/src/main/java/org/springframework/faces/richfaces/RichFacesAjaxHandler.java b/spring-faces/src/main/java/org/springframework/faces/richfaces/RichFacesAjaxHandler.java
index 455fe3b4..25a0d255 100644
--- a/spring-faces/src/main/java/org/springframework/faces/richfaces/RichFacesAjaxHandler.java
+++ b/spring-faces/src/main/java/org/springframework/faces/richfaces/RichFacesAjaxHandler.java
@@ -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));
}
+
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java b/spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java
deleted file mode 100644
index 43531136..00000000
--- a/spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java
+++ /dev/null
@@ -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;
-
-/**
- *
- * A subclass of AjaxViewRoot for use with JSF 2.0.
- *
- *
- *
- * 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.
- *
- *
- * @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);
- }
-
-}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FacesContextHelper.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesContextHelper.java
new file mode 100644
index 00000000..6380acb3
--- /dev/null
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesContextHelper.java
@@ -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;
+
+/**
+ *
+ * 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.
+ *
+ *
+ * @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();
+ }
+ }
+
+}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FacesJsfResourceRequestHandler.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesJsfResourceRequestHandler.java
index f769b186..f7c78bed 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/FacesJsfResourceRequestHandler.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesJsfResourceRequestHandler.java
@@ -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();
+ }
}
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java
index a4bb5d14..96df2c61 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java
@@ -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 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 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 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";
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextMessageDelegate.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextMessageDelegate.java
index f1395320..c4557e7b 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextMessageDelegate.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextMessageDelegate.java
@@ -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 getClientIdsWithMessages() {
return new ClientIdIterator();
}
@@ -102,19 +103,61 @@ public class FlowFacesContextMessageDelegate {
/**
* @see FlowFacesContext#getMessages()
*/
- public Iterator getMessages() {
+ public Iterator getMessages() {
return new FacesMessageIterator();
}
/**
* @see FlowFacesContext#getMessages(String)
*/
- public Iterator getMessages(String clientId) {
+ public Iterator getMessages(String clientId) {
return new FacesMessageIterator(clientId);
}
+ /**
+ * @see Jsf2FlowFacesContext#getMessageList()
+ */
+ public List getMessageList() {
+ return getMessageList(null);
+ }
+
+ /**
+ * @see Jsf2FlowFacesContext#getMessageList(String)
+ */
+ public List getMessageList(String clientId) {
+ List messages = getFacesMessages(clientId);
+ if (null == messages) {
+ return Collections.unmodifiableList(Collections. emptyList());
+ } else {
+ return Collections.unmodifiableList(messages);
+ }
+ }
+
// ------------------ Private helper methods ----------------------//
+ private List getFacesMessages() {
+ return getFacesMessages(null);
+ }
+
+ private List getFacesMessages(String clientId) {
+ List translatedMessages = new ArrayList();
+
+ 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 {
- 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 {
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 identifiedMessageSources = new HashSet();
// 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());
}
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowSerializedView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowSerializedView.java
index 1c3d5d24..5aa54393 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowSerializedView.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowSerializedView.java
@@ -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();
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java
index d7326b8b..9c81325b 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java
@@ -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;
+ }
+
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java
index 4d9f5cb1..742403de 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowFacesContext.java
@@ -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 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 getMessageList(String clientId) {
- return getDelegate().getMessageList(clientId);
+ return getMessageDelegate().getMessageList(clientId);
}
public boolean isPostback() {
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfAjaxHandler.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfAjaxHandler.java
new file mode 100644
index 00000000..4f2102f9
--- /dev/null
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfAjaxHandler.java
@@ -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);
+ }
+ }
+
+}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java
index 933ea91b..00915f38 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java
@@ -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;
}
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java
index 9801fb69..ae4e6594 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java
@@ -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) {
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java
index 6732752c..cbedefc0 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java
@@ -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() {
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java
index 548c3868..74fdf966 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JSFMockHelper.java
@@ -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);
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java
new file mode 100644
index 00000000..051a3378
--- /dev/null
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java
@@ -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(
+ "\n",
+ extractResponseContent());
+ }
+
+ private String extractResponseContent() throws IOException {
+ MockPrintWriter writer = (MockPrintWriter) jsfMock.response().getWriter();
+ return new String(writer.content());
+ }
+
+}
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java
index 2eb5bd90..e0c52ebe 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java
@@ -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());
}
diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java b/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java
index b832ee58..888564c0 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/MockBaseFacesContextFactory.java
@@ -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);
diff --git a/spring-js/src/main/java/org/springframework/js/ajax/AbstractAjaxHandler.java b/spring-js/src/main/java/org/springframework/js/ajax/AbstractAjaxHandler.java
new file mode 100644
index 00000000..637a9340
--- /dev/null
+++ b/spring-js/src/main/java/org/springframework/js/ajax/AbstractAjaxHandler.java
@@ -0,0 +1,43 @@
+package org.springframework.js.ajax;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.context.support.WebApplicationObjectSupport;
+
+public abstract class AbstractAjaxHandler extends WebApplicationObjectSupport implements AjaxHandler {
+
+ private AbstractAjaxHandler delegate;
+
+ public AbstractAjaxHandler(AbstractAjaxHandler delegate) {
+ this.delegate = delegate;
+ }
+
+ public final boolean isAjaxRequest(HttpServletRequest request, HttpServletResponse response) {
+ if (isAjaxRequestInternal(request, response)) {
+ return true;
+ }
+ if (delegate != null) {
+ return delegate.isAjaxRequest(request, response);
+ }
+ return false;
+ }
+
+ public final void sendAjaxRedirect(String targetUrl, HttpServletRequest request, HttpServletResponse response,
+ boolean popup) throws IOException {
+ if (isAjaxRequestInternal(request, response)) {
+ sendAjaxRedirectInternal(targetUrl, request, response, popup);
+ }
+ if (delegate != null) {
+ delegate.sendAjaxRedirect(targetUrl, request, response, popup);
+ }
+ }
+
+ protected abstract boolean isAjaxRequestInternal(HttpServletRequest request, HttpServletResponse response);
+
+ protected abstract void sendAjaxRedirectInternal(String targetUrl, HttpServletRequest request,
+ HttpServletResponse response, boolean popup) throws IOException;
+
+}
diff --git a/spring-js/src/main/java/org/springframework/js/ajax/SpringJavascriptAjaxHandler.java b/spring-js/src/main/java/org/springframework/js/ajax/SpringJavascriptAjaxHandler.java
index b7f84d99..48a6268d 100644
--- a/spring-js/src/main/java/org/springframework/js/ajax/SpringJavascriptAjaxHandler.java
+++ b/spring-js/src/main/java/org/springframework/js/ajax/SpringJavascriptAjaxHandler.java
@@ -25,10 +25,12 @@ import org.springframework.util.StringUtils;
/**
* Ajax handler for Spring Javascript (Spring.js).
*
+ * @see AbstractAjaxHandler
+ *
* @author Jeremy Grelle
* @author Keith Donald
*/
-public class SpringJavascriptAjaxHandler implements AjaxHandler {
+public class SpringJavascriptAjaxHandler extends AbstractAjaxHandler {
/**
* The response header to be set on an Ajax redirect
@@ -50,7 +52,21 @@ public class SpringJavascriptAjaxHandler implements AjaxHandler {
*/
public static final String AJAX_SOURCE_PARAM = "ajaxSource";
- public boolean isAjaxRequest(HttpServletRequest request, HttpServletResponse response) {
+ /**
+ * Create a SpringJavascriptAjaxHandler that is not part of a chain of AjaxHandler's.
+ */
+ public SpringJavascriptAjaxHandler() {
+ this(null);
+ }
+
+ /**
+ * Create a SpringJavascriptAjaxHandler as part of a chain of AjaxHandler's.
+ */
+ public SpringJavascriptAjaxHandler(AbstractAjaxHandler delegate) {
+ super(delegate);
+ }
+
+ protected boolean isAjaxRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String acceptHeader = request.getHeader("Accept");
String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM);
if (AJAX_ACCEPT_CONTENT_TYPE.equals(acceptHeader) || StringUtils.hasText(ajaxParam)) {
@@ -60,7 +76,7 @@ public class SpringJavascriptAjaxHandler implements AjaxHandler {
}
}
- public void sendAjaxRedirect(String targetUrl, HttpServletRequest request, HttpServletResponse response,
+ protected void sendAjaxRedirectInternal(String targetUrl, HttpServletRequest request, HttpServletResponse response,
boolean popup) throws IOException {
if (popup) {
response.setHeader(POPUP_VIEW_HEADER, "true");
diff --git a/spring-js/src/test/java/org/springframework/js/ajax/AbstractAjaxHandlerTests.java b/spring-js/src/test/java/org/springframework/js/ajax/AbstractAjaxHandlerTests.java
new file mode 100644
index 00000000..229488ea
--- /dev/null
+++ b/spring-js/src/test/java/org/springframework/js/ajax/AbstractAjaxHandlerTests.java
@@ -0,0 +1,90 @@
+package org.springframework.js.ajax;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+public class AbstractAjaxHandlerTests extends TestCase {
+
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+
+ protected void setUp() throws Exception {
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ }
+
+ public void testIsAjaxRequest() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(null, true);
+ assertTrue(handler.isAjaxRequest(request, response));
+ }
+
+ public void testIsNotAjaxRequest() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(null, false);
+ assertFalse(handler.isAjaxRequest(request, response));
+ }
+
+ public void testIsAjaxRequestViaDelegate() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(new TestAjaxHandler(null, true), false);
+ assertTrue(handler.isAjaxRequest(request, response));
+ }
+
+ public void testSendAjaxRedirect() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(null, true);
+ handler.sendAjaxRedirect("", request, response, false);
+ assertTrue(handler.wasAjaxRedirectInternalCalled);
+ }
+
+ public void testAjaxRedirectNotSent() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(null, false);
+ handler.sendAjaxRedirect("", request, response, false);
+ assertFalse(handler.wasAjaxRedirectInternalCalled());
+ }
+
+ public void testSendAjaxRedirectViaDelegate() throws Exception {
+ TestAjaxHandler handler = new TestAjaxHandler(new TestAjaxHandler(null, true), false);
+ handler.sendAjaxRedirect("", request, response, false);
+ assertTrue(handler.wasAjaxRedirectInternalCalled());
+ }
+
+ private final class TestAjaxHandler extends AbstractAjaxHandler {
+
+ private boolean isAjaxRequest;
+ private boolean wasAjaxRedirectInternalCalled;
+ private TestAjaxHandler delegate;
+
+ private TestAjaxHandler(TestAjaxHandler delegate, boolean isAjaxRequest) {
+ super(delegate);
+ this.delegate = delegate;
+ this.isAjaxRequest = isAjaxRequest;
+ }
+
+ protected boolean isAjaxRequestInternal(HttpServletRequest request, HttpServletResponse response) {
+ return isAjaxRequest;
+ }
+
+ protected void sendAjaxRedirectInternal(String targetUrl, HttpServletRequest request,
+ HttpServletResponse response, boolean popup) throws IOException {
+ wasAjaxRedirectInternalCalled = true;
+ }
+
+ public TestAjaxHandler getDelegate() {
+ return delegate;
+ }
+
+ public boolean wasAjaxRedirectInternalCalled() {
+ if (wasAjaxRedirectInternalCalled) {
+ return true;
+ } else {
+ return (getDelegate() != null) ? delegate.wasAjaxRedirectInternalCalled() : false;
+ }
+ }
+ }
+
+}
diff --git a/spring-js/src/test/java/org/springframework/js/resource/ResourceServletTests.java b/spring-js/src/test/java/org/springframework/js/resource/ResourceServletTests.java
index ca1259c9..7927ffb9 100644
--- a/spring-js/src/test/java/org/springframework/js/resource/ResourceServletTests.java
+++ b/spring-js/src/test/java/org/springframework/js/resource/ResourceServletTests.java
@@ -31,7 +31,7 @@ public class ResourceServletTests extends TestCase {
public final void testExecute() throws Exception {
- String requestPath = "/dojo/dojo.js";
+ String requestPath = "/spring/Spring.js";
request.setPathInfo(requestPath);
servlet.doGet(request, response);
@@ -41,10 +41,10 @@ public class ResourceServletTests extends TestCase {
public final void testExecute_CombinedResources() throws Exception {
- String requestPath = "/dojo/dojo.js";
+ String requestPath = "/spring/Spring.js";
request.setPathInfo(requestPath);
Map params = new HashMap();
- params.put("appended", "/dijit/dijit.js,/dijit/Dialog.js");
+ params.put("appended", "/spring/Spring-Dojo.js");
request.setParameters(params);
servlet.doGet(request, response);
@@ -53,7 +53,7 @@ public class ResourceServletTests extends TestCase {
public final void testExecute_CompressedResponse() throws Exception {
- String requestPath = "/dojo/dojo.js";
+ String requestPath = "/spring/Spring.js";
request.setPathInfo(requestPath);
request.addHeader("Accept-Encoding", "gzip");
servlet.doGet(request, response);
diff --git a/spring-webflow-samples/booking-faces/.settings/org.springframework.ide.eclipse.core.prefs b/spring-webflow-samples/booking-faces/.settings/org.springframework.ide.eclipse.core.prefs
index c7a934ea..7424d8ed 100755
--- a/spring-webflow-samples/booking-faces/.settings/org.springframework.ide.eclipse.core.prefs
+++ b/spring-webflow-samples/booking-faces/.settings/org.springframework.ide.eclipse.core.prefs
@@ -1,13 +1,27 @@
-#Sun Apr 27 09:05:45 EDT 2008
+#Wed Jul 28 13:06:21 BST 2010
eclipse.preferences.version=1
instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true
org.springframework.ide.eclipse.core.builders.enable.aopreferencemodelbuilder=true
+org.springframework.ide.eclipse.core.builders.enable.beanmetadatabuilder=true
org.springframework.ide.eclipse.core.builders.enable.osgibundleupdater=true
org.springframework.ide.eclipse.core.enable.project.preferences=true
+org.springframework.ide.eclipse.core.validator.enable.com.springsource.server.ide.manifest.core.manifestvalidator=true
org.springframework.ide.eclipse.core.validator.enable.com.springsource.sts.bestpractices.beansvalidator=true
+org.springframework.ide.eclipse.core.validator.enable.com.springsource.sts.server.quickfix.manifestvalidator=false
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.core.springvalidator=true
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.webflow.core.validator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.applicationSymbolicNameRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.applicationVersionRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.bundleActivationPolicyRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.bundleActivatorRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.bundleManifestVersionRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.bundleSymbolicNameRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.bundleVersionRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.exportPackageRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.importRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.parsingProblemsRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.server.ide.manifest.core.requireBundleRule-com.springsource.server.ide.manifest.core.manifestvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.AvoidDriverManagerDataSource-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.ImportElementsAtTopRulee-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.ParentBeanSpecifiesAbstractClassRule-com.springsource.sts.bestpractices.beansvalidator=true
@@ -16,6 +30,11 @@ org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.UnnecessaryValueElementRule-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.UseBeanInheritance-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.legacyxmlusage.jndiobjectfactory-com.springsource.sts.bestpractices.beansvalidator=true
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.server.quickfix.importBundleVersionRule-com.springsource.sts.server.quickfix.manifestvalidator=false
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.server.quickfix.importLibraryVersionRule-com.springsource.sts.server.quickfix.manifestvalidator=false
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.server.quickfix.importPackageVersionRule-com.springsource.sts.server.quickfix.manifestvalidator=false
+org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.server.quickfix.requireBundleVersionRule-com.springsource.sts.server.quickfix.manifestvalidator=false
+org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.autowire.autowire-org.springframework.ide.eclipse.beans.core.beansvalidator=false
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanAlias-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanClass-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanConstructorArgument-org.springframework.ide.eclipse.beans.core.beansvalidator=true
@@ -28,6 +47,7 @@ org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.i
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.methodOverride-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.parsingProblems-org.springframework.ide.eclipse.beans.core.beansvalidator=false
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.requiredProperty-org.springframework.ide.eclipse.beans.core.beansvalidator=false
+org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.toolAnnotation-org.springframework.ide.eclipse.beans.core.beansvalidator=false
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.core.springClasspath-org.springframework.ide.eclipse.core.springvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.action-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.actionstate-org.springframework.ide.eclipse.webflow.core.validator=true
diff --git a/spring-webflow-samples/booking-faces/.springWebflow b/spring-webflow-samples/booking-faces/.springWebflow
index beaf5943..17f800fb 100755
--- a/spring-webflow-samples/booking-faces/.springWebflow
+++ b/spring-webflow-samples/booking-faces/.springWebflow
@@ -1,7 +1,7 @@
1
-
+ src/main/webapp/WEB-INF/flows/booking/booking-flow.xml
@@ -10,8 +10,7 @@
src/main/webapp/WEB-INF/flows/main/main-flow.xml
-
-
+
diff --git a/spring-webflow-samples/booking-faces/ivy.xml b/spring-webflow-samples/booking-faces/ivy.xml
index a0e44e20..b1d136bb 100755
--- a/spring-webflow-samples/booking-faces/ivy.xml
+++ b/spring-webflow-samples/booking-faces/ivy.xml
@@ -30,8 +30,10 @@
+
+
diff --git a/spring-webflow-samples/booking-faces/pom.xml b/spring-webflow-samples/booking-faces/pom.xml
index e3c469a3..5d83895e 100644
--- a/spring-webflow-samples/booking-faces/pom.xml
+++ b/spring-webflow-samples/booking-faces/pom.xml
@@ -42,6 +42,11 @@
com.springsource.org.hibernate.ejb3.3.1.ga
+
+ org.hibernate
+ com.springsource.org.hibernate.validator
+ 4.1.0.GA
+ org.hsqldbcom.springsource.org.hsqldb
@@ -52,6 +57,11 @@
joda-time1.6
+
+ org.slf4j
+ com.springsource.slf4j.log4j
+ 1.5.6
+ org.springframeworkorg.springframework.aop
diff --git a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java
index 8e7875c5..98079fb3 100755
--- a/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java
+++ b/spring-webflow-samples/booking-faces/src/main/java/org/springframework/webflow/samples/booking/Booking.java
@@ -15,6 +15,9 @@ import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
@@ -34,12 +37,17 @@ public class Booking implements Serializable {
private Hotel hotel;
+ @NotNull
private Date checkinDate;
+ @NotNull
private Date checkoutDate;
+ @Pattern(regexp = "[0-9]{16}", message = "Credit card number must be 16 digits.")
private String creditCard;
+ @NotNull
+ @Size(min = 3, message = "A valid credit card name is required.")
private String creditCardName;
private int creditCardExpiryMonth;
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webmvc-config.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webmvc-config.xml
index eb444c87..e11e8e5e 100644
--- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webmvc-config.xml
+++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/config/webmvc-config.xml
@@ -36,6 +36,9 @@
+
+
+
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/enterBookingDetails.xhtml
index 994fde42..37d77f99 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
@@ -3,11 +3,9 @@
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
- xmlns:sf="http://www.springframework.org/tags/faces"
template="/WEB-INF/layouts/standard.xhtml">
-
${booking.hotel.name}
@@ -18,12 +16,8 @@
${booking.hotel.country}
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
@@ -59,11 +51,9 @@
-
-
-
-
-
+
+
+
@@ -116,9 +106,7 @@
-
-
-
+
@@ -128,9 +116,7 @@
-
-
-
+
@@ -150,14 +136,13 @@
-
-
-
-
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/reviewBooking.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/reviewBooking.xhtml
index 1e25bbf6..996afded 100755
--- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/reviewBooking.xhtml
+++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/booking/reviewBooking.xhtml
@@ -3,7 +3,6 @@
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
- xmlns:sf="http://www.springframework.org/tags/faces"
template="/WEB-INF/layouts/standard.xhtml">
@@ -70,7 +69,7 @@