lucas-napse opened a new issue, #14347:
URL: https://github.com/apache/grails-core/issues/14347
Hi @puneetbehl !
In our system we have a large number of domain classes. In our recent
upgrade to grails 5 (5.3.2) that makes use of GORM 7.3.0 of our system, which
was working fine with the Grails 2.5.6, we noticed that the embedded entities
were not loaded in the object that contained it: For example,
```groovy
class Country {
List<Map> regions
static embedded = ['regions']
}
```
In the above `Country` has embedded `regions`, these regions are initialized
as an `empty list` when loaded through GORM. Whereas, in the database
collection `Country` has `regions`. Even, when using the MongoDB API, these
regions are loaded correctly as list of `Document`.
This does not work when using either `Country.findAll()`, or
`Country.createCriteria().list{}`. It maybe breaking with other GORM queries as
well.
Upon, debugging, we noticed that the problem might be caused because the
database returns a `Map` instead of a `Document`. The Groovy class
`MongoEntityPersister` exclusively checks for the class `Document` which breaks
certain conditions. For example:
1. `MongoEntityPersister.loadEmbeddedCollection()`.
When the method is executed, I noticed that in our case it goes through
the `else` branch because the following condition evaluates to `false`
```
(if(Map.class.isAssignableFrom(embeddedCollection.getType())))
```
The above results in embedded `regions` to return an empty `List` because it
is a `Map`.
```
if (embeddedInstances instanceof Collection) { // true
Collection coll = (Collection)embeddedInstances;
for (Object dbo : coll) { // for
each embedded element...
if (dbo instanceof Document) { //
<------ this condition is problematic: is a Map, not a Document
Document nativeEntry = (Document)dbo;
```
By modifying the above to following, might fixes the problem:
```
if (embeddedInstances instanceof Collection) {
Collection coll = (Collection)embeddedInstances;
for (Object dbo : coll) {
if (dbo instanceof Map) { // Map, instead
Document, in this section
Document nativeEntry;
if(dbo instanceof Document){ // then, it is a
Document? then the same thing
nativeEntry = (Document)dbo;
}
else{
nativeEntry = new Document((Map)dbo); // if it
is a Map, a new Document instance is created based on that Map.
}
```
The above change solves the our problem where the `regions` are correctly
loaded via the GORM `Country.findAll` method.
Doing more tests I notice that there is a second problem: if this embedded
entity uses composition, some related attributes are loaded and others are not.
For example: my country has regions, and a region has a region type. This
type of region is a Domain. The regions are embedded in the country, and a
region has only one type of region, as an associated instance (composition).
This instance, the region type, is not loaded.
Performing more debugging, I find that the code goes by:
NativeEntityPersister#refreshObjectStateFromNativeEntry(PersistentEntity,
Object, Serializable, T, boolean)
for (final PersistentProperty prop : props) { // for each
attribute of my region, in the case of regionType...
... more code ...
else if (prop instanceof ToOne) {
// true
if (prop instanceof Embedded) { //
true
Embedded embedded = (Embedded) prop; // isnt null, ok
if(embedded.getAssociatedEntity() != null) { // true
T embeddedEntry = getEmbedded(nativeEntry, propKey);
// <----- THIS section returns null. Why?
... continuing debugging leads to this method ...
public abstract class AbstractMongoObectEntityPersister<T> extends
NativeEntryEntityPersister<T, Object> { ...
... more code ...
protected T getEmbedded(T nativeEntry, String key) {
Object embeddedDocument =
this.getValueRetrievalStrategy().getValue(nativeEntry, key);
return this.isEmbeddedEntry(embeddedDocument) ? embeddedDocument :
null; // <-------- THIS section is the problem...
}
... continuing debugging leads to this method ...
In MongoEntityPersister....
@Override
protected boolean isEmbeddedEntry(Object entry) {
return entry instanceof Document;
}
...clearly this method gives true when it is a Document, but my region type
appears as Map, which gives false. This, by giving false, the caller
(AbstractMongoObectEntityPersister#getEmbedded(T, String)) determines that it
is null, so it does not load my region type.
Making this change should fix it:
@Override
protected boolean isEmbeddedEntry(Object entry) {
return entry instanceof Map;
}
The problem now is that internally we get various errors related to the Map
and Document type. And these are related to the getValue() and setValue()
methods of the MongoEntityPersister class:
public static final ValueRetrievalStrategy<Document>
VALUE_RETRIEVAL_STRATEGY = new ValueRetrievalStrategy<Document>() {
@Override
public Object getValue(Document document, String name) { // <----
Designed for Document, but internally from mongo you get a Map
return document.get(name);
}
@Override
public void setValue(Document document, String name, Object value) {
// <---- Designed for Document, but internally from mongo you get a Map
document.put(name, value);
}
};
This is the error you get after making all the changes I mentioned above:
```
Caused by: java.lang.ClassCastException: class java.util.LinkedHashMap
cannot be cast to class org.bson.Document (java.util.LinkedHashMap is in module
java.base of loader 'bootstrap'; org.bson.Document is in unnamed module of
loader 'app')
at
org.grails.datastore.mapping.mongo.engine.MongoEntityPersister$1.getValue(MongoEntityPersister.java:58)
at
org.grails.datastore.mapping.mongo.engine.AbstractMongoObectEntityPersister.discriminatePersistentEntity(AbstractMongoObectEntityPersister.java:315)
at
org.grails.datastore.mapping.engine.NativeEntryEntityPersister.createObjectFromEmbeddedNativeEntry(NativeEntryEntityPersister.java:368)
at
org.grails.datastore.mapping.engine.NativeEntryEntityPersister.refreshObjectStateFromNativeEntry(NativeEntryEntityPersister.java:443)
at
org.grails.datastore.mapping.mongo.engine.AbstractMongoObectEntityPersister.refreshObjectStateFromNativeEntry(AbstractMongoObectEntityPersister.java:278)
at
org.grails.datastore.mapping.engine.NativeEntryEntityPersister.createObjectFromEmbeddedNativeEntry(NativeEntryEntityPersister.java:370)
at
org.grails.datastore.mapping.mongo.engine.MongoEntityPersister.loadEmbeddedCollection(MongoEntityPersister.java:115)
at
org.grails.datastore.mapping.engine.NativeEntryEntityPersister.refreshObjectStateFromNativeEntry(NativeEntryEntityPersister.java:507)
at
org.grails.datastore.mapping.mongo.engine.AbstractMongoObectEntityPersister.refreshObjectStateFromNativeEntry(AbstractMongoObectEntityPersister.java:281)
at
org.grails.datastore.mapping.engine.NativeEntryEntityPersister.createObjectFromNativeEntry(NativeEntryEntityPersister.java:363)
at
org.grails.datastore.mapping.mongo.query.MongoQuery$MongoResultList.convertDBObject(MongoQuery.java:1392)
at
org.grails.datastore.mapping.mongo.query.MongoQuery$MongoResultList.convertObject(MongoQuery.java:1378)
at
org.grails.datastore.gorm.query.AbstractResultList.convertObject(AbstractResultList.java:99)
at
org.grails.datastore.gorm.query.AbstractResultList.initializeFully(AbstractResultList.java:59)
at
org.grails.datastore.gorm.query.AbstractResultList.size(AbstractResultList.java:206)
at grails.gorm.PagedResultList.size(PagedResultList.java:114)
at grails.gorm.PagedResultList.isEmpty(PagedResultList.java:119)
at
org.grails.plugins.web.rest.render.html.DefaultHtmlRenderer.resolveModelVariableName(DefaultHtmlRenderer.groovy:105)
at
org.grails.plugins.web.rest.render.html.DefaultHtmlRenderer.applyModel(DefaultHtmlRenderer.groovy:88)
at
org.grails.plugins.web.rest.render.html.DefaultHtmlRenderer.render(DefaultHtmlRenderer.groovy:79)
at
grails.artefact.controller.RestResponder$Trait$Helper.internalRespond(RestResponder.groovy:216)
at
grails.artefact.controller.RestResponder$Trait$Helper.respond(RestResponder.groovy:83)
at <our index method of our system controller>
... 72 common frames omitted
```
As a solution we will try to use an older version of gorm, but if this
problem can be solved it would be great since it is always necessary to have
the latest. If you need more information, please comment and I will respond as
soon as possible.
Some details:
| Grails Version: 5.3.2
| JVM Version: 11.0.16.1
| Gorm version: 7.3.0
| Windows 11, with last updates.
(build.gradle)
implementation 'org.grails.plugins:mongodb:7.3.0'
implementation "org.grails:gorm-mongodb-spring-boot:7.3.0"
configurations.all {
// Genera conflictos, tomaba el 3.6 viejo.
exclude module:'mongodb-driver'
}
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]