All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.codec.JsonJacksonCodec Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2024 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson.codec;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.datatype.XMLGregorianCalendar;

import org.redisson.client.codec.BaseCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.type.TypeFactory;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;

/**
 * Json codec based on Jackson implementation.
 * https://github.com/FasterXML/jackson
 * 

* Fully thread-safe. * * @see org.redisson.codec.CborJacksonCodec * @see org.redisson.codec.MsgPackJacksonCodec * * @author Nikita Koksharov * */ public class JsonJacksonCodec extends BaseCodec { public static final JsonJacksonCodec INSTANCE = new JsonJacksonCodec(); @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id") @JsonAutoDetect(fieldVisibility = Visibility.NON_PRIVATE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE) public static class ThrowableMixIn { } protected final ObjectMapper mapObjectMapper; private final Encoder encoder = new Encoder() { @Override public ByteBuf encode(Object in) throws IOException { ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); try { ByteBufOutputStream os = new ByteBufOutputStream(out); mapObjectMapper.writeValue((OutputStream) os, in); return os.buffer(); } catch (IOException e) { out.release(); throw e; } catch (Exception e) { out.release(); throw new IOException(e); } } }; private final Decoder decoder = new Decoder() { @Override public Object decode(ByteBuf buf, State state) throws IOException { return mapObjectMapper.readValue((InputStream) new ByteBufInputStream(buf), Object.class); } }; public JsonJacksonCodec() { this(new ObjectMapper()); } public JsonJacksonCodec(ClassLoader classLoader) { this(createObjectMapper(classLoader, new ObjectMapper())); } public JsonJacksonCodec(ClassLoader classLoader, JsonJacksonCodec codec) { this(createObjectMapper(classLoader, codec.mapObjectMapper.copy())); } private static boolean warmedup = false; private void warmup() { if (getValueEncoder() == null || getValueDecoder() == null || warmedup) { return; } warmedup = true; ByteBuf d = null; try { d = getValueEncoder().encode("testValue"); getValueDecoder().decode(d, null); } catch (IOException e) { e.printStackTrace(); } finally { if (d != null) { d.release(); } } } protected static ObjectMapper createObjectMapper(ClassLoader classLoader, ObjectMapper om) { TypeFactory tf = om.getTypeFactory().withClassLoader(classLoader); om.setTypeFactory(tf); return om; } public JsonJacksonCodec(ObjectMapper mapObjectMapper) { this(mapObjectMapper, true); warmup(); } public JsonJacksonCodec(ObjectMapper mapObjectMapper, boolean copy) { if (copy) { this.mapObjectMapper = mapObjectMapper.copy(); } else { this.mapObjectMapper = mapObjectMapper; } init(this.mapObjectMapper); initTypeInclusion(this.mapObjectMapper); warmup(); } protected void initTypeInclusion(ObjectMapper mapObjectMapper) { 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); mapObjectMapper.setDefaultTyping(mapTyper); } protected void init(ObjectMapper objectMapper) { objectMapper.setSerializationInclusion(Include.NON_NULL); objectMapper.setVisibility(objectMapper.getSerializationConfig() .getDefaultVisibilityChecker() .withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withGetterVisibility(JsonAutoDetect.Visibility.NONE) .withSetterVisibility(JsonAutoDetect.Visibility.NONE) .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.enable(Feature.WRITE_BIGDECIMAL_AS_PLAIN); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); } @Override public Decoder getValueDecoder() { return decoder; } @Override public Encoder getValueEncoder() { return encoder; } @Override public ClassLoader getClassLoader() { if (mapObjectMapper.getTypeFactory().getClassLoader() != null) { return mapObjectMapper.getTypeFactory().getClassLoader(); } return super.getClassLoader(); } public ObjectMapper getObjectMapper() { return mapObjectMapper; } }