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.ejb 3.3.1.ga + + org.hibernate + com.springsource.org.hibernate.validator + 4.1.0.GA + org.hsqldb com.springsource.org.hsqldb @@ -52,6 +57,11 @@ joda-time 1.6 + + org.slf4j + com.springsource.slf4j.log4j + 1.5.6 + org.springframework org.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}
-
- -
- -
-
+ +
Book Hotel @@ -45,11 +39,9 @@

- - - - - + + +

@@ -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 @@
    - +
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/enterSearchCriteria.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/enterSearchCriteria.xhtml index 49780352..ee769b49 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/enterSearchCriteria.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/enterSearchCriteria.xhtml @@ -3,81 +3,78 @@ 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"> - -
-

Search Hotels

- - - - -
-
- Search String: - - - -
-
- Maximum Results: - - - -
-
- -
-
-
-
-
+ +

Search Hotels

+ + + + +
+
+ Search String: + +
+
+ Maximum Results: + + + +
+
+ + + +
+
+
+
- -
-

Current Hotel Bookings

- - - - - Name - #{booking.hotel.name} - - - Address - #{booking.hotel.address} - - - City, State - #{booking.hotel.city}, #{booking.hotel.state} - - - Check in date - - - - - - Check out date - - - - - - Confirmation number - #{booking.id} - - - Action - - - - -
-
+ +

Current Hotel Bookings

+ + + + + Name + #{booking.hotel.name} + + + Address + #{booking.hotel.address} + + + City, State + #{booking.hotel.city}, #{booking.hotel.state} + + + Check in date + + + + + + Check out date + + + + + + Confirmation number + #{booking.id} + + + Action + + + + + + +
\ No newline at end of file diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main-flow.xml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main-flow.xml index ff26cf39..9e84b6cf 100755 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main-flow.xml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/main-flow.xml @@ -24,20 +24,17 @@ - - - - + @@ -50,15 +47,6 @@ - - - - - - - - - diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotel.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotel.xhtml index 018d2ce3..837cf147 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotel.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotel.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"> diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotels.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotels.xhtml index 47eb4ee1..89e26986 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotels.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/flows/main/reviewHotels.xhtml @@ -3,60 +3,60 @@ 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"> - +

Hotel Results

- +

- -
- - - - - - - - - #{h.name} - - - Address - #{h.address} - - - - - - - - #{h.city}, #{h.state}, #{h.country} - - - Zip - #{h.zip} - - - Action - - - -
-
- -
-
- -
-
+
+ + + + + + + + + + #{h.name} + + + Address + #{h.address} + + + + + + + + + #{h.city}, #{h.state}, #{h.country} + + + Zip + #{h.zip} + + + Action + + + +
+ + + +   + + +
- +
diff --git a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml index d36634d8..4464eec0 100644 --- a/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml +++ b/spring-webflow-samples/booking-faces/src/main/webapp/WEB-INF/layouts/standard.xhtml @@ -7,7 +7,7 @@ xmlns:sf="http://www.springframework.org/tags/faces" contentType="text/html" encoding="UTF-8"> - + Spring Faces: Hotel Booking Sample Application @@ -18,7 +18,7 @@ - +