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

org.bson.codecs.pojo.LazyPropertyModelCodec Maven / Gradle / Ivy

There is a newer version: 5.3.0-beta0
Show newest version
/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.bson.codecs.pojo;

import org.bson.BsonReader;
import org.bson.BsonType;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.RepresentationConfigurable;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.configuration.CodecRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.String.format;
import static org.bson.codecs.pojo.PojoSpecializationHelper.specializeTypeData;

class LazyPropertyModelCodec implements Codec {
    private final PropertyModel propertyModel;
    private final CodecRegistry registry;
    private final PropertyCodecRegistry propertyCodecRegistry;
    private final Lock codecLock = new ReentrantLock();
    private volatile Codec codec;

    LazyPropertyModelCodec(final PropertyModel propertyModel, final CodecRegistry registry,
            final PropertyCodecRegistry propertyCodecRegistry) {
        this.propertyModel = propertyModel;
        this.registry = registry;
        this.propertyCodecRegistry = propertyCodecRegistry;
    }

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

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

    @Override
    public Class getEncoderClass() {
        return propertyModel.getTypeData().getType();
    }

    private Codec getPropertyModelCodec() {
        Codec codec = this.codec;
        if (codec == null) {
            codecLock.lock();
            try {
                codec = this.codec;
                if (codec == null) {
                    codec = createCodec();
                    this.codec = codec;
                }
            } finally {
                codecLock.unlock();
            }
        }
        return codec;
    }

    private Codec createCodec() {
        Codec localCodec = getCodecFromPropertyRegistry(propertyModel);
        if (localCodec instanceof PojoCodec) {
            PojoCodec pojoCodec = (PojoCodec) localCodec;
            ClassModel specialized = getSpecializedClassModel(pojoCodec.getClassModel(), propertyModel);
            localCodec = new PojoCodecImpl<>(specialized, registry, propertyCodecRegistry, pojoCodec.getDiscriminatorLookup());
        }
        return localCodec;
    }

    @SuppressWarnings("unchecked")
    private Codec getCodecFromPropertyRegistry(final PropertyModel propertyModel) {
        Codec localCodec;
        try {
            localCodec = propertyCodecRegistry.get(propertyModel.getTypeData());
        } catch (CodecConfigurationException e) {
            return new LazyMissingCodec<>(propertyModel.getTypeData().getType(), e);
        }
        if (localCodec == null) {
            localCodec = new LazyMissingCodec<>(propertyModel.getTypeData().getType(),
                    new CodecConfigurationException("Unexpected missing codec for: " + propertyModel.getName()));
        }
        BsonType representation = propertyModel.getBsonRepresentation();
        if (representation != null) {
            if (localCodec instanceof RepresentationConfigurable) {
                return ((RepresentationConfigurable) localCodec).withRepresentation(representation);
            }
            throw new CodecConfigurationException("Codec must implement RepresentationConfigurable to support BsonRepresentation");
        }
        return localCodec;
    }

