Skip to content

Is there a way to support GenericJackson2JsonRedisSerializer? #387

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

Closed
FalconIA opened this issue Feb 25, 2016 · 6 comments
Closed

Is there a way to support GenericJackson2JsonRedisSerializer? #387

FalconIA opened this issue Feb 25, 2016 · 6 comments
Assignees
Labels
for: stack-overflow A question that's better suited to stackoverflow.com in: redis

Comments

@FalconIA
Copy link

To free up memory, I try to use GenericJackson2JsonRedisSerializer instead.

@Bean
public RedisTemplate<String, ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, ExpiringSession> template = new RedisTemplate<>();
    template.setKeySerializer(template.getStringSerializer());
    template.setHashKeySerializer(template.getStringSerializer());
    template.setValueSerializer(template.getStringSerializer());
    template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    template.setConnectionFactory(connectionFactory);
    return template;
}

Then get a exception:

com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '@class' that is to contain type id  (for class java.lang.Object)
 at [Source: [B@5b44b124; line: 1, column: 283]
    com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:946)
    com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:149)
    com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:101)
    com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:165)
    com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:554)
    com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:42)
    com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
    com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2819)
    org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:117)
    org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:98)
    org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:316)
    org.springframework.data.redis.core.AbstractOperations.deserializeHashMap(AbstractOperations.java:277)
    org.springframework.data.redis.core.DefaultHashOperations.entries(DefaultHashOperations.java:227)
    org.springframework.data.redis.core.DefaultBoundHashOperations.entries(DefaultBoundHashOperations.java:101)
    org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:233)
    org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:220)
    org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:141)
    org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:276)
    org.jasig.cas.client.authentication.AuthenticationFilter.doFilter(AuthenticationFilter.java:103)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:76)
    org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:125)
    org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:65)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

Here is a hash value session example:

{"validFromDate":["java.util.Date",1456392459037],"validUntilDate":null,"attributes":{"@class":"java.util.Collections$EmptyMap"},"principal":{"@class":"org.jasig.cas.client.authentication.AttributePrincipalImpl","name":"cas","attributes":{"@class":"java.util.Collections$EmptyMap"}}}
@FalconIA FalconIA changed the title Is a way to support GenericJackson2JsonRedisSerializer? Is there a way to support GenericJackson2JsonRedisSerializer? Feb 25, 2016
@rwinch
Copy link
Member

rwinch commented Feb 26, 2016

Thanks for the report!

I think maybe someone from the Spring Data Redis team might be better qualified to answer this. @christophstrobl Any ideas?

@christophstrobl
Copy link
Member

MapSession cannot be mapped by jackson without customizing the ObectMapper. Still GenericJackson2JsonRedisSerializer can use a preconfigured ObjectMapper instance.
Please have a look at the follwoing configuration using a MixIn.

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
abstract class MixIn {

    @JsonIgnore
    abstract boolean isExpired();
}

// ...

ObjectMapper mapper = new ObjectMapper()
mapper.setVisibility(
    mapper.getVisibilityChecker()
        .withFieldVisibility(Visibility.ANY)
        .withGetterVisibility(Visibility.NONE)
        .withSetterVisibility(Visibility.NONE)
        .withCreatorVisibility(Visibility.NONE));

mapper.addMixIn(MapSession.class, MixIn.class); // register the MixIn
mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, "@class"); // enable default typing

GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(mapper);

Additional customizations might be required for sepecific session content.

@rwinch
Copy link
Member

rwinch commented Feb 29, 2016

@christophstrobl Thanks for the very detailed answer!

@FalconIA Can you respond if this answers your question?

@rwinch rwinch added the status: waiting-for-feedback We need additional information before we can continue label Feb 29, 2016
@FalconIA
Copy link
Author

FalconIA commented Mar 1, 2016

@christophstrobl Thanks for your answer.
Just know that serialization can be configured using mix-in feature.

I try to customize ObectMapper in my project.
I don't registere MapSession with mix-in.
I just register several min-in class to configure class used in session attributes.

@Bean
public RedisTemplate<String, ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, ExpiringSession> template = new RedisTemplate<>();
    template.setKeySerializer(template.getStringSerializer());
    template.setHashKeySerializer(template.getStringSerializer());
    template.setValueSerializer(template.getStringSerializer());
    template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer(customObjectMapper()));
    template.setConnectionFactory(connectionFactory);
    return template;
}

private ObjectMapper customObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(
            mapper.getVisibilityChecker()
                    .withFieldVisibility(Visibility.ANY)
                    .withGetterVisibility(Visibility.NONE)
                    .withSetterVisibility(Visibility.NONE)
                    .withCreatorVisibility(Visibility.NONE));
    mapper.addMixIn(AssertionImpl.class, AssertionMixin.class); // register the MixIn
    mapper.addMixIn(AttributePrincipalImpl.class, AttributePrincipalMixin.class); // register the MixIn
    mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL, "@class"); // enable default typing
    return mapper;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public static abstract class AssertionMixin {

    @JsonCreator
    AssertionMixin(final @JsonProperty("principal") AttributePrincipal principal) {
    }
}

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public static abstract class AttributePrincipalMixin {

    @JsonCreator
    AttributePrincipalMixin(final @JsonProperty("name") String name) {
    }
}

It works well!
@rwinch @christophstrobl Thanks for your help.

@christophstrobl
Copy link
Member

thanks @FalconIA for letting us know! glad it works!

@rwinch
Copy link
Member

rwinch commented Mar 1, 2016

@FalconIA Awesome! Given your feedback I'm going to close this.

@rwinch rwinch closed this as completed Mar 1, 2016
@rwinch rwinch removed this from the 1.1.1 milestone Mar 1, 2016
@rwinch rwinch added for: stack-overflow A question that's better suited to stackoverflow.com and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 1, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: stack-overflow A question that's better suited to stackoverflow.com in: redis
Projects
None yet
Development

No branches or pull requests

3 participants