org.mule.module.mongo.MongoCloudConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.module.mongo.MongoCloudConnector.java

Source

/**
 * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.md file.
 */

package org.mule.module.mongo;

import static org.mule.module.mongo.api.DBObjects.adapt;
import static org.mule.module.mongo.api.DBObjects.from;
import static org.mule.module.mongo.api.DBObjects.fromCommand;
import static org.mule.module.mongo.api.DBObjects.fromFunction;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.Validate;
import org.bson.types.BasicBSONList;
import org.mule.api.ConnectionException;
import org.mule.api.ConnectionExceptionCode;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Connect;
import org.mule.api.annotations.ConnectionIdentifier;
import org.mule.api.annotations.Connector;
import org.mule.api.annotations.Disconnect;
import org.mule.api.annotations.MetaDataSwitch;
import org.mule.api.annotations.Mime;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.Transformer;
import org.mule.api.annotations.ValidateConnection;
import org.mule.api.annotations.display.Password;
import org.mule.api.annotations.display.Placement;
import org.mule.api.annotations.param.ConnectionKey;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.Optional;
import org.mule.api.annotations.param.Payload;
import org.mule.module.mongo.api.IndexOrder;
import org.mule.module.mongo.api.MongoClient;
import org.mule.module.mongo.api.MongoClientAdaptor;
import org.mule.module.mongo.api.MongoClientImpl;
import org.mule.module.mongo.api.MongoCollection;
import org.mule.module.mongo.api.WriteConcern;
import org.mule.module.mongo.tools.BackupConstants;
import org.mule.module.mongo.tools.IncrementalMongoDump;
import org.mule.module.mongo.tools.MongoDump;
import org.mule.module.mongo.tools.MongoRestore;
import org.mule.transformer.types.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoOptions;
import com.mongodb.ServerAddress;
import com.mongodb.util.JSON;

/**
 * MongoDB is an open source, high-performance, schema-free, document-oriented database that manages
 * collections of BSON documents.
 * 
 * @author MuleSoft, inc.
 */
@Connector(name = "mongo", schemaVersion = "2.0", friendlyName = "Mongo DB", minMuleVersion = "3.4", metaData = MetaDataSwitch.OFF)
public class MongoCloudConnector {
    private static final Logger LOGGER = LoggerFactory.getLogger(MongoCloudConnector.class);

    private static final String CAPPED_DEFAULT_VALUE = "false";
    private static final String WRITE_CONCERN_DEFAULT_VALUE = "DATABASE_DEFAULT";
    private static final String BACKUP_THREADS = "5";
    private static final String DEFAULT_OUTPUT_DIRECTORY = "dump";

    /**
     * The host of the Mongo server, it can also be a list of comma separated hosts for replicas
     */
    @Configurable
    @Optional
    @Default("localhost")
    private String host;

    /**
     * The port of the Mongo server
     */
    @Configurable
    @Optional
    @Default("27017")
    private int port;

    /**
     * The number of connections allowed per host (the pool size, per host)
     */
    @Configurable
    @Optional
    public Integer connectionsPerHost;

    /**
     * Multiplier for connectionsPerHost for # of threads that can block
     */
    @Configurable
    @Optional
    public Integer threadsAllowedToBlockForConnectionMultiplier;

    /**
     * The max wait time for a blocking thread for a connection from the pool in ms.
     */
    @Configurable
    @Optional
    public Integer maxWaitTime;

    /**
     * The connection timeout in milliseconds; this is for establishing the socket connections
     * (open). 0 is default and infinite.
     */
    @Configurable
    @Optional
    @Default("30000")
    private Integer connectTimeout;

    /**
     * The socket timeout. 0 is default and infinite.
     */
    @Configurable
    @Optional
    private Integer socketTimeout;

    /**
     * This controls whether the system retries automatically on connection errors.
     */
    @Configurable
    @Optional
    private Boolean autoConnectRetry;

    /**
     * Specifies if the driver is allowed to read from secondaries or slaves.
     */
    @Configurable
    @Optional
    private Boolean slaveOk;

    /**
     * If the driver sends a getLastError command after every update to ensure it succeeded.
     */
    @Configurable
    @Optional
    public Boolean safe;

    /**
     * If set, the w value of WriteConcern for the connection is set to this.
     */
    @Configurable
    @Optional
    public Integer w;

    /**
     * If set, the wtimeout value of WriteConcern for the connection is set to this.
     */
    @Configurable
    @Optional
    public Integer wtimeout;

    /**
     * Sets the fsync value of WriteConcern for the connection.
     */
    @Configurable
    @Optional
    public Boolean fsync;

    private String database;