    private  ClassModel getSpecializedClassModel(final ClassModel clazzModel, final PropertyModel propertyModel) {
        boolean useDiscriminator = propertyModel.useDiscriminator() == null ? clazzModel.useDiscriminator()
                : propertyModel.useDiscriminator();
        boolean validDiscriminator = clazzModel.getDiscriminatorKey() != null && clazzModel.getDiscriminator() != null;
        boolean changeTheDiscriminator = (useDiscriminator != clazzModel.useDiscriminator()) && validDiscriminator;

        if (propertyModel.getTypeData().getTypeParameters().isEmpty() && !changeTheDiscriminator){
            return clazzModel;
        }

        ArrayList> concretePropertyModels = new ArrayList<>(clazzModel.getPropertyModels());
        PropertyModel concreteIdProperty = clazzModel.getIdPropertyModel();

        List> propertyTypeParameters = propertyModel.getTypeData().getTypeParameters();
        for (int i = 0; i < concretePropertyModels.size(); i++) {
            PropertyModel model = concretePropertyModels.get(i);
            String propertyName = model.getName();
            TypeParameterMap typeParameterMap = clazzModel.getPropertyNameToTypeParameterMap().get(propertyName);
            if (typeParameterMap.hasTypeParameters()) {
                PropertyModel concretePropertyModel = getSpecializedPropertyModel(model, propertyTypeParameters, typeParameterMap);
                concretePropertyModels.set(i, concretePropertyModel);
                if (concreteIdProperty != null && concreteIdProperty.getName().equals(propertyName)) {
                    concreteIdProperty = concretePropertyModel;
                }
            }
        }

        boolean discriminatorEnabled = changeTheDiscriminator ? propertyModel.useDiscriminator() : clazzModel.useDiscriminator();
        return new ClassModel<>(clazzModel.getType(), clazzModel.getPropertyNameToTypeParameterMap(),
                clazzModel.getInstanceCreatorFactory(), discriminatorEnabled, clazzModel.getDiscriminatorKey(),
                clazzModel.getDiscriminator(), IdPropertyModelHolder.create(clazzModel, concreteIdProperty), concretePropertyModels);
    }

    private  PropertyModel getSpecializedPropertyModel(final PropertyModel propertyModel,
                                                             final List> propertyTypeParameters,
                                                             final TypeParameterMap typeParameterMap) {
        TypeData specializedPropertyType = specializeTypeData(propertyModel.getTypeData(), propertyTypeParameters, typeParameterMap);
        if (propertyModel.getTypeData().equals(specializedPropertyType)) {
            return propertyModel;
        }

        return new PropertyModel<>(propertyModel.getName(), propertyModel.getReadName(), propertyModel.getWriteName(),
                specializedPropertyType, null, propertyModel.getPropertySerialization(), propertyModel.useDiscriminator(),
                propertyModel.getPropertyAccessor(), propertyModel.getError(), propertyModel.getBsonRepresentation());
    }

    /**
     * Instances of this codec are supposed to be replaced with usable implementations by {@link LazyPropertyModelCodec#createCodec()}.
     */
    static final class NeedSpecializationCodec extends PojoCodec {
        private final ClassModel classModel;
        private final DiscriminatorLookup discriminatorLookup;
        private final CodecRegistry codecRegistry;

        NeedSpecializationCodec(final ClassModel classModel, final DiscriminatorLookup discriminatorLookup, final CodecRegistry codecRegistry) {
            this.classModel = classModel;
            this.discriminatorLookup = discriminatorLookup;
            this.codecRegistry = codecRegistry;
        }

        @Override
        public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) {
            if (value.getClass().equals(classModel.getType())) {
                throw exception();
            }
            tryEncode(codecRegistry.get(value.getClass()), writer, value, encoderContext);
        }

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

        @SuppressWarnings("unchecked")
        private  void tryEncode(final Codec codec,  final BsonWriter writer, final T value, final EncoderContext encoderContext) {
            try {
                codec.encode(writer, (A) value, encoderContext);
            } catch (Exception e) {
                throw exception();
            }
        }

        @SuppressWarnings("unchecked")
        public T tryDecode(final BsonReader reader, final DecoderContext decoderContext) {
            Codec codec = PojoCodecImpl.getCodecFromDocument(reader, classModel.useDiscriminator(), classModel.getDiscriminatorKey(),
                    codecRegistry, discriminatorLookup, null, classModel.getName());
            if (codec != null) {
                return codec.decode(reader, decoderContext);
            }

            throw exception();
        }

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

        private CodecConfigurationException exception() {
            return new CodecConfigurationException(format("%s contains generic types that have not been specialised.%n"
                    + "Top level classes with generic types are not supported by the PojoCodec.", classModel.getName()));
        }

        @Override
        ClassModel getClassModel() {
            return classModel;
        }

        @Override
        DiscriminatorLookup getDiscriminatorLookup() {
            return discriminatorLookup;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy