https://github.com/HongjianLi/istar
Raw File
Tip revision: 6bd51f63c7de37605dcbfbd3669462a997638ffb authored by Jacky Lee on 05 December 2017, 04:59:10 UTC
Upgraded three.js from r87 to r88
Tip revision: 6bd51f6
web.js
#!/usr/bin/env node

var fs = require('fs'),
	cluster = require('cluster');
if (cluster.isMaster) {
	process.env.PYTHONPATH = process.env.MGL_ROOT + '/MGLToolsPckgs';
	// Allocate arrays to hold ligand properties
	var num_ligands = 23129083,
		mwt = new Float32Array(num_ligands),
		lgp = new Float32Array(num_ligands),
		ads = new Float32Array(num_ligands),
		pds = new Float32Array(num_ligands),
		hbd = new Int16Array(num_ligands),
		hba = new Int16Array(num_ligands),
		psa = new Int16Array(num_ligands),
		chg = new Int16Array(num_ligands),
		nrb = new Int16Array(num_ligands);
	// Parse ligand properties
	var prop = 'idock/16_zprop.bin';
	console.log('Parsing %s', prop);
	var start = Date.now();
	fs.readFile(prop, function(err, buf) {
		if (err) throw err;
		for (i = 0, o = 0; i < num_ligands; ++i, o += 26) {
			mwt[i] = buf.readFloatLE(o +  0);
			lgp[i] = buf.readFloatLE(o +  4);
			ads[i] = buf.readFloatLE(o +  8);
			pds[i] = buf.readFloatLE(o + 12);
			hbd[i] = buf.readInt16LE(o + 16);
			hba[i] = buf.readInt16LE(o + 18);
			psa[i] = buf.readInt16LE(o + 20);
			chg[i] = buf.readInt16LE(o + 22);
			nrb[i] = buf.readInt16LE(o + 24);
		}
		console.log('Parsed %d ligands within %d milliseconds', num_ligands, Date.now() - start);
		// Fork worker processes with cluster
		var numCPUs = require('os').cpus().length;
		console.log('Forking %d worker processes', numCPUs);
		var msg = function(m) {
			if (m.query == '/idock/ligands') {
				var ligands = 0;
				for (var i = 0; i < num_ligands; ++i) {
					if ((m.mwt_lb <= mwt[i]) && (mwt[i] <= m.mwt_ub) && (m.lgp_lb <= lgp[i]) && (lgp[i] <= m.lgp_ub) && (m.ads_lb <= ads[i]) && (ads[i] <= m.ads_ub) && (m.pds_lb <= pds[i]) && (pds[i] <= m.pds_ub) && (m.hbd_lb <= hbd[i]) && (hbd[i] <= m.hbd_ub) && (m.hba_lb <= hba[i]) && (hba[i] <= m.hba_ub) && (m.psa_lb <= psa[i]) && (psa[i] <= m.psa_ub) && (m.chg_lb <= chg[i]) && (chg[i] <= m.chg_ub) && (m.nrb_lb <= nrb[i]) && (nrb[i] <= m.nrb_ub)) ++ligands;
				}
				this.send({uuid: m.uuid, ligands: ligands});
			}
		}
		for (var i = 0; i < numCPUs; i++) {
			cluster.fork().on('message', msg);
		}
		cluster.on('death', function(worker) {
			console.error('Worker process %d died. Restarting...', worker.pid);
			cluster.fork().on('message', msg);
		});
	});
} else {
	// Connect to MongoDB
	var mongodb = require('mongodb');
	new mongodb.MongoClient.connect('mongodb://' + process.argv[2] + '/istar', function(err, db) {
		if (err) throw err;
		db.authenticate(process.argv[3], process.argv[4], function(err, authenticated) {
			if (err) throw err;
			var idock = db.collection('idock');
			var igrow = db.collection('igrow');
			var igrep = db.collection('igrep');
			var usr   = db.collection('usr');
			// Configure express server
			var express = require('express');
			var compress = require('compression');
			var bodyParser = require('body-parser');
			var favicon = require('serve-favicon');
			var errorHandler = require('errorhandler');
			var app = express();
			app.use(compress());
			app.use(bodyParser.urlencoded({ limit: '12mb', extended: false }));
			app.use(errorHandler({ dumpExceptions: true, showStack: true }));
			var env = process.env.NODE_ENV || 'development';
			if (env == 'development') {
				app.use(express.static(__dirname + '/public'));
				app.use(favicon(__dirname + '/public/favicon.ico'));
			} else if (env == 'production') {
				var oneDay = 1000 * 60 * 60 * 24;
				var oneYear = oneDay * 365.25;
				app.use(express.static(__dirname + '/public', { maxAge: oneDay }));
				app.use(favicon(__dirname + '/public/favicon.ico', { maxAge: oneYear }));
			};
			// Define helper variables and functions
			var child_process = require('child_process');
			var validator = require('./public/validator');
			var uuid = require('uuid');
			var msg = {};
			var sync = function(m_uuid, callback) {
				if (msg.uuid !== m_uuid) setImmediate(function() {
					sync(m_uuid, callback);
				});
				else callback(msg.ligands);
			};
			process.on('message', function(m) {
				if (m.ligands !== undefined) {
					msg = m;
				}
			});
			var getJobs = function(req, res, collection, jobFields, progressFields) {
				var v = new validator(req.query);
				if (progressFields === undefined) {
					if (v
						.field('skip').message('must be a non-negative integer').int(0).min(0).copy()
						.failed()) {
						res.json(v.err);
						return;
					};
					collection.find({}, {
						fields: jobFields,
						sort: {'submitted': 1},
						skip: v.res.skip
					}).toArray(function(err, docs) {
						if (err) throw err;
						res.json(docs);
					});
				} else {
					if (v
						.field('skip').message('must be a non-negative integer').int(0).min(0).copy()
						.field('count').message('must be a non-negative integer').int(0).min(0).copy()
						.failed() || v
						.range('skip', 'count')
						.failed()) {
						res.json(v.err);
						return;
					};
					collection.count(function(err, count) {
						if (err) throw err;
						if (v
							.field('count').message('must be no greater than ' + count).max(count)
							.failed()) {
							res.json(v.err);
							return;
						}
						collection.find({}, {
							fields: v.res.count == count ? progressFields : jobFields,
							sort: {'submitted': 1},
							skip: v.res.skip,
							limit: count - v.res.skip
						}).toArray(function(err, docs) {
							if (err) throw err;
							res.json(docs);
						});
					});
				}
			};
			var idockJobFields = {
				'description': 1,
				'ligands': 1,
				'submitted': 1,
				'scheduled': 1,
				'completed': 1,
			};
			var idockProgressFields = {
				'_id': 0,
				'scheduled': 1,
				'completed': 1,
			};
			for (var i = 0; i < 10; ++i) {
				idockJobFields[i] = 1;
				idockProgressFields[i] = 1;
			}
			app.route('/idock/jobs').get(function(req, res) {
				getJobs(req, res, idock, idockJobFields, idockProgressFields);
			}).post(function(req, res) {
				var v = new validator(req.body);
				if (v
					.field('email').message('must be valid').email().copy()
					.field('description').message('must be provided, at most 20 characters').length(1, 20).xss().copy()
					.field('receptor').message('must conform to PDB specification').length(1, 10485760).receptor()
					.field('center_x').message('must be a decimal within [-999, 999]').float().min(-999).max(999)
					.field('center_y').message('must be a decimal within [-999, 999]').float().min(-999).max(999)
					.field('center_z').message('must be a decimal within [-999, 999]').float().min(-999).max(999)
					.field('size_x').message('must be an integer within [10, 30]').float().min(10).max(30)
					.field('size_y').message('must be an integer within [10, 30]').float().min(10).max(30)
					.field('size_z').message('must be an integer within [10, 30]').float().min(10).max(30)
					.field('mwt_lb').message('must be a decimal within [55, 567]').float(390).min(55).max(567).copy()
					.field('mwt_ub').message('must be a decimal within [55, 567]').float(420).min(55).max(567).copy()
					.field('lgp_lb').message('must be a decimal within [-6, 12]').float(1).min(-6).max(12).copy()
					.field('lgp_ub').message('must be a decimal within [-6, 12]').float(3).min(-6).max(12).copy()
					.field('ads_lb').message('must be a decimal within [-57, 29]').float(0).min(-57).max(29).copy()
					.field('ads_ub').message('must be a decimal within [-57, 29]').float(10).min(-57).max(29).copy()
					.field('pds_lb').message('must be a decimal within [-543, 1]').float(-40).min(-543).max(1).copy()
					.field('pds_ub').message('must be a decimal within [-543, 1]').float(0).min(-543).max(1).copy()
					.field('hbd_lb').message('must be an integer within [0, 20]').int(2).min(0).max(20).copy()
					.field('hbd_ub').message('must be an integer within [0, 20]').int(4).min(0).max(20).copy()
					.field('hba_lb').message('must be an integer within [0, 18]').int(4).min(0).max(18).copy()
					.field('hba_ub').message('must be an integer within [0, 18]').int(6).min(0).max(18).copy()
					.field('psa_lb').message('must be an integer within [0, 317]').int(60).min(0).max(317).copy()
					.field('psa_ub').message('must be an integer within [0, 317]').int(80).min(0).max(317).copy()
					.field('chg_lb').message('must be an integer within [-5, 5]').int(0).min(-5).max(5).copy()
					.field('chg_ub').message('must be an integer within [-5, 5]').int(0).min(-5).max(5).copy()
					.field('nrb_lb').message('must be an integer within [0, 35]').int(4).min(0).max(35).copy()
					.field('nrb_ub').message('must be an integer within [0, 35]').int(6).min(0).max(35).copy()
					.failed() || v
					.range('mwt_lb', 'mwt_ub')
					.range('lgp_lb', 'lgp_ub')
					.range('ads_lb', 'ads_ub')
					.range('pds_lb', 'pds_ub')
					.range('hbd_lb', 'hbd_ub')
					.range('hba_lb', 'hba_ub')
					.range('psa_lb', 'psa_ub')
					.range('chg_lb', 'chg_ub')
					.range('nrb_lb', 'nrb_ub')
					.failed()) {
					res.json(v.err);
					return;
				}
				// Send query to master process
				ligands = -1;
				var m_uuid = uuid.v1();
				process.send({
					uuid: m_uuid,
					query: '/idock/ligands',
					mwt_lb: v.res.mwt_lb,
					mwt_ub: v.res.mwt_ub,
					lgp_lb: v.res.lgp_lb,
					lgp_ub: v.res.lgp_ub,
					ads_lb: v.res.ads_lb,
					ads_ub: v.res.ads_ub,
					pds_lb: v.res.pds_lb,
					pds_ub: v.res.pds_ub,
					hbd_lb: v.res.hbd_lb,
					hbd_ub: v.res.hbd_ub,
					hba_lb: v.res.hba_lb,
					hba_ub: v.res.hba_ub,
					psa_lb: v.res.psa_lb,
					psa_ub: v.res.psa_ub,
					chg_lb: v.res.chg_lb,
					chg_ub: v.res.chg_ub,
					nrb_lb: v.res.nrb_lb,
					nrb_ub: v.res.nrb_ub
				});
				sync(m_uuid, function(ligands) {
					if (!(1 <= ligands)) {
						res.json({'ligands': 'the number of filtered ligands must be at least 1'});
						return;
					}
					v.res.ligands = ligands;
					v.res.scheduled = 0;
					v.res.finished = 0;
					for (var i = 0; i < 10; ++i) {
						v.res[i] = 0;
					}
					v.res.submitted = new Date();
					v.res._id = new mongodb.ObjectID();
					var dir = __dirname + '/public/idock/jobs/' + v.res._id;
					fs.mkdir(dir, function (err) {
						if (err) throw err;
						fs.writeFile(dir + '/receptor.pdb', req.body['receptor'], function(err) {
							if (err) throw err;
							child_process.execFile(process.env.MGL_ROOT + '/bin/python', [process.env.MGL_ROOT + '/MGLToolsPckgs/AutoDockTools/Utilities24/prepare_receptor4.pyo', '-A', 'checkhydrogens', '-U', 'nphs_lps_waters_deleteAltB', '-r', 'receptor.pdb'], { cwd: dir }, function(err, stdout, stderr) {
								if (err) {
									fs.unlink(dir + '/receptor.pdb', function(err) {
										if (err) throw err;
										fs.rmdir(dir, function (err) {
											if (err) throw err;
											res.json({ receptor: 'failed to convert PDB to PDBQT' });
										});
									});
								} else {
									fs.writeFile(dir + '/box.conf', ['center_x', 'center_y', 'center_z', 'size_x', 'size_y', 'size_z'].map(function(key) {
										return key + '=' + req.body[key] + '\n';
									}).join(''), function(err) {
										if (err) throw err;
										idock.insert(v.res, { w: 0 });
										res.json({});
									});
								}
							});
						});
					});
				});
			});
			// Get the number of ligands satisfying filtering conditions
			app.route('/idock/ligands').get(function(req, res) {
				// Validate and sanitize user input
				var v = new validator(req.query);
				if (v
					.field('mwt_lb').message('must be a decimal within [55, 567]').float().min(55).max(567).copy()
					.field('mwt_ub').message('must be a decimal within [55, 567]').float().min(55).max(567).copy()
					.field('lgp_lb').message('must be a decimal within [-6, 12]').float().min(-6).max(12).copy()
					.field('lgp_ub').message('must be a decimal within [-6, 12]').float().min(-6).max(12).copy()
					.field('ads_lb').message('must be a decimal within [-57, 29]').float().min(-57).max(29).copy()
					.field('ads_ub').message('must be a decimal within [-57, 29]').float().min(-57).max(29).copy()
					.field('pds_lb').message('must be a decimal within [-543, 1]').float().min(-543).max(1).copy()
					.field('pds_ub').message('must be a decimal within [-543, 1]').float().min(-543).max(1).copy()
					.field('hbd_lb').message('must be an integer within [0, 20]').int().min(0).max(20).copy()
					.field('hbd_ub').message('must be an integer within [0, 20]').int().min(0).max(20).copy()
					.field('hba_lb').message('must be an integer within [0, 18]').int().min(0).max(18).copy()
					.field('hba_ub').message('must be an integer within [0, 18]').int().min(0).max(18).copy()
					.field('psa_lb').message('must be an integer within [0, 317]').int().min(0).max(317).copy()
					.field('psa_ub').message('must be an integer within [0, 317]').int().min(0).max(317).copy()
					.field('chg_lb').message('must be an integer within [-5, 5]').int().min(-5).max(5).copy()
					.field('chg_ub').message('must be an integer within [-5, 5]').int().min(-5).max(5).copy()
					.field('nrb_lb').message('must be an integer within [0, 35]').int().min(0).max(35).copy()
					.field('nrb_ub').message('must be an integer within [0, 35]').int().min(0).max(35).copy()
					.failed() || v
					.range('mwt_lb', 'mwt_ub')
					.range('lgp_lb', 'lgp_ub')
					.range('ads_lb', 'ads_ub')
					.range('pds_lb', 'pds_ub')
					.range('hbd_lb', 'hbd_ub')
					.range('hba_lb', 'hba_ub')
					.range('psa_lb', 'psa_ub')
					.range('chg_lb', 'chg_ub')
					.range('nrb_lb', 'nrb_ub')
					.failed()) {
					res.json(v.err);
					return;
				}
				// Send query to master process
				ligands = -1;
				v.res.query = '/idock/ligands';
				v.res.uuid = uuid.v1();
				process.send(v.res);
				sync(v.res.uuid, function(ligands) {
					res.json(ligands);
				});
			});
/*			// Get a specific idock job
			app.route('/idock/job').get(function(req, res) {
				var v = new validator(req.query);
				if (v
					.field('id').message('must be a valid object id').objectid().copy()
					.failed()) {
					res.json(v.err);
					return;
				};
				idock.findOne({
					'_id': new mongodb.ObjectID(v.res.id),
					'completed': { $exists: 1 },
				}, {
					'_id': 0,
					'description': 1,
				}, function(err, doc) {
					if (err) throw err;
					res.json(doc);
				});
			});
			app.route('/igrow/jobs').get(function(req, res) {
				getJobs(req, res, igrow, {
					'description': 1,
					'idock_id': 1,
					'submitted': 1,
//					'scheduled': 1,
					'completed': 1,
				}, {
					'_id': 0,
//					'scheduled': 1,
					'completed': 1,
				});
			}).post(function(req, res) {
				var v = new validator(req.body);
				if (v
					.field('idock_id').message('must be a valid object id').objectid().copy()
					.field('email').message('must be valid').email().copy()
					.field('description').message('must be provided, at most 20 characters').length(1, 20).xss().copy()
					.field('mms_lb').message('must be a decimal within [0, 1000]').float(300).min(0).max(1000).copy()
					.field('mms_ub').message('must be a decimal within [0, 1000]').float(500).min(0).max(1000).copy()
					.field('nrb_lb').message('must be an integer within [0, 35]' ).int(  0).min( 0).max( 35).copy()
					.field('nrb_ub').message('must be an integer within [0, 35]' ).int( 10).min( 0).max( 35).copy()
					.field('hbd_lb').message('must be an integer within [0, 20]' ).int(  0).min( 0).max( 20).copy()
					.field('hbd_ub').message('must be an integer within [0, 20]' ).int(  5).min( 0).max( 20).copy()
					.field('hba_lb').message('must be an integer within [0, 18]' ).int(  0).min( 0).max( 18).copy()
					.field('hba_ub').message('must be an integer within [0, 18]' ).int( 10).min( 0).max( 18).copy()
					.field('nha_lb').message('must be an integer within [1, 100]').int( 20).min( 1).max(100).copy()
					.field('nha_ub').message('must be an integer within [1, 100]').int( 70).min( 1).max(100).copy()
					.field('lgp_lb').message('must be an integer within [-6, 12]').int(-.4).min(-6).max( 12).copy()
					.field('lgp_ub').message('must be an integer within [-6, 12]').int(5.6).min(-6).max( 12).copy()
					.field('psa_lb').message('must be an integer within [0, 300]').int(  0).min( 0).max(300).copy()
					.field('psa_ub').message('must be an integer within [0, 300]').int(140).min( 0).max(300).copy()
					.field('mrf_lb').message('must be an integer within [0, 300]').int( 40).min( 0).max(300).copy()
					.field('mrf_ub').message('must be an integer within [0, 300]').int(130).min( 0).max(300).copy()
					.failed()) {
					res.json(v.err);
					return;
				};
				idock.findOne({
					'_id': new mongodb.ObjectID(v.res.idock_id),
					'completed': { $exists: 1 },
				}, {}, function(err, doc) {
					if (err) throw err;
					if (!doc) {
						res.json({'idock_id': 'must be a completed idock job id'});
						return;
					}
					v.res.submitted = new Date();
					igrow.insert(v.res, {w: 0});
					res.json({});
				});
			});*/
			app.route('/usr/jobs').get(function(req, res) {
				getJobs(req, res, usr, {
					'description': 1,
					'submitted': 1,
					'started': 1,
					'completed': 1,
				}, {
					'_id': 0,
					'started': 1,
					'completed': 1,
				});
			}).post(function(req, res) {
				var v = new validator(req.body);
				if (v
					.field('email').message('must be valid').email().copy()
					.field('description').message('must be provided, at most 20 characters').length(1, 20).xss().copy()
					.field('format').message('must be mol2, sdf, xyz, pdb, or pdbqt').in(['mol2', 'sdf', 'xyz', 'pdb', 'pdbqt']).copy()
					.field('query').message('must be provided and must not exceed 100KB').length(1, 102400)
					.field('library').message('must be 16 or dfn').in(['16', 'dfn']).copy()
					.failed()) {
					res.json(v.err);
					return;
				}
				v.res.submitted = new Date();
				v.res._id = new mongodb.ObjectID();
				var dir = __dirname + '/public/usr/jobs/' + v.res._id;
				fs.mkdir(dir, function (err) {
					if (err) throw err;
					fs.writeFile(dir + '/query.' + v.res.format, req.body['query'], function(err) {
						if (err) throw err;
						usr.insert(v.res, { w: 0 });
						res.json({});
					});
				});
			});
			app.route('/igrep/jobs').get(function(req, res) {
				getJobs(req, res, igrep, {
					'taxid': 1,
					'submitted': 1,
					'completed': 1,
				});
			}).post(function(req, res) {
				var v = new validator(req.body);
				if (v
					.field('email').message('must be valid').email().copy()
					.field('taxid').message('must be the taxonomy id of one of the 26 genomes').int().in([13616, 9598, 9606, 9601, 10116, 9544, 9483, 10090, 9913, 9823, 9796, 9615, 9986, 7955, 28377, 9103, 59729, 9031, 3847, 9258, 29760, 15368, 7460, 30195, 7425, 7070]).copy()
					.field('queries').message('must conform to the specifications').length(2, 66000).queries().copy()
					.failed()) {
					res.json(v.err);
					return;
				}
				v.res.submitted = new Date();
				v.res._id = new mongodb.ObjectID();
				var dir = __dirname + '/public/igrep/jobs/' + v.res._id;
				fs.mkdir(dir, function (err) {
					if (err) throw err;
					igrep.insert(v.res, {w: 0});
					res.json({});
				});
			});
			// Start listening
			var http_port = 3000, spdy_port = 3443;
			app.listen(http_port);
			require('spdy').createServer(require('https').Server, {
				key: fs.readFileSync(__dirname + '/key.pem'),
				cert: fs.readFileSync(__dirname + '/cert.pem')
			}, app).listen(spdy_port);
			console.log('Worker %d listening on HTTP port %d and SPDY port %d in %s mode', process.pid, http_port, spdy_port, app.settings.env);
		});
	});
}
back to top