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

de.bild.codec.BasicReflectionCodec 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.Id;
import de.bild.codec.annotations.PostLoad;
import de.bild.codec.annotations.PreSave;
import de.bild.codec.annotations.Transient;
import org.bson.BsonReader;
import org.bson.BsonType;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.*;

public class BasicReflectionCodec extends AbstractTypeCodec implements ReflectionCodec {

    private static final Logger LOGGER = LoggerFactory.getLogger(BasicReflectionCodec.class);
    MappedField idField;

    /**
     * a list of the fields to map
     */
    final Map persistenceFields = new LinkedHashMap<>();
    final List postLoadMethods = new ArrayList<>();
    final List preSaveMethods = new ArrayList<>();
    IdGenerator idGenerator;
    boolean isCollectible;

    public BasicReflectionCodec(Type type, TypeCodecRegistry typeCodecRegistry) {
        super(type, typeCodecRegistry);
        // resolve all persistable fields
        for (final FieldTypePair fieldTypePair : ReflectionHelper.getDeclaredAndInheritedFieldTypePairs(type, true)) {
            Field field = fieldTypePair.getField();
            if (!isIgnorable(field)) {
                MappedField mappedField = new MappedField(fieldTypePair, encoderClass, typeCodecRegistry);
                persistenceFields.put(mappedField.getMappedFieldName(), mappedField);
                if (mappedField.isIdField()) {
                    if (idField == null) {
                        idField = mappedField;
                        Id idAnnotation = idField.getAnnotation(Id.class);

                        Class idGeneratorClass = idAnnotation.value();
                        isCollectible = idAnnotation.collectible();
                        try {
                            Constructor idGeneratorConstructor = idGeneratorClass.getDeclaredConstructor();
                            idGeneratorConstructor.setAccessible(true);
                            idGenerator = idGeneratorConstructor.newInstance();
                        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                            throw new IllegalArgumentException("Could not create instance of IdGenerator for class " + type + " Generator class: " + idGeneratorClass, e);
                        }
                    } else {
                        throw new IllegalArgumentException("Id field is used again in class hierarchy! Class " + encoderClass);
                    }
                }
            }
        }


        // resolve lifecycle methods
        for (MethodTypePair methodTypePair : ReflectionHelper.getDeclaredAndInheritedMethods(encoderClass)) {
            Method method = methodTypePair.getMethod();
            if (method.isAnnotationPresent(PostLoad.class)) {
                postLoadMethods.add(method);
            } else if (method.isAnnotationPresent(PreSave.class)) {
                preSaveMethods.add(method);
            }
        }
    }

    @Override
    public Map getPersistenceFields() {
        return persistenceFields;
    }

    public BasicReflectionCodec(Type type, TypeCodecRegistry typeCodecRegistry, Set allDiscriminatorKeys) {
        this(type, typeCodecRegistry);
        for (MappedField persistenceField : persistenceFields.values()) {
            if (allDiscriminatorKeys.contains(persistenceField.getMappedFieldName())) {
                LOGGER.error("A field {} within class {} is named like one of the discriminator keys {}", persistenceField.getMappedFieldName(), encoderClass, allDiscriminatorKeys);
                throw new IllegalArgumentException("A field " + persistenceField.getMappedFieldName() + " within class " + encoderClass + " is named like one of the discriminator keys" + allDiscriminatorKeys);
            }
        }
    }

    protected boolean isIgnorable(final Field field) {
        return field.isAnnotationPresent(Transient.class)
                || Modifier.isTransient(field.getModifiers());
    }


    @Override
    public T decode(BsonReader reader, DecoderContext decoderContext) {
        T newInstance;
        //if reader is in initial state (reader.getCurrentBsonType() == null) or DOCUMENT state
        if (reader.getCurrentBsonType() == null || reader.getCurrentBsonType() == BsonType.DOCUMENT) {
            reader.readStartDocument();
            newInstance = decodeFields(reader, decoderContext, newInstance());
            reader.readEndDocument();
            return newInstance;
        } else {
            LOGGER.error("Expected to read document but reader is in state {}. Skipping value!", reader.getCurrentBsonType());
            reader.skipValue();
            return null;
        }
    }

    @Override
    public T decodeFields(BsonReader reader, DecoderContext decoderContext, T instance) {
        while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
            String fieldName = reader.readName();
            MappedField mappedField = persistenceFields.get(fieldName);
            if (mappedField != null) {
                mappedField.decode(reader, instance, decoderContext);
            } else {
                reader.skipValue();
            }
        }
        postDecode(instance);
        return instance;
    }

    @Override
    public void postDecode(T instance) {
        initializeDefaults(instance);
        for (Method postLoadMethod : postLoadMethods) {
            try {
                postLoadMethod.invoke(instance);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOGGER.warn("@PostLoad method {} could not be called.", postLoadMethod, e);
            }
        }
    }

    @Override
    public void initializeDefaults(T instance) {
        for (MappedField persistenceField : persistenceFields.values()) {
            persistenceField.initializeDefault(instance);
        }
    }

    @Override
    public void encode(BsonWriter writer, T instance, EncoderContext encoderContext) {
        writer.writeStartDocument();
        encodeFields(writer, instance, encoderContext);
        writer.writeEndDocument();
    }

    @Override
    public void encodeFields(BsonWriter writer, T instance, EncoderContext encoderContext) {
        preEncode(instance);
        for (MappedField persistenceField : persistenceFields.values()) {
            persistenceField.encode(writer, instance, encoderContext);
        }
    }

    @Override
    public void preEncode(T instance) {
        for (Method preSaveMethod : preSaveMethods) {
            try {
                preSaveMethod.invoke(instance);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOGGER.warn("@PreSave method {} could not be called.", preSaveMethod, e);
            }
        }
    }

    @Override
    public MappedField getMappedField(String mappedFieldName) {
        return persistenceFields.get(mappedFieldName);
    }

    @Override
    public MappedField getIdField() {
        return idField;
    }

    @Override
    public boolean isCollectible() {
        return isCollectible;
    }

    @Override
    public T generateIdIfAbsentFromDocument(T document) {
        if (idGenerator != null && !documentHasId(document)) {
            boolean couldGenerate = idField.setFieldValue(document, idGenerator.generate());
            if (!couldGenerate) {
                LOGGER.error("Could not set id!");
            }
        }
        return document;
    }

    @Override
    public boolean documentHasId(T document) {
        return getPlainId(document) != null;
    }

    private Object getPlainId(T document) {
        return idField != null ? idField.getFieldValue(document) : null;
    }

    @Override
    public BsonValue getDocumentId(T document) {
        return idGenerator.asBsonValue(getPlainId(document), typeCodecRegistry);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy