How to remove Orchestra converstation context parameter for static resources?

728 Views Asked by At

To provide proper browser caching I want to get rid of the conversationContext parameter, that Apache MyFaces Orchestra adds to every request, for requests to css files.

As Bozho suggested, I've implemented a filter that sets the attribute Orchestra is looking for.

public class ResourceFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse theResponse, FilterChain theChain) throws IOException, ServletException {
    if(shouldNotAppendConversation(request)) {
        request.setAttribute(RequestParameterServletFilter.REQUEST_PARAM_FILTER_CALLED, Boolean.TRUE);
    }

    theChain.doFilter(request, theResponse);
}

private boolean shouldNotAppendConversation(ServletRequest theRequest) {
    HttpServletRequest aRequest = (HttpServletRequest) theRequest;
    String aPath = aRequest.getRequestURI();
    if(aPath.endsWith(".css.jsf")) {
        return true;
    }

    return false;
}

@Override
public void init(FilterConfig theFilterConfig) throws ServletException {
}

@Override
public void destroy() {
}
}

That doesn't work the parameter is still appended to every request. While debugging, I've found out that the filter gets first hit by a request to the jsf site. For sure I want to include the conversation context in that request, so the filter forwards the request directly to the next filter in the chain. The next request that hits the filter (usually the request for a css file) has already the conversation context included in the request.

The strange thing is, if I modify the filter to always set the attribute, all request will not have the conversation context attribute. But that means, the conversation context is also not included in the request for the jsf site (but should).

I've noticed that the links to css files in the generated html of the jsf site also contains the conversation context attribute or not depending on the filter implementation. I guess for this reason the second request has already included the conversation context parameter?

I don't understand why Orchestra is appending the conversation context parameter to every request and not just for the requests where the attribute is not set.

How can I implement the filter to work correctly?

2

There are 2 best solutions below

0
On BEST ANSWER

The next request (e.g. for a CSS file) hitting your filter after the request to your page has already the conversationContext parameter included just because this is how the url for this resource has been rendered by the page in the previous request.

So the control over conversationContext should be taken at render time. The following solution is working for me with JSF 2 (I am using Mojarra 2.1.11, myfaces-orchestra-core20 1.5, RichFaces 4.1.0.Final). A special servlet filter is doing nothing but wraps HttpServletResponse with our own wrapper:

public class RfOrchestraParamControlFilter implements Filter {
   ...
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      response = new RfOrchestraParamControlResponseWrapper((HttpServletResponse)response);
      chain.doFilter(request, response);
   }
   ...
}

The response wrapper tests the url to be encoded for being a richfaces resource and turns Orchestra's ConversationRequestParameterProvider's separation mode on in the current thread for the time of encoding:

package ...
import javax.faces.application.ResourceHandler;
import org.richfaces.resource.ResourceHandlerImpl;
import org.apache.myfaces.orchestra.conversation.ConversationRequestParameterProvider;

public class RfOrchestraParamControlResponseWrapper extends HttpServletResponseWrapper {

   public RfOrchestraParamControlResponseWrapper(HttpServletResponse httpServletResponse) {
      super(httpServletResponse);
   }

   @Override
   public String encodeURL(String url) {
      if (url.contains(ResourceHandler.RESOURCE_IDENTIFIER) || url.contains(ResourceHandlerImpl.RICHFACES_RESOURCE_IDENTIFIER)) {
         boolean current = ConversationRequestParameterProvider.isInSeparationMode();
         /* Disable conversationContext parameter in current thread for the time of rendering link to a resource */
         ConversationRequestParameterProvider.setInSeparationMode(true);

         String result = super.encodeURL(url);

         /* Restore */
         ConversationRequestParameterProvider.setInSeparationMode(current);
         return result;
      }
      else return super.encodeURL(url);
   }

}

(I've had to use String.contains() instead of String.startsWith() when testing the url for being a resource as context path and servlet path happen to prepend the passed url.)

However that does not help either by this moment. The reason is that Orchestra uses its own response wrapping that takes place in its RequestParameterFacesContextFactory, and this wrapping happens after our filter is hit. In this way Orchestra's wrapper turns out to be external to our one which results in our wrapper receiving url too late, when the url has already been intercepted and conversationContext appended.

To avoid this we have a way to make our response wrapper external to Orchestra's one by replacing effect from RequestParameterFacesContextFactory interceptor with RequestParameterServletFilter which actually does the same work. Unfortunately using another filter is not quite exquisite where we might not, but I don't see another way so far.

So, in web.xml place your filter after Orchestra's one:

<filter>
   <filter-name>requestParameterFilter</filter-name>
   <filter-class>org.apache.myfaces.orchestra.requestParameterProvider.RequestParameterServletFilter</filter-class>
</filter>
<filter>
   <filter-name>myOrchestraFilter</filter-name>
   <filter-class>mypkg.RfOrchestraParamControlFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>requestParameterFilter</filter-name>
   <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
<filter-mapping>
   <filter-name>myOrchestraFilter</filter-name>
   <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
0
On

Could not make previous solutions work. I did it this way:

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.stream.Collectors;

import javax.faces.application.ResourceHandler;
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 javax.servlet.http.HttpServletResponseWrapper;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;

/**
 * @author Felipe Riccetto
 */
public class RemoveConversationParamFilter implements Filter {

    public static String removeQueryParameter(final String url,
            final String parameterName) throws URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);
        List<NameValuePair> queryParameters = uriBuilder.getQueryParams()
                .stream().filter(p -> !p.getName().equals(parameterName))
                .collect(Collectors.toList());
        if (queryParameters.isEmpty()) {
            uriBuilder.removeQuery();
        } else {
            uriBuilder.setParameters(queryParameters);
        }
        return uriBuilder.build().toString();
    }

    @Override
    public void destroy() {
        // nothing
    }

    @Override
    public void doFilter(final ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp2 = new HttpServletResponseWrapper(
                (HttpServletResponse) resp) {
            @Override
            public String encodeURL(String url) {

                String s = super.encodeURL(url);

                try {
                    String urlPath = new URL(
                            ((url.toLowerCase().startsWith("http://")
                                    || url.toLowerCase().startsWith("https://"))
                                            ? ""
                                            : "http://fake")
                                    + url).getPath().toString().toLowerCase();
                    if (urlPath.endsWith(".js") || urlPath.endsWith(".css")
                            || urlPath.endsWith(".png")
                            || urlPath.endsWith(".jpeg")
                            || urlPath.endsWith(".jpg")
                            || urlPath.endsWith(".gif") || urlPath.contains(
                                    ResourceHandler.RESOURCE_IDENTIFIER)) {
                        s = removeQueryParameter(s, "conversationContext");
                    }

                } catch (MalformedURLException | URISyntaxException e) {
                    // ignore
                }
                return s;
            }
        };

        chain.doFilter(req, resp2);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // nothing
    }
}

web.xml:

<filter>
    <filter-name>RemoveConversationParamFilter</filter-name>
    <filter-class>RemoveConversationParamFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>RemoveConversationParamFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

This last mapping must be the last in your web.xml