PrettyFaces
Sometimes things are worth writing about.
While working on the PrettyFaces: bookmarking, and SEO extensions for JSF / JSF2, I came across a need to modify the current request parameters in order to “trick” the system into thinking that additional query parameters had been supplied.
Naively, I tried:
request.getParameterMap().put("name", new String[]{"value1", "value2"});
But that doesn’t work, because you aren’t allowed to modify request parameter values once the request has begun processing:
java.lang.IllegalStateException: Cannot find message associated with key parameterMap.locked at org.apache.catalina.util.ParameterMap.put(ParameterMap.java:213)
This means we need to wrap the current request with our own special HttpServletRequestWrapper
that will provide the ability to merge in some extra parameters up front.
The magic is quite simple:
But be careful! If you call request.getParameterMap()
or any method that would call request.getReader()
and begin reading, you will prevent any further calls to request.setCharacterEncoding(...)
, which is a big deal if you are processing i18n requests, or requests with a special content type.
You need to ensure that the new parameters are merged with the original request parameters only when the parameters are requested, so that everyone still has a chance to set the encoding. Otherwise you get nasty warnings like this:
WARNING: PWC4011: Unable to set request character encoding to UTF-8 from context /ocpsoft-pretty-faces-tests, because request parameters have already been read, or ServletRequest.getReader() has already been called
Our little HttpServletRequestWrapper:
You could simply provide a modifiable map implementation, but that would break the contract of the request parameter map.
So using a bit of lazy processing, here’s how we avoid reading the parameters until they are actually requested, while still maintaining the immutable nature of the Map.
/* * PrettyFaces is an OpenSource JSF library to create bookmarkable URLs. * Copyright (C) 2010 - Lincoln Baxter, III <lincoln@ocpsoft.com> This program * is free software: you can redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. You should have received a copy of the GNU Lesser General * Public License along with this program. If not, see the file COPYING.LESSER * or visit the GNU website at <http://www.gnu.org/licenses/>. */ import java.util.Collections; import java.util.Enumeration; import java.util.Map; import java.util.TreeMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public class PrettyFacesWrappedRequest extends HttpServletRequestWrapper { private final Map<String, String[]> modifiableParameters; private Map<String, String[]> allParameters = null; /** * Create a new request wrapper that will merge additional parameters into * the request object without prematurely reading parameters from the * original request. * * @param request * @param additionalParams */ public PrettyFacesWrappedRequest(final HttpServletRequest request, final Map<String, String[]> additionalParams) { super(request); modifiableParameters = new TreeMap<String, String[]>(); modifiableParameters.putAll(additionalParams); } @Override public String getParameter(final String name) { String[] strings = getParameterMap().get(name); if (strings != null) { return strings[0]; } return null; } @Override public Map<String, String[]> getParameterMap() { if (allParameters == null) { allParameters = new TreeMap<String, String[]>(); allParameters.putAll(super.getParameterMap()); allParameters.putAll(modifiableParameters); } //Return an unmodifiable collection because we need to uphold the interface contract. return Collections.unmodifiableMap(allParameters); } @Override public Enumeration<String> getParameterNames() { return Collections.enumeration(getParameterMap().keySet()); } @Override public String[] getParameterValues(final String name) { return getParameterMap().get(name); } }
Once you have your handy request wrapper, you can get working! Keep in mind that you’ll need to do a servlet forward if you want your entire system to gain access to your new parameters. Otherwise, you can just pass this new wrapped object around as needed.
//"request" is the current HttpServletRequest Map<String, String[]> extraParams = new TreeMap<String, String[]>() HttpServletRequest wrappedRequest = new PrettyFacesWrappedRequest(request, extraParams); request.getRequestDispatcher("url").forward(wrappedRequest, response)
Happy hacking! As a final note, I’d like to say that JEE 6 has really turned out to be a great experience. Very pleasant to program on. Well done, SUN!