    private Mongo mongo;

    private MongoClient client;

    /**
     * Adds a new user for this db
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:add-user}
     * 
     * @param newUsername Name of the user
     * @param newPassword Password that will be used for authentication
     */
    @Processor
    public void addUser(final String newUsername, final String newPassword) {
        client.addUser(newUsername, newPassword);
    }

    /**
     * Drop the current database
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:drop-database}
     */
    @Processor
    public void dropDatabase() {
        client.dropDatabase();
    }

    /**
     * Lists names of collections available at this database
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:list-collections}
     * 
     * @return the list of names of collections available at this database
     */
    @Processor
    public Collection<String> listCollections() {
        return client.listCollections();
    }

    /**
     * Answers if a collection exists given its name
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:exists-collection}
     * 
     * @param collection the name of the collection
     * @return if the collection exists
     */
    @Processor
    public boolean existsCollection(final String collection) {
        return client.existsCollection(collection);
    }

    /**
     * Deletes a collection and all the objects it contains. If the collection does not exist, does
     * nothing.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:drop-collection}
     * 
     * @param collection the name of the collection to drop
     */
    @Processor
    public void dropCollection(final String collection) {
        client.dropCollection(collection);
    }

    /**
     * Creates a new collection. If the collection already exists, a MongoException will be thrown.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:create-collection}
     * 
     * @param collection the name of the collection to create
     * @param capped if the collection will be capped
     * @param maxObjects the maximum number of documents the new collection is able to contain
     * @param size the maximum size of the new collection
     */
    @Processor
    public void createCollection(final String collection,
            @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean capped, @Optional final Integer maxObjects,
            @Optional final Integer size) {
        client.createCollection(collection, capped, maxObjects, size);
    }

    /**
     * Inserts an object in a collection, setting its id if necessary.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:insert-object}
     * 
     * @param collection the name of the collection where to insert the given object
     * @param dbObject a {@link DBObject} instance.
     * @param writeConcern the optional write concern of insertion
     * @return the id that was just insterted
     */
    @Processor
    public String insertObject(final String collection, @Optional @Default("#[payload]") final DBObject dbObject,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        return client.insertObject(collection, dbObject, writeConcern);
    }

    /**
     * Inserts an object in a collection, setting its id if necessary.
     * <p/>
     * A shallow conversion into DBObject is performed - that is, no conversion is performed to its
     * values.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:insert-object-from-map}
     * 
     * @param collection the name of the collection where to insert the given object
     * @param elementAttributes alternative way of specifying the element as a literal Map inside a
     *            Mule Flow
     * @param writeConcern the optional write concern of insertion
     * @return the id that was just insterted
     */
    @Processor
    public String insertObjectFromMap(final String collection,
            @Placement(group = "Element Attributes") @Optional final Map<String, Object> elementAttributes,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        return client.insertObject(collection, (DBObject) adapt(elementAttributes), writeConcern);
    }

    /**
     * Updates objects that matches the given query. If parameter multi is set to false, only the
     * first document matching it will be updated. Otherwise, all the documents matching it will be
     * updated.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:update-objects}
     * 
     * @param collection the name of the collection to update
     * @param query the {@link DBObject} query object used to detect the element to update.
     * @param element the {@link DBObject} mandatory object that will replace that one which matches
     *            the query.
     * @param upsert if the database should create the element if it does not exist
     * @param multi if all or just the first object matching the query will be updated
     * @param writeConcern the write concern used to update
     */
    @Processor
    public void updateObjects(final String collection, final DBObject query,
            @Optional @Default("#[payload]") final DBObject element,
            @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean upsert,
            @Optional @Default("true") final boolean multi,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.updateObjects(collection, query, element, upsert, multi, writeConcern);
    }

    /**
     * Updates objects that matches the given query. If parameter multi is set to false, only the
     * first document matching it will be updated. Otherwise, all the documents matching it will be
     * updated.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:update-objects-using-query-map}
     * 
     * @param collection the name of the collection to update
     * @param queryAttributes the query object used to detect the element to update.
     * @param element the {@link DBObject} mandatory object that will replace that one which matches
     *            the query.
     * @param upsert if the database should create the element if it does not exist
     * @param multi if all or just the first object matching the query will be updated
     * @param writeConcern the write concern used to update
     */
    @Processor
    public void updateObjectsUsingQueryMap(final String collection, final Map<String, Object> queryAttributes,
            final DBObject element, @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean upsert,
            @Optional @Default("true") final boolean multi,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.updateObjects(collection, (DBObject) adapt(queryAttributes), element, upsert, multi, writeConcern);
    }

