more on null references

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

more on null references

Dierk König
Hi,

my 'soon to be famous' TutorialEntry domain class currently looks like this:
----
class TutorialEntry {
        @Property Long id;
        @Property Long version;

        // @Property relatesToMany = ['childEntries' : TutorialEntry ]
        // @Property belongsTo     = [ TutorialEntry ]

      @Property String        title
      @Property String        keywords
      @Property TutorialEntry parentEntry                 // <- problem !
      @Property Set           childEntries = new HashSet()
      @Property String        text
      @Property Author        author       = new Author()

      void addToParent(TutorialEntry parent) {
          parentEntry = parent
          parent.childEntries.add(this)
      }
      String toString() { title }
}
----
Old problem:
We could not instantiate 'parentEntry' with 'new TutorialEntry()'
since that would lead to endless recursion.
So we allowed null values when saving.

New problem:
When saving a new TutorialEntry that _has_ a parent, the exception
below is thrown.

cheers
Mittie

----
org.codehaus.groovy.runtime.InvokerInvocationException:
org.springframework.beans.NullValueInNestedPathException: Invalid property
'parentEntry' of bean class [TutorialEntry]: Value of nested property
'parentEntry' is null at
org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
ava:668) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
at groovy.lang.Closure.call(Closure.java:176) at
groovy.lang.Closure.call(Closure.java:171) at
org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
leAction(SimpleGrailsControllerHelper.java:329) at
org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
leURI(SimpleGrailsControllerHelper.java:271) at
org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
leURI(SimpleGrailsControllerHelper.java:117) at
org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequ
est(SimpleGrailsController.java:78) at
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(Si
mpleControllerHandlerAdapter.java:44) at
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServl
et.java:717) at
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServle
t.java:658) at
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkSer
vlet.java:392) at
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.jav
a:357) at javax.servlet.http.HttpServlet.service(HttpServlet.java:616) at
javax.servlet.http.HttpServlet.service(HttpServlet.java:689) at
org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:427) at
org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
icationHandler.java:830) at
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterI
nternal(OpenSessionInViewFilter.java:174) at
org.codehaus.groovy.grails.orm.hibernate.support.GrailsOpenSessionInViewFilt
er.doFilterInternal(GrailsOpenSessionInViewFilter.java:64) at
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestF
ilter.java:76) at
org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
icationHandler.java:821) at
com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java
:119) at
com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:
55) at
org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
icationHandler.java:821) at
org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFi
lterInternal(GrailsReloadServletFilter.java:219) at
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestF
ilter.java:76) at
org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
icationHandler.java:821) at
org.mortbay.jetty.servlet.WebApplicationHandler.dispatch(WebApplicationHandl
er.java:471) at
org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:568) at
org.mortbay.http.HttpContext.handle(HttpContext.java:1565) at
org.mortbay.jetty.servlet.WebApplicationContext.handle(WebApplicationContext
.java:635) at org.mortbay.http.HttpContext.handle(HttpContext.java:1517) at
org.mortbay.http.HttpServer.service(HttpServer.java:954) at
org.mortbay.http.HttpConnection.service(HttpConnection.java:816) at
org.mortbay.http.HttpConnection.handleNext(HttpConnection.java:983) at
org.mortbay.http.HttpConnection.handle(HttpConnection.java:833) at
org.mortbay.http.SocketListener.handleConnection(SocketListener.java:244) at
org.mortbay.util.ThreadedServer.handle(ThreadedServer.java:357) at
org.mortbay.util.ThreadPool$PoolThread.run(ThreadPool.java:534) Caused by:
org.springframework.beans.NullValueInNestedPathException: Invalid property
'parentEntry' of bean class [TutorialEntry]: Value of nested property
'parentEntry' is null at
org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperIm
pl.java:299) at
org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath(Bean
WrapperImpl.java:274) at
org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.j
ava:462) at
org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.j
ava:626) at
org.springframework.beans.BeanWrapperImpl.setPropertyValues(BeanWrapperImpl.
java:653) at
org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.jav
a:418) at
org.springframework.validation.DataBinder.doBind(DataBinder.java:325) at
org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:122) at
org.codehaus.groovy.grails.web.binding.GrailsDataBinder.bind(GrailsDataBinde
r.java:100) at
org.codehaus.groovy.grails.metaclass.SetPropertiesDynamicProperty.set(SetPro
pertiesDynamicProperty.java:74) at
org.codehaus.groovy.grails.commons.metaclass.AbstractDynamicMethods.setPrope
rty(AbstractDynamicMethods.java:118) at
org.codehaus.groovy.grails.commons.metaclass.DelegatingMetaClass.setProperty
(DelegatingMetaClass.java:70) at
gjdk.org.codehaus.groovy.grails.commons.metaclass.DelegatingMetaClass_Groovy
Reflector.invoke(Unknown Source) at
groovy.lang.MetaMethod.invoke(MetaMethod.java:111) at
org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
ava:636) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:144) at
org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:10
4) at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod(ScriptBytecod
eAdapter.java:85) at TutorialEntry.setProperty(TutorialEntry.groovy) at
org.codehaus.groovy.runtime.Invoker.setProperty(Invoker.java:740) at
org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:217
) at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecode
Adapter.java:325) at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty2(ScriptBytecod
eAdapter.java:337) at
TutorialEntryController$_closure8.doCall(TutorialEntryController:64) at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:25) at java.lang.reflect.Method.invoke(Method.java:324) at
org.codehaus.groovy.runtime.ReflectionMetaMethod.invoke(ReflectionMetaMethod
.java:67) at
org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
ava:636) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:156) at
org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:10
4) at
org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod(ScriptBytecod
eAdapter.java:85) at
TutorialEntryController$_closure8.doCall(TutorialEntryController) at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:25) at java.lang.reflect.Method.invoke(Method.java:324) at
org.codehaus.groovy.runtime.ReflectionMetaMethod.invoke(ReflectionMetaMethod
.java:67) at
org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
ava:636) ... 38 more

