org.mule.modules.morphia.MorphiaConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.modules.morphia.MorphiaConnector.java

Source

/**
 * Mule Morphia Connector
 *
 * 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.txt file.
 */

package org.mule.modules.morphia;

import com.github.jmkgreen.morphia.Datastore;
import com.github.jmkgreen.morphia.Key;
import com.github.jmkgreen.morphia.Morphia;
import com.github.jmkgreen.morphia.logging.MorphiaLoggerFactory;
import com.github.jmkgreen.morphia.mapping.Mapper;
import com.github.jmkgreen.morphia.mapping.MappingException;
import com.github.jmkgreen.morphia.mapping.cache.EntityCache;
import com.github.jmkgreen.morphia.query.Query;
import com.github.jmkgreen.morphia.query.UpdateOperations;
import com.github.jmkgreen.morphia.query.UpdateResults;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.CommandResult;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoOptions;
import com.mongodb.ServerAddress;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;

import org.bson.types.ObjectId;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Mime;
import org.mule.api.annotations.Module;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.display.Password;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.Optional;
import org.mule.transformer.types.MimeTypes;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

/**
 * Morphia is a lightweight type-safe library for mapping Java objects to/from MongoDB:
 * <p/>
 * <ul>
 * <li>Easy to use, and very lightweight; reflection is used once per type and cached for good performance.</li>
 * <li>Datastore and DAO<T,V> access abstractions, or roll your own...</li>
 * <li>Type-safe, and Fluent Query support with (runtime) validation</li>
 * <li>Annotations based mapping behavior; there are no XML files.</li>
 * <li>Extensions: Validation (jsr303), and SLF4J Logging</li>
 * </ul>
 *
 * @author MuleSoft, Inc.
 */
@Module(name = "morphia", schemaVersion = "1.0")
public class MorphiaConnector {

    /**
     * List of class names with mappings
     */
    @Configurable
    @Optional
    private List<String> classes;

    /**
     * List of packages with mappings
     */
    @Configurable
    @Optional
    private List<String> packages;

    /**
     * Specifies whether to ignore classes in the package that cannot be mapped
     */
    @Configurable
    @Optional
    @Default("true")
    private boolean ignoreInvalidClasses;

    /**
     * Ensure indexes on the background
     */
    @Configurable
    @Optional
    @Default("true")
    private boolean ensureIndexesOnBackground;

    /**
     * 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
    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;

    /**
     * The username to use in case authentication is required
     */
    @Configurable
    @Optional
    private String username;

    /**
     * The password to use in case authentication is required, null if no authentication is desired
     */
    @Configurable
    @Optional
    @Password
    private String password;

    /**
     * The host of the Mongo server. If the host is part of a replica set then you can specify all the
     * hosts separated by comma.
     */
    @Configurable
    @Optional
    @Default("localhost")
    private String host;

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

    /**
     * Default database
      */
    @Configurable
    @Optional
    private String database;

    /**
     * Datastore Cache
     */
    private LoadingCache<MorphiaCacheKey, Datastore> datastoreCache;

    /**
     * Mongo Instances Cache
     */
    private LoadingCache<MongoCacheKey, Mongo> mongoCache;

    static {
        MorphiaLoggerFactory.registerLogger(SFL4JLogrFactory.class);
    }

