Added a Servlet Filter to ensure that all exits out of JSF end with cleanup (SWF-306)

This commit is contained in:
Ben Hale
2007-06-12 20:17:59 +00:00
parent 33c34b61cb
commit f5d9813d78
4 changed files with 187 additions and 0 deletions

View File

@@ -23,6 +23,18 @@
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<!-- Guarantees that under all JSF exit conditions, the flow context will be cleaned up -->
<filter>
<filter-name>Flow System Cleanup Filter</filter-name>
<filter-class>org.springframework.webflow.executor.jsf.FlowSystemCleanupFilter</filter-class>
</filter>
<!-- Filters all request to *.faces to the Flow System Cleanup Filter for guarenteed cleanup -->
<filter-mapping>
<filter-name>Flow System Cleanup Filter</filter-name>
<url-pattern>*.faces</url-pattern>
</filter-mapping>
<!-- The front controller for the JSF application, responsible for handling all application requests -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>

View File

@@ -34,6 +34,8 @@ Package org.springframework.webflow.executor
be matched against the state of the current flow execution (SWF-303).
* JSF integration code now tears down ExternalContext thread local properly in exceptional situations and when
the RENDER_RESPONSE phase is bypassed (SWF-305).
* JSF can now use a FlowSystemCleanupFilter to ensure that no matter what happens in JSF, the request context
is cleaned up properly (SWF-306).
Changes in version 1.0.3 (19.04.2007)
-------------------------------------

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2004-2007 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.webflow.executor.jsf;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.webflow.context.ExternalContextHolder;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
/**
* A servlet filter used to guarantee that webflow context information is
* cleaned up in a JSF environment.
*
* @author Ben Hale
* @since 1.1
*/
public class FlowSystemCleanupFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
chain.doFilter(request, response);
} finally {
cleanupCurrentFlowExecution(request);
ExternalContextHolder.setExternalContext(null);
}
}
/**
* Cleans up the current flow execution in the request context if necessary.
* Specifically, handles unlocking the execution if necessary, setting the
* holder to null, and cleaning up the flow execution context thread local.
* Can be safely called even if no execution is bound or one is bound but
* not locked.
* @param request the servlet request
*/
private void cleanupCurrentFlowExecution(ServletRequest request) {
if (isFlowExecutionRestored(request)) {
FlowExecutionContextHolder.setFlowExecutionContext(null);
getFlowExecutionHolder(request).unlockFlowExecutionIfNecessary();
request.removeAttribute(getFlowExecutionHolderKey());
}
}
/**
* Returns true if the flow execution has been restored in the current
* thread.
* @param request the servlet request
* @return true if restored, false otherwise
*/
private boolean isFlowExecutionRestored(ServletRequest request) {
return getFlowExecutionHolder(request) != null;
}
/**
* Returns the current flow execution holder for the given servlet request.
* @param request the servlet request
* @return the flow execution holder, or <code>null</code> if none set.
*/
private FlowExecutionHolder getFlowExecutionHolder(ServletRequest request) {
return (FlowExecutionHolder) request.getAttribute(getFlowExecutionHolderKey());
}
private static String getFlowExecutionHolderKey() {
return FlowExecutionHolder.class.getName();
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.webflow.executor.jsf;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import junit.framework.TestCase;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.webflow.context.ExternalContextHolder;
import org.springframework.webflow.engine.impl.FlowExecutionImpl;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.test.MockExternalContext;
public class FlowSystemCleanupFilterTests extends TestCase {
private FlowSystemCleanupFilter filter;
private ServletRequest request;
private ServletResponse response;
private FilterChain chain;
protected void setUp() throws Exception {
filter = new FlowSystemCleanupFilter();
request = new MockHttpServletRequest();
request.setAttribute(getFlowExecutionHolderKey(), new FlowExecutionHolder(new FlowExecutionImpl()));
response = new MockHttpServletResponse();
chain = new MockFilterChain();
FlowExecutionContextHolder.setFlowExecutionContext(new FlowExecutionImpl());
ExternalContextHolder.setExternalContext(new MockExternalContext());
}
public void testCleanup() throws ServletException, IOException {
filter.doFilter(request, response, chain);
assertNull("Should have cleaned up the flow execution", request.getAttribute(getFlowExecutionHolderKey()));
try {
FlowExecutionContextHolder.getFlowExecutionContext();
fail("Should have an empty holder");
} catch (IllegalStateException e) {
}
try {
ExternalContextHolder.getExternalContext();
fail("Should have an empty holder");
} catch (IllegalStateException e) {
}
}
public void testExceptionThrown() throws ServletException, IOException {
try {
filter.doFilter(request, response, new ExceptionThrowingMockFilterChain());
} catch (RuntimeException e) {
assertNull("Should have cleaned up the flow execution", request.getAttribute(getFlowExecutionHolderKey()));
try {
FlowExecutionContextHolder.getFlowExecutionContext();
fail("Should have an empty holder");
} catch (IllegalStateException e1) {
}
try {
ExternalContextHolder.getExternalContext();
fail("Should have an empty holder");
} catch (IllegalStateException e1) {
}
}
}
private static String getFlowExecutionHolderKey() {
return FlowExecutionHolder.class.getName();
}
private class ExceptionThrowingMockFilterChain extends MockFilterChain {
public void doFilter(ServletRequest request, ServletResponse response) {
throw new RuntimeException();
}
}
}