[JAVA-2709] Pojo with method returning an Enum - NPE / "Can't find a codec for class" Created: 15/Dec/17  Updated: 20/Dec/17  Resolved: 19/Dec/17

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: 3.6.0
Fix Version/s: 3.6.1

Type: Bug Priority: Major - P3
Reporter: aneta stępień Assignee: Ross Lawley
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
is duplicated by JAVA-2714 EnumCodec stop to work Closed

 Description   

Using Enums with the PojoCodecProvider.Builder.automatic(true) causes a NPE.

Was
======

Hi again,
I'm trying to use java mongo drive 3.6 to save pojo's in db.
However, if my pojo contains method:

@BsonIgnore
public MyEnum getSomeValue() { ... compute and return value... }

I get NullPointerException in PojoCodecImpl:shouldSpecialize (:332)
In typeParameterMap there is: "cachedConstructor" : "0=0"
but when the code looks for the "cachedConstructor" in classModel.getPropertyModel ,it finds nothing. So classModel.getPropertyModel((String)entry.getKey()).getCodec() ends in NPE.

If I change the name of the metod (so that it is not mistaken with a getter):

@BsonIgnore
public MyEnum someValue() { ... compute and return value... }

I get a
Error: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class

If I remove this method from my pojo, everything works fine.
In general I assume I can have multiple methods that are not getters and setters - and that they will not interfere with saving the pojo. I have added @BsonIgnore to be sure - but it seems that this method is not ignored.



 Comments   
Comment by Githook User [ 20/Dec/17 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross.lawley@gmail.com', 'username': 'rozza'}

Message: Fix PojoCodec NPE with automatic mapping of Enums

JAVA-2709
Branch: 3.6.x
https://github.com/mongodb/mongo-java-driver/commit/546ef898d64e8cf1cc9db16c8d772fa814555aff

Comment by Githook User [ 19/Dec/17 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross.lawley@gmail.com', 'username': 'rozza'}

Message: Fix PojoCodec NPE with automatic mapping of Enums

JAVA-2709
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/00a8966bf053672498df86b916c3174007f62f14

Comment by Ross Lawley [ 15/Dec/17 ]

Thanks jcodagnone it seems the automatic(true) flag tries to make a ClassModel for the Enum.

We'll look to fix for 3.6.1

Ross

Comment by Juan F. Codagnone [ 15/Dec/17 ]

Hi,

I'm getting the (same?) NPE when activating the automatic creation of ClassModel.

At some point during the construction of PojoCodeImpl of the enum during the specialize() it goes fishing for declaringClass as a bean property (which is not).

I was able to reproduce on PojoCustomTest on 2aed4786df048ba5545cf3976c5a29e19f5db66f (Thu Dec 14 12:16:23 2017 +0000)

PropertyModel{propertyName='declaringClass', readName='declaringClass', writeName='declaringClass', typeData=TypeData{type=Class, typeParameters=[SimpleEnum] }}

diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java
index cc2f9f734..0362ac5d2 100644
--- a/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java
+++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java
@@ -261,6 +261,13 @@ public final class PojoCustomTest extends PojoTestCase {
                 fromCodecs(new SimpleEnumCodec()));
         roundTrip(registry, new SimpleEnumModel(SimpleEnum.BRAVO), "{ 'myEnum': 1 }");
     }
+    
+    @Test
+    public void testEnumWithAutomaticCodec() {
+        CodecRegistry registry = fromRegistries(getCodecRegistry(getPojoCodecProviderBuilder(SimpleEnumModel.class).automatic(true)),
+                fromCodecs(new SimpleEnumCodec()));
+        roundTrip(registry, new SimpleEnumModel(SimpleEnum.BRAVO), "{ 'myEnum': 1 }");
+    }
 
     @Test
     @SuppressWarnings("unchecked")

Edit Added the stacktrace:

java.lang.NullPointerException
	at org.bson.codecs.pojo.PojoCodecImpl.shouldSpecialize(PojoCodecImpl.java:337)
	at org.bson.codecs.pojo.PojoCodecImpl.<init>(PojoCodecImpl.java:59)
	at org.bson.codecs.pojo.PojoCodecProvider.getPojoCodec(PojoCodecProvider.java:90)
	at org.bson.codecs.pojo.PojoCodecProvider.get(PojoCodecProvider.java:68)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:43)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55)
	at org.bson.codecs.configuration.ChildCodecRegistry.get(ChildCodecRegistry.java:51)
	at org.bson.codecs.configuration.CodecRegistries$1.get(CodecRegistries.java:141)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
	at org.bson.codecs.pojo.FallbackPropertyCodecProvider.get(FallbackPropertyCodecProvider.java:38)
	at org.bson.codecs.pojo.PropertyCodecRegistryImpl.get(PropertyCodecRegistryImpl.java:44)
	at org.bson.codecs.pojo.PojoCodecImpl.addToCache(PojoCodecImpl.java:204)
	at org.bson.codecs.pojo.PojoCodecImpl.specialize(PojoCodecImpl.java:79)
	at org.bson.codecs.pojo.PojoCodecImpl.<init>(PojoCodecImpl.java:60)
	at org.bson.codecs.pojo.PojoCodecProvider.getPojoCodec(PojoCodecProvider.java:90)
	at org.bson.codecs.pojo.PojoCodecProvider.get(PojoCodecProvider.java:68)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:43)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55)
	at org.bson.codecs.configuration.ChildCodecRegistry.get(ChildCodecRegistry.java:51)
	at org.bson.codecs.configuration.CodecRegistries$1.get(CodecRegistries.java:141)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
	at org.bson.codecs.pojo.EnumPropertyCodecProvider.get(EnumPropertyCodecProvider.java:41)
	at org.bson.codecs.pojo.PropertyCodecRegistryImpl.get(PropertyCodecRegistryImpl.java:44)
	at org.bson.codecs.pojo.PojoCodecImpl.addToCache(PojoCodecImpl.java:204)
	at org.bson.codecs.pojo.PojoCodecImpl.specialize(PojoCodecImpl.java:79)
	at org.bson.codecs.pojo.PojoCodecImpl.<init>(PojoCodecImpl.java:60)
	at org.bson.codecs.pojo.PojoCodecProvider.getPojoCodec(PojoCodecProvider.java:79)
	at org.bson.codecs.pojo.PojoCodecProvider.get(PojoCodecProvider.java:68)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:43)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55)
	at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
	at org.bson.codecs.pojo.PojoTestCase.encodesTo(PojoTestCase.java:105)
	at org.bson.codecs.pojo.PojoTestCase.roundTrip(PojoTestCase.java:95)
	at org.bson.codecs.pojo.PojoCustomTest.testEnumWithAutomaticCodec(PojoCustomTest.java:269)
	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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:539)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:761)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:461)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:207)
 

Comment by Ross Lawley [ 15/Dec/17 ]

Hi Aneta Stępień,

I'm still unable to reproduce the error on the 3.6.0 version of the driver. In fact the code above works as expected and if I set a default simpleEnum value, then the property myEnum is encoded but the field simpleEnum is ignored - as per the annotation.

Looks like there is something else missing, that is causing the error. Could you output the full stacktrace? Perhaps that will throw some light on the issue.

Ross

Comment by aneta stępień [ 15/Dec/17 ]

Thank you for quick response!

You are right, your code works. I checked the differences and found the one that matters - the enum exists in the abstract class, which my pojo extends. Here is a simplified code that results in the same error:

public abstract class ComplexEnumIgnoredModel {
 
    private Integer myInteger;
 
    @BsonIgnore
    private SimpleEnum simpleEnum;
 
    public SimpleEnum getMyEnum(){
        return simpleEnum;
    }
 
    public Integer getMyInteger() {
        return myInteger;
    }
 
    public void setMyInteger(Integer myInteger) {
        this.myInteger = myInteger;
    }
 
}

public class ComplexEnumIgnoredModelConcrete extends ComplexEnumIgnoredModel {
 
    public ComplexEnumIgnoredModelConcrete() {
    }
}

One note - in the real example there si no field with this enum - only a method that calculates it:

@BsonIgnore
public SimpleEnum getEnumValue() {
        return SimpleEnum.getByName(this.getClass().getSimpleName());
}

Comment by Ross Lawley [ 15/Dec/17 ]

Hi Aneta Stępień,

Thanks for the ticket, sorry to hear you're having an issue. Can I confirm this is with the 3.6.0 version of the java driver? I was able to encode and decode the following POJO:

package org.bson.codecs.pojo.entities;
 
import org.bson.codecs.pojo.annotations.BsonIgnore;
 
public final class SimpleEnumIgnoredModel {
 
    @BsonIgnore
    private SimpleEnum myEnum;
    private Integer myInteger;
 
    public SimpleEnumIgnoredModel() {
    }
 
    public SimpleEnumIgnoredModel(final SimpleEnum myEnum, final Integer myInteger) {
        this.myEnum = myEnum;
        this.myInteger = myInteger;
    }
 
    public SimpleEnum getMyEnum() {
        return myEnum;
    }
 
    public void setMyEnum(final SimpleEnum myEnum) {
        this.myEnum = myEnum;
    }
 
    public Integer getMyInteger() {
        return myInteger;
    }
 
    public void setMyInteger(final Integer myInteger) {
        this.myInteger = myInteger;
    }
 
    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 
        SimpleEnumIgnoredModel that = (SimpleEnumIgnoredModel) o;
 
        if (getMyEnum() != that.getMyEnum()) return false;
        return getMyInteger() != null ? getMyInteger().equals(that.getMyInteger()) : that.getMyInteger() == null;
    }
 
    @Override
    public int hashCode() {
        int result = getMyEnum() != null ? getMyEnum().hashCode() : 0;
        result = 31 * result + (getMyInteger() != null ? getMyInteger().hashCode() : 0);
        return result;
    }
}

It created a Document in the shape of: {myInteger: 1}.

If you could provide more information on the POJO, so that I can reproduce the error that would be great.

Ross

Generated at Thu Feb 08 08:57:53 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.