org.opentdc.mongo.AbstractMongodbServiceProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.opentdc.mongo.AbstractMongodbServiceProvider.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Arbalo AG
 *
 * 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 org.opentdc.mongo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import org.bson.Document;
import org.bson.types.ObjectId;
import org.opentdc.service.exception.InternalServerErrorException;
import org.opentdc.service.exception.NotFoundException;
import org.opentdc.service.exception.ValidationException;

import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.MongoException;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;

import static com.mongodb.client.model.Filters.*;

/**
 * Abstract implementation of mongodb access.
 * Use it as follows:  connect(); getCollection(name); your operations; disconnect().
 * The data must be converted into a org.bson.Document first.
 * @author Bruno Kaiser
 *
 * @param <T>
 */
public class AbstractMongodbServiceProvider<T> {
    private static final Logger logger = Logger.getLogger(AbstractMongodbServiceProvider.class.getName());
    private static String mongodbHost = null;
    private static int mongodbPort = 27017; // default mongodb port
    private static String mongodbName = null;
    private static String mongodbUser = null;
    private static String mongodbPwd = null; // Password of dbUser in mongoDB
    private static MongoClient mongoClient = null; // represents a pool of connections for a database cluster
    protected MongoCollection<Document> collection = null; // corresponds to a table
    protected String collectionName = null;

    /**
     * Constructor
     * @param context  the servlet context with configuration attributes
     */
    public AbstractMongodbServiceProvider(ServletContext context) {
        // TODO: support multiple ServerAddresses for replica sets  host1;host2;host3
        mongodbHost = context.getInitParameter("mongodbHost");
        if (mongodbHost == null || mongodbHost.isEmpty()) {
            mongodbHost = "localhost"; // only for test environment
        }
        // TODO: support multiple ports for replica sets    port1;port2;port3
        String _buf = context.getInitParameter("mongodbPort");
        if (_buf != null && !_buf.isEmpty()) {
            mongodbPort = new Integer(_buf).intValue();
        }
        String _dbName = context.getInitParameter("mongodbName");
        if (_dbName == null || _dbName.isEmpty()) {
            mongodbName = "opentdc_v1";
        } else {
            mongodbName = _dbName;
        }
        mongodbUser = context.getInitParameter("mongodbUser"); // may be null !
        mongodbPwd = context.getInitParameter("mongodbPwd"); // may be null !
        logger.info("AbstractMongodbServiceProvider: mongodbHost=" + mongodbHost + ", mongodbPort=" + mongodbPort
                + ", mongodbName=" + mongodbName + ", mongodbUser=" + mongodbUser);
    }

    /**
     * Establish a connection to the MongoDB database.
     * @throws InternalServerErrorException if the connection could not be established
     */
    protected static void connect() throws InternalServerErrorException {
        ServerAddress _serverAddress = new ServerAddress(mongodbHost, mongodbPort);
        if (mongoClient != null) {
            logger.warning("re-connecting to an already open mongoDB connection");
        } else {
            try {
                if (mongodbUser == null || mongodbUser.isEmpty() || mongodbPwd == null || mongodbPwd.isEmpty()) {
                    logger.warning("access to MongoDB is insecure");
                    mongoClient = new MongoClient(_serverAddress);
                } else {
                    MongoCredential _credential = MongoCredential.createCredential(mongodbUser, mongodbName,
                            mongodbPwd.toCharArray());
                    mongoClient = new MongoClient(_serverAddress, Arrays.asList(_credential));
                }
                // default WriteConcern.ACKNOWLEDGED
                // optionally, choose another WriteConcern eg. _mongoClient.setWriteConcern(WriteConcern.JOURNALED);
            } catch (MongoException _ex) {
                logger.info(_ex.getMessage());
                logger.info(_ex.getStackTrace().toString());
                throw new InternalServerErrorException(
                        "could not establish a connection to the mongodb: MongoException in AbstractMongodbServiceProvider.constructor");
            }
        }
    }

