com.haulmont.cuba.core.sys.serialization.KryoSerialization.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.sys.serialization.KryoSerialization.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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 com.haulmont.cuba.core.sys.serialization;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.CollectionSerializer;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.kryo.serializers.JavaSerializer;
import com.esotericsoftware.kryo.util.ObjectMap;
import com.esotericsoftware.kryo.util.Util;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.haulmont.chile.core.model.impl.MetaClassImpl;
import com.haulmont.chile.core.model.impl.MetaPropertyImpl;
import com.haulmont.cuba.core.entity.BaseEntityInternalAccess;
import com.haulmont.cuba.core.entity.BaseGenericIdEntity;
import de.javakaffee.kryoserializers.*;
import de.javakaffee.kryoserializers.cglib.CGLibProxySerializer;
import de.javakaffee.kryoserializers.guava.ImmutableListSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableMapSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableMultimapSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableSetSerializer;
import org.apache.commons.lang.ClassUtils;
import org.eclipse.persistence.indirection.IndirectCollection;
import org.eclipse.persistence.indirection.IndirectContainer;
import org.eclipse.persistence.internal.indirection.UnitOfWorkQueryValueHolder;
import org.eclipse.persistence.internal.indirection.jdk8.IndirectList;
import org.eclipse.persistence.internal.indirection.jdk8.IndirectMap;
import org.eclipse.persistence.internal.indirection.jdk8.IndirectSet;
import org.objenesis.instantiator.ObjectInstantiator;
import org.objenesis.strategy.InstantiatorStrategy;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * The serialization implementation using Kryo serialization
 */
public class KryoSerialization implements Serialization {

    protected static final Logger log = LoggerFactory.getLogger(KryoSerialization.class);

    protected static final List<String> INCLUDED_VALUE_HOLDER_FIELDS = Arrays.asList("value", "isInstantiated",
            "mapping", "sourceAttributeName", "relationshipSourceObject");

    protected boolean onlySerializable = true;
    protected final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
        @Override
        protected Kryo initialValue() {
            return newKryoInstance();
        }
    };

    public KryoSerialization() {
    }

    public KryoSerialization(boolean onlySerializable) {
        this.onlySerializable = onlySerializable;
    }

    protected Kryo newKryoInstance() {
        Kryo kryo = new Kryo();
        kryo.setInstantiatorStrategy(new CubaInstantiatorStrategy());
        if (onlySerializable) {
            kryo.setDefaultSerializer(CubaFieldSerializer.class);
        }

        //To work properly must itself be loaded by the application classloader (i.e. by classloader capable of loading
        //all the other application classes). For web application it means placing this class inside webapp folder.
        kryo.setClassLoader(KryoSerialization.class.getClassLoader());

        kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
        kryo.register(Collections.EMPTY_LIST.getClass(), new CollectionsEmptyListSerializer());
        kryo.register(Collections.EMPTY_MAP.getClass(), new CollectionsEmptyMapSerializer());
        kryo.register(Collections.EMPTY_SET.getClass(), new CollectionsEmptySetSerializer());
        kryo.register(Collections.singletonList("").getClass(), new CollectionsSingletonListSerializer());
        kryo.register(Collections.singleton("").getClass(), new CollectionsSingletonSetSerializer());
        kryo.register(Collections.singletonMap("", "").getClass(), new CollectionsSingletonMapSerializer());
        kryo.register(BitSet.class, new BitSetSerializer());
        kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer());
        kryo.register(InvocationHandler.class, new JdkProxySerializer());
        UnmodifiableCollectionsSerializer.registerSerializers(kryo);
        SynchronizedCollectionsSerializer.registerSerializers(kryo);

        kryo.register(CGLibProxySerializer.CGLibProxyMarker.class, new CGLibProxySerializer());
        ImmutableListSerializer.registerSerializers(kryo);
        ImmutableSetSerializer.registerSerializers(kryo);
        ImmutableMapSerializer.registerSerializers(kryo);
        ImmutableMultimapSerializer.registerSerializers(kryo);
        kryo.register(IndirectList.class, new IndirectContainerSerializer());
        kryo.register(IndirectMap.class, new IndirectContainerSerializer());
        kryo.register(IndirectSet.class, new IndirectContainerSerializer());

        kryo.register(org.eclipse.persistence.indirection.IndirectList.class, new IndirectContainerSerializer());
        kryo.register(org.eclipse.persistence.indirection.IndirectMap.class, new IndirectContainerSerializer());
        kryo.register(org.eclipse.persistence.indirection.IndirectSet.class, new IndirectContainerSerializer());

        //classes with custom serialization methods
        kryo.register(HashMultimap.class, new CubaJavaSerializer());
        kryo.register(ArrayListMultimap.class, new CubaJavaSerializer());
        kryo.register(MetaClassImpl.class, new CubaJavaSerializer());
        kryo.register(MetaPropertyImpl.class, new CubaJavaSerializer());
        kryo.register(UnitOfWorkQueryValueHolder.class, new UnitOfWorkQueryValueHolderSerializer(kryo));

        return kryo;
    }

    @Override
    @SuppressWarnings("finally")
    public void serialize(Object object, OutputStream os) {
        try (Output output = new CubaOutput(os)) {
            if (object instanceof BaseGenericIdEntity
                    && BaseEntityInternalAccess.isManaged((BaseGenericIdEntity) object)) {
                BaseEntityInternalAccess.setDetached((BaseGenericIdEntity) object, true);
            }
            kryos.get().writeClassAndObject(output, object);
        }
    }

    @Override
    public Object deserialize(InputStream is) {
        try (Input input = new Input(is)) {
            return kryos.get().readClassAndObject(input);
        }
    }

    @Override
    public byte[] serialize(Object object) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        serialize(object, bos);
        return bos.toByteArray();
    }

    @Override
    public Object deserialize(byte[] bytes) {
        if (bytes == null) {
            return null;
        }

        return deserialize(new ByteArrayInputStream(bytes));
    }

    public Object copy(Object object) {
        if (object == null) {
            return null;
        }
        return kryos.get().copy(object);
    }

    public static class IndirectContainerSerializer extends CollectionSerializer {
        @Override
        public void write(Kryo kryo, Output output, Collection collection) {
            boolean isNotInstantiated = collection instanceof IndirectContainer
                    && !((IndirectContainer) collection).isInstantiated();
            output.writeBoolean(isNotInstantiated);
            if (!isNotInstantiated) {
                super.write(kryo, output, collection);
            }
        }

        @Override
        public Collection read(Kryo kryo, Input input, Class<Collection> type) {
            boolean isNotInstantiated = input.readBoolean();
            if (!isNotInstantiated) {
                return super.read(kryo, input, type);
            } else {
                IndirectCollection indirectCollection = (IndirectCollection) kryo.newInstance((Class) type);
                indirectCollection.setValueHolder(new UnfetchedValueHolder());
                return (Collection) indirectCollection;
            }
        }
    }

    public static class CubaJavaSerializer extends JavaSerializer {
        @Override
        public Object read(Kryo kryo, Input input, Class type) {
            try {
                ObjectMap graphContext = kryo.getGraphContext();
                ObjectInputStream objectStream = (ObjectInputStream) graphContext.get(this);
                if (objectStream == null) {
                    objectStream = new ObjectInputStream(input) {
                        @Override
                        protected Class<?> resolveClass(ObjectStreamClass desc)
                                throws IOException, ClassNotFoundException {
                            return ClassUtils.getClass(KryoSerialization.class.getClassLoader(), desc.getName());
                        }
                    };
                    graphContext.put(this, objectStream);
                }
                return objectStream.readObject();
            } catch (Exception ex) {
                throw new KryoException("Error during Java deserialization.", ex);
            }
        }
    }

    public class UnitOfWorkQueryValueHolderSerializer extends KryoSerialization.CubaFieldSerializer {

        public UnitOfWorkQueryValueHolderSerializer(Kryo kryo) {
            super(kryo, UnitOfWorkQueryValueHolder.class);
        }

        @Override
        protected void rebuildCachedFields(boolean minorRebuild) {
            super.rebuildCachedFields(minorRebuild);
            List<CachedField> excludedFields = Arrays.stream(getFields())
                    .filter(cachedField -> !INCLUDED_VALUE_HOLDER_FIELDS.contains(cachedField.getField().getName()))
                    .collect(Collectors.toList());
            excludedFields.forEach(this::removeField);
        }

        @Override
        @SuppressWarnings("unchecked")
        public void write(Kryo kryo, Output output, Object object) {
            boolean isNotInstantiated = object instanceof UnitOfWorkQueryValueHolder
                    && !((UnitOfWorkQueryValueHolder) object).isInstantiated();
            output.writeBoolean(isNotInstantiated);
            if (!isNotInstantiated) {
                super.write(kryo, output, object);
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public Object read(Kryo kryo, Input input, Class type) {
            boolean isNotInstantiated = input.readBoolean();
            if (!isNotInstantiated) {
                return super.read(kryo, input, type);
            } else {
                return new UnfetchedWeavedAttributeValueHolder();
            }
        }
    }

    /**
     * Strategy first tries to find and use a no-arg constructor and if it fails to do so, it should try to use
     * {@link StdInstantiatorStrategy} as a fallback, because this one does not invoke any constructor at all.
     * Strategy is a copy {@link Kryo.DefaultInstantiatorStrategy}, but if instantiation fails, use fallback to create object
     */
    public static class CubaInstantiatorStrategy implements org.objenesis.strategy.InstantiatorStrategy {
        private final InstantiatorStrategy fallbackStrategy = new StdInstantiatorStrategy();

        @Override
        public ObjectInstantiator newInstantiatorOf(Class type) {
            // Use ReflectASM if the class is not a non-static member class.
            Class enclosingType = type.getEnclosingClass();
            boolean isNonStaticMemberClass = enclosingType != null && type.isMemberClass()
                    && !Modifier.isStatic(type.getModifiers());
            if (!isNonStaticMemberClass) {
                try {
                    final ConstructorAccess access = ConstructorAccess.get(type);
                    return () -> {
                        try {
                            return access.newInstance();
                        } catch (Exception ex) {
                            if (log.isTraceEnabled()) {
                                log.trace("Unable instantiate class {}", Util.className(type), ex);
                            }
                            return fallbackStrategy.newInstantiatorOf(type).newInstance();
                        }
                    };
                } catch (Exception ignored) {
                }
            }
            // Reflection.
            try {
                Constructor ctor;
                try {
                    ctor = type.getConstructor((Class[]) null);
                } catch (Exception ex) {
                    ctor = type.getDeclaredConstructor((Class[]) null);
                    ctor.setAccessible(true);
                }
                final Constructor constructor = ctor;
                return () -> {
                    try {
                        return constructor.newInstance();
                    } catch (Exception ex) {
                        if (log.isTraceEnabled()) {
                            log.trace("Unable instantiate class {}", Util.className(type), ex);
                        }
                        return fallbackStrategy.newInstantiatorOf(type).newInstance();
                    }
                };
            } catch (Exception ignored) {
            }
            return fallbackStrategy.newInstantiatorOf(type);
        }
    }

    public static class CubaFieldSerializer<T> extends FieldSerializer<T> {
        public CubaFieldSerializer(Kryo kryo, Class type) {
            super(kryo, type);
        }

        public CubaFieldSerializer(Kryo kryo, Class type, Class[] generics) {
            super(kryo, type, generics);
        }

        @Override
        protected T create(Kryo kryo, Input input, Class<T> type) {
            checkIncorrectClass(type);
            return super.create(kryo, input, type);
        }

        @Override
        public void write(Kryo kryo, Output output, T object) {
            checkIncorrectObject(object);
            super.write(kryo, output, object);
        }

        @Override
        public T read(Kryo kryo, Input input, Class<T> type) {
            checkIncorrectClass(type);
            return super.read(kryo, input, type);

        }

        protected void checkIncorrectClass(Class type) {
            if (type != null && !Serializable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(String.format(
                        "Class is not registered: %s\nNote: To register this class use: kryo.register(\"%s\".class);",
                        Util.className(type), Util.className(type)));
            }
        }

        protected void checkIncorrectObject(T object) {
            if (object != null && !(object instanceof Serializable)) {
                String className = Util.className(object.getClass());
                throw new IllegalArgumentException(String.format(
                        "Class is not registered: %s\nNote: To register this class use: kryo.register(\"%s\".class);",
                        className, className));
            }
        }
    }

    public static class CubaOutput extends Output {

        public CubaOutput(OutputStream outputStream) {
            super(outputStream);
        }

        @Override
        public void close() {
            //Prevent close stream. Stream closed only by:
            //com.haulmont.cuba.core.sys.remoting.HttpServiceExporter,
            //com.haulmont.cuba.core.sys.remoting.ClusteredHttpInvokerRequestExecutor()
            //Only flush buffer to output stream
            flush();
        }
    }
}