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

com.mastfrog.giulius.mongodb.async.GiuliusMongoAsyncModule Maven / Gradle / Ivy

There is a newer version: 2.9.7
Show newest version
/*
 * The MIT License
 *
 * Copyright 2015 Tim Boudreau.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mastfrog.giulius.mongodb.async;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.mastfrog.asyncpromises.mongo.CollectionPromises;
import com.mastfrog.giulius.Dependencies;
import com.mastfrog.settings.Settings;
import com.mastfrog.util.preconditions.Checks;
import com.mastfrog.util.preconditions.ConfigurationError;
import com.mongodb.async.client.MongoClient;
import com.mongodb.async.client.MongoClientSettings;
import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.async.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;

/**
 * Supports MongoDB Guice bindings, allowing @Named to be used to inject
 * collections, and similar. Binds the following
 * 
    *
  • MongoCLientSettings (optional - you can provide your own) - the default * one is initialized from settings properties, using key names defined as * constant string fields on this class
  • *
  • CodecRegistry (used by MongoClientSettings if you don't provide your * own)
  • *
  • MongoClient
  • *
  • MongoDatabase
  • *
  • MongoCollection - bind multiple collections and inject them using * @Named - if you specify a type, you can inject MongoCollection * parameterized on either your type or Document
  • *
  • CollectionPromises - bind multiple collections and inject a * promise/builder based way to access them using @Named - if you specify a * type, you can inject MongoCollection parameterized on either your type or * Document
  • *
* * @author Tim Boudreau */ public class GiuliusMongoAsyncModule extends AbstractModule implements MongoAsyncConfig { private MongoClientSettings settings; private volatile boolean done; private final Set> bindings = new HashSet<>(); private final Set> initializers = new HashSet<>(); private final Set codecProviders = new HashSet<>(); private final Set> codecs = new HashSet<>(); private final Set>> codecTypes = new HashSet<>(); private final Set> codecProviderTypes = new HashSet<>(); @SuppressWarnings("LeakingThisInConstructor") public GiuliusMongoAsyncModule() { Java8DateTimeCodecProvider.installCodecs(this); } /** * Add a codec to decode objects from BSON. * * @param prov The provider * @return this */ @Override public GiuliusMongoAsyncModule withCodecProvider(CodecProvider prov) { checkDone(); checkSettings(); codecProviders.add(prov); return this; } private void checkSettings() { if (settings != null) { throw new ConfigurationError("You are providing your own " + "MongoClientSettings - set up its codec registry directly" + "in that case"); } } /** * Add a codec to decode objects from BSON. * * @param prov The codec * @return this */ @Override public GiuliusMongoAsyncModule withCodec(Codec prov) { checkDone(); checkSettings(); codecs.add(prov); return this; } /** * Add a codec to decode objects from BSON, by type. The passed * CodecProvider will be instantiated by Guice and may contain injected * elements. * * @param prov The provider * @return this */ @Override public GiuliusMongoAsyncModule withCodecProvider(Class prov) { checkDone(); checkSettings(); codecProviderTypes.add(prov); return this; } /** * Add a codec to decode objects from BSON, by type. The passed Codec will * be instantiated by Guice and may contain injected elements. * * @param prov The provider * @return this */ @Override public GiuliusMongoAsyncModule withCodec(Class> prov) { checkDone(); checkSettings(); codecTypes.add(prov); return this; } private Class dynCodecs; public GiuliusMongoAsyncModule withDynamicCodecs(Class codecs) { checkDone(); if (dynCodecs != null) { throw new ConfigurationError("Dynamic codecs already set"); } this.dynCodecs = codecs; return this; } static class DefaultFallbackCodecs implements DynamicCodecs { @Override public Codec createCodec(Class type, CodecConfigurationException ex) { throw ex; } } @Singleton private class CodecRegistryImpl implements CodecRegistry { private final Provider deps; private CodecRegistry registry; private final Provider fallback; CodecRegistryImpl(Provider deps, Provider fallback) { this.deps = deps; this.fallback = fallback; } private CodecRegistry get() { if (registry != null) { return registry; } Dependencies deps = this.deps.get(); List providers = new LinkedList<>(GiuliusMongoAsyncModule.this.codecProviders); List> codecs = new LinkedList<>(GiuliusMongoAsyncModule.this.codecs); for (Class c : codecProviderTypes) { providers.add(deps.getInstance(c)); } for (Class> c : codecTypes) { codecs.add(deps.getInstance(c)); } int total = providers.size() + codecs.size(); if (total == 0) { return DEFAULT_CODEC_REGISTRY; } List all = new LinkedList<>(); if (!codecs.isEmpty()) { CodecRegistry forProviders = CodecRegistries.fromCodecs(codecs); all.add(forProviders); } if (!providers.isEmpty()) { CodecRegistry forCodecs = CodecRegistries.fromProviders(providers); all.add(forCodecs); } all.add(DEFAULT_CODEC_REGISTRY); all.add(CodecRegistries.fromProviders(new Java8DateTimeCodecProvider())); return registry = CodecRegistries.fromRegistries(all); } public String toString() { StringBuilder sb = new StringBuilder(super.toString()).append('{'); for (CodecProvider prov : codecProviders) { sb.append("prov: ").append(prov).append(','); } for (Codec codec : codecs) { sb.append("codec: " + codec).append(","); } return sb.toString(); } @Override public Codec get(Class type) { try { return get().get(type); } catch (CodecConfigurationException ex) { return fallback.get().createCodec(type, ex); } } } // XXX when 3.1.0 is stable, replace with MongoClients.getDefaultCodecRegistry() private static final CodecRegistry DEFAULT_CODEC_REGISTRY = MongoClients.getDefaultCodecRegistry(); // = fromProviders(asList( // new ValueCodecProvider(), // new DocumentCodecProvider(), // new BsonValueCodecProvider())); private void checkDone() { if (done) { throw new ConfigurationError("Cannot configure module after the injector has been initialized"); } } /** * Use the passed client settings instead of deriving them from settings * key/value pairs. * * @param settings The settings * @return this */ @Override public GiuliusMongoAsyncModule withClientSettings(MongoClientSettings settings) { checkDone(); this.settings = settings; return this; } /** * Bind the collection with the passed name to @Named DBCollection of * the same name, so it is injectable as * @Named("someBinding") MongoCollection<MyType>. * where MyType is tha passed type. * * @param bindingName The binding and collection name * @return this */ public GiuliusMongoAsyncModule bindCollection(String bindingName, Class type) { return bindCollection(bindingName, bindingName, type); } /** * Bind a collection so it is injectable as * @Named("someBinding") MongoCollection<Document>. * * @param bindingName The name of the binding and the collection in * question * @return this */ public GiuliusMongoAsyncModule bindCollection(String bindingName) { bindCollection(bindingName, bindingName); return this; } /** * Add an initializer which will be called when collections are created, and * before and after the MongoClient is initialized. This can * also be accomplished by simply binding the initializer as an eager * singleton, if the module in question does not have direct access to this * one. * * @param initializerType The type of initializer * @return this */ public GiuliusMongoAsyncModule withInitializer(Class initializerType) { checkDone(); initializers.add(initializerType); return this; } /** * Bind the collection with the passed name to @Named DBCollection wth * the passed binding name * * @param bindingName The binding used in @Named * @param collectionName The collection name * @return this */ @Override public GiuliusMongoAsyncModule bindCollection(String bindingName, String collectionName) { checkDone(); bindings.add(new CollectionBinding(collectionName, bindingName, null, Document.class)); return this; } /** * Bind the collection with the passed name to @Named DBCollection of * the same name, so it is injectable as * @Named("someBinding") MongoCollection<MyType>. * where MyType is tha passed type. * * @param bindingName The name of the binding that will appear in * @Named annotations * @param collectionName The name of the collection to be created/used in * MongoDB, which may differ from the binding name * @return this */ @Override public GiuliusMongoAsyncModule bindCollection(String bindingName, String collectionName, Class type) { checkDone(); bindings.add(new CollectionBinding(collectionName, bindingName, null, type)); return this; } @Override protected void configure() { Provider dbNameProvider = binder().getProvider(Key.get(String.class, Names.named(SETTINGS_KEY_DATABASE_NAME))); Provider registryProvider = binder().getProvider(MongoAsyncInitializer.Registry.class); Provider settingsProvider = binder().getProvider(Settings.class); ExistingCollections existing = new ExistingCollections(dbNameProvider, registryProvider, settingsProvider); bind(ExistingCollections.class).toInstance(existing); for (Class itype : this.initializers) { bind(itype).asEagerSingleton(); } if (settings != null) { bind(MongoClientSettings.class).toInstance(settings); } else { bind(MongoClientSettings.class).toProvider(MongoClientSettingsProvider.class).asEagerSingleton(); } if (this.dynCodecs != null) { bind(DynamicCodecs.class).to(this.dynCodecs); } bind(CodecRegistry.class).toInstance(new CodecRegistryImpl(binder().getProvider(Dependencies.class), binder().getProvider(DynamicCodecs.class))); // Bind this as an eager singleton so that the client shutdown hook runs before the // MongoHarness shutdown hook shuts down the server - otherwise, can get exceptions thrown // during shutdown bind(MongoClient.class).toProvider(IndirectMongoClientProvider.class); bind(MongoDatabase.class).toProvider(MongoDatabaseProvider.class).in(Scopes.SINGLETON); for (CollectionBinding binding : bindings) { existing.addBound(binding.collection, binding.opts); binding.bind(binder()); } } @Singleton static final class IndirectMongoClientProvider implements Provider { private final Provider prov; private AsyncMongoClientProvider mongoprovider; @Inject IndirectMongoClientProvider(Provider prov) { this.prov = prov; } @Override public MongoClient get() { if (mongoprovider == null) { synchronized (this) { if (mongoprovider == null) { mongoprovider = prov.get(); } } } return mongoprovider.get(); } } private static final class CollectionBinding { private final String collection; private final String bindingName; private final CreateCollectionOptions opts; private Class type; public CollectionBinding(String collection, String bindingName, CreateCollectionOptions opts, Class type) { Checks.notNull("collection", collection); this.collection = collection; this.bindingName = bindingName == null ? collection : bindingName; this.opts = opts != null ? opts : new CreateCollectionOptions(); this.type = type; } @Override public boolean equals(Object obj) { return obj instanceof CollectionBinding && ((CollectionBinding) obj).collection.equals(collection); } @Override public int hashCode() { return collection.hashCode() * 37; } @Override public String toString() { return collection + ":" + bindingName + " with " + opts; } @SuppressWarnings("unchecked") void bind(Binder binder) { Provider clientProvider = binder.getProvider(MongoClient.class); Provider existingProvider = binder.getProvider(ExistingCollections.class); MongoTypedCollectionProvider docProvider = new MongoTypedCollectionProvider<>(collection, Document.class, existingProvider, clientProvider); Provider> futProvider = new FutureCollectionProvider(docProvider); CollectionPromisesProvider cpProvider = new CollectionPromisesProvider<>(docProvider); binder.bind(COLLECTION_PROMISES).annotatedWith(Names.named(bindingName)).toProvider(cpProvider); binder.bind(MONGO_DOCUMENT_COLLECTION).annotatedWith(Names.named(bindingName)).toProvider(docProvider); binder.bind(FUTURE_COLLECTION).annotatedWith(Names.named(bindingName)).toProvider(futProvider); if (type != Document.class) { MongoTypedCollectionProvider typedProvider = docProvider.withType(type); Type t = new FakeType<>(type); Key> key = (Key>) Key.get(t, Names.named(bindingName)); binder.bind(key).toProvider(typedProvider); CollectionPromisesProvider promises = new CollectionPromisesProvider<>(typedProvider); Type ct = new FakeType2<>(type); Key> promiseKey = (Key>) Key.get(ct, Names.named(bindingName)); binder.bind(promiseKey).toProvider(promises); Type ft = new FakeType3(type); Key> futureKey = (Key>) Key.get(ft, Names.named(bindingName)); binder.bind(futureKey).toProvider(new TypedFutureCollectionProvider<>(futProvider, type)); } } } static final class TypedFutureCollectionProvider implements Provider> { private final Provider> delegate; private final Class type; public TypedFutureCollectionProvider(Provider> delegate, Class type) { this.delegate = delegate; this.type = type; } @Override public MongoFutureCollection get() { return delegate.get().withDocumentClass(type); } } static final class FutureCollectionProvider implements Provider> { private final Provider> provider; public FutureCollectionProvider(Provider> provider) { this.provider = provider; } @Override public MongoFutureCollection get() { return new MongoFutureCollection<>(provider); } } /** * TypeLiteral for MongoCollection parameterized on BSON Document. */ public static final TypeLiteral> MONGO_DOCUMENT_COLLECTION = new TL(); public static final TypeLiteral> COLLECTION_PROMISES = new CPL(); public static final TypeLiteral> FUTURE_COLLECTION = new MFCD(); static class TL extends TypeLiteral> { } static class CPL extends TypeLiteral> { } static class MFCD extends TypeLiteral> { } static class FakeType implements ParameterizedType { private final Class genericType; public FakeType(Class genericType) { this.genericType = genericType; } public String getTypeName() { return MongoCollection.class.getName(); } public Type[] getActualTypeArguments() { return new Type[]{genericType}; } public Type getRawType() { return MongoCollection.class; } public Type getOwnerType() { return null; } } static class FakeType2 implements ParameterizedType { private final Class genericType; public FakeType2(Class genericType) { this.genericType = genericType; } public String getTypeName() { return CollectionPromises.class.getName(); } public Type[] getActualTypeArguments() { return new Type[]{genericType}; } public Type getRawType() { return CollectionPromises.class; } public Type getOwnerType() { return null; } } static class FakeType3 implements ParameterizedType { private final Class genericType; public FakeType3(Class genericType) { this.genericType = genericType; } public String getTypeName() { return MongoFutureCollection.class.getName(); } public Type[] getActualTypeArguments() { return new Type[]{genericType}; } public Type getRawType() { return MongoFutureCollection.class; } public Type getOwnerType() { return null; } } static class CollectionPromisesProvider implements Provider> { private final MongoTypedCollectionProvider prov; public CollectionPromisesProvider(MongoTypedCollectionProvider prov) { this.prov = prov; } @Override public CollectionPromises get() { return new CollectionPromises<>(prov.get()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy