Skip to content
Snippets Groups Projects
mongodb.js 11.5 KiB
Newer Older
W. Ross Morrow's avatar
W. Ross Morrow committed
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * MONGODB READY SERVER
 * 
 * Copyright Stanford GSB DARC Team 
 * 
 * Created by W. Ross Morrow, Research Computing Specialist
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

'use strict';

// mongodb resources
const _mongodb = require( 'mongodb' );

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * LOCAL DATA
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// local variables
var _config = null , 
	_log 	= null , 
	_util 	= null , 
	_client = null , 
	_col 	= null , 
	_server = null ;

// (default) MongoDB collection names... pretty nondescript
var mongodbNames = { 
	"options" 	: "options" , 
	"errors" 	: "errors" , 
};

const DEFAULT_MONGODB_TIMEOUT = 60000; // one minute

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * PROCESS OPTIONS
 * 
 * Here we can do any option processing, after reading options from the database. 
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var _proco = null;
const noopOptionProcessing = ( o ) => ( o );

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * (WRAPPED) MONGODB LOAD ROUTINE
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// load client
const loadMongoDBClient = ( config ) => {

	return new Promise( ( resolve , reject ) => {

		// local variables
		var col = { } , waitingForDB , waitingTimer;

		// define collection names to search for
W. Ross Morrow's avatar
W. Ross Morrow committed
		if( config.mdb.names ) {
			mongodbNames = { ...mongodbNames , ..._config.mdb.names };
		}

		// why are we waiting? To (try to) run sync instead of async? 
		// Promises are much nicer. But this is really about printing. 
		waitingForDB = true;
		waitingTimer = setInterval( () => {
			_log.info( "Waiting for MongoDB connection..." )
		} , 1000 );

		// connect to MongoDB
		let mdbOptions = { useNewUrlParser: true , useUnifiedTopology: true };
		_mongodb.MongoClient.connect( config.mdb.uri , mdbOptions , ( err , client ) => {
W. Ross Morrow's avatar
W. Ross Morrow committed

		    if( err ) { return reject(err); }

		    // write out connection event to log
			_log.info( "Connected to MongoDB." );

		    // clear the print interval
			waitingForDB = false; clearInterval( waitingTimer );

	    	// get list of collections to compare against 
	    	client.db( config.mdb.db ).listCollections( {} , { nameOnly : true } ).toArray( ( err , items ) => {

	    		// list of collection names
	    		var names = items.map( i => i.name );

	    		if( _config.mdb.names ) {
W. Ross Morrow's avatar
W. Ross Morrow committed

		    		// this is the list of __desired__ collections __missing__ 
		    		var missing = Object.values( mongodbNames ).filter( (i) => ( names.indexOf(i) < 0 ) );
W. Ross Morrow's avatar
W. Ross Morrow committed

		    		// process different possibilities... 
		    		if( missing.length === 0 ) { // there are NO missing collections
W. Ross Morrow's avatar
W. Ross Morrow committed

				    	// define the collection references
				    	Object.keys( mongodbNames ).forEach( c => {
				    		// wrapper reference for each collection "c", named "mongodbNames[c]" in this MongoDB instance
				    		col[c] = client.db( config.mdb.db ).collection( mongodbNames[c] );
				    		// log that we loaded this collection
				    		_log.info( `  Loaded collection "${c}" (from ${mongodbNames[c]})` );
				    	} );
W. Ross Morrow's avatar
W. Ross Morrow committed

			    		// we're done
			    		resolve( { client : client , col : col } );
W. Ross Morrow's avatar
W. Ross Morrow committed

		    		} else { // there are SOME missing collections
W. Ross Morrow's avatar
W. Ross Morrow committed

		    			let done = 0 , todo = Object.keys( mongodbNames ).length;
W. Ross Morrow's avatar
W. Ross Morrow committed

				    	// define the collection references
				    	Object.keys( mongodbNames ).forEach( c => {
W. Ross Morrow's avatar
W. Ross Morrow committed

				    		if( missing.indexOf( mongodbNames[c] ) >= 0 ) { // collection does NOT exist
W. Ross Morrow's avatar
W. Ross Morrow committed

				    			// create collection... is the callback ok? need coordinator? 
				    			client.db( config.mdb.db ).createCollection( mongodbNames[c] , ( error , newcol ) => {
				    				// wrapper reference for each collection "c", named "mongodbNames[c]" in this MongoDB instance
				    				col[c] = newcol;
						    		// log that we created this collection
						    		_log.info( `  Created collection "${mongodbNames[c]}" (for ${c})` );
						    		// 
						    		done += 1; if( done == todo ) { resolve( {client:client,col:col} ); }
				    			} );
W. Ross Morrow's avatar
W. Ross Morrow committed

				    		} else {

					    		// wrapper reference for each collection "c", named "mongodbNames[c]" in this MongoDB instance
					    		col[c] = client.db( _config.mdb.db ).collection( mongodbNames[c] );
					    		// log that we loaded this collection
					    		_log.info( `  Loaded collection "${c}" (from ${mongodbNames[c]})` );
					    		// 
						    	done += 1; if( done == todo ) { resolve( {client:client,col:col} ); }

				    		}
W. Ross Morrow's avatar
W. Ross Morrow committed

W. Ross Morrow's avatar
W. Ross Morrow committed

	    		} else {
	    			names.forEach( c => {
	    				col[c] = client.db( _config.mdb.db ).collection( c )
	    			} );
W. Ross Morrow's avatar
W. Ross Morrow committed
	    		}

	    	} );

			// always close the connection when the process exits
			process.on( 'exit' , client.close );

		} );

	} );

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * MONGODB LOAD ROUTINE(S) FOR US
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// function to (re-)load MongoDB given a config. Can be null, in which case the local module version
// will be copied and used
const reloadMongoDB = ( config ) => {

	return new Promise( ( resolve , reject ) => {

		// make a local copy of the default config, as we don't want to overwrite this
		if( ! config ) { config = Object.assign( _config , {} ); }

		if( ! config.mdb.uri ) {
			return reject( 'Configuration passed to mongodb.reloadMongoDB must have a mongodb field with connection information.' );
		}

		// make sure to close the current, locally stored client before creating another one
		if( _client ) { _client.close(); _client = null; _col = {}; }

		var timeout = setTimeout( 
			// take some kind of action, given that we may not be connected to MongoDB...
			() => { reject( "MongoDB reload timed out" ); } , 
			( _config.mdb.timeout ? _config.mdb.timeout : DEFAULT_MONGODB_TIMEOUT )
		);

		// here is where we setup MongoDB, using the more technical routine above
		loadMongoDBClient( config )
			.then( result => { 

				// don't timeout
				clearTimeout( timeout );

				// assign local references to the client and collections (which have "rebooted")
				_client = result.client , _col = result.col;

			    // get options, and proceed from there
			    var _opts = _client.db( config.mdb.db ).collection( "options" );

			    // what to do * after * getting options... this will be the callback 
			    // to the "find options" mongodb call below
			    const withOptions = ( err , item ) => {
			    	if( err ) { return reject( err ); }
			    	config = { ..._proco( item ) , ...config }; 
					resolve( { config : config , col : _col } ); // note we're just passing on... 
			    }

			    // figure out which options to get, and then call the function defined above
			    if( ! config.options || config.options === "" ) {
			    	_log.info( "Using most recent options stored in MongoDB" );
				    _opts.findOne( {} , { sort : { $natural : -1 } } , withOptions );
			    } else {
			    	_log.info( `Using specific options: ${config.options}` );
				    _opts.findOne( { "name" : config.options } , { sort : { $natural : -1 } } , withOptions );
			    }

			    // rely on callback withOptions passed to the _opts.findOne call above

			} ).catch( reject );

	} );

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * MODULE EXPORTS
 * 
 * We export a function that expects config, log, and utility objects, and returns functions
 * relevant to loading and working with any MongoDB client thus created. 
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

module.exports = ( c , l , u ) => {
	_config = c , _log = l , _util = u; // store local references to the config, log, and util modules
	_proco = noopOptionProcessing; // set "trivial" option processing
	return {
		setOptionProcessor : ( f ) => { _proco = f; } , 
		reloadMongoDB  : reloadMongoDB , 
		getClient      : () => { return _client; } , 
		getCollections : () => { return _col; } , 
		closeClient    : () => { _col = {}; if( _client ) { _client.close(); } } , 
	};
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 
 * Copyright 2020+, Stanford GSB DARC Team
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */