Expand my Community achievements bar.

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.