    /**
     * Set and return the collection with name collectionName. In MongoDB, a collection
     * corresponds to a table in a relational database.
     * @param collectionName the name of the collection to retrieve.
     * @return a collection of Documents
     * @throws InternalServerErrorException if there is a problem getting the collection
     * @throws ValidationException if the collectionName is not correct
     */
    protected MongoCollection<Document> getCollection(String collectionName)
            throws InternalServerErrorException, ValidationException {
        if (collectionName == null || collectionName.isEmpty()) {
            throw new ValidationException("collectionName ist not set");
        }
        if (mongoClient == null) {
            throw new InternalServerErrorException("trying to get collection <" + collectionName
                    + "> from non-existing DB; please connect() first.");
        }
        try {
            MongoDatabase _db = mongoClient.getDatabase(mongodbName);
            collection = _db.getCollection(collectionName);
            this.collectionName = collectionName;
        } catch (Exception _ex) {
            logger.info(_ex.getMessage());
            logger.info(_ex.getStackTrace().toString());
            throw new InternalServerErrorException("could not get collection <" + collectionName + ">.");
        }
        return collection;
    }

    /**
     * Return a list of Documents. In MongoDB, a Document corresponds to a row in a relational DB.
     * @param position the element to start the return set with
     * @param size the amount of documents to return
     * @return a list of Documents (ie database objects)
     */
    protected List<Document> list(int position, int size) {
        // TODO: queries are not yet supported
        FindIterable<Document> _iterableResult = (size == 0) ? collection.find().skip(position)
                : collection.find().skip(position).limit(size);
        List<Document> _resultList = new ArrayList<Document>();
        for (Document _doc : _iterableResult) {
            _resultList.add(_doc);
        }
        logger.info("list(queryObject, " + position + ", " + size + ") -> " + _resultList.size() + " objects.");
        return _resultList;
    }

    /**
     * Create a new database object (aka Document). 
     * The ID should not be set as it is generated by the MongoDB.
     * @param document the object to insert into the database.
     */
    protected void create(Document document) {
        try {
            collection.insertOne(document);
            logger.info("create(" + document + ") -> OK");
        } catch (Exception _ex) {
            logger.info(_ex.getMessage());
            logger.info(_ex.getStackTrace().toString());
            throw new InternalServerErrorException("could not create a dbobject");
        }
    }

    /**
     * Retrieve one single object based on its ID.
     * @param id the unique identificator of the object
     * @return the object found or null if there was no such document
     */
    protected Document readOne(String id) {
        logger.info("readOne(" + id + ")");
        Document _doc = null;
        try {
            _doc = collection.find(eq("_id", new ObjectId(id))).first();
        } catch (Exception _ex) {
            logger.warning(_ex.getMessage());
            logger.warning(_ex.getStackTrace().toString());
            // logging the error, returning with null (ie not found)
            // typically this is IllegalArgumentException: invalid hexadecimal representation of an ObjectId
        }
        return _doc;
    }

    /**
     * Update the database object with a given unique id.
     * @param id the unique identificator of the object to update
     * @param document the new data of the database object
     * @return the updated object containing the new data
     * @throws NotFoundException if no object with the given id was found
     * @throws InternalServerErrorException if there was a problem getting the object from mongodb
     */
    protected void update(String id, Document document) throws NotFoundException, InternalServerErrorException {
        UpdateResult _result = null;
        try {
            logger.info("update(" + id + ", " + document.toJson() + ")");
            _result = collection.updateOne(eq("_id", new ObjectId(id)), new Document("$set", document));
            // TODO: better handling of UpdateResult
        } catch (Exception _ex) {
            logger.info(_ex.getMessage());
            logger.info(_ex.getStackTrace().toString());
            throw new InternalServerErrorException("could not update dbobject <" + id + ">");
        }
        if (_result.getModifiedCount() == 0) {
            throw new NotFoundException(
                    "dbobject with id <" + id + "> was not found in collection " + collectionName + ".");
        }
        logger.info("update(" + id + ", " + document.toJson() + ") -> OK");
    }

    /**
     * Delete the database object with a given unique id.
     * @param id the unique identificator of the object to delete
     * @throws NotFoundException if no object with the given id is found
     */
    protected void deleteOne(String id) throws NotFoundException {
        DeleteResult _result = collection.deleteOne(eq("_id", new ObjectId(id)));
        if (_result.getDeletedCount() == 0) {
            throw new NotFoundException(
                    "dbobject with id <" + id + "> was not found in collection " + collectionName + ".");
        }
        logger.info("deleteOne(" + id + ") -> OK");
    }

    /**
     * Disconnect the MongoDB.
     */
    protected static void disconnect() {
        if (mongoClient == null) {
            logger.warning("trying to close a non-existing mongo-connection");
        } else {
            mongoClient.close();
        }
        logger.info("disconnect() -> OK");
    }
}