    /**
     * Updates objects that matches the given query. If parameter multi is set to false, only the
     * first document matching it will be updated. Otherwise, all the documents matching it will be
     * updated.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:update-objects-using-map}
     * 
     * @param collection the name of the collection to update
     * @param queryAttributes the query object used to detect the element to update.
     * @param elementAttributes the mandatory object that will replace that one which matches the
     *            query.
     * @param upsert if the database should create the element if it does not exist
     * @param multi if all or just the first object matching the query will be updated
     * @param writeConcern the write concern used to update
     */
    @Processor
    public void updateObjectsUsingMap(final String collection,
            @Placement(group = "Query Attributes") final Map<String, Object> queryAttributes,
            @Placement(group = "Element Attributes") final Map<String, Object> elementAttributes,
            @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean upsert,
            @Optional @Default("true") final boolean multi,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.updateObjects(collection, (DBObject) adapt(queryAttributes), (DBObject) adapt(elementAttributes),
                upsert, multi, writeConcern);
    }

    /**
     * Update objects using a mongo function
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:update-objects-by-function}
     * 
     * @param collection the name of the collection to update
     * @param function the function used to execute the update
     * @param query the {@link DBObject} query object used to detect the element to update.
     * @param element the {@link DBObject} mandatory object that will replace that one which matches
     *            the query.
     * @param upsert if the database should create the element if it does not exist
     * @param multi if all or just the first object matching the query will be updated
     * @param writeConcern the write concern used to update
     */
    @Processor
    public void updateObjectsByFunction(final String collection, final String function, final DBObject query,
            final DBObject element, @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean upsert,
            @Optional @Default(value = "true") final boolean multi,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        final DBObject functionDbObject = fromFunction(function, element);

        client.updateObjects(collection, query, functionDbObject, upsert, multi, writeConcern);
    }

    /**
     * Update objects using a mongo function
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample
     * mongo:update-objects-by-function-using-map}
     * 
     * @param collection the name of the collection to update
     * @param function the function used to execute the update
     * @param queryAttributes the query object used to detect the element to update.
     * @param elementAttributes the mandatory object that will replace that one which matches the
     *            query.
     * @param upsert if the database should create the element if it does not exist
     * @param multi if all or just the first object matching the query will be updated
     * @param writeConcern the write concern used to update
     */
    @Processor
    public void updateObjectsByFunctionUsingMap(final String collection, final String function,
            final Map<String, Object> queryAttributes, final Map<String, Object> elementAttributes,
            @Optional @Default(CAPPED_DEFAULT_VALUE) final boolean upsert,
            @Optional @Default(value = "true") final boolean multi,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        final DBObject functionDbObject = fromFunction(function, (DBObject) adapt(elementAttributes));

        client.updateObjects(collection, (DBObject) adapt(queryAttributes), functionDbObject, upsert, multi,
                writeConcern);
    }

    /**
     * Inserts or updates an object based on its object _id.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:save-object}
     * 
     * @param collection the collection where to insert the object
     * @param element the mandatory {@link DBObject} object to insert.
     * @param writeConcern the write concern used to persist the object
     */
    @Processor
    public void saveObject(final String collection, @Optional @Default("#[payload]") final DBObject element,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.saveObject(collection, from(element), writeConcern);
    }

    /**
     * Inserts or updates an object based on its object _id.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:save-object-from-map}
     * 
     * @param collection the collection where to insert the object
     * @param elementAttributes the mandatory object to insert.
     * @param writeConcern the write concern used to persist the object
     */
    @Processor
    public void saveObjectFromMap(final String collection,
            @Placement(group = "Element Attributes") final Map<String, Object> elementAttributes,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.saveObject(collection, (DBObject) adapt(elementAttributes), writeConcern);
    }

    /**
     * Removes all the objects that match the a given optional query. If query is not specified, all
     * objects are removed. However, please notice that this is normally less performant that
     * dropping the collection and creating it and its indices again
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:remove-objects}
     * 
     * @param collection the collection whose elements will be removed
     * @param query the optional {@link DBObject} query object. Objects that match it will be
     *            removed.
     * @param writeConcern the write concern used to remove the object
     */
    @Processor
    public void removeObjects(final String collection, @Optional @Default("#[payload]") final DBObject query,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.removeObjects(collection, query, writeConcern);
    }

