Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.0] Fix exception handling in WebappLifecycleListener#requestDestroyed() #5567

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions impl/src/main/java/com/sun/faces/config/InitFacesContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* A special, minimal implementation of FacesContext used at application initialization time. The ExternalContext
* returned by this FacesContext only exposes the ApplicationMap.
*/
public class InitFacesContext extends NoOpFacesContext {
public class InitFacesContext extends NoOpFacesContext {

private static Logger LOGGER = FacesLogger.CONFIG.getLogger();
private static final String INIT_FACES_CONTEXT_ATTR_NAME = RIConstants.FACES_PREFIX + "InitFacesContext";
Expand All @@ -62,6 +62,8 @@ public class InitFacesContext extends NoOpFacesContext {

private ELContext elContext = new NoOpELContext();

private ExceptionHandler exceptionHandler;

public InitFacesContext(ServletContext servletContext) {
servletContextAdapter = new ServletContextAdapter(servletContext);
servletContext.setAttribute(INIT_FACES_CONTEXT_ATTR_NAME, this);
Expand Down Expand Up @@ -113,7 +115,10 @@ public Application getApplication() {

@Override
public ExceptionHandler getExceptionHandler() {
return new ExceptionHandlerImpl(false);
if (exceptionHandler == null) {
exceptionHandler = new ExceptionHandlerImpl(false);
}
return exceptionHandler;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ public void setResponseCharacterEncoding(String responseCharacterEncoding) {
public void setResponseHeader(String name, String value) {
}

@Override
public void setResponseStatus(int statusCode) {
}

@Override
public void addResponseHeader(String name, String value) {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.sun.faces.application;

import com.sun.faces.config.InitFacesContext;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.junit.JUnitFacesTestCaseBase;
import com.sun.faces.mock.MockHttpServletRequest;
import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.context.FacesContext;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable;


public class WebappLifecycleListenerTestCase extends JUnitFacesTestCaseBase {

private final static String SYNTHETIC_EXCEPTION_MESSAGE = "Synthetic exception";

@AfterEach
public void tearDown() throws Exception {
// The InitFacesContext initialized during this test tends to stay around, affecting subsequent tests. Following
// steps are done to get rid of it.

// This closes the MockFacesContext created in this test setUp method.
if (FacesContext.getCurrentInstance() != null) {
FacesContext.getCurrentInstance().release();
}

// This removes the InitFacesContext initialized in lifecycleListener.requestDestroyed(event).
if (FacesContext.getCurrentInstance() instanceof InitFacesContext) {
((InitFacesContext) FacesContext.getCurrentInstance()).removeInitContextEntryForCurrentThread();
}

// To release ServletContextFacesContextFactory, which also keeps reference to InitFacesContext instance.
FactoryFinder.releaseFactories();
}
/**
* Tests that exception handling in WebappLifecycleListener.requestDestroyed(event) works.
*/
@Test
public void testRequestDestroyedExceptionHandling() {
// Create lifecycle listener and related objects.
WebappLifecycleListener lifecycleListener = new WebappLifecycleListener(null);

WebConfiguration webConfiguration = WebConfiguration.getInstance(servletContext);
webConfiguration.setOptionEnabled(EnableDistributable, true);

// Create a request event. Make it cause an exception inside the lifecycleListener.requestDestroyed(event) call.
ServletRequestEvent event = new ServletRequestEvent(servletContext, new MockHttpServletRequest()) {
@Override
public ServletRequest getServletRequest() {
// This is just a convenient place to throw the exception for testing purposes. In the real impl the
// exception would come from a surrounding code, not from this particular method.
throw new RuntimeException(SYNTHETIC_EXCEPTION_MESSAGE);
}
};

try {
// Call the requestDestroyed() lifecycle method. It should handle the synthetic exception thrown from
// event.getServletRequest() and rethrow it as FacesException.
// Certainly, we don't expect an UnsupportedOperationException from FacesContext.getExceptionHandler()
// instead.
lifecycleListener.requestDestroyed(event);
Assertions.fail("We expect a FacesException to be thrown from lifecycleListener.requestDestroyed(event)");
} catch (FacesException e) {
// FacesException has been produced as expected. The exception message should be the original synthetic
// exception message.
Assertions.assertEquals(SYNTHETIC_EXCEPTION_MESSAGE, e.getMessage());
}
}
}