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

de.bild.codec.PojoCodecProvider Maven / Gradle / Ivy

Go to download

A very fast POJO codec for MongoDB (used in conjunction with the Mongo Java Driver) that handles generic types as well as polymorphic class hierarchies

There is a newer version: 2.8.2
Show newest version
package de.bild.codec;


import de.bild.codec.annotations.DecodeUndefinedHandlingStrategy;
import de.bild.codec.annotations.EncodeNullHandlingStrategy;
import org.bson.BsonDocument;
import org.bson.BsonReader;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.Predicate;

/**
 * Provides a codec for Pojos
 * Use the internal builder to register classes and packages that can be handled by the codec
 */
public class PojoCodecProvider implements CodecProvider {
    private static final Logger LOGGER = LoggerFactory.getLogger(PojoCodecProvider.class);
    private final TypesModel typesModel;
    private final PojoContext pojoContext;


    PojoCodecProvider(final Set> classes,
                      final Set packages,
                      final Set> ignoreAnnotations,
                      Set> ignoreTypesMatchingClassNamePredicates,
                      Set> ignoreClasses, List typeCodecProviders,
                      final List codecResolvers,
                      CodecConfiguration codecConfiguration, ClassResolver classResolver) {
        this.typesModel = new TypesModel(classes, packages, ignoreAnnotations, ignoreTypesMatchingClassNamePredicates, ignoreClasses, classResolver);
        this.pojoContext = new PojoContext(typesModel, codecResolvers, typeCodecProviders, codecConfiguration);
    }

    public static Builder builder() {
        return new Builder();
    }

    @Override
    public  Codec get(Class clazz, CodecRegistry registry) {
        // if clazz has type parameters, we warn the user that generic class definitions are problematic
        Codec codec = pojoContext.get(clazz, registry);
        if (codec instanceof TypeCodec) {
            if (clazz != null && clazz.getTypeParameters().length > 0) {
                LOGGER.warn("Generic classes will only be encoded/decoded with their upper bounds! " +
                        "We could prohibit handling of the pojo codec for those generic classes, " +
                        "but then user would loose flexibility when subclassing such classes. Class: {}", clazz.toGenericString());
            }
            TypeCodec typeCodec = (TypeCodec) codec;
            // generate dynamic proxy to add CollectibleCodec functionality
            if (typeCodec.isCollectible()) {
                LOGGER.debug("Enhancing {} to be collectible codec.", typeCodec);
                // For easy of use, adding all implemented interfaces to the proxy, so the proxy can be used for most use cases
                // Unfortunately the functionalities of the underlying concrete codec class will be missing.
                ArrayList> proxyInterfaceList = new ArrayList<>(Arrays.asList(typeCodec.getClass().getInterfaces()));
                proxyInterfaceList.add(CollectibleCodec.class);
                proxyInterfaceList.add(DelegatingCodec.class); // so users can retrieve the delegating codec form the proxy.

                return (CollectibleCodec) Proxy.newProxyInstance(
                        PojoCodecProvider.class.getClassLoader(),
                        proxyInterfaceList.toArray(new Class[1]),
                        new CollectibleCodecDelegator(typeCodec));
            }
        }
        return codec;
    }

    public Bson getTypeFilter(Class clazz, CodecRegistry registry) {
        Codec codec = get(clazz, registry);
        if (codec instanceof TypeCodec) {
            return ((TypeCodec) codec).getTypeFilter();
        }
        return null;
    }

    /**
     * delegator for CollectibleCodec
     */
    private static class CollectibleCodecDelegator implements InvocationHandler, CollectibleCodec, DelegatingCodec {
        private final TypeCodec delegatingCodec;

        public CollectibleCodecDelegator(TypeCodec delegatingCodec) {
            this.delegatingCodec = delegatingCodec;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                if (method.getDeclaringClass() == CollectibleCodec.class || method.getDeclaringClass() == DelegatingCodec.class) {
                    return method.invoke(this, args);
                } else {
                    return method.invoke(delegatingCodec, args);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOGGER.warn("An exception was caught while invoking the delegate {} with args {}", method, args);
                LOGGER.debug("Original exception when invoking target.", e);
                // rethrowing cause instead of invocationexception
                throw e.getCause();
            }

        }

        @Override
        public T generateIdIfAbsentFromDocument(T document) {
            return delegatingCodec.generateIdIfAbsentFromDocument(document);
        }

        @Override
        public boolean documentHasId(T document) {
            return delegatingCodec.documentHasId(document);
        }

        @Override
        public BsonValue getDocumentId(T document) {
            return delegatingCodec.getDocumentId(document);
        }

        @Override
        public T decode(BsonReader reader, DecoderContext decoderContext) {
            return delegatingCodec.decode(reader, decoderContext);
        }

