We need to upgrade an elderly web application to run under GlassFish 3 instead of Tomcat in order to get EAR deployments (Glassfish was chosen as it is the reference JEE 6 implementation)
Unfortunately it very quickly turned out that the mechanism that ensures that a user is logged in does not work properly and complains that getWriter() has already been called (which is most likely correct) and I cannot figure out why.
The approach is that we have a filter on the complete set of JSP-files which checks that the user is logged in, and if not, redirects to the login page using filterChain.doFilter(servletRequest, servletResponse);
. The user state (including credentials) is stored in a so called controller object in session scope which is set from the login validation java code.
Stack trace from Glassfish:
java.lang.IllegalStateException: PWC3990: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:676)
at org.apache.catalina.connector.ResponseFacade.getOutputStream(ResponseFacade.java:205)
at org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:176)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at com.XXX.LoggedInToXXXFilter.doFilter(LoggedInToXXXFilter.java:61)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
....
web.xml snippet
<?xml version="1.0"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<description>
XXX provides a web interface for a given user.
</description>
<display-name>
XXX
</display-name>
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>/WEB-INF/online-faces-config.xml</param-value>
</context-param>
<context-param>
<param-name>org.apache.myfaces.ALLOW_JAVASCRIPT</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>
org.apache.myfaces.webapp.StartupServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>
javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<session-config>
<!-- idle time in minutes before user is automatically logged out by the container -->
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>MyFacesExtensionsFilter</filter-name>
<filter-class>
org.apache.myfaces.webapp.filter.ExtensionsFilter
</filter-class>
<init-param>
<param-name>maxFileSize</param-name>
<param-value>1m</param-value>
<!-- description>Set the size limit for uploaded files.
Format: 10 - 10 bytes
10k - 10 KB
10m - 10 MB
1g - 1 GB
</description-->
</init-param>
</filter>
<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages -->
<filter-mapping>
<filter-name>MyFacesExtensionsFilter</filter-name>
<!-- servlet-name must match the name of your javax.faces.webapp.FacesServlet entry -->
<servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.) -->
<filter-mapping>
<filter-name>MyFacesExtensionsFilter</filter-name>
<url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
</filter-mapping>
<filter>
<description>Ensure user is logged in</description>
<filter-name>LoggedInToXXXFilter</filter-name>
<filter-class>
com.XXX.servlet.filters.LoggedInToXXXFilter
</filter-class>
<init-param>
<param-name>signon_page</param-name>
<param-value>/login.jsf</param-value>
</init-param>
<init-param>
<param-name>autologout_page</param-name>
<param-value>/autologout.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoggedInToXXXFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- filter>
<filter-name>extensionsFilter</filter-name>
<filter-class>org.apache.myfaces.component.html.util.ExtensionsFilter</filter-class>
<init-param>
<param-name>uploadMaxFileSize</param-name>
<param-value>100m</param-value>
</init-param>
<init-param>
<param-name>uploadThresholdSize</param-name>
<param-value>100k</param-value>
</init-param>
</filter-->
<!-- filter-mapping>
<filter-name>extensionsFilter</filter-name>
<url-pattern>*.jsf</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>extensionsFilter</filter-name>
<url-pattern>/faces/*</url-pattern>
</filter-mapping-->
<!-- error-page>
<exception-type>java.lang.IllegalArgumentException</exception-type>
<location>/WEB-INF/jsp/IllegalArgumentException.jsp</location>
</error-page-->
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<location>/WEB-INF/jsp/RuntimeException.jsp</location>
</error-page>
<!-- error-page>
<exception-type>com.transaxiom.axsWHSweb.struts.action.UserIsNotLoggedInException</exception-type>
<location>/WEB-INF/jsp/UserIsNotLoggedInException.jsp</location>
</error-page-->
<error-page>
<exception-type>
com.XXX.struts.action.SecurityViolationException
</exception-type>
<location>/WEB-INF/jsp/SecurityViolationException.jsp</location>
</error-page>
<error-page>
<exception-type>
com.XXX.logic.UncheckedCommunicationException
</exception-type>
<location>/WEB-INF/jsp/CommunicationException.jsp</location>
</error-page>
<error-page>
<exception-type>
com.XXX.logic.ConnectionNotCreatedException
</exception-type>
<location>
/WEB-INF/jsp/ConnectionNotCreatedException.jsp
</location>
</error-page>
<!-- error-page>
<exception-type>com.XXX.logic.UncheckedConnectionNotCreatedException</exception-type>
<location>/WEB-INF/jsp/ConnectionNotCreatedException.jsp</location>
</error-page-->
<!-- filter>
<filter-name>MyFacesExtensionsFilter</filter-name>
<filter-class>org.apache.myfaces.component.html.util.ExtensionsFilter</filter-class>
<init-param>
<param-name>maxFileSize</param-name>
<param-value>20m</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>MyFacesExtensionsFilter</filter-name>
<url-pattern>*.faces</url-pattern>
</filter-mapping-->
</web-app>
Filter code from LoggedInToXXXFilter.java:
(The stacktrace happens in the filterChain.doFilter(servletRequest, servletResponse)
line.
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
boolean ok = false;
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String servletPath = request.getServletPath();
if ((servletPath.equals(signOnPage) == true) || servletPath.endsWith(".css") || servletPath.equals(autologoutPage)) {
ok = true;
} else {
Controller controller = Controller.getControllerFromSession(request.getSession(false));
if ((controller != null) && controller.isSignedOn()) {
ok = true;
}
}
if (ok) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
// Hop to the sign on page.
// http://forum.java.sun.com/thread.jspa?threadID=548967&messageID=2676856
ServletContext servletContext = filterConfig.getServletContext();
URL url = new URL(new URL(request.getRequestURL().toString()), (request.getContextPath() + signOnPage));
((HttpServletResponse) servletResponse).sendRedirect(url.toString());
}
} else {
// Only for http requests
filterChain.doFilter(servletRequest, servletResponse);
}
}
Could a possible reason be that we still bring our own JSF libraries (MyFaces 1.1.4 with Tomahawk)?
EDIT: Updated question with complete (but anonymized) web.xml. Note there is a lot of commented out stuff. I left it in as not to accidentally delete important information
EDIT: Experimented with the sun-web-app configuration file, and found it didn't make a difference. What is interesting is that after logging in, the login page throws the exception but I can manually navigate to the main page (also JSF) and see two more pages with functionality fine. There are three pages in addition to the login page which throws the exception.
My initial thought was that the separating feature would be the t-taglib (for Tomahawk) but that after a quick investigation does not seem to be the case as some of the working pages use Tomahawk and some doesn't.
EDIT: Comparing two jsp-pages, one which failed, another one which didn't did not reveal any obvious difference which should cause this. As it was pointed out that there has been reported this very bug with Tomahawk 1.1 and we were using 1.1.3, I have now upgraded to the latest Apache Myfaces Tomahawk 1.1.9, which appears to have resolved the issue (with no sun-web-app at all).
I don't have a full explanation (i.e. I don't know where getWriter
gets called) but this might be a bug in Tomahawk 1.1.3 / MyFaces 1.1.4 as reported in Jira issues like TOMAHAWK-579 or MYFACES-1310 (with the same IllegalStateException
as per Servlet specification). Note that this bug seems to be container dependent, as you are experiencing.
So, either try with more recent versions of Tomahawk / MyFaces (see the compatibility matrix) or get the patch corresponding to the fix in r442340 and apply it to the branch 1.1.3 of Tomahawk. The later option is maybe the easiest one. At least, this is what I would try.
This can have two causes:
Filter
in the chain before ExtensionsFilter
which is (indirectly) calling getWriter()
.In this particular case, it look like that both sendRedirect()
and doFilter()
has been called in the same request-response chain (because sendRedirect()
may implicitly call getWriter()
). When a Filter
calls sendRedirect()
, it should not be doing doFilter()
afterwards. The posted code doesn't prove this, but maybe there are some lines been removed from it for sanitization, or there's another filter before in the chain which does exactly that.
Update: after thinking once more about this and looking in the ExtensionsFilter
's source, the ExtensionsFilter
actually obtains the OutputStream
after filtering the request/response. So, the page, servlet or any other Javacode which has been called/executed by the URL in question has (implicitly) called the getWriter()
.
Update 2: Glassfish v3 ships by default with Sun Mojarra JSF 2.0 reference implementation. It might have collided somehow with the MyFaces 1.x implementation shipped in the webproject. You can instruct Glassfish v3 that you prefer to use MyFaces by setting useMyFaces
or (the newer) useBundledJsf
property to true
in /WEB-INF/sun-web.xml
. Have you used it? Give it a try.
<sun-web-app>
<class-loader delegate="false"/>
<property name="useBundledJsf" value="true"/>
</sun-web-app>
Also see Alternative JSF implementations on GlassFish - MyFaces and Tomahawk.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With