Did you ever want to modify an HttpServletRequest parameter?
While there are good reasons that the map of request parameters in javax.servlet.http.HttpServletRequest is unmodifieable, there are situations in which you still might want to add an additional request parameter or change the name of an existing one - i.e. to enable more REST-style passing of identifier parameters.
Anyway, with this prelude I hope I have made it clear that what I'm going to show you now might not be the best way to do things but that is then entirely up to you to decide.
I did recently stumble across such an requirement multiple times and so I thought I share the results of my thinking. When I started to think about it I was able to come up with two ways of implementing mutable param maps:
- Using javax.servlet.http.HttpServletRequestWrapper, which was added in version 2.3 of the servlet spec
- Using a java.lang.reflect.Proxy to achieve something similar
package de.woerd;
import javax.servlet.http.HttpServletRequest;
public interface MutableHttpServletRequest extends HttpServletRequest
{
public void setParameter(String name, String[] value);
}
I opted for the slightly odd string array type value, as we will in a second use a map of string array values to minimize our coding effort.
Ok, so now we need to do two things:
- we need to have some way of storing the parameter that is passed to us and
- we need to intercept all calls that retrieve anything parameter related from the request
package de.woerd;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
public class HttpServletRequestProxy implements InvocationHandler
{
private HttpServletRequest proxiedRequest;
private Map<String, String[]> mutableParams = new HashMap<String, String[]>();
private static Set<Method> interceptedMethods = new HashSet<Method>();
{
try
{
interceptedMethods.add(HttpServletRequest.class.getMethod("getParameter", new Class[]{String.class}));
interceptedMethods.add(HttpServletRequest.class.getMethod("getParameterNames", new Class[]{}));
interceptedMethods.add(HttpServletRequest.class.getMethod("getParameterValues", new Class[]{String.class}));
interceptedMethods.add(HttpServletRequest.class.getMethod("getParameterMap", new Class[]{}));
interceptedMethods.add(MutableHttpServletRequest.class.getMethod("setParameter", new Class[]{String.class, String[].class}));
}
catch (Exception e)
{
System.out.println("Couldn't build list of intercepted methods");
}
}
public HttpServletRequestProxy(HttpServletRequest request)
{
this.proxiedRequest = request;
this.mutableParams.putAll(request.getParameterMap());
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (interceptedMethods.contains(method))
{
if (method.getName().equals("setParameter"))
{
mutableParams.put((String) args[0], (String[]) args[1]);
return null;
}
else if (method.getName().equals("getParameterNames"))
{
return Collections.enumeration(mutableParams.keySet());
}
else if (method.getName().equals("getParameterMap"))
{
return Collections.unmodifiableMap(mutableParams);
}
else if (method.getName().equals("getParameter"))
{
String[] value = mutableParams.get(args[0]);
if (value != null)
return (value.length > 0 ? value[0] : null);
else
return null;
}
else if (method.getName().equals("getParameterValues"))
{
return mutableParams.get(args[0]);
}
else
throw new RuntimeException("Method " + method.getName() + " is supposed to be intercepted but no code to intercept it is present");
}
else
return method.invoke(proxiedRequest, args); // just call through to original
}
}
There is clearly room for improvement there; I'd prefer a switch of sorts instead of if/else/if/else ..., the copying of the complete params map into our own one could be handles in a lazier fashion etc. - but I think you get the basic idea.You can then use the proxy like this:
HttpServletRequest original; //comes from somwe servlet or filter
MutableHttpServletRequest wrapped = (MutableHttpServletRequest) Proxy.newProxyInstance(original.getClass().getClassLoader(), new Class[]{MutableHttpServletRequest.class},
new HttpServletRequestProxy(original));
Now the reason I used the proxy is that if you want to use the Spring framework's data binding, you might already been handed a request that isn't a plain request in your controller and when you then wrap a multipart file upload request in a subclass of javax.servlet.http.HttpServletRequestWrapper and give that to org.springframework.web.bind.ServletRequestDataBinder you're in for trouble. Now how to properly solve this with my proxy I leave to you ...
Posted at 11:14AM Jun 17, 2008 by joerg in Allgemein |