        @Override
        public void encode(BsonWriter writer, T value, EncoderContext encoderContext) {
            delegatingCodec.encode(writer, value, encoderContext);
        }

        @Override
        public Class getEncoderClass() {
            return delegatingCodec.getEncoderClass();
        }

        @Override
        public TypeCodec getDelegate() {
            return delegatingCodec;
        }
    }

    /**
     *
     */
    public static class Builder {
        private Set packages = new HashSet<>();
        private Set> classes = new HashSet<>();
        private List codecResolvers = new ArrayList<>();
        private Set> ignoreAnnotations = new HashSet<>();
        private Set> ignoreTypesMatchingClassNamePredicates = new HashSet<>();
        private Set> ignoreClasses = new HashSet<>();
        private ClassResolver classResolver;
        private List typeCodecProviders = new ArrayList<>();
        private EncodeNullHandlingStrategy.Strategy encodeNullHandlingStrategy = EncodeNullHandlingStrategy.Strategy.CODEC;
        private DecodeUndefinedHandlingStrategy.Strategy decodeUndefinedHandlingStrategy = DecodeUndefinedHandlingStrategy.Strategy.KEEP_POJO_DEFAULT;
        private boolean encodeNulls = false;

        public Builder setPackages(Set packages) {
            this.packages = packages;
            return this;
        }

        public Builder register(String... packages) {
            this.packages.addAll(Arrays.asList(packages));
            return this;
        }

        public Builder register(Class... classes) {
            this.classes.addAll(Arrays.asList(classes));
            return this;
        }

        /**
         * If you need to provide a mechanism to scan packages for model classes, register a {@link ClassResolver}
         *
         * @param classResolver the resolver for classes within packages
         * @return this Builder
         */
        public Builder registerClassResolver(ClassResolver classResolver) {
            this.classResolver = classResolver;
            return this;
        }

        public Builder ignoreTypesAnnotatedWith(Class... annotations) {
            this.ignoreAnnotations.addAll(Arrays.asList(annotations));
            return this;
        }

        /**
         * If you need to exclude private inner classes form the domain model, use a Predicate
         *
         * @param ignoreTypesMatchingClassNamePredicates
         * @return the Builder
         */
        public Builder ignoreTypesMatchingClassNamePredicate(Predicate... ignoreTypesMatchingClassNamePredicates) {
            this.ignoreTypesMatchingClassNamePredicates.addAll(Arrays.asList(ignoreTypesMatchingClassNamePredicates));
            return this;
        }

        /**
         * If ypu can point to the classes to be ignored, you can do this here
         *
         * @return the Builder
         */
        public Builder ignoreClasses(Class... ignoreClasses) {
            this.ignoreClasses.addAll(Arrays.asList(ignoreClasses));
            return this;
        }

        /**
         * In case you need to register
         *
         * @param typeCodecProviders
         * @return
         */
        public Builder register(TypeCodecProvider... typeCodecProviders) {
            this.typeCodecProviders.addAll(Arrays.asList(typeCodecProviders));
            return this;
        }

        public Builder encodeNullHandlingStrategy(EncodeNullHandlingStrategy.Strategy encodeNullHandlingStrategy) {
            if (encodeNullHandlingStrategy != null) {
                this.encodeNullHandlingStrategy = encodeNullHandlingStrategy;
            }
            return this;
        }

        public Builder decodeUndefinedHandlingStrategy(DecodeUndefinedHandlingStrategy.Strategy decodeUndefinedHandlingStrategy) {
            if (decodeUndefinedHandlingStrategy != null) {
                this.decodeUndefinedHandlingStrategy = decodeUndefinedHandlingStrategy;
            }
            return this;
        }

        public Builder encodeNulls(boolean encodeNulls) {
            this.encodeNulls = encodeNulls;
            return this;
        }

        /**
         * A CodecResolver is supposed to provide specialized codecs in case the default implementation
         * {@link BasicReflectionCodec} is not sufficient
         *
         * @param codecResolvers a list of CodecResolvers to be registered
         * @return the builder
         */
        public Builder registerCodecResolver(CodecResolver... codecResolvers) {
            this.codecResolvers.addAll(Arrays.asList(codecResolvers));
            return this;
        }

        public PojoCodecProvider build() {
            CodecConfiguration codecConfiguration = new CodecConfiguration(encodeNulls, encodeNullHandlingStrategy, decodeUndefinedHandlingStrategy);
            return new PojoCodecProvider(classes, packages, ignoreAnnotations, ignoreTypesMatchingClassNamePredicates, ignoreClasses, typeCodecProviders, codecResolvers, codecConfiguration, classResolver);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy