Grails

Binding to Domain with embedded objects fails

Details

  • Type: Bug Bug
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 1.0.4, 1.0.5, 1.1
  • Fix Version/s: 1.2-M2
  • Component/s: Persistence
  • Labels:
    None

Description

Trying to work with a standard embedded object (composition)

class Person {

    static embedded = ['address']

    String name
    int age
    Address address = new Address()

}

class Address {
    String street
    String street2
    String city
    String state
    String zip
    
    static constraints = {
        street2(nullable:true)
    }
}

Attempting to databind from a form that contains things like:

<input type="text" id="address.state" name="address.state" value="${fieldValue(bean:personInstance,field:'address.state')}" />

If the embedded object is a null instance you get:

Server running. Browse to http://localhost:8080/testapp
2009-04-17 20:34:47,792 [15588806@qtp0-5] ERROR errors.GrailsExceptionResolver  - org.springframework.beans.NullValueInNestedPathException: Invalid property 'address' of bean class [Person]: Value of nested property 'address' is null
org.codehaus.groovy.runtime.InvokerInvocationException: org.springframework.beans.NullValueInNestedPathException: Invalid property 'address' of bean class [Person]: Value of nested property 'address' is null
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1061)
	at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:910)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:892)
	at groovy.lang.Closure.call(Closure.java:279)
	at groovy.lang.Closure.call(Closure.java:274)

If you instantiate the instance prior to binding the data you get:

2009-04-17 20:36:10,058 [13260127@qtp2-0] ERROR errors.GrailsExceptionResolver  - java.lang.ClassCastException: Address$__clinit__closure1
org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.ClassCastException: Address$__clinit__closure1
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1061)
	at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:910)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:892)
	at groovy.lang.Closure.call(Closure.java:279)
	at groovy.lang.Closure.call(Closure.java:274)

Activity

Hide
Geoff Lane added a comment -

Further info.
The java.lang.ClassCastException: Address$_clinit_closure1 is related to constraints on the embedded object. If the constraints static block is removed, then the code works.
(If you don't create the embedded object yourself you still get an NPE)

Show
Geoff Lane added a comment - Further info. The java.lang.ClassCastException: Address$_clinit_closure1 is related to constraints on the embedded object. If the constraints static block is removed, then the code works. (If you don't create the embedded object yourself you still get an NPE)
Hide
Gareth Davis added a comment -

After hitting a wall wall with 1.0.3 I've spent the morning trying to pin down the actual cause of this issue.
In the released 1.1 source code:

/src/web/org/codehaus/groovy/grails/web/binding/GrailsDataBinder.java:280

private ConstrainedProperty getConstrainedPropertyForPropertyValue(Map constrainedProperties, PropertyValue propertyValue) {

final String propertyName = propertyValue.getName();
if(propertyName.indexOf(PATH_SEPARATOR) > -1) {
String[] propertyNames = propertyName.split("
.");
Object target = getTarget();
Object value = getPropertyValueForPath(target, propertyNames);
if(value != null) {
MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(value.getClass());
if(mc.hasProperty(value, CONSTRAINTS_PROPERTY) != null) { Map nestedConstrainedProperties = (Map)mc.getProperty(value, CONSTRAINTS_PROPERTY); // << this isn't the case for nested 'some' components return (ConstrainedProperty)nestedConstrainedProperties.get(propertyNames[propertyNames.length-1]); }
}
}
else { return (ConstrainedProperty)constrainedProperties.get(propertyName); }
return null;
}

What seems to have happened is that a property accessor is attached to domain classes in org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin#addValidationMethods but this doesn't seem to get applied for classes defined in other .groovy files. This actually leads to a work around which is to define your components as top level domain objects, from your example above this would mean adding Address.groovy to your app. The side effect of this is that a table will be created for the address class which is lame, but it'll still work as embedded just fine.

Show
Gareth Davis added a comment - After hitting a wall wall with 1.0.3 I've spent the morning trying to pin down the actual cause of this issue. In the released 1.1 source code: /src/web/org/codehaus/groovy/grails/web/binding/GrailsDataBinder.java:280 private ConstrainedProperty getConstrainedPropertyForPropertyValue(Map constrainedProperties, PropertyValue propertyValue) { final String propertyName = propertyValue.getName(); if(propertyName.indexOf(PATH_SEPARATOR) > -1) { String[] propertyNames = propertyName.split("
."); Object target = getTarget(); Object value = getPropertyValueForPath(target, propertyNames); if(value != null) { MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(value.getClass()); if(mc.hasProperty(value, CONSTRAINTS_PROPERTY) != null) { Map nestedConstrainedProperties = (Map)mc.getProperty(value, CONSTRAINTS_PROPERTY); // << this isn't the case for nested 'some' components return (ConstrainedProperty)nestedConstrainedProperties.get(propertyNames[propertyNames.length-1]); } } } else { return (ConstrainedProperty)constrainedProperties.get(propertyName); } return null; } What seems to have happened is that a property accessor is attached to domain classes in org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin#addValidationMethods but this doesn't seem to get applied for classes defined in other .groovy files. This actually leads to a work around which is to define your components as top level domain objects, from your example above this would mean adding Address.groovy to your app. The side effect of this is that a table will be created for the address class which is lame, but it'll still work as embedded just fine.
Hide
Geoff Lane added a comment -

Still a problem in 1.1.1

org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.ClassCastException: Address$_clinit_closure1

at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)

at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)

at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1062)

at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:926)

at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:893)

at groovy.lang.Closure.call(Closure.java:279)

at groovy.lang.Closure.call(Closure.java:274)

at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleAction(SimpleGrailsControllerHelper.java:368)

at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.executeAction(SimpleGrailsControllerHelper.java:243)

at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:203)

at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:138)

at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequest(SimpleGrailsController.java:88)

at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)

at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:264)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)

at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:511)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)

at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1124)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361)

at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)

at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)

at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)

at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417)

at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:334)

at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126)

at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:293)

at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:269)

at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:261)

at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:181)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.obtainContent(GrailsPageFilter.java:221)

at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:126)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java:101)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:65)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:96)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)

at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)

at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)

at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361)

at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)

at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)

at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)

at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417)

at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)

at org.mortbay.jetty.Server.handle(Server.java:324)

at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:534)

at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:879)

at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:741)

at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:213)

at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:403)

at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)

at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:522)

Caused by: java.lang.ClassCastException: Address$_clinit_closure1

at PersonController$_closure8.doCall(PersonController.groovy:90)

at PersonController$_closure8.doCall(PersonController.groovy)

Show
Geoff Lane added a comment - Still a problem in 1.1.1 org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.ClassCastException: Address$_clinit_closure1 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1062) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:926) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:893) at groovy.lang.Closure.call(Closure.java:279) at groovy.lang.Closure.call(Closure.java:274) at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleAction(SimpleGrailsControllerHelper.java:368) at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.executeAction(SimpleGrailsControllerHelper.java:243) at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:203) at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:138) at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequest(SimpleGrailsController.java:88) at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48) at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:264) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:511) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1124) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:334) at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126) at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:293) at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:269) at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:261) at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:181) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.obtainContent(GrailsPageFilter.java:221) at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:126) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java:101) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:65) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) at org.mortbay.jetty.Server.handle(Server.java:324) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:534) at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:879) at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:741) at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:213) at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:403) at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409) at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:522) Caused by: java.lang.ClassCastException: Address$_clinit_closure1 at PersonController$_closure8.doCall(PersonController.groovy:90) at PersonController$_closure8.doCall(PersonController.groovy)

People

Vote (6)
Watch (6)

Dates

  • Created:
    Updated:
    Resolved: