diff --git a/build.gradle b/build.gradle index 06d8a024..ee311eda 100644 --- a/build.gradle +++ b/build.gradle @@ -208,8 +208,8 @@ project("spring-faces") { provided("javax.el:el-api:1.0") provided("javax.servlet:javax.servlet-api:3.0.1") provided("javax.portlet:portlet-api:2.0") - provided("com.sun.faces:jsf-api:2.1.7") - provided("com.sun.faces:jsf-impl:2.1.7") + provided("com.sun.faces:jsf-api:2.2.5") + provided("com.sun.faces:jsf-impl:2.2.5") provided("org.apache.myfaces.core:myfaces-impl:2.1.7") optional("com.sun.facelets:jsf-facelets:1.1.14") optional("org.richfaces.framework:richfaces-api:3.1.4.GA") diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java index 8932832f..c24d38de 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContextLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2010 the original author or authors. + * Copyright 2004-2014 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. @@ -22,18 +22,35 @@ import org.springframework.webflow.execution.FlowExecutionListenerAdapter; import org.springframework.webflow.execution.RequestContext; /** - * A {@link FlowExecutionListener} that creates a {@link FlowFacesContext} instance when a flow request is submitted and - * releases it when the request has been processed. + * A {@link FlowExecutionListener} that creates a {@link FlowFacesContext} + * instance when a flow request is submitted and releases it when the request + * has been processed. * * @author Rossen Stoyanchev */ public class FlowFacesContextLifecycleListener extends FlowExecutionListenerAdapter { + public static final String DEFAULT_FACES_CONTEXT = + FlowFacesContextLifecycleListener.class.getName() + ".DEFAULT_FACES_CONTEXT"; + /** * Creates a new instance of {@link FlowFacesContext} that is then available for the duration of the request. * @param context the current flow request context */ public void requestSubmitted(RequestContext context) { + + FacesContext facesContext = getRequestFacesContext(context); + if (facesContext != null) { + // FacesContext already created, just wrap it (sets "current" instance internally) + if (JsfRuntimeInformation.isAtLeastJsf20()) { + new Jsf2FlowFacesContext(context, facesContext); + } + else { + new FlowFacesContext(context, facesContext); + } + return; + } + FlowFacesContext.newInstance(context, FlowLifecycle.newInstance()); } @@ -42,7 +59,15 @@ public class FlowFacesContextLifecycleListener extends FlowExecutionListenerAdap * @param context the source of the event */ public void requestProcessed(RequestContext context) { + + if (getRequestFacesContext(context) != null) { + return; + } + FacesContext.getCurrentInstance().release(); } + private FacesContext getRequestFacesContext(RequestContext context) { + return (FacesContext) context.getExternalContext().getRequestMap().get(DEFAULT_FACES_CONTEXT); + } } 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 eee23d44..7c0d89bb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2010 the original author or authors. + * Copyright 2004-2014 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. @@ -31,6 +31,7 @@ import javax.faces.context.Flash; import javax.faces.context.PartialViewContext; import javax.faces.context.PartialViewContextFactory; import javax.faces.event.PhaseId; +import javax.faces.lifecycle.ClientWindow; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -265,5 +266,23 @@ public class Jsf2FlowFacesContext extends FlowFacesContext { public void setSessionMaxInactiveInterval(int interval) { delegate.setSessionMaxInactiveInterval(interval); } + + // --------------- JSF 2.2 Pass-through delegate methods ------------------// + + public String getApplicationContextPath() { + return delegate.getApplicationContextPath(); + } + + public String getSessionId(boolean create) { + return delegate.getSessionId(create); + } + + public ClientWindow getClientWindow() { + return delegate.getClientWindow(); + } + + public void setClientWindow(ClientWindow window) { + delegate.setClientWindow(window); + } } } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowResourceResolver.java b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowResourceResolver.java index c3343ce4..b0f54970 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowResourceResolver.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2010 the original author or authors. + * Copyright 2004-2014 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. @@ -19,6 +19,8 @@ import java.io.IOException; import java.net.URL; import javax.faces.FacesException; +import javax.faces.application.ResourceHandler; +import javax.faces.context.FacesContext; import javax.faces.view.facelets.ResourceResolver; import org.springframework.context.ApplicationContext; @@ -29,8 +31,8 @@ import org.springframework.webflow.execution.RequestContextHolder; import com.sun.faces.facelets.impl.DefaultResourceResolver; /** - * Resolves Facelets templates using Spring Resource paths such as "classpath:foo.xhtml". Configure it via a context - * parameter in web.xml: + * Resolves Facelets templates using Spring Resource paths such as "classpath:foo.xhtml". + * Configure it via a context parameter in web.xml: * *
  * <context-param/> 
@@ -41,11 +43,12 @@ import com.sun.faces.facelets.impl.DefaultResourceResolver;
  */
 public class Jsf2FlowResourceResolver extends ResourceResolver {
 
-	ResourceResolver delegateResolver = new DefaultResourceResolver();
+	ResourceResolver delegateResolver;
 
 	public URL resolveUrl(String path) {
 
 		if (!JsfUtils.isFlowRequest()) {
+			initDelegateIfNecessary();
 			return delegateResolver.resolveUrl(path);
 		}
 
@@ -61,6 +64,7 @@ public class Jsf2FlowResourceResolver extends ResourceResolver {
 			if (viewResource.exists()) {
 				return viewResource.getURL();
 			} else {
+				initDelegateIfNecessary();
 				return delegateResolver.resolveUrl(path);
 			}
 		} catch (IOException ex) {
@@ -68,4 +72,12 @@ public class Jsf2FlowResourceResolver extends ResourceResolver {
 		}
 	}
 
+	private void initDelegateIfNecessary() {
+		if (this.delegateResolver == null) {
+			FacesContext facesContext = FacesContext.getCurrentInstance();
+			ResourceHandler resourceHandler = facesContext.getApplication().getResourceHandler();
+			this.delegateResolver = new DefaultResourceResolver(resourceHandler);
+		}
+	}
+
 }
diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfFlowHandlerAdapter.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfFlowHandlerAdapter.java
index 18e368d7..f33ae6ce 100644
--- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfFlowHandlerAdapter.java
+++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfFlowHandlerAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2004-2010 the original author or authors.
+ * Copyright 2004-2014 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.
@@ -15,6 +15,7 @@
  */
 package org.springframework.faces.webflow;
 
+import javax.faces.context.FacesContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -32,9 +33,11 @@ import org.springframework.webflow.mvc.servlet.FlowHandlerAdapter;
 public class JsfFlowHandlerAdapter extends FlowHandlerAdapter {
 
 	public void afterPropertiesSet() throws Exception {
-		boolean initializeAjaxHandler = getAjaxHandler() == null;
+
+		boolean isAjaxHandlerConfigured = (getAjaxHandler() != null);
 		super.afterPropertiesSet();
-		if (initializeAjaxHandler) {
+
+		if (!isAjaxHandlerConfigured) {
 			if (JsfRuntimeInformation.isAtLeastJsf20()) {
 				JsfAjaxHandler ajaxHandler = new JsfAjaxHandler();
 				ajaxHandler.setApplicationContext(getApplicationContext());
@@ -45,7 +48,17 @@ public class JsfFlowHandlerAdapter extends FlowHandlerAdapter {
 
 	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
 			throws Exception {
-		return super.handle(request, response, handler);
+
+		FacesContextHelper helper = new FacesContextHelper();
+		try {
+			FacesContext facesContext = helper.getFacesContext(getServletContext(), request, response);
+			request.setAttribute(FlowFacesContextLifecycleListener.DEFAULT_FACES_CONTEXT, facesContext);
+			return super.handle(request, response, handler);
+
+		} finally {
+			request.removeAttribute(FlowFacesContextLifecycleListener.DEFAULT_FACES_CONTEXT);
+			helper.releaseIfNecessary();
+		}
 	}
 
 }
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
index e2ea1d5d..d7ab0db2 100644
--- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java
+++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfAjaxHandlerTests.java
@@ -23,9 +23,7 @@ public class JsfAjaxHandlerTests extends TestCase {
 
 	public void testSendAjaxRedirect() throws Exception {
 		ajaxHandler.sendAjaxRedirectInternal("/target", jsfMock.request(), jsfMock.response(), false);
-		assertEquals(
-				"\n",
-				jsfMock.contentAsString());
+		assertTrue(this.jsfMock.contentAsString().matches(""));
 		assertEquals("application/xml", jsfMock.response().getContentType());
 	}