    /**
     * Construct a new instance of Morphia
     */
    @PostConstruct
    public void init() throws ClassNotFoundException {
        this.mongoCache = CacheBuilder.newBuilder().maximumSize(10).build(new CacheLoader<MongoCacheKey, Mongo>() {
            public Mongo load(MongoCacheKey cacheKey) {
                return new Mongo(cacheKey.getSeeds(), cacheKey.getOptions());
            }
        });

        this.datastoreCache = CacheBuilder.newBuilder().maximumSize(50)
                .build(new CacheLoader<MorphiaCacheKey, Datastore>() {
                    private Morphia morphia;

                    @Override
                    public Datastore load(MorphiaCacheKey morphiaCacheKey) throws Exception {
                        initMorphia();

                        List<ServerAddress> seeds = getSeeds(morphiaCacheKey.getHost(), morphiaCacheKey.getPort());

                        MongoCacheKey mongoCacheKey = new MongoCacheKey(seeds, getMongoOptions());

                        Mongo mongo = mongoCache.get(mongoCacheKey);

                        Datastore datastore = this.morphia.createDatastore(mongo, morphiaCacheKey.getDatabase(),
                                morphiaCacheKey.getUsername(), morphiaCacheKey.getPassword().toCharArray());

                        datastore.ensureIndexes(ensureIndexesOnBackground);
                        datastore.ensureCaps();

                        return datastore;
                    }

                    private void initMorphia() throws ClassNotFoundException {
                        if (morphia == null) {
                            morphia = new Morphia();

                            if (classes != null) {
                                for (String className : classes) {
                                    this.morphia.map(Class.forName(className));
                                }
                            }

                            if (packages != null) {
                                for (String packageName : packages) {
                                    this.morphia.mapPackage(packageName, ignoreInvalidClasses);
                                }
                            }
                        }
                    }

                    private List<ServerAddress> getSeeds(String host, int port) throws UnknownHostException {
                        List<ServerAddress> seeds = new ArrayList<ServerAddress>();
                        if (host.indexOf(',') != -1) {
                            StringTokenizer tokenizer = new StringTokenizer(host, ",");
                            while (tokenizer.hasMoreTokens()) {
                                seeds.add(new ServerAddress(tokenizer.nextToken(), port));
                            }
                        } else {
                            seeds.add(new ServerAddress(host, port));
                        }

                        return seeds;
                    }

                    private MongoOptions getMongoOptions() {
                        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;

                        return options;
                    }
                });
    }

    @PreDestroy
    public void shutdown() {
        ConcurrentMap<MongoCacheKey, Mongo> map = this.mongoCache.asMap();
        for (MongoCacheKey key : map.keySet()) {
            map.get(key).close();
        }
    }

    /**
     * Method invoked when a new datastore needs to be accessed
     *
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     */
    public Datastore getDatastore(String username, String password, String database, String host, Integer port)
            throws ExecutionException {
        String trueUsername = null;
        String truePassword = null;
        String trueDatabase = null;
        String trueHost = null;
        Integer truePort = null;

        if (username == null) {
            trueUsername = this.username;
        } else {
            trueUsername = username;
        }

        if (database == null) {
            trueDatabase = this.database;
        } else {
            trueDatabase = database;
        }

        if (password == null) {
            truePassword = this.password;
        } else {
            truePassword = password;
        }

        if (host == null) {
            trueHost = this.host;
        } else {
            trueHost = host;
        }

        if (port == null) {
            truePort = this.port;
        } else {
            truePort = port;
        }

        MorphiaCacheKey morphiaCacheKey = new MorphiaCacheKey(trueDatabase, trueUsername, truePassword, trueHost,
                truePort);
        return this.datastoreCache.get(morphiaCacheKey);
    }

    /**
     * Add a new user to the database
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:add-user}
     *
     * @param newUsername    Username to be created
     * @param newPassword    Password that will be used for authentication
     * @param targetDatabase Database at which this user will be created. It defaults to the current one if not specified.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     */
    @Processor
    public void addUser(String newUsername, String newPassword, @Optional String targetDatabase,
            @Optional String username, @Optional @Password String password, @Optional String host,
            @Optional Integer port, @Optional String database) throws ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        WriteResult writeResult = null;
        if (targetDatabase == null) {
            writeResult = datastore.getDB().addUser(newUsername, newPassword.toCharArray());
        } else {
            writeResult = datastore.getDB().getMongo().getDB(targetDatabase).addUser(newUsername,
                    newPassword.toCharArray());
        }
        if (!writeResult.getLastError().ok()) {
            throw new MongoException(writeResult.getLastError().getErrorMessage());
        }
    }

    /**
     * Drop the current database
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:drop-database}
     *
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     */
    @Processor
    public void dropDatabase(@Optional String username, @Optional @Password String password, @Optional String host,
            @Optional Integer port, @Optional String database) throws ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        datastore.getDB().dropDatabase();
    }

    /**
     * Saves the entity (Object) and updates the @Id field
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:save}
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:save-with-ref}
     *
     * @param object       Object to be saved
     * @param writeConcern Sets the write concern for this database. It Will be used for writes to any collection in
     *                     this database. See the documentation for {@link WriteConcern} for more information.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An instance of {@link com.github.jmkgreen.morphia.Key}
     */
    @Processor
    public Object save(@Optional @Default("#[payload]") Object object,
            @Optional @Default("ACKNOWLEDGED") WriteConcern writeConcern, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.save(object, writeConcern.getMongoWriteConcern());
    }

    /**
     * Saves the iterable collection and updates the @Id field
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:save-multiple}
     *
     * @param collection   The iterable collection to be saved
     * @param writeConcern Sets the write concern for this database. It Will be used for writes to any collection in
     *                     this database. See the documentation for {@link WriteConcern} for more information.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An iterable collection of {@link com.github.jmkgreen.morphia.Key}
     */
    @Processor
    public Iterable<Object> saveMultiple(@Optional @Default("#[payload]") Iterable collection,
            @Optional @Default("ACKNOWLEDGED") WriteConcern writeConcern, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.save(collection, writeConcern.getMongoWriteConcern());
    }

    /**
     * Does the object exists?
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:exists}
     *
     * @param filters   Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param className Class name of the object to retrieve
     * @param username the username to use in case authentication is required
     * @param exception The exception that needs to be thrown if the object does not exist. It must be a RuntimeException.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return True if it exists, false otherwise
     * @throws ExecutionException if there is a connection exception
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public boolean exists(String className, Map<String, Object> filters, @Optional String exception,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String username, @Optional @Password String password,
            @Optional String host, @Optional Integer port, @Optional String database)
            throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className).setFilters(filters)
                .setDisableValidation(disableValidation).setDisableCursorTimeout(disableCursorTimeout)
                .setDisableSnapshotMode(disableSnapshotMode);
        boolean exists = datastore.getCount(queryBuilder.getQuery()) > 0;

        if (!exists && exception != null) {
            throw getExceptionFromClassName(exception);
        }

        return exists;
    }

    /**
     * Validates that the object does not exist and throws an exception if it does
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:exists}
     *
     * @param filters   Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param className Class name of the object to retrieve
     * @param username the username to use in case authentication is required
     * @param exception The exception that needs to be thrown if the object exists. It must be a RuntimeException.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return True if it doesn't exists, false otherwise
     * @throws ExecutionException if there is a connection exception
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public void notExists(String className, Map<String, Object> filters, String exception,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String username, @Optional @Password String password,
            @Optional String host, @Optional Integer port, @Optional String database)
            throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className).setFilters(filters)
                .setDisableValidation(disableValidation).setDisableCursorTimeout(disableCursorTimeout)
                .setDisableSnapshotMode(disableSnapshotMode);
        boolean exists = datastore.getCount(queryBuilder.getQuery()) > 0;

        if (exists) {
            throw getExceptionFromClassName(exception);
        }
    }

    /**
     * Does the object exists?
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:exists-by-id}
     *
     * @param id        Id of the object to retrieve
     * @param className Class name of the object to retrieve
     * @param exception The exception that needs to be thrown if the object does not exist. It must be a RuntimeException.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return True if it exists, false otherwise
     * @throws ExecutionException if there is a connection exception
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public boolean existsById(String className, Object id, @Optional String exception, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        boolean exists = datastore.exists(new Key(Class.forName(className), id)) != null;

        if (exception != null && !exists) {
            throw getExceptionFromClassName(exception);
        }

        return exists;
    }

    /**
     * Retrieve an entity (Object) using the specified id
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:retrieve}
     *
     * @param id        Id of the object to retrieve
     * @param className Class name of the object to retrieve
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return The retrieve object or null if cannot be found
     * @throws ExecutionException if there is a connection exception
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public Object retrieve(String className, Object id, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.get(Class.forName(className), id);
    }

    /**
     * Count total objects of the specified class
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:count-by-class}
     *
     * @param className Class of the object to count
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return The count
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public long countByClass(String className, @Optional String username, @Optional @Password String password,
            @Optional String host, @Optional Integer port, @Optional String database)
            throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.getCount(Class.forName(className));
    }

    /**
     * Count total objects using the embedded querying criteria
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:count}
     *
     * @param className The name of the object class to count
     * @param filters   Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return The count
     * @throws Exception if there is an exception
     */
    @Processor
    public Object count(String className, @Optional Map<String, Object> filters,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String username, @Optional @Password String password,
            @Optional String host, @Optional Integer port, @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);
        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className);

        if (disableCursorTimeout != null) {
            queryBuilder.setDisableCursorTimeout(disableCursorTimeout);
        }

        if (disableValidation != null) {
            queryBuilder.setDisableValidation(disableValidation);
        }

        if (disableSnapshotMode != null) {
            queryBuilder.setDisableSnapshotMode(disableSnapshotMode);
        }

        if (filters != null) {
            queryBuilder.setFilters(filters);
        }

        return datastore.getCount(queryBuilder.getQuery());
    }

    /**
     * Find all instances by type using the specified query criteria
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:find}
     *
     * @param className            Type class name
     * @param filters              Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param offset               Starts the query results at a particular zero-based offset.
     * @param limit                Limit the fetched result set to a certain number of values.
     * @param order                Sorts based on a property (defines return order).
     * @param fields               Limit the returned fields to the ones specified.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param exception The exception that needs to be thrown if the criteria does not match any result. The exception must be a RuntimeException
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An object collection
     * @throws Exception if there is an exception
     */
    @Processor
    public Object find(String className, @Optional Map<String, Object> filters, @Optional Integer offset,
            @Optional Integer limit, @Optional String order, @Optional List<String> fields,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String exception, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);

        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className)
                .setDisableCursorTimeout(disableCursorTimeout).setDisableValidation(disableValidation)
                .setDisableSnapshotMode(disableSnapshotMode).setFilters(filters).setOffset(offset).setLimit(limit)
                .setOrder(order).setFields(fields);

        List<?> findResult = queryBuilder.getQuery().asList();

        if (exception != null && findResult.isEmpty()) {
            throw getExceptionFromClassName(exception);
        }

        return findResult;
    }

    /**
     * Find a single instance of type using the specified query criteria
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:find-single}
     *
     * @param className            Type class name
     * @param filters              Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param fields               Limit the returned fields to the ones specified.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param exception The exception that needs to be thrown if the criteria does not match any result. The exception must be a RuntimeException
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An object collection
     * @throws Exception if there is an exception
     */
    @Processor
    public Object findSingle(String className, @Optional Map<String, Object> filters, @Optional List<String> fields,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String exception, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {

        Datastore datastore = getDatastore(username, password, database, host, port);

        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className)
                .setDisableCursorTimeout(disableCursorTimeout).setDisableValidation(disableValidation)
                .setDisableSnapshotMode(disableSnapshotMode).setFilters(filters).setFields(fields);

        Object findResult = queryBuilder.getQuery().get();

        if (exception != null && findResult == null) {
            throw getExceptionFromClassName(exception);
        }

        return findResult;
    }

    /**
     * Find all object ids of type using the specified query criteria
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:find-ids}
     *
     * @param className            Type class name
     * @param filters              Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param offset               Starts the query results at a particular zero-based offset.
     * @param limit                Limit the fetched result set to a certain number of values.
     * @param order                Sorts based on a property (defines return order).
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshotted mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return A collection of {@link com.github.jmkgreen.morphia.Key}s
     * @throws Exception if there is an exception
     */
    @Processor
    public Object findIds(String className, @Optional Map<String, Object> filters, @Optional Integer offset,
            @Optional Integer limit, @Optional String order, @Optional Boolean disableCursorTimeout,
            @Optional Boolean disableSnapshotMode, @Optional Boolean disableValidation, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);

        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className)
                .setDisableCursorTimeout(disableCursorTimeout).setDisableValidation(disableValidation)
                .setDisableSnapshotMode(disableSnapshotMode).setFilters(filters).setOffset(offset).setLimit(limit)
                .setOrder(order);

        List<?> result = queryBuilder.getQuery().asKeyList();
        if (result.size() == 1) {
            return result.get(0);
        } else {
            return result;
        }
    }

    /**
     * Deletes the given entity (by id)
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:delete-by-id}
     *
     * @param className Class of the object to delete
     * @param id        Id of the object to delete
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An instance of @{link WriteResult}
     * @throws ClassNotFoundException If the class cannot be found
     */
    @Processor
    public WriteResult deleteById(String className, Object id, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.delete(Class.forName(className), id);
    }

    /**
     * Deletes the given entity (by @Id)
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:delete-single}
     *
     * @param object Object to delete
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws ExecutionException if there is a connection exception
     * @return An instance of @{link WriteResult}
     * @throws Exception if there is an exception
     */
    @Processor
    public WriteResult deleteSingle(@Optional @Default("#[payload]") Object object, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);
        return datastore.delete(object);
    }

    /**
     * Deletes the given entities based on the query (first item only).
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:find-and-delete}
     *
     * @param className            Type class name
     * @param filters              Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param offset               Starts the query results at a particular zero-based offset.
     * @param limit                Limit the fetched result set to a certain number of values.
     * @param order                Sorts based on a property (defines return order).
     * @param fields               Limit the returned fields to the ones specified.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshot mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param exception The exception that needs to be thrown if there is an error on the update or not document is updated or inserted.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return the deleted Entity
     * @throws Exception if there is an exception
     */
    @Processor
    public Object findAndDelete(String className, @Optional Map<String, Object> filters, @Optional Integer offset,
            @Optional Integer limit, @Optional String order, @Optional List<String> fields,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String exception, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);

        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className)
                .setDisableCursorTimeout(disableCursorTimeout).setDisableValidation(disableValidation)
                .setDisableSnapshotMode(disableSnapshotMode).setFilters(filters).setOffset(offset).setLimit(limit)
                .setOrder(order).setFields(fields);

        Object deletedObject = datastore.findAndDelete(queryBuilder.getQuery());
        if (deletedObject == null && exception != null) {
            throw getExceptionFromClassName(exception);
        }
        return deletedObject;
    }

    /**
     * Deletes the entities that match the query criteria
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:delete}
     *
     * @param className            Type class name
     * @param filters              Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param fields               Limit the returned fields to the ones specified.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode  Disable snapshot mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation    Turns off validation
     * @param exception The exception that needs to be thrown if there is an error while deleting
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @throws Exception if there is an exception while deleting
     */
    @Processor
    public void delete(String className, @Optional Map<String, Object> filters, @Optional List<String> fields,
            @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional Boolean disableValidation, @Optional String exception, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);

        QueryBuilder queryBuilder = QueryBuilder.newBuilder(datastore, className)
                .setDisableCursorTimeout(disableCursorTimeout).setDisableValidation(disableValidation)
                .setDisableSnapshotMode(disableSnapshotMode).setFilters(filters).setFields(fields);

        WriteResult writeResult = datastore.delete(queryBuilder.getQuery());
        if (!writeResult.getLastError().ok() && exception != null) {
            throw getExceptionFromClassName(exception);
        }
    }

    /**
     * Updates the given entity based on the query and the configured operations
     *
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:update}
     *
     * @param className Type class name
     * @param filters Filter criteria. Criteria is a composite of the field name and the operator ("field >", or "field in"). All criteria are implicitly combined with a logical "and".
     * @param offset Starts the query results at a particular zero-based offset.
     * @param limit Limit the fetched result set to a certain number of values.
     * @param disableCursorTimeout Disables cursor timeout on server
     * @param disableSnapshotMode Disable snapshot mode (default mode). This will be faster but changes made
     *                             during the cursor may cause duplicates.
     * @param disableValidation Turns off validation
     * @param set sets the fields values
     * @param unset removes the fields
     * @param add adds the values to an array field
     * @param addAll adds multiple values to an array field
     * @param removeFirst removes first value of the array field
     * @param removeLast removes last value of the array field
     * @param remove removes all the given values from the array field using $pull
     * @param removeAll removes all the given values from the array field using $pullAll
     * @param allowDups allows duplicates
     * @param createIfMissing If not found create the entity
     * @param writeConcern Sets the write concern for this database. It Will be used for writes to any collection in
     *                     this database. See the documentation for {@link WriteConcern} for more information.
     * @param exception The exception that needs to be thrown if there is an error on the update or not document is updated or inserted.
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return the results of the update
     * @throws Exception if there is an exception while performing the update
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Processor
    public UpdateResults update(String className, @Optional Map<String, Object> filters, @Optional Integer offset,
            @Optional Integer limit, @Optional Boolean disableCursorTimeout, @Optional Boolean disableSnapshotMode,
            @Optional @Default("false") Boolean disableValidation, @Optional Map<String, Object> set,
            @Optional List<String> unset, @Optional Map<String, Object> add,
            @Optional Map<String, List<Object>> addAll, @Optional List<String> removeFirst,
            @Optional List<String> removeLast, @Optional Map<String, Object> remove,
            @Optional Map<String, List<Object>> removeAll, @Optional @Default("false") boolean allowDups,
            @Optional @Default("false") boolean createIfMissing,
            @Optional @Default("ACKNOWLEDGED") WriteConcern writeConcern, @Optional String exception,
            @Optional String username, @Optional @Password String password, @Optional String host,
            @Optional Integer port, @Optional String database) throws Exception {
        Datastore datastore = getDatastore(username, password, database, host, port);

        Class entityClass = Class.forName(className);

        Query query = QueryBuilder.newBuilder(datastore, className).setDisableCursorTimeout(disableCursorTimeout)
                .setDisableValidation(disableValidation).setDisableSnapshotMode(disableSnapshotMode)
                .setFilters(filters).setOffset(offset).setLimit(limit).getQuery();

        UpdateOperations upOps = datastore.createUpdateOperations(entityClass);

        if (disableValidation) {
            upOps.disableValidation();
        }

        if (set != null) {
            for (String field : set.keySet()) {
                if (set.get(field) != null) {
                    upOps = upOps.set(field, set.get(field));
                }
            }
        }

        if (unset != null) {
            for (String field : unset) {
                upOps = upOps.unset(field);
            }
        }

        if (add != null) {
            for (String field : add.keySet()) {
                if (add.get(field) != null) {
                    upOps = upOps.add(field, add.get(field), allowDups);
                }
            }
        }

        if (addAll != null) {
            for (String field : addAll.keySet()) {
                if (addAll.get(field) != null) {
                    upOps = upOps.addAll(field, addAll.get(field), allowDups);
                }
            }
        }

        if (removeFirst != null) {
            for (String field : removeFirst) {
                upOps = upOps.removeFirst(field);
            }
        }

        if (removeLast != null) {
            for (String field : removeLast) {
                upOps = upOps.removeLast(field);
            }
        }

        if (removeAll != null) {
            for (String field : removeAll.keySet()) {
                if (removeAll.get(field) != null) {
                    upOps = upOps.removeAll(field, removeAll.get(field));
                }
            }
        }

        if (remove != null) {
            for (String field : remove.keySet()) {
                if (remove.get(field) != null) {
                    upOps = upOps.removeAll(field, remove.get(field));
                }
            }
        }

        UpdateResults updateResults = datastore.update(query, upOps, createIfMissing,
                writeConcern.getMongoWriteConcern());

        if (exception != null && throwUpdateException(createIfMissing, updateResults)) {
            throw getExceptionFromClassName(exception);
        }

        return updateResults;
    }

    /**
     * Calculates aggregates values without the need for complex map-reduce operations
     *
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:aggregate}
     *
     * @param collection collection name
     * @param pipeline list of pipeline operators
     * @param exception The exception that needs to be thrown if there is an error executing the aggregation query
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return the aggregation result
     * @throws Exception if there is an exception while aggregating
     */
    @Processor
    public BasicDBList aggregate(String collection, List<Pipeline> pipeline, @Optional String exception,
            @Optional String username, @Optional @Password String password, @Optional String host,
            @Optional Integer port, @Optional String database) throws Exception {
        if (!pipeline.isEmpty()) {
            Datastore datastore = getDatastore(username, password, database, host, port);
            List<DBObject> dbObjects = new ArrayList<DBObject>();
            for (Pipeline pipelineOperator : pipeline) {
                Object dbObject = JSON.parse(pipelineOperator.toJson());
                if (dbObject == null || !(dbObject instanceof DBObject)) {
                    throw new IllegalArgumentException("Illegal pipeline operator '" + pipelineOperator + "'");
                }
                dbObjects.add((DBObject) dbObject);
            }
            BasicDBObjectBuilder builder = BasicDBObjectBuilder.start().add("aggregate", collection);
            builder.append("pipeline", dbObjects.toArray());
            CommandResult result = datastore.getDB().command(builder.get());
            if (result.ok()) {
                return (BasicDBList) result.get("result");
            }
            if (exception != null) {
                throw getExceptionFromClassName(exception);
            }
        }
        // Return an empty list
        return new BasicDBList();
    }

    private boolean throwUpdateException(boolean createIfMissing, UpdateResults updateResults) {
        return (!createIfMissing && !updateResults.getUpdatedExisting()) || (createIfMissing
                && !updateResults.getUpdatedExisting() && !(updateResults.getInsertedCount() > 0));
    }

    /**
     * Transform a Morphia object into JSON
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:object-to-json}
     *
     * @param object Object to transform
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return A string containing JSON
     * @throws IOException if there is an exception
     * @throws ExecutionException if there is a connectivity problem
     */
    @Processor
    @Mime(MimeTypes.JSON)
    public String objectToJson(@Optional @Default("#[payload]") Object object, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws IOException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);

        if (isListClass(object.getClass())) {
            if (((List) object).size() == 0) {
                throw new IllegalArgumentException("The list is empty");
            }
            if (datastore.getMapper().isMapped(((List) object).get(0).getClass())) {
                List originalList = (List) object;
                LinkedList<DBObject> dbObjectList = new LinkedList<DBObject>();
                for (Object innerObject : originalList) {
                    dbObjectList.addLast(datastore.getMapper().toDBObject(innerObject));
                }
                return JSON.serialize(dbObjectList);
            } else if (((List) object).get(0) instanceof Key) {
                List originalList = (List) object;
                JsonFactory jsonFactory = new JsonFactory();
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(outputStream);
                jsonGenerator.writeStartArray();
                for (Object innerObject : originalList) {
                    Key keyObject = (Key) innerObject;
                    ObjectId id = (ObjectId) keyObject.getId();
                    jsonGenerator.writeString(id.toStringMongod());
                }
                jsonGenerator.writeEndArray();
                jsonGenerator.flush();
                return new String(outputStream.toByteArray());
            } else {
                throw new IllegalArgumentException(
                        ((List) object).get(0).getClass().getName() + " is not a Morphia-mapped type");
            }
        } else if (datastore.getMapper().isMapped(object.getClass())) {
            return JSON.serialize(datastore.getMapper().toDBObject(object));
        } else if (object instanceof Key) {
            JsonFactory jsonFactory = new JsonFactory();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(outputStream);
            Key keyObject = (Key) object;
            ObjectId id = (ObjectId) keyObject.getId();
            jsonGenerator.writeString(id.toStringMongod());
            jsonGenerator.flush();
            return new String(outputStream.toByteArray());
        } else {
            throw new IllegalArgumentException(object.getClass().getName() + " is not a Morphia-mapped type");
        }
    }

    /**
     * Transform a JSON object into Morphia object
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:json-to-object}
     *
     * @param className Name of the class representing the type
     * @param json      String containing JSON
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return The parsed object
     * @throws ClassNotFoundException if class not found
     * @throws ExecutionException if there is a connectivity problem
     */
    @Processor
    public Object jsonToObject(String className, @Optional @Default("#[payload]") String json,
            @Optional String username, @Optional @Password String password, @Optional String host,
            @Optional Integer port, @Optional String database) throws ClassNotFoundException, ExecutionException {
        Datastore datastore = getDatastore(username, password, database, host, port);

        Object object = JSON.parse(json);
        if (object == null || !(object instanceof DBObject)) {
            throw new IllegalArgumentException("Unable to convert JSON string into an object");
        }

        EntityCache entityCache = datastore.getMapper().createEntityCache();

        return datastore.getMapper().fromDBObject(Class.forName(className), (DBObject) object, entityCache);

    }

    /**
     * Transforms an entity to a DBRef object
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:entity-to-dbref}
     *
     * @param entity The entity to transform
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return The DB Reference
     * @throws ExecutionException if there is a connectivity problem
     */
    @Processor
    public DBRef entityToDbref(@Optional @Default("#[payload]") Object entity, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ExecutionException {

        Datastore datastore = getDatastore(username, password, database, host, port);
        Mapper mapper = datastore.getMapper();
        Object id = mapper.getId(entity);
        if (id == null) {
            throw new IllegalArgumentException("Could not get id from entity");
        }
        return new DBRef(datastore.getDB(), datastore.getCollection(entity.getClass()).getName(), id);
    }

    /**
     * Returns a DBRef object from a given classname and id
     * <p/>
     * {@sample.xml ../../../doc/mule-module-morphia.xml.sample morphia:create-dbref}
     *
     *
     * @param className The class of the DBRef
     * @param id The ID of the DBRef
     * @param username the username to use in case authentication is required
     * @param password the password to use in case authentication is required, null
     *                 if no authentication is desired
     * @param host     The host of the Mongo server. If the host is part of a replica set then you can specify all the hosts
     *                 separated by comma.
     * @param port     The port of the Mongo server
     * @param database The database name of the Mongo server
     * @return The DB Reference
     * @throws ClassNotFoundException If class not found
     * @throws ExecutionException If there is a connectivity problem
     */
    @Processor
    public DBRef createDbref(String className, Object id, @Optional String username,
            @Optional @Password String password, @Optional String host, @Optional Integer port,
            @Optional String database) throws ClassNotFoundException, ExecutionException {

        Datastore datastore = getDatastore(username, password, database, host, port);
        Mapper mapper = datastore.getMapper();
        return new DBRef(datastore.getDB(), datastore.getCollection(Class.forName(className)).getName(), id);
    }

    /**
     * Checks whether the specified class parameter is an instance of {@link List }
     *
     * @param clazz <code>Class</code> to check.
     * @return
     */
    private boolean isListClass(Class clazz) {
        List<Class> classes = new ArrayList<Class>();
        computeClassHierarchy(clazz, classes);
        return classes.contains(List.class);
    }

    /**
     * Get all superclasses and interfaces recursively.
     *
     * @param classes List of classes to which to add all found super classes and interfaces.
     * @param clazz   The class to start the search with.
     */
    private void computeClassHierarchy(Class clazz, List classes) {
        for (Class current = clazz; (current != null); current = current.getSuperclass()) {
            if (classes.contains(current)) {
                return;
            }
            classes.add(current);
            for (Class currentInterface : current.getInterfaces()) {
                computeClassHierarchy(currentInterface, classes);
            }
        }
    }

    /**
     *
     * Returns an instance of the RuntimeException class name received
     *
     * @param className the class name of the RuntimeException
     * @return an instance of the RuntimeException
     * @throws ClassNotFoundException if the provided class name does not exist
     * @throws ExecutionException if there is an error instantiating the class
     */
    private RuntimeException getExceptionFromClassName(String className)
            throws ClassNotFoundException, ExecutionException {
        try {
            Class<? extends RuntimeException> exception = (Class<? extends RuntimeException>) Class
                    .forName(className);
            return exception.newInstance();
        } catch (InstantiationException ie) {
            throw new ExecutionException("Error getting instance from exception class " + className, ie);
        } catch (IllegalAccessException iae) {
            throw new ExecutionException("Error getting instance from exception class " + className, iae);
        }
    }

    private String removeQuotes(String text) {
        String result = text.trim();
        if (result.startsWith("'") || result.startsWith("\"")) {
            result = result.substring(1, result.length() - 1);
        }
        return result;
    }

    public List<String> getClasses() {
        return classes;
    }

    public void setClasses(List<String> classes) {
        this.classes = classes;
    }

    public List<String> getPackages() {
        return packages;
    }

    public void setPackages(List<String> packages) {
        this.packages = packages;
    }

    public boolean isIgnoreInvalidClasses() {
        return ignoreInvalidClasses;
    }

    public void setIgnoreInvalidClasses(boolean ignoreInvalidClasses) {
        this.ignoreInvalidClasses = ignoreInvalidClasses;
    }

    public boolean isEnsureIndexesOnBackground() {
        return ensureIndexesOnBackground;
    }

    public void setEnsureIndexesOnBackground(boolean ensureIndexesOnBackground) {
        this.ensureIndexesOnBackground = ensureIndexesOnBackground;
    }

    public Integer getConnectionsPerHost() {
        return connectionsPerHost;
    }

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

    public Integer getThreadsAllowedToBlockForConnectionMultiplier() {
        return threadsAllowedToBlockForConnectionMultiplier;
    }

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

    public Integer getMaxWaitTime() {
        return maxWaitTime;
    }

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

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

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

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

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

    public Boolean getAutoConnectRetry() {
        return autoConnectRetry;
    }

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

    public Boolean getSlaveOk() {
        return slaveOk;
    }

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

    public Boolean getSafe() {
        return safe;
    }

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

    public Integer getW() {
        return w;
    }

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

    public Integer getWtimeout() {
        return wtimeout;
    }

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

    public Boolean getFsync() {
        return fsync;
    }

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

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public String getDatabase() {
        return database;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

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

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