Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.
SOLVED

SlingFilter for injecting additional markup into response: IllegalStateException

Avatar

Level 4

Hi,

I'm trying to write a custom Sling Filter for injecting additional HTML markup into the response.


Original response:

<html> <head> ... </head> <body> ... </body> </html>

Response after custom filter:

<html> <head> ... <custom-tag> ... </custom-tag> </head> <body> ... </body> </html>

This is the filter I wrote using Sling Filters:

import java.io.CharArrayWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.sling.SlingFilter; import org.apache.felix.scr.annotations.sling.SlingFilterScope; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.wcm.api.Page; @SlingFilter(generateComponent = false, generateService = true, order = -50001, scope = SlingFilterScope.REQUEST) @Component(immediate = true, metatype = false) public class TealiumFilter implements Filter { private Logger logger = LoggerFactory.getLogger(TealiumFilter.class); public void init(FilterConfig filterConfig) throws ServletException { } @Activate protected void activate(final Map<String, Object> props) { logger.warn("***** TEALIUM FILTER *****"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; Resource resource = slingRequest.getResource(); if(resource != null && !resource.getPath().contains("qfusion/")) { chain.doFilter(request, response); return; } Page page = resource.adaptTo(Page.class); CharResponseWrapper responseWrapper = new CharResponseWrapper((HttpServletResponse) response); chain.doFilter(request, responseWrapper); // Get writer must be called after chain.doFilter // otherwise request for non html resources (images, css, js, etc.) // results in an IllegalStateException PrintWriter out = response.getWriter(); String servletResponse = new String(responseWrapper.toString()); if(servletResponse != null && servletResponse.length() > 0 && // Servlet response must be non empty responseWrapper.getContentType().contains("text/html") && // Servlet response must be html page != null && // The resource the request points to must resolve to a CQ Page servletResponse.contains("<head>")) { // Servlet response must contain head tag logger.warn("**** APPLYING TEALIUM FILTER ****"); CharArrayWriter caw = new CharArrayWriter(); caw.write(servletResponse.substring(0, servletResponse.indexOf("</head>")-1)); //logger.warn("before manipulation: " + caw.toString()); caw.write("<meta name=\"og:title\" content=\"og:"+ page.getTitle() +"\" />"); caw.write(servletResponse.substring(servletResponse.indexOf("</head>")-1, servletResponse.length())); //logger.warn("after manipulation: " + caw.toString()); response.setContentLength(caw.toString().length()); out.write(caw.toString()); } else { out.write(servletResponse); } out.close(); } public void destroy() { } }

The problem here is the line of code for getting the PrintWriter from the response. If I move this line of code *before* the call to chain.doFilter then I have an IllegalStateException for all the requests that do not point to an HTML resource (css, js, images, ...).

chain.doFilter(request, responseWrapper); // Get writer must be called after chain.doFilter // otherwise request for non html resources (images, css, js, etc.) // results in an IllegalStateException PrintWriter out = response.getWriter();

Why I have this exception only for non-html resources? Do you have any real example of implementation of Sling Filter that modifies the HTML response sent to the client?

Thank you in advance!

1 Accepted Solution

Avatar

Correct answer by
Employee

Hi,

This is defined in the servlet specification. If getWriter() is called after getOutputStream(), it throws an IllegalStateException. See http://docs.oracle.com/javaee/5/api/javax/servlet/ServletResponse.html#getWriter()

Regards,

Justin

View solution in original post

6 Replies

Avatar

Correct answer by
Employee

Hi,

This is defined in the servlet specification. If getWriter() is called after getOutputStream(), it throws an IllegalStateException. See http://docs.oracle.com/javaee/5/api/javax/servlet/ServletResponse.html#getWriter()

Regards,

Justin

Avatar

Level 4

Hi Justin,

thank you. The reason of the exception was clear to me but the solution for solving was not! :-)

However, in case it can help someone I found out how to solve my issue: I dont call response.getWriter() anymore for request that has content-type != text/html. For such requests, getWriter() has already been called by another servlet in the chain. This caused the IllegalStateException.

If anyone can confirm this assumption it would be really appreciated!

I attach the final code below for reference:

import java.io.CharArrayWriter; import java.io.IOException; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.sling.SlingFilter; import org.apache.felix.scr.annotations.sling.SlingFilterScope; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.wcm.api.Page; @SlingFilter(generateComponent = false, generateService = true, order = -50001, scope = SlingFilterScope.REQUEST) @Component(immediate = true, metatype = false) public class TealiumFilter implements Filter { private Logger logger = LoggerFactory.getLogger(TealiumFilter.class); public void init(FilterConfig filterConfig) throws ServletException { } @Activate protected void activate(final Map<String, Object> props) { logger.warn("***** TEALIUM FILTER *****"); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; Resource resource = slingRequest.getResource(); if((resource != null && !resource.getPath().contains("mysite/"))) { chain.doFilter(request, response); return; } Page page = resource.adaptTo(Page.class); CharResponseWrapper responseWrapper = new CharResponseWrapper((HttpServletResponse) response); chain.doFilter(request, responseWrapper); String servletResponse = new String(responseWrapper.toString()); if(servletResponse != null && servletResponse.length() > 0 && // Servlet response must be non empty responseWrapper.getContentType().contains("text/html") && // Servlet response must be html page != null && // The resource the request points to must resolve to a CQ Page servletResponse.contains("</head>")) { // Servlet response must contain head tag logger.warn("**** APPLYING TEALIUM FILTER ****"); CharArrayWriter caw = new CharArrayWriter(); caw.write(servletResponse.substring(0, servletResponse.indexOf("</head>")-1)); //logger.warn("before manipulation: " + caw.toString()); caw.write("<meta name=\"og:title\" content=\"og:"+ page.getTitle() +"\" />"); caw.write(servletResponse.substring(servletResponse.indexOf("</head>")-1, servletResponse.length())); //logger.warn("after manipulation: " + caw.toString()); response.setContentLength(caw.toString().length()); response.getWriter().write(caw.toString()); } } public void destroy() { } }

Thank you!

Avatar

Level 4

Thank you Sham. That could be another good example of modifying the response sent to the client with a Sling Filter.

However this still does not anwser to my doubt. Why do I get an IllegalStateException if a call getWriter after chain.doFilter call only for non-html resources?

Thank you!

Avatar

Employee

Hi,

That looks correct to me. Although honestly a better approach for this would be to use the Sling Rewriter.

Justin

Avatar

Level 1

Old Thread, but for some reason Sling Documentation mentions that SlingFilters can just be used to manipulate request entities, which kind of is misleading.