Details
-
Type:
Bug
-
Status:
Closed
-
Priority:
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)
Attachments
-
$i18n.getText("admin.common.words.hide")
- testapp.zip
- 17/Apr/09 8:57 PM
- 153 kB
- Geoff Lane
-
- testapp/.classpath 6 kB
- testapp/.project 0.5 kB
- testapp/.../org.codehaus.groovy.eclipse.preferences.prefs 0.1 kB
- testapp/application.properties 0.1 kB
- testapp/build.xml 5 kB
- testapp/grails-app/conf/BootStrap.groovy 0.1 kB
- testapp/grails-app/conf/Config.groovy 3 kB
- testapp/grails-app/.../DataSource.groovy 0.6 kB
- testapp/grails-app/.../resources.groovy 0.0 kB
- testapp/grails-app/.../UrlMappings.groovy 0.2 kB
- testapp/.../PersonController.groovy 3 kB
- testapp/grails-app/domain/Person.groovy 0.3 kB
- __MACOSX/testapp/.../domain/._Person.groovy 0.2 kB
- testapp/grails-app/.../messages.properties 3 kB
- testapp/.../messages_de.properties 3 kB
- testapp/.../messages_es.properties 3 kB
- testapp/.../messages_fr.properties 2 kB
- testapp/.../messages_it.properties 2 kB
- testapp/.../messages_ja.properties 2 kB
- testapp/.../messages_nl.properties 3 kB
- testapp/.../messages_pt_BR.properties 3 kB
- testapp/.../messages_ru.properties 4 kB
- testapp/.../messages_th.properties 5 kB
- testapp/.../messages_zh_CN.properties 2 kB
- testapp/grails-app/views/error.gsp 2 kB
- testapp/grails-app/views/index.gsp 0.9 kB
- testapp/grails-app/.../layouts/main.gsp 0.7 kB
- testapp/grails-app/.../person/create.gsp 5 kB
- __MACOSX/testapp/.../person/._create.gsp 0.2 kB
- testapp/grails-app/views/person/edit.gsp 5 kB
Activity
- All
- Comments
- Work Log
- History
- Activity
- Git Commits
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.
."); 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.
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)
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)