The getWriter() Error
Recently I was investigating a simple JSF web application that was deployed to weblogic 10 and got the following error
]] Root cause of ServletException.
java.lang.IllegalStateException: strict servlet API: cannot call getWriter() after getOutputStream()
at weblogic.servlet.internal.ServletResponseImpl.getWriter(ServletResponseImpl.java:309)
at com.sun.faces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:186)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:266)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:159)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
......
Since the first line says about weblogic, I thought it must be some problem caused by the weblogic. Later on, I found the error was actually caused by the isf-impl.jar library. The second line in the stack trace is
com.sun.faces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:186)
The class ViewHandlerImpl is from jsf-impl-1.2.jar (Implementation-Version: 1.2-b20-FCS). The method renderView() has the following code at line 186:
responseWriter = newWriter.cloneWithWriter(response.getWriter());
The variable "response" here is the vendor implementation specific. And here it is the weblogic ServletResponseImpl. When it calls getWriter(), error happens.
The problem here is actually not the class ServletResponseImpl. The ViewHandlerImpl should not have called resonse.getWriter().
After I changed the jar from jsf-impl-1.2.jar to jsf-impl-1.2_15.jar, the application worked fine. I checked the source code of ViewHandlerImpl.java in the new jar. This time there is no call to the response.getWriter().
A lesson from this is that the final place that gives error is not necessarily the place where the error needs to be fixed. Sometimes the error is caused by the upper stream class that does not follow the rule.
Another Stack Trace
The following is the starcktrace of another error related to the jar jsf-impl-1.2.jar. I listed it here so we can see the steps that lead to the compiling of a jsp page
Error 500--Internal Server Error
weblogic.servlet.jsp.CompilationException: Failed to compile JSP /CustomerSearch.jsp
CustomerSearch.jsp:23:128: The method setVar(String) in the type DataTableTag is not applicable for the arguments (ValueExpression)
^--------^
at weblogic.servlet.jsp.JavelinxJSPStub.reportCompilationErrorIfNeccessary(JavelinxJSPStub.java:226)
at weblogic.servlet.jsp.JavelinxJSPStub.compilePage(JavelinxJSPStub.java:162)
at weblogic.servlet.jsp.JspStub.prepareServlet(JspStub.java:246)
at weblogic.servlet.jsp.JspStub.prepareServlet(JspStub.java:191)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:235)
at weblogic.servlet.internal.ServletStubImpl.onAddToMapException(ServletStubImpl.java:408)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:318)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175)
at weblogic.servlet.internal.RequestDispatcherImpl.invokeServlet(RequestDispatcherImpl.java:502)
at weblogic.servlet.internal.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:248)
at com.sun.faces.context.ExternalContextImpl.dispatch(ExternalContextImpl.java:414)
at com.sun.faces.application.ViewHandlerImpl.executePageToBuildView(ViewHandlerImpl.java:455)
at com.sun.faces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:139)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:266)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:159)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:266)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292)
at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:26)
at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
at weblogic.servlet.internal.RequestEventsFilter.doFilter(RequestEventsFilter.java:27)
at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3592)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2202)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2108)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1432)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
We can see that the weblogic uses its own classes to compile the jsp page from the following line and the lines before it:
at weblogic.servlet.jsp.JavelinxJSPStub.compilePage
(JavelinxJSPStub.java:162)
My conclusion is the following.
- Weblogic has its own JSP compiler implementation to compile a JSP file.
- When weblogic compiles a JSP page, it refers to the tld file to generate the java code for the tags used in the JSP page. The tld files can be contained in the jsf-impl jar file. However Weblogic does not need the java class files in the jsf-impl jar file. It just needs the tld file as the specification to generate the java code for JSP.
- The jsf-impl jar file contains both the tld file and the tag class files.
- In the error case shown in the stacktrace above, the java code generated by the weblogic JSP compiler and the tld file html_basic.tld in jsf-impl-1.2.jar does not match the tag class DataTableTag.class in jsf-impl-1.2.jar. So error occurs.
More on the TLD File
According to the book "Core JavaServer Faces, 2nd edition", page 373, the JSF implementation searches for TLD files in the following locations:
- The WEB-INF directory or one of its subdirectories
- The META-INF directory or any JAR file in the WEB-INF/lib directory
In the jsp file, you use one of the following formats to declare the tag library:
<%@ tablib uri="http://java.sun.com/jsf/core" prefix="f" %>
or something like the following
<html
xmlns:f="http://java.sun.com/jsf/core"
>
You can see that it does not specify the location of the TLD file. So how does the server find the exact TLD file that matches the declaraion? The answer is to use the URI. In this case, the URI is http://java.sun.com/jsf/core. The TLD file has to define the URI attribute. The following is from the jsf-core.tld file:
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>f</short-name>
<uri>http://java.sun.com/jsf/core</uri>
<description>
The core JavaServer Faces custom actions that are independent of
any particular RenderKit.
</description>