com.callidusrobotics.droptables.configuration.MongoFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.callidusrobotics.droptables.configuration.MongoFactory.java

Source

/**
 * Copyright (C) 2015 Rusty Gerard
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.callidusrobotics.droptables.configuration;

import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Environment;

import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.NotEmpty;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;

/**
 * Factory to manage connection pools for a single database.
 * <p>
 * Supports up to two connection pools: a read-write client for use by the
 * application, and optionally a read-only client for use by report generators.
 * <br>
 * Access control is handled using role-based authorization within the database.
 *
 * @author Rusty Gerard
 * @since 0.0.1
 * @see DropTablesConfig
 */
public class MongoFactory {
    private enum LockFlags {
        MONGO_CLIENT, DATASTORE
    }

    private transient final Lock[] locks = new Lock[LockFlags.values().length];
    private transient final Morphia morphia = new Morphia();
    private transient MongoClient rwClient, roClient;
    private transient Datastore rwDatastore, roDatastore;

    @NotEmpty
    private String dbName;

    @NotEmpty
    private String host;

    @Min(1)
    @Max(65535)
    private int port;

    // A Mongo user with read-write access is required
    @Valid
    @NotNull
    LoginInfo rwUser;

    // A Mongo user with read-only access is optional
    @Valid
    LoginInfo roUser;

    public MongoFactory() {
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new ReentrantLock();
        }
    }

    @JsonProperty
    public String getDbName() {
        return dbName;
    }

    @JsonProperty
    public void setDbName(String dbName) {
        this.dbName = dbName;
    }

    @JsonProperty
    public String getHost() {
        return host;
    }

    @JsonProperty
    public void setHost(String host) {
        this.host = host;
    }

    @JsonProperty
    public int getPort() {
        return port;
    }

    @JsonProperty
    public void setPort(int port) {
        this.port = port;
    }

    @JsonProperty("serviceUser")
    public void setRwUser(LoginInfo rwUser) {
        this.rwUser = rwUser;
    }

    @JsonProperty("reportsUser")
    public void setRoUser(LoginInfo roUser) {
        this.roUser = roUser;
    }

    public Morphia getMorphia() {
        return morphia;
    }

    /**
     * Generates a connection pool using the read-write credentials.
     *
     * @param env
     *          The application's environment
     * @return The connection pool, never null
     * @throws UnknownHostException
     * @see #setRwUser(LoginInfo)
     */
    public MongoClient buildReadWriteClient(Environment env) throws UnknownHostException {
        if (rwClient != null) {
            return rwClient;
        }

        Lock lock = locks[LockFlags.MONGO_CLIENT.ordinal()];
        lock.lock();

        try {
            if (rwClient == null) {
                rwClient = buildClient(env, rwUser.getUsername(), rwUser.getPassword());
            }
        } finally {
            lock.unlock();
        }

        return rwClient;
    }

    /**
     * Generates a connection pool using the read-only credentials. <br>
     * If the read-only credentials are not provided, or if the read-only
     * credentials are the same as the read-write credentials, it returns the
     * read-write connection pool instead.
     *
     * @param env
     *          The application's environment
     * @return The connection pool, never null
     * @throws UnknownHostException
     * @see #setRoUser(LoginInfo)
     */
    public MongoClient buildReadOnlyClient(Environment env) throws UnknownHostException {
        if (roClient != null) {
            return roClient;
        }

        if (roUser == null || rwUser.equals(roUser)) {
            roClient = buildReadWriteClient(env);
        }

        Lock lock = locks[LockFlags.MONGO_CLIENT.ordinal()];
        lock.lock();

        try {
            if (roClient == null && roUser != null) {
                roClient = buildClient(env, roUser.getUsername(), roUser.getPassword());
            }
        } finally {
            lock.unlock();
        }

        return roClient;
    }

    private MongoClient buildClient(Environment env, String username, String password) throws UnknownHostException {
        final MongoClient mongoClient;
        if (StringUtils.isBlank(username)) {
            mongoClient = new MongoClient(new ServerAddress(host, port));
        } else {
            char[] passwordChars = password == null ? new char[0] : password.toCharArray();
            mongoClient = new MongoClient(new ServerAddress(host, port),
                    Arrays.asList(MongoCredential.createCredential(username, dbName, passwordChars)));
        }

        env.lifecycle().manage(new Managed() {
            @Override
            public void start() throws Exception {
            }

            @Override
            public void stop() throws Exception {
                mongoClient.close();
            }
        });

        return mongoClient;
    }

    /**
     * Generates a connection wrapper using the read-write credentials.
     *
     * @param env
     *          The application's environment
     * @return The connection wrapper, never null
     * @throws UnknownHostException
     * @see #buildReadWriteClient(Environment)
     */
    public Datastore buildReadWriteDatastore(Environment env) throws UnknownHostException {
        if (rwDatastore != null) {
            return rwDatastore;
        }

        Lock lock = locks[LockFlags.DATASTORE.ordinal()];
        lock.lock();

        try {
            if (rwDatastore == null) {
                rwDatastore = morphia.createDatastore(buildReadWriteClient(env), dbName);
            }
        } finally {
            lock.unlock();
        }

        return rwDatastore;
    }

    /**
     * Generates a connection wrapper using the read-only credentials.
     *
     * @param env
     *          The application's environment.
     * @return The connection wrapper, never null
     * @throws UnknownHostException
     * @see #buildReadOnlyClient(Environment)
     */
    public Datastore buildReadOnlyDatastore(Environment env) throws UnknownHostException {
        if (roDatastore != null) {
            return roDatastore;
        }

        if (roUser == null || rwUser.equals(roUser)) {
            roDatastore = buildReadWriteDatastore(env);
        }

        Lock lock = locks[LockFlags.DATASTORE.ordinal()];
        lock.lock();

        try {
            if (roDatastore == null) {
                roDatastore = morphia.createDatastore(buildReadOnlyClient(env), dbName);
            }
        } finally {
            lock.unlock();
        }

        return roDatastore;
    }
}