Making a request parameter binding case insensitive
Asked Answered
S

3

12

I have a requirement where I have to make requestParams to bind properly even if the cases of the param name changes. Note:I am using spring 3.2

For eg: http://localhost:8080/sample/home?**userName**=xxx or http://localhost:8080/sample/home?username=xxx or http://localhost:8080/sample/home?usernaMe=xxx should map properly to my @RequestParam value.

@RequestMapping(value = "home", method = RequestMethod.GET)
public goToHome(@RequestParam(value = "userName", required = false) String userName) {

}

All the three urls should call the above method and bind the user name properly. Please give me suggestions on how to implement this by implementing new argument handler resolver? Overriding spring config classes to implement generically is preferred over changing the logic in the code for all @RequestParam.

Stavropol answered 9/4, 2015 at 6:56 Comment(1)
Why? Whoever wrote this requirement probably thinks it's trivial, but it isn't.Fictive
V
14

Spring has a LinkedCaseInsensitiveMap Which you could use to do case insensitive lookups.

An implementation could look like the following.

package biz.deinum.web.filter;

import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;

/**
 * Wrapper for an {@link HttpServletRequest} to make the lookup of parameters case insensitive. The functionality
 * is achieved by using the {@link LinkedCaseInsensitiveMap} from Spring.
 * 
 * @author Marten Deinum
 */
public class CaseInsensitiveRequestFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(new CaseInsensitiveHttpServletRequestWrapper(request), response);
    }

    private static class CaseInsensitiveHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final LinkedCaseInsensitiveMap<String[]> params = new LinkedCaseInsensitiveMap<>();

        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request
         * @throws IllegalArgumentException if the request is null
         */
        private CaseInsensitiveHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            params.putAll(request.getParameterMap());
        }

        @Override
        public String getParameter(String name) {
            String[] values = getParameterValues(name);
            if (values == null || values.length == 0) {
                return null;
            }
            return values[0];
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return Collections.unmodifiableMap(this.params);
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(this.params.keySet());
        }

        @Override
        public String[] getParameterValues(String name) {
            return (String[])params.get(name);
        }
    }
}
Visitant answered 9/4, 2015 at 8:25 Comment(7)
@M. Deinum should this filter register somewhere?Vickers
getParameterValues has compile error. params.get(name) returns ObjectVickers
Please check this: #32364626Vickers
@Vickers it was made with java8 compiles fine here, for some reason the stack overflow code committed the <String[]> on the definition of the instance variable.Visitant
@M. Deinum I use java 8 and I encounered with this compile errorVickers
@Vickers you can register it in servlet initializer by overwriting the method 'getServletFilters'. See my answer for example.Auriculate
Perfect, my project only worked after annotating with @Component.Would this filter have any notable performance loss?Change
H
1

You could write a servlet filter that does this. But it does need some coding work.

Here is the link to the code - http://www.acooke.org/cute/Forcinglow0.html

Something like this - in this servlet filter convert parameters to lower case

public final class LowerCaseParametersFilter implements Filter {
 @Override
public void doFilter(final ServletRequest request,
                     final ServletResponse response,
                     final FilterChain chain)
        throws IOException, ServletException {
    if (request instanceof HttpServletRequest) {
        LOG.debug("Wrapping request");
        chain.doFilter(new LowerCaseRequest((HttpServletRequest) request),
                       response);
    } else {
        LOG.warn(format("Not wrapping request: %s", request.getClass()));
        chain.doFilter(request, response);
    }
}
}

Here is the xml config - u wuld need

 <bean id="delegatingFilter"
      class="org.springframework.web.filter.DelegatingFilterProxy"

      p:targetBeanName="lowerParams"/>
 <bean id="lowerParams"   
      class="com.isti.bss.mvc.LowerCaseParametersFilter"/>

I did some research and found this Case-insensitive query string request paramters

   public class HttpCustomParamFilter implements Filter
  {

   private static class HttpServletRequestCustomeWrapper extends HttpServletRequestWrapper
   {
       private String[] parameterValues;

    @Override
    public String[] getParameterValues(String name)
    {
        Map<String, String[]> localParameterMap = super.getParameterMap();

        // Handle case insensitivity of http request paramters like start, count, query, sort, filter etc.
        if (localParameterMap != null && !localParameterMap.isEmpty())
        {
            parameterValues = new String[localParameterMap.size()];
            for (String key : localParameterMap.keySet())
            {
                if (name.equalsIgnoreCase(key))
                    parameterValues = localParameterMap.get(key);
                else
                    parameterValues = null;
            }
        }
        return parameterValues;
    }
Histogen answered 9/4, 2015 at 7:8 Comment(9)
Then all request will be converted to lower case while passing through the filter.So I have to change my controller @requestParam(value="username").But what i need is i want binding to happen without changing my controller logic.I need binding to happen what ever case request is coming.FYI, Binding is happening in requestparam method argument resolver.I want to customize this to bind ignoring case.Stavropol
how many parameters are u thinking about? is it only the username parameter?Histogen
I want binding to happen for all the controller method ignoring cases.I just posted one sample method.Stavropol
try the solution i posted - i think it would work bcos spring calls get parameter names, but it does need quite a bit of overriding - Also found a reference to this - forum.spring.io/forum/spring-projects/web/…Histogen
Instead of converting to lowercase add the parameters to a LinkedCaseInsensitiveMap which would work in your case. You would need to override all getParameter methods (4 of them in total).Visitant
The implementation of HttpServletRequestCustomeWrapper is a little flawed. You shouldn't store the parameterValues as an instance variable. Theoretically that can result in weird behavior.Visitant
@paulJohn : I think filter in acooke.org/cute/Forcinglow0.html and using LinkedCaseInsensitiveMap will work.I will try and see.Stavropol
@RenganathanV great. If it works would appreciate if my answer is marked correct.Histogen
see this response: https://mcmap.net/q/1009626/-spring-mvc-case-insensitive-get-parameters-mappingHards
A
0

Solution with implementing custom Filter.

Filter:

public final class CaseInsensitiveParametersFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            chain.doFilter(new CustomHttpServletRequestWrapper((HttpServletRequest) request), response);
        } else {
            chain.doFilter(request, response);
        }

    }

    private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private Map<String, String[]> lowerCaseParams = new HashMap<>();

        public CustomHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            Map<String, String[]> originalParams = request.getParameterMap();
            for (Map.Entry<String, String[]> entry : originalParams.entrySet()) {
                lowerCaseParams.put(entry.getKey().toLowerCase(), entry.getValue());
            }
        }

        @Override
        public String getParameter(String name) {
            String[] values = getParameterValues(name);
            if (values != null && values.length > 0) {
                return values[0];
            } else {
                return null;
            }
        }

        @Override
        public Map getParameterMap() {
            return Collections.unmodifiableMap(lowerCaseParams);
        }

        @Override
        public Enumeration getParameterNames() {
            return Collections.enumeration(lowerCaseParams.keySet());
        }

        @Override
        public String[] getParameterValues(String name) {
            return lowerCaseParams.get(name);
        }

    }

Servlet initializer:

public class RestServiceInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new CaseInsensitiveParametersFilter()};
    }
}
Auriculate answered 3/4, 2019 at 17:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.