Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make initDirectFieldAccess work with array, List and Map [SPR-12805] #17402

Closed
spring-issuemaster opened this issue Mar 11, 2015 · 6 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Mar 11, 2015

Hans Desmet opened SPR-12805 and commented

Currently initDirectFieldAccess does not work with array, List and Map.

Given following command object class

package be.vdab.web;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CommandObject {
	private String[] array = new String[1];
	private List<String> list = Arrays.asList("");
	private Map<Integer, String> map = new HashMap<>();

	public String[] getArray() {
		return array;
	}

	public List<String> getList() {
		return list;
	}

	public void setList(List<String> list) {
		this.list = list;
	}

	public void setArray(String[] array) {
		this.array = array;
	}

	public Map<Integer, String> getMap() {
		return map;
	}

	public void setMap(Map<Integer, String> map) {
		this.map = map;
	}

}

, the following controller:

package be.vdab.web;

import java.util.Arrays;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/")
public class IndexController {
	@RequestMapping(method = RequestMethod.GET)
	public ModelAndView get() {
		return new ModelAndView("/WEB-INF/JSP/index.jsp", "commandObject",
				new CommandObject());
	}

	@RequestMapping(method = RequestMethod.POST)
	public String post(CommandObject commandObject) {
		System.out.println(Arrays.toString(commandObject.getArray()));
		System.out.println(commandObject.getList());
		System.out.println(commandObject.getMap());
		return "redirect:";
	}

	@InitBinder("commandObject")
	void initBinder(WebDataBinder binder) {
		binder.initDirectFieldAccess();
	}
}

and the following JSP

<%@page contentType='text/html' pageEncoding='UTF-8' session='false'%>
<%@taglib prefix='form' uri='http://www.springframework.org/tags/form'%>
<!doctype html>
<html lang='nl'>
<head>
<title>initDirectFieldAccess</title>
</head>
<body>
<form:form action='${url}' commandName='commandObject'>
<form:input path='array[0]'/>
<form:input path='list[0]'/>
<form:input path='map[1]'/>
<input type='submit'>
</form:form>
</body>
</html>

you get following stacktrace:

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/initdirectfieldaccess] threw exception [org.springframework.beans.NotReadablePropertyException: Invalid property 'array[0]' of bean class [be.vdab.web.CommandObject]: Field 'array[0]' does not exist] with root cause
org.springframework.beans.NotReadablePropertyException: Invalid property 'array[0]' of bean class [be.vdab.web.CommandObject]: Field 'array[0]' does not exist
	at org.springframework.beans.DirectFieldAccessor.getPropertyValue(DirectFieldAccessor.java:116)
	at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99)
	at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:229)
	at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:168)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:188)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:154)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.autogenerateId(AbstractDataBoundFormElementTag.java:141)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.resolveId(AbstractDataBoundFormElementTag.java:132)
	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:116)
	at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:422)
	at org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:142)
	at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:84)
	at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:80)
	at org.apache.jsp.WEB_002dINF.JSP.index_jsp._jspx_meth_form_005finput_005f0(index_jsp.java:194)
	at org.apache.jsp.WEB_002dINF.JSP.index_jsp._jspx_meth_form_005fform_005f0(index_jsp.java:151)
	at org.apache.jsp.WEB_002dINF.JSP.index_jsp._jspService(index_jsp.java:108)
	at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:431)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:466)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
	at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:168)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1228)
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1011)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:955)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
	at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Unknown Source)

When you put the line binder.initDirectFieldAccess(); in comment in the
class IndexController, the program works.


Affects: 4.1.5

Issue Links:

  • #14339 org.springframework.beans.DirectFieldAccessor fails to go through field paths recursively
  • #16820 DirectFieldAccessor should support collections
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Mar 11, 2015

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Mar 11, 2015

Juergen Hoeller commented

This was explicitly left out of our 4.1 revision of the direct field access mechanism, where we focused on nested field traversal for a start. We'll reconsider collection and array bindings for 4.2, tracked by this issue now.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 21, 2015

Stéphane Nicoll commented

I worked on this feature and I've pushed an initial prototype on my fork (see e6b49ee9)

The basic idea is to stop having two implementations of ConfigurablePropertyAccessor but instead to move all the code of BeanWrapperImpl to some abstract class that does not know how to handle a given property: getting/setting the value, retrieving its type, checking whether it's readable or writable, etc. These basic features are abstracted in PropertyHandler with an implementation that uses PropertyDescriptor (BeanWrapperImpl) and one that uses java.lang.reflect.Field (DirectFieldAccessor).

The end result is that 99% of the features are now shared between the two and most of the tests pass (once the getter/setter inconsistencies with their related field have been fixed).

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 21, 2015

Stéphane Nicoll commented

DirectFieldAccessor and BeanWrapperImpl now share a base implementation. Array, list and map access is therefore supported for field access.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 21, 2015

Stéphane Nicoll commented

We took the decision to merge these extra features in the existing AbstractPropertyDescriptor. Unfortunately, others are already extending from that so we should leave it untouched. We'll revisit that and split those features in a separated base class.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented May 22, 2015

Stéphane Nicoll commented

AbstractPropertyAccessor has been restored to its previous versions and these new features have moved to AbstractNestablePropertyAccessor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.