Spring mvc. case insensitive get parameters mapping
Asked Answered
W

4

11

according this answer I try to write my code:

pojo:

class MyBean{

    public String getValueName() {
        return valueName;
    }

    public void setValueName(String valueName) {
        this.valueName = valueName;
    }

    String valueName;
}

inside controller:

    @ModelAttribute
    public MyBean createMyBean() {
        return new MyBean();
    }
    @RequestMapping(value = "/getMyBean", method = RequestMethod.GET)
    public String getMyBean(@ModelAttribute MyBean myBean) {
        System.out.println(myBean.getValueName());
        return "pathToJsp";
    }

web.xml configuration:

<filter>
    <filter-name>caseInsensitiveFilter</filter-name>
    <filter-class>com.terminal.interceptor.CaseInsensitiveRequestFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>caseInsensitiveFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Filter:

@Component
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 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 getParameterMap() {
            return Collections.unmodifiableMap(this.params);
        }

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

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

In debug I see that filter method invoke but I cannot achieve case insentive get parameters mapping.

for example localhost:8081/getMyBean?valueName=trololo works but localhost:8081/getMyBean?valuename=trololo - not

Westnorthwest answered 2/9, 2015 at 22:24 Comment(8)
Why do you think that, in your current configuration, the Filter should be invoked?Dielle
According following answer: https://mcmap.net/q/529708/-add-a-servlet-filter-in-a-spring-boot-applicationWestnorthwest
So you're using Spring Boot?Dielle
@Sotirios Delimanolis I am not absolutelu sure but I have dependency on spring-framework-bom in my pomWestnorthwest
@Sotirios Delimanolis topic updatedWestnorthwest
@Roxy please be attentive. I meantioned about this link in my topicWestnorthwest
In the contructor of CaseInsensitiveHttpServletRequestWrapper where you do params.putAll(request.getParameterMap()), instead of directly putting the parameter map, if you iterate over the parameter map and put individually key, value params with converting the key to lowercase or uppercase, wouldn't it work?Tamaru
Did you find a solution regarding @ModelAttribute mapping?Adder
E
5

Your problem, I believe, is @ModelAttribute. You're asking Spring to map the parameters to MyBean object, and the property inside this object is valueName.

In order Spring to reflect the params to the object, it needs to be in the correct case.

You have 2 options:

  • change the property name to valuename in your MyBean object, and all property names to lowercase, it should work with help of your solution.
  • remove @ModelAttribute and put @RequestParam for each property. If you have many props, this could be painful.
Edible answered 5/9, 2015 at 21:54 Comment(5)
both options is not nice. I want to encapsulate new functionality in separated code. It is madness to replace valueName with valuenameWestnorthwest
Well, i understand but there is not much to do, i believe.Edible
why? this doesn't look very hard. I know that there are exist renaming processors and a lot of confused stuffs inside spring mvc. Why I cannot just replace equals with equalsIgnoreCase in proper place ?Westnorthwest
The issueis, spring has its annotation prosessors. When is see @ModelAttribute annotation, it calls reflection and try to set the properties of the model. This is different than RequestParam. I looked to the annotation processor and couldnt see a way to cheat. Maybe, you should copy that annotation processor, and change it the way you want. I really understand and i agree that there should be a simpler way to do it, but unf. I cannot see.Edible
One thanks for your effort) let to wait another answers. maybe someone can suggest something more relevantWestnorthwest
S
1

Here is what you can do...

Create the domain(POJO) with all lowercase variables

public class MyBean{
    private String valuename;

    public String getValuename() {
        return valuename;
    }

    public void setValuename(String valuename) {
        this.valuename = valuename;
    }
}

Then create a class which will extend HttpServletRequestWrapper

public class CustomWrappedRequest extends HttpServletRequestWrapper
{
    private final Map<String, String[]> modifiableParameters;
    private Map<String, String[]> allParameters = null;

    public CustomWrappedRequest(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 super.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap()
    {
        if (allParameters == null)
        {
            allParameters = new TreeMap<String, String[]>();
            allParameters.putAll(super.getParameterMap());
            allParameters.putAll(modifiableParameters);
        }
        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);
    }
}

Last add a filter with appropriate web.xml config the doFilter() will look like this

public void doFilter(ServletRequest request, ServletResponse reponse, FilterChain chain)
            throws IOException, ServletException {
        Map<String, String[]> params = request.getParameterMap();
        Map<String, String[]> extraParams = new TreeMap<String, String[]>();
        Iterator<String> i = params.keySet().iterator();

        while ( i.hasNext() )
          {
            String key = (String) i.next();
            String value = ((String[]) params.get( key ))[ 0 ];
            extraParams.put(key.toLowerCase(), new String[] {value});

          }
        HttpServletRequest wrappedRequest = new CustomWrappedRequest((HttpServletRequest)request, extraParams);

        chain.doFilter(wrappedRequest, reponse);

    }

Here the filter will convet the params into lowercase and attach it to your custom made request. Then use can use @ModelAttribute in the controller code to get the desired object.

Hope it helps :)

Sapanwood answered 9/9, 2015 at 11:0 Comment(3)
valuename is awfulWestnorthwest
I know that... but if you want case insensitive mapping of request params for which spring does not provide any hooks... I believe you will have to stick with either lowercase or uppercase for your domains.Sapanwood
Spring is open source project. I believe that a lot of spring commiters visit SO. Maybe ticket will be created.Westnorthwest
B
1

Actually, you have to change the things in the CaseInsensitiveRequestFilter class as per your bean variable name. In your case the variable is valueName, so for every request it will convert it as per your variable setter method camel case injection and then match as per your request. Just try for your custom requirements:

package biz.deinum.web.filter;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

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

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<>();

        private CaseInsensitiveHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            Map<String, String[]> map = request.getParameterMap();
            Set set = map.entrySet();
            Iterator it = set.iterator();
            Map<String, String[]> tempMap = new HashMap<String, String[]>(); 
            while (it.hasNext()) {
                Map.Entry<String, String[]> entry = (Entry<String, String[]>) it.next();
                String key = entry.getKey();
                // Keep your parameter bean name here in your case it is "valueName"
                String beanParamaterName = "valueName";
                if(key.equalsIgnoreCase(beanParamaterName)){
                    tempMap.put(key.toLowerCase(), entry.getValue());
                }
            }
            params.putAll(tempMap);
        }

        @Override
        public String getParameter(String name) {
            String[] values = getParameterValues(name);
            System.out.println(values.toString()+"-");
            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) {
            System.out.println(name);
            return (String[])params.get(name);
        }
    }
}
Brody answered 11/9, 2015 at 14:22 Comment(0)
F
0

Your enum param:

enum YourEnum {
    ENUM1, ENUM2, ENUM3
}

Add this converter class:

class StringToEnumConverter implements Converter<String, YourEnum> {
    @Override
    YourEnum convert(String source) {
        return YourEnum.valueOf(source.toUpperCase());
    }
}

and this Configuration:

class ApiWebConfiguration implements WebMvcConfigurer {

    @Override
    void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToEnumConverter());
    }
...
}

voila, case-unsensitive enum parameter.

Faliscan answered 5/1, 2023 at 10:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.