    /**
     * Removes all the objects that match the a given optional query. If query is not specified, all
     * objects are removed. However, please notice that this is normally less performant that
     * dropping the collection and creating it and its indices again
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:remove-using-query-map}
     * 
     * @param collection the collection whose elements will be removed
     * @param queryAttributes the query object. Objects that match it will be removed.
     * @param writeConcern the write concern used to remove the object
     */
    @Processor
    public void removeUsingQueryMap(final String collection,
            @Placement(group = "Query Attributes") final Map<String, Object> queryAttributes,
            @Optional @Default(WRITE_CONCERN_DEFAULT_VALUE) final WriteConcern writeConcern) {
        client.removeObjects(collection, (DBObject) adapt(queryAttributes), writeConcern);
    }

    /**
     * Transforms a collection into a collection of aggregated groups, by applying a supplied
     * element-mapping function to each element, that transforms each one into a key-value pair,
     * grouping the resulting pairs by key, and finally reducing values in each group applying a
     * suppling 'reduce' function.
     * <p/>
     * Each supplied function is coded in JavaScript.
     * <p/>
     * Note that the correct way of writing those functions may not be obvious; please consult
     * MongoDB documentation for writing them.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:map-reduce-objects}
     * 
     * @param collection the name of the collection to map and reduce
     * @param mapFunction a JavaScript encoded mapping function
     * @param reduceFunction a JavaScript encoded reducing function
     * @param outputCollection the name of the output collection to write the results, replacing
     *            previous collection if existed, mandatory when results may be larger than 16MB. If
     *            outputCollection is unspecified, the computation is performed in-memory and not
     *            persisted.
     * @return an iterable that retrieves the resulting collection of {@link DBObject}
     */
    @Processor
    public Iterable<DBObject> mapReduceObjects(final String collection, final String mapFunction,
            final String reduceFunction, @Optional final String outputCollection) {
        return client.mapReduceObjects(collection, mapFunction, reduceFunction, outputCollection);
    }

    /**
     * Counts the number of objects that match the given query. If no query is passed, returns the
     * number of elements in the collection
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:count-objects}
     * 
     * @param collection the target collection
     * @param query the optional {@link DBObject} query for counting objects. Only objects matching
     *            it will be counted. If unspecified, all objects are counted.
     * @return the amount of objects that matches the query
     */
    @Processor
    public long countObjects(final String collection, @Optional @Default("#[payload]") final DBObject query) {
        return client.countObjects(collection, query);
    }

    /**
     * Counts the number of objects that match the given query. If no query is passed, returns the
     * number of elements in the collection
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:count-objects-using-query-map}
     * 
     * @param collection the target collection
     * @param queryAttributes the optional query for counting objects. Only objects matching it will
     *            be counted. If unspecified, all objects are counted.
     * @return the amount of objects that matches the query
     */
    @Processor
    public long countObjectsUsingQueryMap(final String collection,
            @Placement(group = "Query Attributes") @Optional final Map<String, Object> queryAttributes) {
        return client.countObjects(collection, (DBObject) adapt(queryAttributes));
    }

    /**
     * Finds all objects that match a given query. If no query is specified, all objects of the
     * collection are retrieved. If no fields object is specified, all fields are retrieved.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-objects}
     * 
     * @param collection the target collection
     * @param query the optional {@link DBObject} query object. If unspecified, all documents are
     *            returned.
     * @param fields alternative way of passing fields as a literal List
     * @param numToSkip number of objects skip (offset)
     * @param limit limit of objects to return
     * @return an iterable of {@link DBObject}
     */
    @Processor
    public Iterable<DBObject> findObjects(final String collection, @Optional @Default("") final DBObject query,
            @Placement(group = "Fields") @Optional final List<String> fields, @Optional final Integer numToSkip,
            @Optional final Integer limit) {
        return client.findObjects(collection, query, fields, numToSkip, limit);
    }

    /**
     * Finds all objects that match a given query. If no query is specified, all objects of the
     * collection are retrieved. If no fields object is specified, all fields are retrieved.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-objects-using-query-map}
     * 
     * @param collection the target collection
     * @param queryAttributes the optional query object. If unspecified, all documents are returned.
     * @param fields alternative way of passing fields as a literal List
     * @param numToSkip number of objects skip (offset)
     * @param limit limit of objects to return
     * @return an iterable of {@link DBObject}
     */
    @Processor
    public Iterable<DBObject> findObjectsUsingQueryMap(final String collection,
            @Placement(group = "Query Attributes") @Optional final Map<String, Object> queryAttributes,
            @Placement(group = "Fields") @Optional final List<String> fields, @Optional final Integer numToSkip,
            @Optional final Integer limit) {
        return client.findObjects(collection, (DBObject) adapt(queryAttributes), fields, numToSkip, limit);
    }

    /**
     * Finds the first object that matches a given query. Throws a {@link MongoException} if no one
     * matches the given query
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-one-object}
     * 
     * @param collection the target collection
     * @param query the mandatory {@link DBObject} query object that the returned object matches.
     * @param fields alternative way of passing fields as a literal List
     * @return a non-null {@link DBObject} that matches the query.
     */
    @Processor
    public DBObject findOneObject(final String collection, @Optional @Default("#[payload]") final DBObject query,
            @Placement(group = "Fields") @Optional final List<String> fields) {
        return client.findOneObject(collection, query, fields);

    }

    /**
     * Finds the first object that matches a given query. Throws a {@link MongoException} if no one
     * matches the given query
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-one-object-using-query-map}
     * 
     * @param collection the target collection
     * @param queryAttributes the mandatory query object that the returned object matches.
     * @param fields alternative way of passing fields as a literal List
     * @return a non-null {@link DBObject} that matches the query.
     */
    @Processor
    public DBObject findOneObjectUsingQueryMap(final String collection,
            @Placement(group = "Query Attributes") final Map<String, Object> queryAttributes,
            @Placement(group = "Fields") @Optional final List<String> fields) {
        return client.findOneObject(collection, (DBObject) adapt(queryAttributes), fields);

    }

    /**
     * Creates a new index
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:create-index}
     * 
     * @param collection the name of the collection where the index will be created
     * @param field the name of the field which will be indexed
     * @param order the indexing order
     */
    @Processor
    public void createIndex(final String collection, final String field,
            @Optional @Default("ASC") final IndexOrder order) {
        client.createIndex(collection, field, order);
    }

    /**
     * Drops an existing index
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:drop-index}
     * 
     * @param collection the name of the collection where the index is
     * @param index the name of the index to drop
     */
    @Processor
    public void dropIndex(final String collection, final String index) {
        client.dropIndex(collection, index);
    }

    /**
     * List existent indices in a collection
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:list-indices}
     * 
     * @param collection the name of the collection
     * @return a collection of {@link DBObject} with indices information
     */
    @Processor
    public Collection<DBObject> listIndices(final String collection) {
        return client.listIndices(collection);
    }

    /**
     * Creates a new GridFSFile in the database, saving the given content, filename, contentType,
     * and extraData, and answers it.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:create-file-from-payload}
     * 
     * @param payload the mandatory content of the new gridfs file. It may be a java.io.File, a
     *            byte[] or an InputStream.
     * @param filename the mandatory name of new file.
     * @param contentType the optional content type of the new file
     * @param metadata the optional {@link DBObject} metadata of the new content type
     * @return the new GridFSFile {@link DBObject}
     * @throws IOException IOException
     */
    @Processor
    public DBObject createFileFromPayload(@Payload final Object payload, final String filename,
            @Optional final String contentType, @Optional final DBObject metadata) throws IOException {
        final InputStream stream = toStream(payload);
        try {
            return client.createFile(stream, filename, contentType, metadata);
        } finally {
            stream.close();
        }
    }

    private InputStream toStream(final Object content) throws FileNotFoundException {
        if (content instanceof InputStream) {
            return (InputStream) content;
        }
        if (content instanceof byte[]) {
            return new ByteArrayInputStream((byte[]) content);
        }
        if (content instanceof File) {
            return new FileInputStream((File) content);
        }
        throw new IllegalArgumentException("Content " + content + " is not supported");
    }

    /**
     * Lists all the files that match the given query
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-files}
     * 
     * @param query a {@link DBObject} query the optional query
     * @return a {@link DBObject} files iterable
     */
    @Processor
    public Iterable<DBObject> findFiles(@Optional @Default("#[payload]") final DBObject query) {
        return client.findFiles(from(query));
    }

    /**
     * Lists all the files that match the given query
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-files-using-query-map}
     * 
     * @param queryAttributes the optional query attributes
     * @return a {@link DBObject} files iterable
     */
    @Processor
    public Iterable<DBObject> findFilesUsingQueryMap(
            @Placement(group = "Query Attributes") @Optional final Map<String, Object> queryAttributes) {
        return client.findFiles((DBObject) adapt(queryAttributes));
    }

    /**
     * Answers the first file that matches the given query. If no object matches it, a
     * MongoException is thrown.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-one-file}
     * 
     * @param query the {@link DBObject} mandatory query
     * @return a {@link DBObject}
     */
    @Processor
    public DBObject findOneFile(final DBObject query) {
        return client.findOneFile(from(query));
    }

    /**
     * Answers the first file that matches the given query. If no object matches it, a
     * MongoException is thrown.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:find-one-file-using-query-map}
     * 
     * @param queryAttributes the mandatory query
     * @return a {@link DBObject}
     */
    @Processor
    public DBObject findOneFileUsingQueryMap(
            @Placement(group = "Query Attributes") final Map<String, Object> queryAttributes) {
        return client.findOneFile((DBObject) adapt(queryAttributes));
    }

    /**
     * Answers an inputstream to the contents of the first file that matches the given query. If no
     * object matches it, a MongoException is thrown.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:get-file-content}
     * 
     * @param query the {@link DBObject} mandatory query
     * @return an InputStream to the file contents
     */
    @Processor
    public InputStream getFileContent(@Optional @Default("#[payload]") final DBObject query) {
        return client.getFileContent(from(query));
    }

    /**
     * Answers an inputstream to the contents of the first file that matches the given
     * queryAttributes. If no object matches it, a MongoException is thrown.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:get-file-content-using-query-map}
     * 
     * @param queryAttributes the mandatory query attributes
     * @return an InputStream to the file contents
     */
    @Processor
    public InputStream getFileContentUsingQueryMap(
            @Placement(group = "Query Attributes") final Map<String, Object> queryAttributes) {
        return client.getFileContent((DBObject) adapt(queryAttributes));
    }

    /**
     * Lists all the files that match the given query, sorting them by filename. If no query is
     * specified, all files are listed.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:list-files}
     * 
     * @param query the {@link DBObject} optional query
     * @return an iterable of {@link DBObject}
     */
    @Processor
    public Iterable<DBObject> listFiles(@Optional @Default("#[payload]") final DBObject query) {
        return client.listFiles(from(query));
    }

    /**
     * Lists all the files that match the given query, sorting them by filename. If no query is
     * specified, all files are listed.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:list-files-using-query-map}
     * 
     * @param queryAttributes the optional query
     * @return an iterable of {@link DBObject}
     */
    @Processor
    public Iterable<DBObject> listFilesUsingQueryMap(
            @Placement(group = "Query Attributes") @Optional final Map<String, Object> queryAttributes) {
        return client.listFiles((DBObject) adapt(queryAttributes));
    }

    /**
     * Removes all the files that match the given query. If no query is specified, all files are
     * removed
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:remove-files}
     * 
     * @param query the {@link DBObject} optional query
     */
    @Processor
    public void removeFiles(@Optional @Default("#[payload]") final DBObject query) {
        client.removeFiles(from(query));
    }

    /**
     * Removes all the files that match the given query. If no query is specified, all files are
     * removed
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:remove-files-using-query-map}
     * 
     * @param queryAttributes the optional query
     */
    @Processor
    public void removeFilesUsingQueryMap(
            @Placement(group = "Query Attributes") @Optional final Map<String, Object> queryAttributes) {
        client.removeFiles((DBObject) adapt(queryAttributes));
    }

    /**
     * Executes a command on the database
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:remove-files}
     * 
     * @param commandName The command to execute on the database
     * @param commandValue The value for the command
     * @return The result of the command
     */
    @Processor
    public DBObject executeCommand(final String commandName, @Optional final String commandValue) {
        final DBObject dbObject = fromCommand(commandName, commandValue);

        return client.executeComamnd(dbObject);
    }

    /**
     * Executes a dump of the database to the specified output directory. If no output directory is
     * provided then the default /dump directory is used.
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:dump}
     * 
     * @param outputDirectory output directory path, if no output directory is provided the default
     *            /dump directory is assumed
     * @param outputName output file name, if it's not specified the database name is used
     * @param zip whether to zip the created dump file or not
     * @param oplog point in time backup (requires an oplog)
     * @param threads amount of threads to execute the dump
     * @throws IOException if an error occurs during the dump
     */
    @Processor
    public void dump(@Optional @Default(DEFAULT_OUTPUT_DIRECTORY) final String outputDirectory,
            @Optional final String outputName, @Optional @Default("false") final boolean zip,
            @Optional @Default("false") final boolean oplog, @Optional @Default(BACKUP_THREADS) final int threads)
            throws IOException {
        final MongoDump mongoDump = new MongoDump(client);
        mongoDump.setZip(zip);
        if (oplog) {
            mongoDump.setOplog(oplog);
            mongoDump.addDB(mongo.getDB(BackupConstants.ADMIN_DB));
            mongoDump.addDB(mongo.getDB(BackupConstants.LOCAL_DB));
        }
        mongoDump.dump(outputDirectory, database, outputName != null ? outputName : database, threads);
    }

    /**
     * Executes an incremental dump of the database
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:incremental-dump}
     * 
     * @param outputDirectory output directory path, if no output directory is provided the default
     *            /dump directory is assumed
     * @param incrementalTimestampFile file that keeps track of the last timestamp processed, if no
     *            file is provided one is created on the output directory
     * @throws IOException if an error occurs during the incremental dump
     */
    @Processor
    public void incrementalDump(@Optional @Default(DEFAULT_OUTPUT_DIRECTORY) final String outputDirectory,
            @Optional final String incrementalTimestampFile) throws IOException {
        final IncrementalMongoDump incrementalMongoDump = new IncrementalMongoDump();
        incrementalMongoDump.addDB(mongo.getDB(BackupConstants.ADMIN_DB));
        incrementalMongoDump.addDB(mongo.getDB(BackupConstants.LOCAL_DB));
        incrementalMongoDump.setIncrementalTimestampFile(incrementalTimestampFile);
        incrementalMongoDump.dump(outputDirectory, database);
    }

    /**
     * Takes the output from the dump and restores it. Indexes will be created on a restore. It only
     * does inserts with the data to restore, if existing data is there, it will not be replaced.
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:restore}
     * 
     * @param inputPath input path to the dump files, it can be a directory, a zip file or just a
     *            bson file
     * @param drop whether to drop existing collections before restore
     * @param oplogReplay replay oplog for point-in-time restore
     * @throws IOException if an error occurs during restore of the database
     */
    @Processor
    public void restore(@Optional @Default(DEFAULT_OUTPUT_DIRECTORY) final String inputPath,
            @Optional @Default("false") final boolean drop, @Optional @Default("false") final boolean oplogReplay)
            throws IOException {
        final MongoRestore mongoRestore = new MongoRestore(client, database);
        mongoRestore.setDrop(drop);
        mongoRestore.setOplogReplay(oplogReplay);
        mongoRestore.restore(inputPath);
    }

    /**
     * Convert JSON to DBObject.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:jsonToDbobject}
     * 
     * @param input the input for this transformer
     * @return the converted {@link DBObject}
     */
    @Transformer(sourceTypes = { String.class })
    public static DBObject jsonToDbobject(final String input) {
        return (DBObject) JSON.parse(input);
    }

    /**
     * Convert DBObject to Json.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:dbobjectToJson}
     * 
     * @param input the input for this transformer
     * @return the converted string representation
     */
    @Mime(MimeTypes.JSON)
    @Transformer(sourceTypes = { DBObject.class })
    public static String dbobjectToJson(final DBObject input) {
        return JSON.serialize(input);
    }

    /**
     * Convert a BasicBSONList into Json.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:bsonListToJson}
     * 
     * @param input the input for this transformer
     * @return the converted string representation
     */
    @Mime(MimeTypes.JSON)
    @Transformer(sourceTypes = { BasicBSONList.class })
    public static String bsonListToJson(final BasicBSONList input) {
        return JSON.serialize(input);
    }

    /**
     * Convert a BasicBSONList into Json.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:mongoCollectionToJson}
     * 
     * @param input the input for this transformer
     * @return the converted string representation
     */
    @Mime(MimeTypes.JSON)
    @Transformer(sourceTypes = { MongoCollection.class })
    public static String mongoCollectionToJson(final MongoCollection input) {
        return JSON.serialize(input);
    }

    /**
     * Convert a DBObject into Map.
     * <p/>
     * {@sample.xml ../../../doc/mongo-connector.xml.sample mongo:dbObjectToMap}
     * 
     * @param input the input for this transformer
     * @return the converted Map representation
     */
    @SuppressWarnings("rawtypes")
    @Transformer(sourceTypes = { DBObject.class })
    public static Map dbObjectToMap(final DBObject input) {
        return input.toMap();
    }

    /**
     * Method invoked when a {@link MongoSession} needs to be created.
     * 
     * @param username the username to use for authentication. NOTE: Please use a dummy user if you
     *            have disabled Mongo authentication
     * @param password the password to use for authentication. NOTE: Please use a dummy password if
     *            you have disabled Mongo authentication
     * @param database Name of the database
     * @return the newly created {@link MongoSession}
     * @throws org.mule.api.ConnectionException
     */
    @Connect
    public void connect(@ConnectionKey final String username, @Password final String password,
            @Optional @Default("test") final String database) throws ConnectionException {
        DB db = null;
        try {
            final MongoOptions options = new MongoOptions();

            if (connectionsPerHost != null) {
                options.connectionsPerHost = connectionsPerHost;
            }
            if (threadsAllowedToBlockForConnectionMultiplier != null) {
                options.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
            }
            if (maxWaitTime != null) {
                options.maxWaitTime = maxWaitTime;
            }
            if (connectTimeout != null) {
                options.connectTimeout = connectTimeout;
            }
            if (socketTimeout != null) {
                options.socketTimeout = socketTimeout;
            }
            if (autoConnectRetry != null) {
                options.autoConnectRetry = autoConnectRetry;
            }
            if (slaveOk != null) {
                options.slaveOk = slaveOk;
            }
            if (safe != null) {
                options.safe = safe;
            }
            if (w != null) {
                options.w = w;
            }
            if (wtimeout != null) {
                options.wtimeout = wtimeout;
            }
            if (fsync != null) {
                options.fsync = fsync;
            }
            if (database != null) {
                this.database = database;
            }

            final String[] hosts = host.split(",\\s?");
            if (hosts.length == 1) {
                mongo = new Mongo(new ServerAddress(host, port), options);
            } else {
                final List<ServerAddress> servers = new ArrayList<ServerAddress>();
                for (final String host : hosts) {
                    servers.add(new ServerAddress(host, port));
                }
                mongo = new Mongo(servers, options);
            }
            db = getDatabase(mongo, username, password, database);
        } catch (final MongoException me) {
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, null, me.getMessage());
        } catch (final UnknownHostException e) {
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN_HOST, null, e.getMessage());
        }
        this.client = new MongoClientImpl(db);
    }

    /**
     * Method invoked when the {@link MongoSession} is to be destroyed.
     * 
     * @throws IOException in case something goes wrong when disconnecting.
     */
    @Disconnect
    public void disconnect() throws IOException {
        if (client != null) {
            try {
                client.close();
            } catch (final Exception e) {
                LOGGER.warn("Failed to properly close client: " + client, e);
            } finally {
                client = null;
            }
        }

        if (mongo != null) {
            try {
                mongo.close();
            } catch (final Exception e) {
                LOGGER.warn("Failed to properly close mongo: " + mongo, e);
            } finally {
                mongo = null;
            }
        }
    }

    @ValidateConnection
    public boolean isConnected() {
        return this.client != null;
    }

    @ConnectionIdentifier
    public String connectionId() {
        return mongo == null ? "n/a" : Mongo.class.getName() + System.identityHashCode(mongo);
    }

    private DB getDatabase(final Mongo mongo, final String username, final String password, final String database)
            throws ConnectionException {
        final DB db = mongo.getDB(database);
        if (password != null) {
            Validate.notNull(username, "Username must not be null if password is set");
            if (!db.isAuthenticated()) {
                if (!db.authenticate(username, password.toCharArray())) {
                    throw new ConnectionException(ConnectionExceptionCode.INCORRECT_CREDENTIALS, null,
                            "Couldn't connect with the given credentials");
                }
            }
        }
        return db;
    }

    protected MongoClient adaptClient(final MongoClient client) {
        return MongoClientAdaptor.adapt(client);
    }

    public String getHost() {
        return host;
    }

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

    public int getPort() {
        return port;
    }

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

    public String getDatabase() {
        return database;
    }

    public void setDatabase(final String database) {
        this.database = database;
    }

    public Integer getConnectionsPerHost() {
        return connectionsPerHost;
    }

    public void setConnectionsPerHost(final Integer connectionsPerHost) {
        this.connectionsPerHost = connectionsPerHost;
    }

    public Integer getThreadsAllowedToBlockForConnectionMultiplier() {
        return threadsAllowedToBlockForConnectionMultiplier;
    }

    public void setThreadsAllowedToBlockForConnectionMultiplier(
            final Integer threadsAllowedToBlockForConnectionMultiplier) {
        this.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
    }

    public Integer getMaxWaitTime() {
        return maxWaitTime;
    }

    public void setMaxWaitTime(final Integer maxWaitTime) {
        this.maxWaitTime = maxWaitTime;
    }

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(final Integer connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(final Integer socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    public Boolean getAutoConnectRetry() {
        return autoConnectRetry;
    }

    public void setAutoConnectRetry(final Boolean autoConnectRetry) {
        this.autoConnectRetry = autoConnectRetry;
    }

    public Boolean getSlaveOk() {
        return slaveOk;
    }

    public void setSlaveOk(final Boolean slaveOk) {
        this.slaveOk = slaveOk;
    }

    public Boolean getSafe() {
        return safe;
    }

    public void setSafe(final Boolean safe) {
        this.safe = safe;
    }

    public Integer getW() {
        return w;
    }

    public void setW(final Integer w) {
        this.w = w;
    }

    public Integer getWtimeout() {
        return wtimeout;
    }

    public void setWtimeout(final Integer wtimeout) {
        this.wtimeout = wtimeout;
    }

    public Boolean getFsync() {
        return fsync;
    }

    public void setFsync(final Boolean fsync) {
        this.fsync = fsync;
    }
}