Friday, May 6, 2011

Extending ParametersInterceptor to get Request Header data

Sometimes, particularly when you work with OpenAM, you need to be pretty friendly with the request header. I am sure a lot of other things use it as well, but OpenAM is where I have seen it the most - once you log in, OpenAM passes user attributes back in the request header. This is very useful, however, it does present one annoyance when your web application makes use of Struts, and that is the fact that you need to extract the parameter from the request header.

This is not a big deal, just a little bit of an annoyance. Basically, you need to follow the steps here for making your action ServletRequestAware. This instructs Struts to inject the HttpServletRequest object into your action. Once you do that, you can simply call request.getHeader("headerName") to get the header object you are looking for.

That is nice and all, but being the inquisitive and annoying personality I am, I wanted something cleaner. I don't like having to deal with the HttpServletRequest object - that is one of the benefits of Struts, hiding that stuff. So I set out to make an interceptor to do it for me. As it turns out, it was quite a bit easier than I would have expected.

First off, lets define the goal. I wanted an interceptor that would read parameters from the request headers, and inject them into my action class. So, for example, if I want the user's uid attribute, I just want to have the following in my action class:
private String uid;

public void setUid(String uid)
{
    this.uid = uid;
}

public String getUid()
{
    return uid;
}

Since this functionality is very similar to what the params interceptor does for me already, I looked there first. The Struts params interceptor points to the class ParametersInterceptor, included in the Struts 2 library. This is a fairly beefy bit of code, but one thing jumped out in particular:
/**
 * Gets the parameter map to apply from wherever appropriate
 *
 * @param ac The action context
 * @return The parameter map to apply
 */
protected Map<String, Object> retrieveParameters(ActionContext ac) {
    return ac.getParameters();
}

As it turns out, the Struts authors have been very kind to us here, as the ParametersInterceptor is built to be quite easily overridable. So, to build our interceptor, the code can be quite simple, really. All you need is this:
public class RequestHeaderInterceptor extends ParametersInterceptor implements StrutsStatics {
    private static final long serialVersionUID = 1L;
 
    /**
     * Gets the parameter map to apply from wherever appropriate
     *
     * @param ac The action context
     * @return The parameter map to apply
     */
    @Override
    protected Map<String, Object> retrieveParameters(ActionContext ac) {
        Map<String, Object> params = new HashMap<String, Object>();
  
        HttpServletRequest request = (HttpServletRequest) ac.get(HTTP_REQUEST);
        Enumeration<String> names = request.getHeaderNames();
        while ( names.hasMoreElements() )
        {
            String name = (String)names.nextElement();
            String val = request.getHeader(name);
            params.put(name, val);
        }
  
        return params;
    }
}

One more minor annoyance to this is that the ParametersInterceptor, and therefore this new RequestHeaderInterceptor, needs to be in the middle of the interceptor stack. That means that you must forgo the use of the defaultStack, and roll you own instead. It would look a little something like this:
<interceptor-stack name="customDefaultStack">
    <interceptor-ref name="exception"/>
    <interceptor-ref name="alias"/>
    <interceptor-ref name="servletConfig"/>
    <interceptor-ref name="i18n"/>
    <interceptor-ref name="prepare"/>
    <interceptor-ref name="chain"/>
    <interceptor-ref name="debugging"/>
    <interceptor-ref name="scopedModelDriven"/>
    <interceptor-ref name="modelDriven"/>
    <interceptor-ref name="fileUpload"/>
    <interceptor-ref name="checkbox"/>
    <interceptor-ref name="multiselect"/>
    <interceptor-ref name="staticParams"/>
    <interceptor-ref name="actionMappingParams"/>
    <interceptor-ref name="requestHeader" />
    <interceptor-ref name="params">
      <param name="excludeParams">dojo\..*,^struts\..*</param>
    </interceptor-ref>
    <interceptor-ref name="conversionError"/>
    <interceptor-ref name="validation">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
    <interceptor-ref name="workflow">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
</interceptor-stack>

Nice and simple, and now you can get your request headers directly injected into your action.

One more optional step, if you don't want to have all of your actions injected but prefer a more manual step to request that they be injected, you can create a custom interceptor, like so:
public interface RequestHeaderParameterAware
{
}

Then you update your RequestHeaderInterceptor class, adding the following:
@Override
    public String doIntercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        if (action instanceof RequestHeaderParameterAware) {
            return super.doIntercept(invocation);
        }
        return invocation.invoke();
    }

With that little bit of extra code, you now have a system in which any actions implementing the RequestHeaderParameterAware interface will have request headers automatically injected.

No comments:

Post a Comment