org.grails.datastore.mapping.mongo.config.MongoMappingContext.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.datastore.mapping.mongo.config.MongoMappingContext.java

Source

/* Copyright (C) 2010 SpringSource
 *
 * 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.grails.datastore.mapping.mongo.config;

import com.mongodb.MongoClientURI;
import groovy.lang.Closure;

import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.regex.Pattern;

import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.types.*;
import org.grails.datastore.bson.codecs.BigDecimalCodec;
import org.grails.datastore.bson.codecs.CodecExtensions;
import org.grails.datastore.bson.codecs.encoders.SimpleEncoder;
import org.grails.datastore.gorm.mongo.geo.*;
import org.grails.datastore.gorm.mongo.simple.EnumType;
import org.grails.datastore.mapping.config.AbstractGormMappingFactory;
import org.grails.datastore.mapping.config.ConfigurationUtils;
import org.grails.datastore.mapping.core.connections.ConnectionSourceSettings;
import org.grails.datastore.mapping.document.config.Attribute;
import org.grails.datastore.mapping.document.config.Collection;
import org.grails.datastore.mapping.document.config.DocumentMappingContext;
import org.grails.datastore.mapping.model.*;

import org.grails.datastore.mapping.model.types.Custom;
import org.grails.datastore.mapping.model.types.Identity;
import org.grails.datastore.mapping.mongo.MongoConstants;
import org.grails.datastore.mapping.mongo.MongoDatastore;
import org.grails.datastore.mapping.mongo.connections.AbstractMongoConnectionSourceSettings;
import org.grails.datastore.bson.codecs.CodecCustomTypeMarshaller;
import org.grails.datastore.mapping.reflect.ClassUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.env.PropertyResolver;

/**
 * Models a {@link org.grails.datastore.mapping.model.MappingContext} for Mongo.
 *
 * @author Graeme Rocher
 */
@SuppressWarnings("rawtypes")
public class MongoMappingContext extends DocumentMappingContext {
    private static final String DECIMAL_TYPE_CLASS_NAME = "org.bson.types.Decimal128";
    /**
     * Java types supported as mongo property types.
     */
    private static final Set<String> MONGO_NATIVE_TYPES = new HashSet<>(Arrays.asList(Double.class.getName(),
            String.class.getName(), Document.class.getName(), "com.mongodb.DBObject",
            org.bson.types.Binary.class.getName(), org.bson.types.ObjectId.class.getName(), "com.mongodb.DBRef",
            Boolean.class.getName(), Date.class.getName(), Pattern.class.getName(), Symbol.class.getName(),
            Integer.class.getName(), Code.class.getName(), "org.bson.types.BSONTimestamp", DECIMAL_TYPE_CLASS_NAME,
            "org.bson.types.CodeWScope", "org.bson.types.Code", "org.bson.types.Binary", Long.class.getName(),
            UUID.class.getName(), byte[].class.getName(), Byte.class.getName()

    ));

    private CodecRegistry codecRegistry;
    private Map<Class, Boolean> hasCodecCache = new HashMap<>();

    public MongoMappingContext(String defaultDatabaseName) {
        this(defaultDatabaseName, null);
    }

    public MongoMappingContext(String defaultDatabaseName, Closure defaultMapping) {
        this(defaultDatabaseName, defaultMapping, new Class[0]);
    }

    /**
     * Constructs a new {@link MongoMappingContext} for the given arguments
     *
     * @param defaultDatabaseName The default database name
     * @param defaultMapping The default database mapping configuration
     * @param classes The persistent classes
     */
    public MongoMappingContext(String defaultDatabaseName, Closure defaultMapping, Class... classes) {
        super(defaultDatabaseName, defaultMapping);
        initialize(classes);

    }

    /**
     * Constructs a new {@link MongoMappingContext} for the given arguments
     *
     * @param configuration The configuration
     * @param classes The persistent classes
     * @deprecated  Use {@link #MongoMappingContext(AbstractMongoConnectionSourceSettings, Class[])} instead
     *
     */
    @Deprecated
    public MongoMappingContext(PropertyResolver configuration, Class... classes) {
        this(getDefaultDatabaseName(configuration),
                configuration.getProperty(MongoSettings.SETTING_DEFAULT_MAPPING, Closure.class, null), classes);
    }

    /**
     * Construct a new context for the given settings and classes
     *
     * @param settings The settings
     * @param classes The classes
     */
    public MongoMappingContext(AbstractMongoConnectionSourceSettings settings, Class... classes) {
        super(settings.getDatabase(), settings);
        initialize(classes);
    }

    /**
     * @return The codec registry for this mapping context
     */
    public CodecRegistry getCodecRegistry() {
        return codecRegistry;
    }

    @Override
    protected void initialize(ConnectionSourceSettings settings) {
        super.initialize(settings);

        AbstractMongoConnectionSourceSettings mongoConnectionSourceSettings = (AbstractMongoConnectionSourceSettings) settings;
        List<Class<? extends Codec>> codecClasses = mongoConnectionSourceSettings.getCodecs();

        if (mongoConnectionSourceSettings.isDecimalType() && ClassUtils.isPresent(DECIMAL_TYPE_CLASS_NAME)) {
            MONGO_NATIVE_TYPES.add(BigDecimal.class.getName());
            MONGO_NATIVE_TYPES.add(BigInteger.class.getName());
            SimpleEncoder.enableBigDecimalEncoding();

            codecClasses.add(BigDecimalCodec.class);
        }

        Iterable<Codec> codecList = ConfigurationUtils.findServices(codecClasses, Codec.class);
        List<Codec<?>> codecs = new ArrayList<>();
        for (Codec codec : codecList) {
            codecs.add(codec);
        }

        if (mongoConnectionSourceSettings.getCodecRegistry() != null) {
            this.codecRegistry = CodecRegistries.fromRegistries(mongoConnectionSourceSettings.getCodecRegistry(),
                    CodecRegistries.fromCodecs(codecs));
        } else {
            this.codecRegistry = CodecRegistries.fromCodecs(codecs);
        }
    }

    private void initialize(Class[] classes) {
        registerMongoTypes();
        final ConverterRegistry converterRegistry = getConverterRegistry();

        converterRegistry.addConverter(new Converter<String, ObjectId>() {
            public ObjectId convert(String source) {
                if (ObjectId.isValid(source)) {
                    return new ObjectId(source);
                } else {
                    return null;
                }
            }
        });

        converterRegistry.addConverter(new Converter<ObjectId, String>() {
            public String convert(ObjectId source) {
                return source.toString();
            }
        });

        converterRegistry.addConverter(new Converter<byte[], Binary>() {
            public Binary convert(byte[] source) {
                return new Binary(source);
            }
        });

        converterRegistry.addConverter(new Converter<Binary, byte[]>() {
            public byte[] convert(Binary source) {
                return source.getData();
            }
        });

        converterRegistry.addConverter(new Converter<Decimal128, BigDecimal>() {
            @Override
            public BigDecimal convert(Decimal128 source) {
                return source.bigDecimalValue();
            }
        });

        converterRegistry.addConverter(new Converter<BigDecimal, Decimal128>() {
            @Override
            public Decimal128 convert(BigDecimal source) {
                return new Decimal128(source);
            }
        });

        converterRegistry.addConverter(new Converter<Decimal128, BigInteger>() {
            @Override
            public BigInteger convert(Decimal128 source) {
                return source.bigDecimalValue().toBigInteger();
            }
        });

        converterRegistry.addConverter(new Converter<BigInteger, Decimal128>() {
            @Override
            public Decimal128 convert(BigInteger source) {
                return new Decimal128(new BigDecimal(source.toString()));
            }
        });

        for (Converter converter : CodecExtensions.getBsonConverters()) {
            converterRegistry.addConverter(converter);
        }

        addPersistentEntities(classes);
        hasCodecCache.clear();
    }

    /**
     * Check whether a type is a native mongo type that can be stored by the mongo driver without conversion.
     * @param clazz The class to check.
     * @return true if no conversion is required and the type can be stored natively.
     */
    public static boolean isMongoNativeType(Class clazz) {
        return MongoMappingContext.MONGO_NATIVE_TYPES.contains(clazz.getName())
                || Bson.class.isAssignableFrom(clazz.getClass());
    }

    public static String getDefaultDatabaseName(PropertyResolver configuration) {
        String connectionString = configuration.getProperty(MongoDatastore.SETTING_CONNECTION_STRING, String.class,
                null);

        if (connectionString != null) {
            MongoClientURI mongoClientURI = new MongoClientURI(connectionString);
            String database = mongoClientURI.getDatabase();
            if (database != null) {
                return database;
            }
        }
        return configuration.getProperty(MongoSettings.SETTING_DATABASE_NAME, "test");
    }

