How to prevent parameter binding from interpreting commas in Spring 3.0.5?
Asked Answered
V

7

64

Consider the following controller method:

@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(@RequestParam(value = "fq", required = false) String[] filterQuery) {
    logger.debug(fq = " + StringUtils.join(filterQuery, "|"));
}

Here is the output for different fq combinations:

  1. /test?fq=foo results in fq = foo
  2. /test?fq=foo&fq=bar results in fq = foo|bar
  3. /test?fq=foo,bar results in fq = foo|bar
  4. /test?fq=foo,bar&fq=bash results in fq = foo,bar|bash
  5. /test?fq=foo,bar&fq= results in fq = foo,bar|

Example 3 is the problem. I expect (want/need) it to output fq = foo,bar.

I've tried escaping the comma with \ and using %3C but niether work.

If I look at the HttpServletRequest object's version:

String[] fqs = request.getParameterValues("fq");
logger.debug(fqs = " + StringUtils.join(fqs, "|"));

It prints the expected output: fqs = foo,bar. So the "problem" is with the Spring data binding.

I could by-pass Spring's binding and use HttpServletRequest but I really don't want to as I'm using a backing bean in my real code (same thing is happening) and don't wish to re-implement the binding functionality. I'm hoping someone can provide a simple way of preventing this behavior via escaping or some other mechanism.

TIA

UPDATE: I posted this Q on Twitter and got a reply saying the expected output appears with Spring 3.0.4.RELEASE. I've now confirmed this is the case and thus is a temporary fix. I'll go ahead and log this as a bug on the Spring JIRA system. If anyone can provide a work around or fix with 3.0.5, I'll accept their answer.

Valiancy answered 15/2, 2011 at 0:3 Comment(3)
Logged as bug: jira.springsource.org/browse/SPR-7963Valiancy
I suggest you add your resolution as an answer to your own question to make it clearer to others that you have found a solution.Musty
Thanks for the suggestion Phillip. It turns out that the fix of using 3.0.4 works for @RequestMapping but does NOT fix the same issue when binding to a form-backing bean. So I've not got a fix for my application yet. I've still not got a comment/update on the Spring Jira issue yet - which is a bit slack of them I think.Valiancy
D
35

I've tested your code: it's unbelievable, but I can't reproduce your issue. I've downloaded the latest version of spring (3.0.5), this is my controller:

package test;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/test/**")
public class MyController {

    private static final Logger logger = Logger.getLogger(MyController.class);

    @RequestMapping(value = "/test/params", method = RequestMethod.GET)
    public void test(SearchRequestParams requestParams, BindingResult result) {
    logger.debug("fq = " + StringUtils.join(requestParams.getFq(), "|"));
    }
}

this is my SearchRequestParams class:

package test;

public class SearchRequestParams {
    private String[] fq;

    public String[] getFq() {
    return fq;
    }

    public void setFq(String[] fq) {
    this.fq = fq;
    }
}

and this is my simple spring configuration:

<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

<bean class="test.MyController" />

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>

I've tested my code within tomcat 7.0.8; when I type http://localhost:8080/testweb/test/params.htm?fq=foo,bar I'm able to read in my log file this line: DEBUG fq = foo,bar. What are the the differences from my code to yours? Am I doing something wrong? I'd like to help you, so if you have any doubts or if I can do some other tests for you, it will be a pleasure.

UPDATE / SOLUTION
With your code I've reproduced the issue; you have the tag <mvc:annotation-driven /> in your dispatcher servlet configuration, so you silently use a default conversion service, instance of FormattingConversionService, which contains a default converter from String to String[] that uses comma as separator. You have to use a different conversion service bean containing your own converter from String to String[]. You should use a different separator, I've choosed to use ";" because it's the separator commonly used into query string ("?first=1;second=2;third=3"):

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

public class CustomStringToArrayConverter implements Converter<String, String[]>{
   @Override
    public String[] convert(String source) {
        return StringUtils.delimitedListToStringArray(source, ";");
    }
}

Then you have to specify this conversion service bean in your configuration:

<mvc:annotation-driven conversion-service="conversionService" />

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="au.org.ala.testspringbinding.CustomStringToArrayConverter" />
        </list>
    </property>
</bean>

The issue has fixed, now you should check for any side effects. I hope you don't need in your application the original conversion from String to String[] (with comma as separator). ;-)

Dumuzi answered 8/3, 2011 at 23:57 Comment(5)
Thanks for that. The code you pasted is basically what I'm using. I've still got the controller method in my webapp and I just tried it again and still see the "foo|bar" in my version. I'm now wondering if I'm getting one of those strange maven dependency problems as I have a dependency on a project that uses an older version of Spring. I'll make a brand new "bare" Spring project and try it out and report back.Valiancy
I just created a new Maven webapp (JEE 6) with Spring MVC 3.0.5 support (using Netbeans). I modified the pom a bit and created two classes as per this answer. Ran the webapp in both Jetty and tomcat (6.0.20) and I still get the output fq = foo|bar (I have the output going to the browser now). I've put the code up on Google Code here: svn checkout ala-hubs.googlecode.com/svn/trunk/testSpringBinding - would love to get to the bottom of this...Valiancy
Well, I'm glad to help you, let me see your code. I will post some news in a few hours.Dumuzi
Awesome research and work - this answer didn't fit my project exactly but I do have to say the question and answer are great.Pater
What's the Java code equivalent to the XML you've posted?Annapolis
B
34

I have found the most elegant and the shortest way for me - add @InitBinder to a @Controller:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor(null));
}

It will convert String to String[] without using separator (null param), with Spring class org.springframework.beans.propertyeditors.StringArrayPropertyEditor. If someone in same project will use new default conversion way, it will be ok.

Brooking answered 4/12, 2013 at 12:26 Comment(5)
Easy implementation, perfect solution to my code - thanks!Pater
Works in Spring 4 as well. However, if declaring params as List<String> rather than String[], it won't work, even when using List as the class. I didn't have the time to dig into Spring internals to find out why and just changed to using String[] for my parameters rather than List<String>.Slip
It's not clear from the JavaDoc, but using null as separator param doesn't seem to be supported. It's better to use a actual character other than a comma.Cathexis
This workaround also worked for me with @RestController in spring boot version 1.5.19Parkway
Best answer for me. @beaudet, it is StringArrayPropertyEditor, so naturally it won't work for lists. Try binder.registerCustomEditor(List.class, new CustomCollectionEditor(List.class));Touchy
B
8

rougou's comment was the only solution that worked for me because I was using List<String> instead of String[].

Add this to your controller to eliminate parsing comma-deliminated values:

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(List.class, new CustomCollectionEditor(List.class));
    }
Buryat answered 25/2, 2021 at 15:49 Comment(1)
This solution is working for me with ListDicrotic
V
4

As suggested by Philip Potter, I'm posting the "update" to my question as an answer, as it might've been easy to miss...

Down-grading from Spring 3.0.5.RELEASE to 3.0.4.RELEASE fixed the issue, when using the @RequestParam annotation, suggesting it is a bug with 3.0.5.

However, it does NOT fix the related issue, when binding to a form-backing bean - which is what I have in my webapp. I've tested all version back to 3.0.0.RELEASE and get the same result (/test?fq=foo,bar produces fq = foo|bar).

E.g.

@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(SearchRequestParams requestParams, BindingResult result) {
    logger.debug("fq = " + StringUtils.join(requestParams.getFq(), "|"));
}

where SearchRequestParams contains a field String[] fq.

If anyone has a fix for this, I'll gladly accept their answer.

Valiancy answered 6/3, 2011 at 22:45 Comment(1)
Update for posterity: Javanna correctly identified the issue in the accepted answer above.Valiancy
Z
3

javanna already pointed out the correct root cause. I just wanted to further point out that you can also remove the StringToArrayConverter altogether as shown here and here.

Zechariah answered 20/11, 2012 at 21:20 Comment(0)
D
1

The following is my journey of bypassing comma delimiting in a MVC request

This is my understanding of Springs evolution of these type of solutions: The documentation is very vague about what is the latest solution.

First was WebMvcConfigAdapter implementing the interface WebMvcConfig. This was eventually deprecated

That was replaced by WebMvcConfigSupport. This was eventually deprecated but it was better than the first solution. The main issued was that it turned off the MVC auto-configuration and had side issues like swagger.html not working and actuator info being missing pretty format and the date became a large decimal

The latest is a revised interface WebMvcConfig that implements default methods using Java 8 features.

Create a class in my case, WebConfigUUID could be AnyClass, that implements the later version of WebMvcConfig

This allows you to change what you need, in our case a custom converter, without impacting anything else or having to override another to get swagger to work, or deal with actuator info output

The following are the two classes that implemented my change to bypass comma delimiting of strings to a list in processing the MVC request:
It still produces a list of strings but with only one value.

import java.util.Collection;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfigUUID implements WebMvcConfigurer {
  @Override
  public void addFormatters(FormatterRegistry registry)  {
    registry.removeConvertible(String.class,Collection.class);
    registry.addConverter(String.class,Collection.class,BypassCommaDelimiterConfiguration.commaDelimiterBypassedParsingConverter());
  }
}

import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.converter.Converter;

public class BypassCommaDelimiterConfiguration  {
    public static Converter<String, List<String>> commaDelimiterBypassedParsingConverter() {
        return new Converter<String, List<String>>() {
            @Override
            public List<String> convert(final String source) {
                final List<String> classes = new ArrayList<String>();
                classes.add(source);
                return classes;
            }
        };
    }
}
Deficient answered 15/3, 2019 at 14:49 Comment(1)
I meant WebMvcConfigurer as represented in the codeDeficient
L
0

Its a hack but, have you considered passing your params delimited with '-'

/test?fq=foo-bar results in fq = foo-bar
/test?fq=foo-bar&fq=bash results in fq = foo-bar|bash 

Or any other delimiter maybe ~, or !,or ^, or ???

Ladykiller answered 7/3, 2011 at 5:36 Comment(1)
The parameter values come from Solr facets - and although I could munge them at index time to replace the comma with something_else, it is too much extra effort for what is, I agree, a hack.Valiancy

© 2022 - 2024 — McMap. All rights reserved.