Lee Francis Williams
Lee Francis Williams

Reputation: 145

Point not a valid property - MongoDB & Grails 3.3+

Having a really weird issue in Grails and MongoDB where in my production environment I get the following error.

java.lang.IllegalArgumentException: Property [location] is not a valid property of class [domain].Tracking
        at org.grails.datastore.mapping.reflect.FieldEntityAccess$FieldEntityReflector.getPropertyReader(FieldEntityAccess.java:268)
        at org.grails.datastore.mapping.reflect.FieldEntityAccess$FieldEntityReflector.getProperty(FieldEntityAccess.java:286)
        at grails.gorm.validation.PersistentEntityValidator.validatePropertyWithConstraint(PersistentEntityValidator.groovy:319)
        at grails.gorm.validation.PersistentEntityValidator.validate(PersistentEntityValidator.groovy:76)
        at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:124)
        at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:153)
        at org.grails.datastore.gorm.GormValidateable$Trait$Helper.validate(GormValidateable.groovy:71)
        at org.grails.datastore.gorm.GormValidateable$Trait$Helper$validate$1.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:136)
        at [domain].Tracking.validate(Tracking.groovy)
        at org.grails.datastore.gorm.GormInstanceApi.doSave(GormInstanceApi.groovy:332)
        at org.grails.datastore.gorm.GormInstanceApi.doSave(GormInstanceApi.groovy)
        at sun.reflect.GeneratedMethodAccessor113.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1225)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
        at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:947)
        at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:930)
        at org.codehaus.groovy.runtime.InvokerHelper.invokeMethodSafe(InvokerHelper.java:92)
        at org.grails.datastore.gorm.GormInstanceApi$_save_closure5.doCall(GormInstanceApi.groovy:179)
        at sun.reflect.GeneratedMethodAccessor112.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
        at groovy.lang.Closure.call(Closure.java:418)
        at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:54)
        at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:124)
        at com.sun.proxy.$Proxy111.doInSession(Unknown Source)
        at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:319)
        at org.grails.datastore.gorm.AbstractDatastoreApi.execute(AbstractDatastoreApi.groovy:40)
        at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:178)
        at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
        at org.grails.datastore.gorm.GormEntity$Trait$Helper$save.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:136)
        at [domain].Tracking.save(Tracking.groovy)
        at [domain].Tracking.save(Tracking.groovy)
        at org.grails.datastore.gorm.GormEntity$save$0.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
        at autovision.web.BootStrap$_closure1.doCall(BootStrap.groovy:139)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1099)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
        at groovy.lang.Closure.call(Closure.java:418)
        at groovy.lang.Closure.call(Closure.java:412)
        at grails.util.Environment.evaluateEnvironmentSpecificBlock(Environment.java:541)
        at grails.util.Environment.executeForEnvironment(Environment.java:534)
        at grails.util.Environment.executeForCurrentEnvironment(Environment.java:510)
        at org.grails.web.servlet.boostrap.DefaultGrailsBootstrapClass.callInit(DefaultGrailsBootstrapClass.java:74)
        at org.grails.web.servlet.context.GrailsConfigUtils.executeGrailsBootstraps(GrailsConfigUtils.java:65)
        at org.grails.plugins.web.servlet.context.BootStrapClassRunner.onStartup(BootStrapClassRunner.groovy:53)
        at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy:261)
        at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347)
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:883)
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:144)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:84)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:393)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:380)
        at grails.boot.GrailsApp$run.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:136)
        at autovision.web.Application.main(Application.groovy:8)

This is happening when I try to insert a new Tracking Object into the DB like so :

  Tracking tp = new Tracking()
  tp.setUser(user)
  nowCal.add(Calendar.MINUTE,i++)
  tp.setCreated(nowCal.getTime())
  tp.setSpeed(10f)
  tp.setLocation(new Point(points[1],points[0]))
  tp.save(flush:true)

  user.addToTracking(tp);

And my Tracking class is defined as so :

import grails.mongodb.geo.Point

class Tracking {

    Point location

    Date created

    float speed

    static belongsTo = [user:User]

    static constraints = {
        speed nullable:true
    }

    static mapping = {
        location geoIndex:'2dsphere'
    }
}

I really can't see what the problem is here, I Think I've defined everything correctly as it shows in the GORM documentation : http://gorm.grails.org/latest/mongodb/manual/#geoSpatial

The other thing is this code works perfectly well when running in non prod mode.

so executing grails run-app works but grails prod run-app doesn't..

Any help would be much appreciated.

Thanks

Lee.

Upvotes: 1

Views: 658

Answers (2)

Olivier Expemag
Olivier Expemag

Reputation: 1

If someone has the same problem, I put here an encountered error solved by the fers490 solution (thank you very much fers490) :

