1

Short problem description

I can not convert url params like ...?name=John&sports[]=Volleyball&sports[]=Volleyball to the following Java class in Spring MVC.

class PersonFilter {       
    String name;
    List<String> sports; 
}

I need to use brackets in the sports[] parameter name.

Long problem description

I use DataTables framework with Spring MVC. I need to add some complex filter properties to datatables ajax request as follows:

$('#table').DataTable({
    serverSide: true,
    ajax: {
        url: "https://server/some/path",
        data: function (data) {
            data.name = "John",
            data.sports = ["Football", "Volleyball"]
        }
    }
});

Created DataTables request with sorting (parameters order and columns) and custom filter (parameters name and sports[]) looks like:

https://server/some/path?name=John&sports[]=Volleyball&sports[]=Football&columns[0][name]=name&order[0][column]=0&order[0][dir]=asc

I need to bind URL parameters to the PersonFilter class containing java.util.List.

class PersonFilter {       
    String name;
    List<String> sports; 
}

Controller looks like:

@Controller
class PersonController {

    @RequestMapping(path = "/search")
    public List<Person> search(PersonFilter personFilter) {
       ...
    }
}

Spring allows following syntax for List binding (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#beans-beans-conventions):

1) sports=Volleyball&sports=Football -> OK
2) sports[0]=Volleyball&sports[1]=Football -> OK

But Spring can not convert property of name sports[] to List.

sports[]=Volleyball&sports[]=Football -> FAILs (expects index in brackets)

java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_121]
    at java.lang.Integer.parseInt(Integer.java:592) ~[na:1.8.0_121]
    at java.lang.Integer.parseInt(Integer.java:615) ~[na:1.8.0_121]
    at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:354) ~[spring-beans-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280) ~[spring-beans-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95) ~[spring-beans-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:859) ~[spring-context-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.validation.DataBinder.doBind(DataBinder.java:755) ~[spring-context-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:192) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:106) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:150) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:114) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:160) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.6.jar:8.5.6]

What I've tried:

Use JQuery ajax setting jQuery.ajaxSettings.traditional = true. But in this case DataTables parameters for sorting (order, columns) results in [Object], so I am not able to parse sort parameters.

https://server/some/path?name=John&sports=Volleyball&sports=Football&columns=[object Object]&order=[object Object]

Thank you for any suggestion!

2 Answers 2

1

Finally I have decided to modify parameters of the request in a filter.

I have created HttpServletRequestWrapper which removes brackets [] from the request parameters.

public class JQueryArrayParameterRequestWrapper extends HttpServletRequestWrapper {
    public static final String BRACKETS_SUFFIX = "[]";

    private Map<String, String[]> _parameterMap;

    public JQueryArrayParameterRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        if (_parameterMap == null) {
            _parameterMap = modifyParameterMap(super.getParameterMap());
        }
        return _parameterMap;
    }

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

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

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

    private Map<String, String[]> modifyParameterMap(Map<String, String[]> oldParameterMap) {
        Map<String, String[]> newParameterMap = new HashMap<>(oldParameterMap.size());
        for (Map.Entry<String, String[]> entry : oldParameterMap.entrySet()) {
            String key = entry.getKey();
            if (key.endsWith(BRACKETS_SUFFIX)) {
                // remove brackets from parameter name
                String newKey = key.substring(0, key.length() - BRACKETS_SUFFIX.length());
                newParameterMap.put(newKey, entry.getValue());
            } else {
                // leave parameter unmodified
                newParameterMap.put(key, entry.getValue());
            }
        }
        return newParameterMap;
    }
}

Then I have created filter:

public class JQueryArrayParameterFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        /* empty */
    }

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

    @Override
    public void destroy() {
        /* empty */
    }
}

Finally I have registered filter for DataTables requests.

@Configuration
public class AppConfiguration { 
    @Bean
    public JQueryArrayParameterFilter jQueryArrayParameterFilter() {
        return new JQueryArrayParameterFilter();
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean(JQueryArrayParameterFilter filter) {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(filter);
        filterRegistrationBean.addUrlPatterns( "/datatables/*");
        return filterRegistrationBean;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0
  data: function (data) {
        data.name = "John",
        data.sports = ["Football", "Volleyball"]
        data = JSON.stringify(data);
}

Try to stringify the object before sending the request ad the above code.

3 Comments

This solution would be fine. But I use custom DataTablesMethodArgumentReslover for Datatables sorting and paging like the Spring Sort one SortHandlerMethodArgumentResolver. When all would be packed in JSON, I can not use existing DataTablesMethodArgumentResolver. Any suggestion for this problem?
Sorry but I'm not sure to understand, DataTablesMethodArgumentReslover is a java class? You should model a java class that encapsulate the http request that the js framework send, then stringify everything as described and Spring will automatcally bind your request in an instance of the 2 object as input of your method i.e. .....(PersonFilter personFilter, AnotherObjectForDatatableRequest anothe)
Yes, DataTablesMethodArgumentReslover is a class. The problem is when I stringify the request. Spring binds only and only one method argument annotated by @RequestBody. Unlike url request parameters binding it is impossible to use multiple @RequestBody annotations for parsing method arguments from the request body. And the second reason is, that DataTablesMethodArgumentReslover class is able to bind method argument from params only (like the Spring implementation of SortHandlerMethodArgumentResolver class).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.