/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.jooby.mongodb; import static java.util.Objects.requireNonNull; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.jooby.Env; import org.jooby.Env.ServiceKey; import org.jooby.internal.mongodb.AutoIncID; import org.jooby.internal.mongodb.GuiceObjectFactory; import org.mongodb.morphia.Datastore; import org.mongodb.morphia.EntityInterceptor; import org.mongodb.morphia.Morphia; import org.mongodb.morphia.annotations.PrePersist; import org.mongodb.morphia.mapping.Mapper; import com.google.inject.Binder; import com.typesafe.config.Config; /** * Extends {@link Mongodb} with object-document mapping via {@link Morphia}. * * Exposes {@link Morphia} and {@link Datastore} services. * * <h1>usage</h1> * * <p> * application.conf: * </p> * * <pre> * db = "mongodb://localhost/mydb" * </pre> * * <pre> * { * use(new Monphia()); * * get("/", req {@literal ->} { * Datastore ds = req.require(Datastore.class); * // work with mydb datastore * }); * } * </pre> * * <h1>options</h1> * <h2>morphia callback</h2> * <p> * The {@link Morphia} callback let you map classes and/or set mapper options. * </p> * * <pre> * { * use(new Monphia() * .doWith((morphia, config) {@literal ->} { * // work with morphia * morphia.map(MyObject.class); * }); * ); * } * </pre> * * For more detailed information, check * <a href="https://github.com/mongodb/morphia/wiki/MappingObjects">here</a> * * <h2>datastore callback</h2> * <p> * This {@link Datastore} callback is executed only once, it's perfect for checking indexes: * </p> * * <pre> * { * use(new Monphia() * .doWith(datastore {@literal ->} { * // work with datastore * datastore.ensureIndexes(); * datastore.ensureCap(); * }); * ); * } * </pre> * * For more detailed information, check * <a href="https://github.com/mongodb/morphia/wiki/Datastore#ensure-indexes-and-caps">here</a> * * <h2>auto-incremental ID</h2> * <p> * This modules comes with auto-incremental ID generation. * </p> * <p> * usage: * </p> * <pre> * { * use(new Monphia().with(IdGen.GLOBAL); // or IdGen.LOCAL * } * </pre> * <p> * ID must be of type: {@link Long} and annotated with {@link GeneratedValue}: * </p> * <pre> * @Entity * public class MyEntity { * @Id @GeneratedValue Long id; * } * </pre> * * <p> * There two ID gen: * </p> * <ul> * <li>GLOBAL: generates a global and unique ID regardless of entity type.</li> * <li>LOCAL: generates an unique ID per entity type.</li> * </ul> * * <h1>entity listeners</h1> * * <p> * Guice will create and inject entity listeners (when need it). * </p> * * <pre> * public class MyListener { * * private Service service; * * @Inject * public MyListener(Service service) { * this.service = service; * } * * @PreLoad void preLoad(MyObject object) { * service.doSomething(object); * } * * } * </pre> * * <p> * NOTE: ONLY Constructor injection is supported. * </p> * * @author edgar * @since 0.5.0 */ public class Monphia extends Mongodb { private BiConsumer<Morphia, Config> morphiaCbck; private Consumer<Datastore> callback; private IdGen gen = null; /** * Creates a new {@link Monphia} module. * * @param db Name of the property with the connection URI. */ public Monphia(final String db) { super(db); } /** * Creates a new {@link Monphia} using the default property: <code>db</code>. */ public Monphia() { } /** * Morphia startup callback, from here you can map classes, set mapper options, etc.. * * @param callback Morphia callback. * @return This module. */ public Monphia doWith(final BiConsumer<Morphia, Config> callback) { this.morphiaCbck = requireNonNull(callback, "Mapper callback is required."); return this; } /** * {@link Datastore} startup callback, from here you can call {@link Datastore#ensureIndexes()}. * * @param callback Datastore callback. * @return This module. */ public Monphia doWith(final Consumer<Datastore> callback) { this.callback = requireNonNull(callback, "Datastore callback is required."); return this; } /** * <p> * Setup up an {@link EntityInterceptor} on {@link PrePersist} events that generates an * incremental ID. * </p> * * Usage: * <pre> * { * use(new Monphia().with(IdGen.GLOBAL); * } * </pre> * * <p> * ID must be of type: {@link Long} and annotated with {@link GeneratedValue}: * </p> * <pre> * @Entity * public class MyEntity { * @Id @GeneratedValue Long id; * } * </pre> * * @param gen an {@link IdGen} strategy * @return This module. */ public Monphia with(final IdGen gen) { this.gen = requireNonNull(gen, "ID Gen is required."); return this; } @Override public void configure(final Env env, final Config conf, final Binder binder) { configure(env, conf, binder, (uri, client) -> { String db = uri.getDatabase(); Mapper mapper = new Mapper(); Morphia morphia = new Morphia(mapper); if (this.morphiaCbck != null) { this.morphiaCbck.accept(morphia, conf); } Datastore datastore = morphia.createDatastore(client, mapper, db); if (gen != null) { mapper.addInterceptor(new AutoIncID(datastore, gen)); } if (callback != null) { callback.accept(datastore); } ServiceKey serviceKey = env.serviceKey(); serviceKey.generate(Morphia.class, db, k -> binder.bind(k).toInstance(morphia)); serviceKey.generate(Datastore.class, db, k -> binder.bind(k).toInstance(datastore)); env.onStart(registry -> new GuiceObjectFactory(registry, morphia)); }); } }