Context : Grails 6.2.0 / Java 17 / Mongodb 7.0.14 (community server running on the same machine) / Debian 12 / Tomcat 10 behind Apache2 (mod_jk)

  1. I gradle->assembled my project
  2. I posted my xxx-plain.war to the debian server
  3. I converted the war with javax2Jakarta
  4. I copied the converted war to the webapps directory
  5. I restarted apache2/tomcat10/mongodb

And, my whole web app works, but the type Shape (grails.mongodb.geo.Shape) or type Point (grails.mongodb.geo.Point) in domains triggers an exception when the app is trying to read such a domain. My other domains, without spatial fields, work perfectly.

class Book {
     Shape bboxp
     Point center
    …
    static mapping = {
         compoundIndex bboxp:"2dsphere", center:"2dsphere", bookId:1, alias:1
     }
}
Book ret = Book.read(oid) //triggers an IllegalArgumentException
Book b = Book.collection.findOne([alias: book]) as Book  //triggers an IllegalArgumentException too
Book metabook = Book.get(params.id) //triggers an IllegalArgumentException too

If I start my server using the stand alone war, it works like a charm ! But I need my app in Tomcat behind Apache2, mainly for efficiency, flexibility and security I can’t understand what the problem is, why only the spatial fields (Shape, Point…) drive to an error ?

The stacktrace : Exception IllegalArgumentException : 3_1 NTT Interceptor EXCEPTION (Afterview) : errors / index / [controller:errors, action:index] Property [bboxp] is not a valid property of class book.Book ---- Property [bboxp] is not a valid property of class book.Book

Solved by the fers490 3. solution

Upvotes: 0

fers490
fers490

Reputation: 46

I had the same issue, and after two days of debugging finally found a workaround.

The problem was caused by the following:

  • There is a Grails utility class called org.grails.datastore.mapping.reflect.FieldEntityAccess, apparently used to access entities fields using reflection. That class has an inner class called FieldEntityReflector. Every time a FieldEntityAccess instance is created, there is an instance of FieldEntityReflector created internally. All those instances are stored in a static map called REFLECTORS, whose keys are the entity names, apparently for performance reasons.
  • During bean initialization, mongoDatastore bean creates one FieldEntityAccess for each entity, passing a DocumentPersistentEntity as constructor parameter.
  • In the same way, grailsDomainClassMappingContext bean, unaware of mongoDatastore creates its own FieldEntityAccess for each entity, passing down KeyValuePersistentEntity as parameter. Those FieldEntityAccess instances and their corresponding EntityReflectors are unaware of mongo and therefore don't support mongo specific types like Point.
  • Given that the REFLECTORS map keys are entity names, EntityReflectors created last for the same entity, override previously created reflectors, and are used by all EntityReflector instances.
  • When grailsDomainClassMappingContext reflectors are created last, they are used when mongo tries to persist and entity and therefore throw an error.

In short, the error is thrown when grailsDomainClassMappingContext bean is initialized after mongoDatastore bean.

In development mode, UrlMappingsGrailsPlugin plugin creates some beans used to allow url mappings reloading. Those beans trigger instantiation of grailsDomainClassMappingContext bean before mongoDatastore bean. As the mongo beans are created later, the error is not thrown. But, in production mode, those beans are not created, and mongoDatastore is created before grailsDomainClassMappingContext, therefore throwing the error.

Potential solutions

  1. Force grailsDomainClassMappingContext bean instantiation before mongoDatastore: I was unable to do that after several attempts, maybe because of grails bean instantiation logic.
  2. Force UrlMappingsGrailsPlugin instantiation even on production mode: I prefered not to go that way due to possible permformance implications.
  3. Use the static method clearReflectors of FieldEntityAccess class to clear all the reflectors after system initialization:** I decided to do that and the system started to work. I haven't tested it in depth, but was able to persist Point correctly.

In order to call the method, I created a bean depending on both grailsDomainClassMappingContext and mongoDatastore and called FieldEntityAccess.clearReflectors()

package app.utils

import org.grails.datastore.mapping.mongo.MongoDatastore
import org.grails.datastore.mapping.reflect.FieldEntityAccess
import org.grails.datastore.mapping.model.MappingContext

class MongoDatastoreHolder {

    MongoDatastore mongo

    MappingContext context

    MongoDatastoreHolder(MappingContext context, MongoDatastore mongo) {
        this.context = context
        this.mongo = mongo
        FieldEntityAccess.clearReflectors()
    }
}

And defined that bean in resources.groovy:

import app.utils.MongoDatastoreHolder

beans = {
    mongoDatastoreHolder(MongoDatastoreHolder, ref('grailsDomainClassMappingContext'), ref('mongoDatastore')) { bean ->
        bean.lazyInit = false
    }
}

Hope it helps, and sorry for my bad english, this is my first StackOverflow answer.

Upvotes: 3

Related Questions