Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having trouble serializing Ebean classes with Jackson module #8

Open
jamie-nxtgencare opened this issue Nov 14, 2017 · 6 comments
Open

Comments

@jamie-nxtgencare
Copy link

Hi.

I'm receiving the following error message when trying to serialize an enhanced ebean model object:

com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type <ebean type> (by serializer of type io.ebean.jackson.CommonBeanSerializer)

I'm using a library to store serialized objects in a cache, however, I've stripped all of my code down to the bare bones to try to troubleshoot this issue and can duplicate it simply with:

ObjectMapper mapper = new ObjectMapper().registerModule(new JacksonEbeanModule());
mapper.writeValueAsString(<new EbeanEntity()>);

That being said, I've been able to create a new entity the exact same way and convert it to JSON using eBeanServer.toJson(obj), so as far as I can tell there's nothing wrong with my object.

I'm also not sure if I have a version mismatch or something:

Ebean

compile 'io.ebean:ebean:11.3.1'
compile 'io.ebean:ebean-spring-txn:10.1.1'

ebean-jackson:
compile group: 'io.ebean', name: 'ebean-jackson', version: '10.1.1'

I'm bringing in jackson as a dependency to another jackson library:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.8'

It's version matches the version of that jar: 2.8.8.

Let me know if this is an issue on my end or an actual problem. If you need more details about the contents of my model objects, I'd be happy to oblige, however, I've tried this with a number of different models and they all have the same problem; tracing through the code, it never even calls CommonBeanSerializer.serialize, so it's not even getting to the point where it's looking at the contents of the object yet.

@rbygrave
Copy link
Member

Do you have the full stack trace? Can you include it?

@jamie-nxtgencare
Copy link
Author

Right now the first call to serialize data is populating a cache if it has nothing in it. This is on a Spring boot application ready event: @EventListener(ApplicationReadyEvent.class)

So you'll see that's most of the first part of the stack trace. I imagine the "caused by" part of this stack trace is the part you are concerned with, but this is the full trace.

java.lang.reflect.UndeclaredThrowableException: Failed to invoke event listener method
HandlerMethod details:
Bean [xxx.MyService$$EnhancerBySpringCGLIB$$a39dfff5]
Method [public void xxx.MyService.initCache() throws com.fasterxml.jackson.core.JsonProcessingException]
Resolved arguments:
       at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:270)
       at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:174)
       at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:137)
       at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
       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.boot.context.event.EventPublishingRunListener.finished(EventPublishingRunListener.java:101)
       at org.springframework.boot.SpringApplicationRunListeners.callFinishedListener(SpringApplicationRunListeners.java:79)
       at org.springframework.boot.SpringApplicationRunListeners.finished(SpringApplicationRunListeners.java:72)
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
       at xxx.Application.main(Application.java:15)
       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.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyEbeanModel (by serializer of type io.ebean.jackson.CommonBeanSerializer)
       at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284)
       at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110)
       at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135)
       at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:160)
       at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
       at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
       at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681)
       at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3057)
       at xxx.MyService.initCache(MyService.java:51)
       at xxx.MyService$$FastClassBySpringCGLIB$$272c8844.invoke(<generated>)
       at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
       at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:669)
       at xxx.MyService$$EnhancerBySpringCGLIB$$a39dfff5.initCache(<generated>)
       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.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:253)
       ... 18 common frames omitted```

@jamie-nxtgencare
Copy link
Author

Note also that I'm calling writeValueAsString to simplify what's happening. Previously, I was using Redisson, which is configured internally to use an ObjectMapper configured with JacksonEbeanModule. But the error is the same.

The following is a stacktrace in my intended usage, but it's pretty much the same, except Redisson is the one trying to do the serialization:

java.lang.IllegalArgumentException: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyModel (by serializer of type io.ebean.jackson.CommonBeanSerializer) (through reference chain: java.util.HashSet[0])
        at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:247)
        at org.redisson.RedissonMap.putAllOperationAsync(RedissonMap.java:310)
        at org.redisson.RedissonMap.putAllAsync(RedissonMap.java:275)
        at org.redisson.RedissonMap.putAll(RedissonMap.java:266)
        at xxx.MyService.initCache(MyService.java:48)
        at xxx.MyService$$FastClassBySpringCGLIB$$272c8844.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:669)
        at xxx.MyService$$EnhancerBySpringCGLIB$$258b4150.initCache(<generated>)
        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.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:253)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:174)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:137)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
        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.boot.context.event.EventPublishingRunListener.finished(EventPublishingRunListener.java:101)
        at org.springframework.boot.SpringApplicationRunListeners.callFinishedListener(SpringApplicationRunListeners.java:79)
        at org.springframework.boot.SpringApplicationRunListeners.finished(SpringApplicationRunListeners.java:72)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
        at xxx.Application.main(Application.java:15)
        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.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyModel (by serializer of type io.ebean.jackson.CommonBeanSerializer) (through reference chain: java.util.HashSet[0])
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284)
        at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110)
        at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135)
        at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:160)
        at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:151)
        at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:25)
        at com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase.serializeWithType(AsArraySerializerBase.java:263)
        at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
        at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681)
        at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:3014)
        at org.redisson.codec.JsonJacksonCodec$1.encode(JsonJacksonCodec.java:75)
        at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:245)
        ... 31 common frames omitted```

@jamie-nxtgencare
Copy link
Author

I believe I've traced down the source of the problem; even though I wasn't using Redisson for anything, I was still configuring the JsonJacksonCodec that it required, and I was supplying it with the same ObjectMapper I was using to serialize with by hand.

If I don't configure this object, the problem goes away. It appears as though configuring Redisson with this codec modifies the original object mapper passed into it and enables type inclusion. I'm trying to figure out what setting it is that it enables that causes mapping to no longer work; if I don't configure this codec and use a plain ObjectMapper, I don't even need ebean-jackson to serialize and deserialize my ebean objects.

@jamie-nxtgencare
Copy link
Author

jamie-nxtgencare commented Nov 15, 2017

So it looks like the objectMapper is configured to ignore everything but the fields:
objectMapper .setVisibilityChecker(objectMapper.getSerializationConfig().getDefaultVisibilityChecker() .withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withGetterVisibility(JsonAutoDetect.Visibility.NONE) .withSetterVisibility(JsonAutoDetect.Visibility.NONE) .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));

That causes an infinite recursion error when using an otherwise vanilla ObjectMapper (through the EbeanInterceptor field).

When I use the ebean-jackson module, this problem goes away, but unfortunately the codec also enables type inclusion:

TypeResolverBuilder<?> mapTyper = new DefaultTypeResolverBuilder(DefaultTyping.NON_FINAL) {
            public boolean useForType(JavaType t) {
                switch (_appliesFor) {
                case NON_CONCRETE_AND_ARRAYS:
                    while (t.isArrayType()) {
                        t = t.getContentType();
                    }
                    // fall through
                case OBJECT_AND_NON_CONCRETE:
                    return (t.getRawClass() == Object.class) || !t.isConcrete();
                case NON_FINAL:
                    while (t.isArrayType()) {
                        t = t.getContentType();
                    }
                    // to fix problem with wrong long to int conversion
                    if (t.getRawClass() == Long.class) {
                        return true;
                    }
                    if (t.getRawClass() == XMLGregorianCalendar.class) {
                        return false;
                    }
                    return !t.isFinal(); // includes Object.class
                default:
                    // case JAVA_LANG_OBJECT:
                    return (t.getRawClass() == Object.class);
                }
            }
        };
        mapTyper.init(JsonTypeInfo.Id.CLASS, null);
        mapTyper.inclusion(JsonTypeInfo.As.PROPERTY);
        mapper.setDefaultTyping(mapTyper);

Which I suspect is what this module can't handle.

In any case, I'm confident the problem isn't with ebean-jackson. Why this codec accepts an ObjectMapper and mangles it is beyond me (presumably just serializing things into a supported format for redis), but it seems to take quite a few liberties in usage expectations.

Thanks for looking into this.

@SympathyForTheDev
Copy link

SympathyForTheDev commented Aug 27, 2018

I have a workaround for this issue by using the @JsonAutoDetect annotation on an extended class of io.ebean.model like this

@MappedSuperclass
@JsonAutoDetect(setterVisibility = PUBLIC_ONLY, getterVisibility = PUBLIC_ONLY, creatorVisibility = PUBLIC_ONLY, fieldVisibility = PUBLIC_ONLY)
public abstract class BaseModel extends Model

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants