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

org.redisson.codec.ProtobufCodec 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 com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
import com.google.protobuf.MessageLite;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import org.redisson.client.codec.BaseCodec;
import org.redisson.client.codec.Codec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class ProtobufCodec extends BaseCodec {
    private final Class mapKeyClass;
    private final Class mapValueClass;
    private final Class valueClass;

    //classes in blacklist will not be serialized using protobuf ,but instead will use blacklistCodec
    private final Set protobufBlacklist;
    //default value is JsonJacksonCodec
    private final Codec blacklistCodec;

    public ProtobufCodec(Class mapKeyClass, Class mapValueClass) {
        this(mapKeyClass, mapValueClass, null, null);
    }

    /**
     * @param blacklistCodec classes in protobufBlacklist will use this codec
     */
    public ProtobufCodec(Class mapKeyClass, Class mapValueClass, Codec blacklistCodec) {
        this(mapKeyClass, mapValueClass, null, blacklistCodec);
    }

    public ProtobufCodec(Class valueClass) {
        this(null, null, valueClass, null);
    }

    /**
     * @param blacklistCodec classes in protobufBlacklist will use this codec
     */
    public ProtobufCodec(Class valueClass, Codec blacklistCodec) {
        this(null, null, valueClass, blacklistCodec);
    }

    private ProtobufCodec(Class mapKeyClass, Class mapValueClass, Class valueClass, Codec blacklistCodec) {
        this.mapKeyClass = mapKeyClass;
        this.mapValueClass = mapValueClass;
        this.valueClass = valueClass;
        if (blacklistCodec == null) {
            this.blacklistCodec = new JsonJacksonCodec();
        } else {
            if (blacklistCodec instanceof ProtobufCodec) {
                //will loop infinitely when encode or decode
                throw new IllegalArgumentException("BlacklistCodec can not be ProtobufCodec");
            }
            this.blacklistCodec = blacklistCodec;
        }

        protobufBlacklist = new HashSet<>();
        protobufBlacklist.addAll(BasicSerializerFactoryConcreteGetter.getConcreteKeySet());
        protobufBlacklist.add(ArrayList.class.getName());
        protobufBlacklist.add(HashSet.class.getName());
        protobufBlacklist.add(HashMap.class.getName());
    }

    public void addBlacklist(Class clazz) {
        protobufBlacklist.add(clazz.getName());
    }

    public void removeBlacklist(Class clazz) {
        protobufBlacklist.remove(clazz.getName());
    }

    @Override
    public Decoder getValueDecoder() {
        return createDecoder(valueClass, blacklistCodec.getValueDecoder());
    }

    @Override
    public Encoder getValueEncoder() {
        return createEncoder(valueClass, blacklistCodec.getValueEncoder());
    }

    @Override
    public Decoder getMapValueDecoder() {
        return createDecoder(mapValueClass, blacklistCodec.getMapValueDecoder());
    }

    @Override
    public Encoder getMapValueEncoder() {
        return createEncoder(mapValueClass, blacklistCodec.getMapValueEncoder());
    }

    @Override
    public Decoder getMapKeyDecoder() {
        return createDecoder(mapKeyClass, blacklistCodec.getMapKeyDecoder());
    }

    @Override
    public Encoder getMapKeyEncoder() {
        return createEncoder(mapKeyClass, blacklistCodec.getMapKeyEncoder());
    }

    private Decoder createDecoder(Class clazz, Decoder blacklistDecoder) {
        if (clazz == null) {
            throw new IllegalArgumentException("class to create protobuf decoder can not be null");
        }

        return new Decoder() {
            @Override
            public Object decode(ByteBuf buf, State state) throws IOException {
                //use blacklistDecoder
                if (protobufBlacklist.contains(clazz.getName())) {
                    return blacklistDecoder.decode(buf, state);
                }

                InputStream is = new ByteBufInputStream(buf);
                if (MessageLite.class.isAssignableFrom(clazz)) {
                    //native deserialize
                    try {
                        return clazz.getDeclaredMethod("parseFrom", InputStream.class).invoke(clazz, is);
                    } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
                        throw new IOException(e);
                    }
                } else {
                    //protostuff
                    return ProtostuffUtils.deserialize(is, clazz);
                }
            }
        };
    }

    private Encoder createEncoder(Class clazz, Encoder blacklistEncoder) {
        if (clazz == null) {
            throw new IllegalArgumentException("class to create protobuf encoder can not be null");
        }
        return new Encoder() {
            @Override
            public ByteBuf encode(Object in) throws IOException {
                //use blacklistEncoder
                if (protobufBlacklist.contains(clazz.getName())) {
                    return blacklistEncoder.encode(in);
                }

                ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
                if (MessageLite.class.isAssignableFrom(clazz)) {
                    //native serialize
                    out.writeBytes(((MessageLite) in).toByteArray());
                } else {
                    //protostuff
                    ByteBufOutputStream os = new ByteBufOutputStream(out);
                    ProtostuffUtils.serialize(os, in);
                }
                return out;
            }
        };
    }

    private static final class ProtostuffUtils {

        @SuppressWarnings("unchecked")
        public static  void serialize(OutputStream os, T obj) throws IOException {
            LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
            try {
                ProtostuffIOUtil.writeTo(os, obj, RuntimeSchema.getSchema((Class) obj.getClass()), buffer);
            } finally {
                buffer.clear();
            }
        }

        public static  T deserialize(InputStream is, Class clazz) throws IOException {
            Schema schema = RuntimeSchema.getSchema(clazz);
            T obj = schema.newMessage();
            ProtostuffIOUtil.mergeFrom(is, obj, schema);
            return obj;
        }

    }

    private abstract static class BasicSerializerFactoryConcreteGetter extends BasicSerializerFactory {
        protected BasicSerializerFactoryConcreteGetter(SerializerFactoryConfig config) {
            super(config);
        }

        private static Set getConcreteKeySet() {
            Set concreteKeySet = new HashSet<>();
            if (_concrete != null && !_concrete.isEmpty()) {
                concreteKeySet.addAll(_concrete.keySet());
            }
            if (_concreteLazy != null && !_concreteLazy.isEmpty()) {
                concreteKeySet.addAll(_concreteLazy.keySet());
            }
            return concreteKeySet;
        }
    }
}