    private final class MongoDocumentMappingFactory
            extends AbstractGormMappingFactory<MongoCollection, MongoAttribute> {
        @Override
        protected Class<MongoAttribute> getPropertyMappedFormType() {
            return MongoAttribute.class;
        }

        @Override
        protected Class<MongoCollection> getEntityMappedFormType() {
            return MongoCollection.class;
        }

        @Override
        public Identity<MongoAttribute> createIdentity(PersistentEntity owner, MappingContext context,
                PropertyDescriptor pd) {
            Identity<MongoAttribute> identity = super.createIdentity(owner, context, pd);
            identity.getMapping().getMappedForm().setTargetName(MongoConstants.MONGO_ID_FIELD);
            return identity;
        }

        @Override
        public boolean isCustomType(Class<?> propertyType) {
            return super.isCustomType(propertyType) || hasCodecForType(propertyType);
        }

        @Override
        public Custom<MongoAttribute> createCustom(PersistentEntity owner, MappingContext context,
                final PropertyDescriptor pd) {
            if (hasCodecForType(pd.getPropertyType())) {
                CodecCustomTypeMarshaller customTypeMarshaller = new CodecCustomTypeMarshaller(
                        codecRegistry.get(pd.getPropertyType()), MongoMappingContext.this);
                return new Custom<MongoAttribute>(owner, context, pd, customTypeMarshaller) {
                    PropertyMapping<MongoAttribute> propertyMapping = createPropertyMapping(this, owner);

                    public PropertyMapping<MongoAttribute> getMapping() {
                        return propertyMapping;
                    }
                };
            } else {
                return super.createCustom(owner, context, pd);
            }
        }

        @Override
        public boolean isSimpleType(Class propType) {
            if (propType == null)
                return false;
            if (propType.isArray()) {
                return isSimpleType(propType.getComponentType()) || super.isSimpleType(propType);
            }
            return isMongoNativeType(propType) || super.isSimpleType(propType);
        }
    }

    private boolean hasCodecForType(Class propType) {
        if (hasCodecCache.containsKey(propType)) {
            return hasCodecCache.get(propType);
        } else {
            Boolean hasCodec;
            try {
                hasCodec = codecRegistry.get(propType) != null;
            } catch (CodecConfigurationException e) {
                hasCodec = false;
            }
            hasCodecCache.put(propType, hasCodec);
            return hasCodec;
        }
    }

    protected void registerMongoTypes() {
        MappingFactory<Collection, Attribute> mappingFactory = getMappingFactory();
        mappingFactory.registerCustomType(new GeometryCollectionType());
        mappingFactory.registerCustomType(new PointType());
        mappingFactory.registerCustomType(new PolygonType());
        mappingFactory.registerCustomType(new LineStringType());
        mappingFactory.registerCustomType(new MultiLineStringType());
        mappingFactory.registerCustomType(new MultiPointType());
        mappingFactory.registerCustomType(new MultiPolygonType());
        mappingFactory.registerCustomType(new ShapeType());
        mappingFactory.registerCustomType(new BoxType());
        mappingFactory.registerCustomType(new CircleType());
        mappingFactory.registerCustomType(new EnumType());
    }

    @Override
    protected MappingFactory createDocumentMappingFactory(Closure defaultMapping) {
        MongoDocumentMappingFactory mongoDocumentMappingFactory = new MongoDocumentMappingFactory();
        mongoDocumentMappingFactory.setDefaultMapping(defaultMapping);
        return mongoDocumentMappingFactory;
    }

    @Override
    public PersistentEntity createEmbeddedEntity(Class type) {
        return new DocumentEmbeddedPersistentEntity(type, this);
    }

    class DocumentEmbeddedPersistentEntity extends EmbeddedPersistentEntity {

        private DocumentCollectionMapping classMapping;

        public DocumentEmbeddedPersistentEntity(Class type, MappingContext ctx) {
            super(type, ctx);
            classMapping = new DocumentCollectionMapping(this, ctx);
        }

        @Override
        public ClassMapping getMapping() {
            return classMapping;
        }

        public class DocumentCollectionMapping extends AbstractClassMapping<Collection> {
            private Collection mappedForm;

            public DocumentCollectionMapping(PersistentEntity entity, MappingContext context) {
                super(entity, context);
                this.mappedForm = (Collection) context.getMappingFactory()
                        .createMappedForm(DocumentEmbeddedPersistentEntity.this);
            }

            @Override
            public Collection getMappedForm() {
                return mappedForm;
            }
        }
    }
}