Reply | Threaded
Open this post in threaded view
|

Re: more on null references

graemer
On 06/03/06, Dierk Koenig <[hidden email]> wrote:
> Hi,
>
> my 'soon to be famous' TutorialEntry domain class currently looks like this:

I think its great that you're testing it with a challenging data model :-)

> ----
> class TutorialEntry {
>         @Property Long id;
>         @Property Long version;
>
>         // @Property relatesToMany = ['childEntries' : TutorialEntry ]
>         // @Property belongsTo     = [ TutorialEntry ]
>
>       @Property String        title
>       @Property String        keywords
>       @Property TutorialEntry parentEntry                 // <- problem !
>       @Property Set           childEntries = new HashSet()
>       @Property String        text
>       @Property Author        author       = new Author()
>
>       void addToParent(TutorialEntry parent) {
>           parentEntry = parent
>           parent.childEntries.add(this)
>       }
>       String toString() { title }
> }
> ----
> Old problem:
> We could not instantiate 'parentEntry' with 'new TutorialEntry()'
> since that would lead to endless recursion.
> So we allowed null values when saving.
>
> New problem:
> When saving a new TutorialEntry that _has_ a parent, the exception
> below is thrown.

This is not new this is the same problem, it occurs in the line

tutorialEntry.properties = params

This is where the request parameters are bound (doing auto type
conversion) to the properties of the domain class. I'm not sure of the
best way to deal with this. I thought about maybe implementing
something that auto-instantiates a null property when attempting to
bind the value to it if its a domain class, but then you have the same
problem of potential recursion and stack over flows...

The most elegant solution to this problem may in fact be to simply
change Grails to catch this exception and warn the user that a value
cannot be bound because it is null and it is then up to the user to
manage the data model

What do you think?

Graeme

>
> cheers
> Mittie
>
> ----
> org.codehaus.groovy.runtime.InvokerInvocationException:
> org.springframework.beans.NullValueInNestedPathException: Invalid property
> 'parentEntry' of bean class [TutorialEntry]: Value of nested property
> 'parentEntry' is null at
> org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
> ava:668) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
> at groovy.lang.Closure.call(Closure.java:176) at
> groovy.lang.Closure.call(Closure.java:171) at
> org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
> leAction(SimpleGrailsControllerHelper.java:329) at
> org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
> leURI(SimpleGrailsControllerHelper.java:271) at
> org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.hand
> leURI(SimpleGrailsControllerHelper.java:117) at
> org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequ
> est(SimpleGrailsController.java:78) at
> org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(Si
> mpleControllerHandlerAdapter.java:44) at
> org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServl
> et.java:717) at
> org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServle
> t.java:658) at
> org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkSer
> vlet.java:392) at
> org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.jav
> a:357) at javax.servlet.http.HttpServlet.service(HttpServlet.java:616) at
> javax.servlet.http.HttpServlet.service(HttpServlet.java:689) at
> org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:427) at
> org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
> icationHandler.java:830) at
> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterI
> nternal(OpenSessionInViewFilter.java:174) at
> org.codehaus.groovy.grails.orm.hibernate.support.GrailsOpenSessionInViewFilt
> er.doFilterInternal(GrailsOpenSessionInViewFilter.java:64) at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestF
> ilter.java:76) at
> org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
> icationHandler.java:821) at
> com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java
> :119) at
> com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:
> 55) at
> org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
> icationHandler.java:821) at
> org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFi
> lterInternal(GrailsReloadServletFilter.java:219) at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestF
> ilter.java:76) at
> org.mortbay.jetty.servlet.WebApplicationHandler$CachedChain.doFilter(WebAppl
> icationHandler.java:821) at
> org.mortbay.jetty.servlet.WebApplicationHandler.dispatch(WebApplicationHandl
> er.java:471) at
> org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:568) at
> org.mortbay.http.HttpContext.handle(HttpContext.java:1565) at
> org.mortbay.jetty.servlet.WebApplicationContext.handle(WebApplicationContext
> .java:635) at org.mortbay.http.HttpContext.handle(HttpContext.java:1517) at
> org.mortbay.http.HttpServer.service(HttpServer.java:954) at
> org.mortbay.http.HttpConnection.service(HttpConnection.java:816) at
> org.mortbay.http.HttpConnection.handleNext(HttpConnection.java:983) at
> org.mortbay.http.HttpConnection.handle(HttpConnection.java:833) at
> org.mortbay.http.SocketListener.handleConnection(SocketListener.java:244) at
> org.mortbay.util.ThreadedServer.handle(ThreadedServer.java:357) at
> org.mortbay.util.ThreadPool$PoolThread.run(ThreadPool.java:534) Caused by:
> org.springframework.beans.NullValueInNestedPathException: Invalid property
> 'parentEntry' of bean class [TutorialEntry]: Value of nested property
> 'parentEntry' is null at
> org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperIm
> pl.java:299) at
> org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath(Bean
> WrapperImpl.java:274) at
> org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.j
> ava:462) at
> org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.j
> ava:626) at
> org.springframework.beans.BeanWrapperImpl.setPropertyValues(BeanWrapperImpl.
> java:653) at
> org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.jav
> a:418) at
> org.springframework.validation.DataBinder.doBind(DataBinder.java:325) at
> org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:122) at
> org.codehaus.groovy.grails.web.binding.GrailsDataBinder.bind(GrailsDataBinde
> r.java:100) at
> org.codehaus.groovy.grails.metaclass.SetPropertiesDynamicProperty.set(SetPro
> pertiesDynamicProperty.java:74) at
> org.codehaus.groovy.grails.commons.metaclass.AbstractDynamicMethods.setPrope
> rty(AbstractDynamicMethods.java:118) at
> org.codehaus.groovy.grails.commons.metaclass.DelegatingMetaClass.setProperty
> (DelegatingMetaClass.java:70) at
> gjdk.org.codehaus.groovy.grails.commons.metaclass.DelegatingMetaClass_Groovy
> Reflector.invoke(Unknown Source) at
> groovy.lang.MetaMethod.invoke(MetaMethod.java:111) at
> org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
> ava:636) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
> at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:144) at
> org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:10
> 4) at
> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod(ScriptBytecod
> eAdapter.java:85) at TutorialEntry.setProperty(TutorialEntry.groovy) at
> org.codehaus.groovy.runtime.Invoker.setProperty(Invoker.java:740) at
> org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:217
> ) at
> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecode
> Adapter.java:325) at
> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty2(ScriptBytecod
> eAdapter.java:337) at
> TutorialEntryController$_closure8.doCall(TutorialEntryController:64) at
> sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
> ) at
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
> .java:25) at java.lang.reflect.Method.invoke(Method.java:324) at
> org.codehaus.groovy.runtime.ReflectionMetaMethod.invoke(ReflectionMetaMethod
> .java:67) at
> org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
> ava:636) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:345)
> at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:156) at
> org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:10
> 4) at
> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod(ScriptBytecod
> eAdapter.java:85) at
> TutorialEntryController$_closure8.doCall(TutorialEntryController) at
> sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39
> ) at
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
> .java:25) at java.lang.reflect.Method.invoke(Method.java:324) at
> org.codehaus.groovy.runtime.ReflectionMetaMethod.invoke(ReflectionMetaMethod
> .java:67) at
> org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.j
> ava:636) ... 38 more
>
>
Reply | Threaded
Open this post in threaded view
|

RE: more on null references

Dierk König
> > Old problem:
> > We could not instantiate 'parentEntry' with 'new TutorialEntry()'
> > since that would lead to endless recursion.
> > So we allowed null values when saving.
> >
> > New problem:
> > When saving a new TutorialEntry that _has_ a parent, the exception
> > below is thrown.
>
> This is not new this is the same problem, it occurs in the line
>
> tutorialEntry.properties = params

*hehe* yeah, I saw it happening on the same line but at a later
time so I thought it may have a different cause.

> This is where the request parameters are bound (doing auto type
> conversion) to the properties of the domain class. I'm not sure of the
> best way to deal with this. I thought about maybe implementing
> something that auto-instantiates a null property when attempting to
> bind the value to it if its a domain class, but then you have the same
> problem of potential recursion and stack over flows...

Yes, I kind of thought into the same direction: looking for the type
and then doing newInstance().
I think we can never really keep free of potentially endless recursion
(unless Groovy itself can catch that).
What I like about the current approach is that it gives us total
freedom about the constructors of the 'prototypes', which is a good thing.
Auto-instantiating can prove really tricky.

> The most elegant solution to this problem may in fact be to simply
> change Grails to catch this exception and warn the user that a value
> cannot be bound because it is null and it is then up to the user to
> manage the data model

This currently looks like the best and most stable solution to me.

At least: giving a more explaining error indication.

Leaving the reference null would simply mean: leave me alone, I care
for myself. That said, shouldn't we demand such properties to be 'optional'?
Or the other way around: can we spare 'optionals' be simply setting
references to null? (convention over configuration :-))

cheers
Mittie