# Source: NodeApiServer.js

    "use strict";

    // NOTE: This class instantiates individually but shares a single
    // http server after listen() is called on any instance

    // Import external names locally
    var Shared = require('./Shared'),
    	express = require('express'),
    	bodyParser = require('body-parser'),
    	cors = require('cors'),
    	async = require('async'),
    	fs = require('fs'),
    	http = require('http'),
    	https = require('https'),
    	url = require('url'),
    	pem = require('pem'),
    	app = express(),
    	server,
    	Core,
    	CoreInit,
    	Db,
    	NodeApiServer,
    	ReactorIO,
    	Overload,
    	_access = {},
    	_accessOrder = [],
    	_io = {};

    /**
     * The NodeApiServer class.
     * @class
     * @constructor
     */
    NodeApiServer = function () {
    	this.init.apply(this, arguments);
    };

    /**
     * The init method that can be overridden or extended.
     * @param {Core} core The ForerunnerDB core instance.
     */
    NodeApiServer.prototype.init = function (core) {
    	var self = this;
    	self._core = core;
    	self._app = app;

    	this.name('ApiServer');
    	this.rootPath('/fdb');
    };

    Shared.addModule('NodeApiServer', NodeApiServer);
    Shared.mixin(NodeApiServer.prototype, 'Mixin.Common');
    Shared.mixin(NodeApiServer.prototype, 'Mixin.Events');
    Shared.mixin(NodeApiServer.prototype, 'Mixin.ChainReactor');

    Core = Shared.modules.Core;
    CoreInit = Core.prototype.init;
    Db = Shared.modules.Db;
    ReactorIO = Shared.modules.ReactorIO;
    Overload = Shared.overload;

    Shared.synthesize(NodeApiServer.prototype, 'name');
    Shared.synthesize(NodeApiServer.prototype, 'rootPath', function (val) {
    	if (val !== undefined && server) {
    		throw('Cannot set rootPath of server after start() has been called!');
    	}

    	if (val !== undefined && !server) {
    		this._rootSectionCount = (val.split('/').length - 1);
    		if (this._rootSectionCount < 0) {
    			this._rootSectionCount =  0;
    		}
    	}

    	return this.$super.call(this, val);
    });

    /**
     * Starts the rest server listening for requests against the ip and
     * port number specified.
     * @param {String} host The IP address to listen on, set to 0.0.0.0 to
     * listen on all interfaces.
     * @param {String} port The port to listen on.
     * @param {Object} options An options object.
     * @param {Function=} callback The method to call when the server has
     * started (or failed to start).
     * @returns {NodeApiServer}
     */
    NodeApiServer.prototype.start = function (host, port, options, callback) {
    	var self = this;

    	// Start listener
    	if (!server) {
    		this._startData = {
    			host: host,
    			port: port,
    			options: options,
    			callback: callback
    		};

    		if (options && options.cors === true) {
    			// Enable cors middleware
    			app.use(cors({origin: true}));

    			// Allow preflight CORS
    			app.options('*', cors({origin: true}));
    		}

    		// Parse body in requests
    		app.use(bodyParser.json());

    		// Parse JSON as a query parameter
    		app.use(function (req, res, next) {
    			var query,
    				jsonData,
    				urlObj = url.parse(req.url);

    			req.json = req.json || {};

    			if (urlObj.query) {
    				try {
    					// Try to parse the query parameter as URI encoded
    					query = urlObj.query;
    					query = decodeURIComponent(query);

    					if (query) {
    						// We got a query param, try to decode as JSON
    						jsonData = JSON.parse(decodeURIComponent(query));

    						// Now copy any fields from jsonData to req.json
    						Shared.mixin(req.json, jsonData);
    					}
    				} catch (e) {
    					// Check for normal query params
    					if (req.query && Object.keys(req.query).length > 0) {
    						return next();
    					} else {
    						res.status(500).send('Error parsing query string ' + query + ' ' + e);
    						return;
    					}
    				}
    			}

    			return next();
    		});

    		// Activate routes
    		this._defineRoutes();

    		self._createHttpServer(options, function (err, httpServer) {
    			if (!err) {
    				server = httpServer.listen(port, host, function (err) {
    					if (!err) {
    						if (options && options.ssl && options.ssl.enable) {
    							console.log('ForerunnerDB REST API listening at https://%s:%s/fdb', host, port);
    						} else {
    							console.log('ForerunnerDB REST API listening at http://%s:%s/fdb', host, port);
    						}

    						if (callback) {
    							callback(false, server);
    						}

    						self.emit('started');
    					} else {
    						console.log('Listen error', err);
    						callback(err);
    					}
    				});
    			}
    		});
    	} else {
    		// Server already running
    		if (callback) {
    			callback(false, server);
    		}
    	}

    	return this;
    };

    /**
     * Gets the express app (router).
     * @returns {*}
     */
    NodeApiServer.prototype.serverApp = function () {
    	return app;
    };

    /**
     * Gets the express library.
     * @returns {*|exports|module.exports}
     */
    NodeApiServer.prototype.express = function () {
    	return express;
    };

    NodeApiServer.static = function (urlPath, folderPath) {
    	return app.use(urlPath, express.static(folderPath));
    };

    /**
     * Creates the http(s) server instance.
     * @param options
     * @param callback
     * @private
     */
    NodeApiServer.prototype._createHttpServer = function (options, callback) {
    	var self = this,
    		ssl,
    		httpServer,
    		i;

    	if (options && options.ssl && options.ssl.enable) {
    		// Check if we were provided certificate data
    		if (options.ssl.key && options.ssl.cert) {
    			ssl = {
    				key: fs.readFileSync(options.ssl.key),
    				cert: fs.readFileSync(options.ssl.cert)
    			};

    			if (options.ssl.ca) {
    				ssl.ca = [];
    				for (i = 0; i < options.ssl.ca.length; i++) {
    					ssl.ca.push(fs.readFileSync(options.ssl.ca[i]));
    				}
    			}

    			httpServer = https.createServer(ssl, app);

    			callback(false, httpServer);
    		} else {
    			// Check for existing certs we have already generated
    			fs.stat('./forerunnerdbTempCert/privkey.pem', function (err, stat) {
    				if (!err) {
    					fs.stat('./forerunnerdbTempCert/fullchain.pem', function (err, stat) {
    						if (!err) {
    							ssl = {
    								key: fs.readFileSync('./forerunnerdbTempCert/key.pem'),
    								cert: fs.readFileSync('./forerunnerdbTempCert/cert.pem')
    							};

    							httpServer = https.createServer(ssl, app);

    							callback(false, httpServer);
    						} else {
    							self._generateSelfCertHttpsServer(callback);
    						}
    					});
    				} else {
    					self._generateSelfCertHttpsServer(callback);
    				}
    			});
    		}
    	} else {
    		httpServer = http.createServer(app);
    		callback(false, httpServer);
    	}
    };

    /**
     * Uses PEM module to generate a self-signed SSL certificate and creates
     * an https server from it, returning it in the callback.
     * @param callback
     * @private
     */
    NodeApiServer.prototype._generateSelfCertHttpsServer = function (callback) {
    	var ssl,
    		httpServer;

    	// Generate our own self-signed cert for easy use of https prototyping
    	pem.createCertificate({
    		days: 1,
    		selfSigned: true
    	}, function(err, keys) {
    		if (!err) {
    			// Write self-signed cert data so we can re-use it
    			fs.writeFile('./forerunnerdbTempCert/key.pem', keys.serviceKey, function () {});
    			fs.writeFile('./forerunnerdbTempCert/cert.pem', keys.certificate, function () {});

    			// Assign data to the ssl object
    			ssl = {
    				key: keys.serviceKey,
    				cert: keys.certificate
    			};

    			httpServer = https.createServer(ssl, app);

    			callback(false, httpServer);
    		} else {
    			throw('Cannot enable SSL, generating a self-signed certificate failed: ' +  err);
    		}
    	});
    };

    /**
     * Stops the server listener.
     */
    NodeApiServer.prototype.stop = function () {
    	if (server) {
    		server.close();
    		server = undefined;

    		this.emit('stopped');
    		return true;
    	}

    	return false;
    };

    /**
     * Handles requests from clients to the endpoints the database exposes.
     * @param req
     * @param res
     */
    NodeApiServer.prototype.handleRequest = function (req, res) {
    	var self = this,
    		method = req.method,
    		dbName = req.params.dbName,
    		objType = req.params.objType,
    		objName = req.params.objName,
    		objId = req.params.objId,
    		query,
    		options,
    		db,
    		obj,
    		urlPath,
    		pathSections;

    	if (self.debug && self.debug()) {
    		console.log(self.logIdentifier() + ' Received REST: ' + method + ' ' + req.url);
    	}

    	// Check permissions
    	self.hasPermission(dbName, objType, objName, method, req, function (err, results) {
    		if (!err) {
    			if (self.debug && self.debug()) {
    				console.log(self.logIdentifier() + ' REST call is allowed: ' + method + ' ' + req.url);
    			}

    			// Check if the database has this type of object
    			// TODO: Do we want to call collectionExists (objType + 'Exists') here?
    			if (typeof self._core.db(dbName)[objType] === 'function') {
    				// Get the query and option params
    				query = req.json && req.json.$query ? req.json.$query : undefined;
    				options = req.json && req.json.$options ? req.json.$options : undefined;

    				db = self._core.db(dbName);
    				obj = db[objType](objName);

    				// Check the object type for special considerations
    				if (objType === 'procedure') {
    					if (obj && obj.exec && typeof obj.exec === 'function') {
    						// Handle procedure
    						if (self.debug && self.debug()) {
    							console.log(self.logIdentifier() + ' REST executing procedure: ' + method + ' ' + req.url);
    						}

    						return obj.exec(req, res);
    					} else {
    						if (self.debug && self.debug()) {
    							console.log(self.logIdentifier() + ' REST procedure not found: ' + method + ' ' + req.url);
    						}

    						return res.status(404).send('Procedure "' + objName + '" not found!');
    					}
    				}

    				// Get url path
    				urlPath = url.parse(req.url).pathname;

    				// Split path into sections
    				pathSections = urlPath.split('/');

    				if (self._rootPath) {
    					// Remove the first 5 sections as we already used these (rootPath, dbName, objType, objName, itemId)
    					pathSections.splice(0, 4 + self._rootSectionCount);
    				} else {
    					// Remove the first 4 sections as we already used these (dbName, objType, objName, itemId)
    					pathSections.splice(0, 4);
    				}

    				switch (method) {
    					case 'HEAD':
    					case 'GET':
    						if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    							if (db.debug()) {
    								console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    							}

    							obj.once('ready', function () {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    								}
    								self._handleResponse(req, res, dbName, objType, objName, obj, pathSections, obj.find(query, options));
    							});
    						} else {
    							self._handleResponse(req, res, dbName, objType, objName, obj, pathSections, obj.find(query, options));
    						}
    						break;

    					case 'POST':
    						if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    							if (db.debug()) {
    								console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    							}

    							obj.once('ready', function () {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    								}

    								obj.insert(req.body, function (result) {
    									res.send(result);
    								});
    							});
    						} else {
    							obj.insert(req.body, function (result) {
    								res.send(result);
    							});
    						}
    						break;

    					case 'PUT':
    						if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    							if (db.debug()) {
    								console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    							}

    							obj.once('ready', function () {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    								}

    								res.send(obj.updateById(objId, {$replace: req.body}));
    							});
    						} else {
    							res.send(obj.updateById(objId, {$replace: req.body}));
    						}
    						break;

    					case 'PATCH':
    						if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    							if (db.debug()) {
    								console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    							}

    							obj.once('ready', function () {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    								}

    								res.send(obj.updateById(objId, req.body));
    							});
    						} else {
    							res.send(obj.updateById(objId, req.body));
    						}
    						break;

    					case 'DELETE':
    						if (!objId) {
    							// Remove all
    							if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    								}

    								obj.once('ready', function () {
    									if (db.debug()) {
    										console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    									}
    									res.send(obj.remove(query, options));
    								});
    							} else {
    								res.send(obj.remove(query, options));
    							}
    						} else {
    							// Remove one
    							if (obj.isProcessingQueue && obj.isProcessingQueue()) {
    								if (db.debug()) {
    									console.log(db.logIdentifier() + ' Waiting for async queue: ' + objName);
    								}

    								obj.once('ready', function () {
    									if (db.debug()) {
    										console.log(db.logIdentifier() + ' Async queue complete: ' + objName);
    									}
    									res.send(obj.removeById(objId, options));
    								});
    							} else {
    								res.send(obj.removeById(objId, options));
    							}
    						}
    						break;

    					default:
    						res.status(403).send('Unknown method');
    						break;
    				}

    				if (self.debug && self.debug()) {
    					console.log(self.logIdentifier() + ' REST call finished: ' + method + ' ' + req.url);
    				}
    			} else {
    				if (self.debug && self.debug()) {
    					console.log(self.logIdentifier() + ' REST call object type unknown: ' + method + ' ' + req.url);
    				}

    				res.status(500).send('Unknown object type: ' + objType);
    			}
    		} else {
    			if (self.debug && self.debug()) {
    				console.log(self.logIdentifier() + ' REST call permission denied: ' + method + ' ' + req.url);
    			}

    			res.status(403).send(err);
    		}
    	});
    };

    NodeApiServer.prototype._handleResponse = function (req, res, dbName, objType, objName, obj, pathSections, responseData) {
    	var self = this,
    		pathSection,
    		tmpParts,
    		pathIdField = obj._primaryKey !== undefined ? obj._primaryKey : '_id',
    		i;

    	// Check if we need to generate a sub-document query
    	if (pathSections.length) {
    		// Get the next path section
    		pathSection = pathSections.shift();

    		if (responseData instanceof Array) {
    			// Set the id field to default
    			pathIdField = '_id';

    			// Check if the patch section has a colon in it, denoting a search field and value
    			if (pathSection.indexOf(':') > -1) {
    				// Split the path section
    				tmpParts = pathSection.split(':');
    				pathIdField = tmpParts[0];
    				pathSection = tmpParts[1];
    			}

    			// The path section must correspond to an id in the array of items
    			for (i = 0; i < responseData.length; i++) {
    				if (responseData[i][pathIdField] === pathSection) {
    					return self._handleResponse(req, res, dbName, objType, objName, obj, pathSections, responseData[i]);
    				}
    			}

    			// Could not find a matching document, send nothing back
    			return self._sendResponse(req, res);
    		}

    		// Grab the document sub-data
    		return self._handleResponse(req, res, dbName, objType, objName, obj, pathSections, responseData[pathSection]);
    	} else {
    		return self._sendResponse(req, res, responseData);
    	}
    };

    NodeApiServer.prototype._sendResponse = function (req, res, data) {
    	var statusCode = data !== undefined ? 200 : 404;

    	switch (req.method) {
    		case 'HEAD':
    			// Only send status codes - head is a unique case in that a
    			// blank data string means we should send 204 (no content)
    			// not 200, and if undefined we can still send 404
    			if (data !== undefined) {
    				res.sendStatus(204);
    			} else {
    				res.sendStatus(404);
    			}
    			break;

    		case 'GET':
    			// Send status code and data
    			res.status(statusCode).send(data);
    			break;

    		default:
    			res.send(data);
    			break;
    	}
    };

    /**
     * Handles client requests to open an EventSource connection to our
     * server-sent events server.
     * @param req
     * @param res
     */
    NodeApiServer.prototype.handleSyncRequest = function (req, res) {
    	var self = this,
    		dbName = req.params.dbName,
    		objType = req.params.objType,
    		objName = req.params.objName,
    		db,
    		obj;

    	// Check permissions
    	self.hasPermission(dbName, objType, objName, "SYNC", req, function (err, results) {
    		if (!err) {
    			// Check if the database has this type of object
    			// TODO: Do we want to call collectionExists (objType + 'Exists') here?
    			if (typeof self._core.db(dbName)[objType] === 'function') {
    				db = self._core.db(dbName);
    				obj = db[objType](objName);

    				// Let request last as long as possible
    				req.socket.setTimeout(0x7FFFFFFF);

    				// Ensure we have basic IO objects set up
    				_io[objType] = _io[objType] || {};
    				_io[objType][objName] = _io[objType][objName] || {
    						messageId: 0,
    						clients: []
    					};

    				// Add this resource object the io clients array
    				_io[objType][objName].clients.push({req: req, res: res});

    				// Check if we already have an io for this object
    				if (!_io[objType][objName].io) {
    					// Setup a chain reactor IO node to intercept CRUD packets
    					// coming from the object (collection, view etc), and then
    					// pass them to the clients
    					_io[objType][objName].io = new ReactorIO(obj, self, function (chainPacket) {
    						self.sendToAll(_io[objType][objName], chainPacket.type, chainPacket.data);

    						// Returning false informs the chain reactor to continue propagation
    						// of the chain packet down the graph tree
    						return false;
    					});
    				}

    				req.socket.setNoDelay(true);

    				// Send headers for event-stream connection
    				res.writeHead(200, {
    					'Content-Type': 'text/event-stream',
    					'Cache-Control': 'no-cache',
    					'Connection': 'keep-alive'
    				});

    				res.write(':ok\n\n');

    				// Send connected message to the client with messageId zero
    				self.sendToClient(res, 0, 'connected', "{}");

    				req.on("close", function() {
    					// Remove this client from the array
    					var index = _io[objType][objName].clients.indexOf(res);

    					if (index > -1) {
    						_io[objType][objName].clients.splice(index, 1);
    					}
    				});
    			} else {
    				res.status(500).send('Unknown object type: ' + objType);
    			}
    		} else {
    			res.status(403).send(err);
    		}
    	});
    };

    /**
     * Sends server-sent-events message to all connected clients that are listening
     * to the changes in the IO that is passed.
     * @param io
     * @param eventName
     * @param data
     */
    NodeApiServer.prototype.sendToAll = function (io, eventName, data) {
    	var self = this,
    		clientArr,
    		cleanData,
    		res,
    		i;

    	// Increment the message counter
    	io.messageId++;

    	clientArr = io.clients;
    	cleanData = self.jStringify(data);

    	// Loop client resource and write data out to socket
    	for (i = 0; i < clientArr.length; i++) {
    		res = clientArr[i].res;
    		self.sendToClient(res, io.messageId, eventName, cleanData);
    	}
    };

    /**
     * Sends data to individual client.
     * @param res
     * @param messageId
     * @param eventName
     * @param stringifiedData Data to send in already-stringified format.
     */
    NodeApiServer.prototype.sendToClient = function (res, messageId, eventName, stringifiedData) {
    	res.write('event: ' + eventName + '\n');
    	res.write('id: ' + messageId + '\n');
    	res.write("data: " + stringifiedData + '\n\n');

    	//console.log('EventStream: "' + eventName + '" ' + stringifiedData);
    };

    /**
     * Checks for permission to access the specified object.
     * @param {String} dbName
     * @param {String} objType
     * @param {String} objName
     * @param {String} methodName
     * @param {Object} req
     * @param {Function} callback
     * @returns {*}
     */
    NodeApiServer.prototype.hasPermission = function (dbName, objType, objName, methodName, req, callback) {
    	var permissionMethods = this._core.api.access(dbName, objType, objName, methodName);

    	if (!permissionMethods || !permissionMethods.length) {
    		// No permissions set, deny access by default
    		return callback('403 Access Forbidden');
    	}

    	// Add a method right at the beginning of the waterfall
    	// that passes in the req object so all the access control
    	// methods can analyse the request for info they might need
    	permissionMethods.splice(0, 0, function (cb) {
    		cb(null, dbName, objType, objName, methodName, req);
    	});

    	// Loop the access methods and call each one in turn until an error
    	// response is found, then callback a failure
    	async.waterfall(permissionMethods, callback);
    };

    /**
     * Defines an access rule for an object and HTTP method combination. When
     * access is requested via a REST call, the function provided will be
     * executed and the callback from that method will determine if the
     * access will be allowed or denied. Multiple access functions can
     * be provided for a single model and method allowing authentication
     * checks to be stacked.
     *
     * This call also allows you to pass a wildcard "*" instead of the objType,
     * objName and methodName parameters which will match all items. This
     * allows you to set permissions globally.
     *
     * If no access permissions are present ForerunnerDB will automatically
     * deny any request to the resource by default.
     * @name access
     * @method NodeApiServer.access
     * @param {String} dbName The name of the database to set access rules for.
     * @param {String} objType The type of object that the name refers to
     * e.g. "collection", "view" etc. This effectively maps to a method name on
     * the Db class instance. If you can do db.collection() then you can use the
     * string "collection" since it maps to the db.collection() method. This means
     * you can also use this to map to custom classes as long as you register
     * an accessor method on the Db class.
     * @param {String} objName The object name to apply the access rule to.
     * @param {String} methodName The name of the HTTP method to apply the access
     * function to e.g. "GET", "POST", "PUT", "PATCH" etc.
     * @param {Function|String} checkFunction The function to call when an access
     * attempt is made against the collection. A callback method is passed to this
     * function which should be called after the function has finished
     * processing. If you do not need custom logic to allow or deny access you
     * can simply pass the string "allow" or "deny" instead of a function and
     * ForerunnerDB will handle the logic automatically.
     * @returns {*}
     */
    NodeApiServer.prototype.access = function (dbName, objType, objName, methodName, checkFunction) {
    	var methodArr,
    		accessOrderItem,
    		foundEntry = false,
    		i;

    	if (objType !== undefined && objName !== undefined && methodName !== undefined) {
    		if (checkFunction !== undefined) {
    			if (checkFunction === 'allow') {
    				// Provide default allow method
    				checkFunction = this._allowMethod;
    			} else if (checkFunction === 'deny') {
    				// Provide default deny method
    				checkFunction = this._denyMethod;
    			}

    			if (typeof checkFunction !== 'function') {
    				throw('Cannot create an access rule with a checkFunction argument of a type other than function!');
    			}

    			// Set new access permission with callback "checkFunction"
    			_access.db = _access.db || {};
    			_access.db[dbName] = _access.db[dbName] || {};
    			_access.db[dbName][objType] = _access.db[dbName][objType] || {};
    			_access.db[dbName][objType][objName] = _access.db[dbName][objType][objName] || {};
    			_access.db[dbName][objType][objName][methodName] = true;

    			_accessOrder.push({
    				dbName: dbName,
    				objType: objType,
    				objName: objName,
    				methodName: methodName,
    				checkFunction: checkFunction
    			});

    			return this;
    		}

    		methodArr = [];

    		// Do quick lookup check
    		if (_access.db && _access.db[dbName] && _access.db[dbName][objType] && _access.db[dbName][objType][objName] && _access.db[dbName][objType][objName][methodName]) {
    			foundEntry = true;
    		} else if (_access.db && _access.db[dbName] && _access.db[dbName][objType] && _access.db[dbName][objType][objName] && _access.db[dbName][objType][objName]['*']) {
    			foundEntry = true;
    		} else if (_access.db && _access.db[dbName] && _access.db[dbName][objType] && _access.db[dbName][objType]['*'] && _access.db[dbName][objType]['*'][methodName]) {
    			foundEntry = true;
    		} else if (_access.db && _access.db[dbName] && _access.db[dbName][objType] && _access.db[dbName][objType]['*'] && _access.db[dbName][objType]['*']['*']) {
    			foundEntry = true;
    		} else if (_access.db && _access.db[dbName] && _access.db[dbName]['*'] && _access.db[dbName]['*']['*'] && _access.db[dbName]['*']['*']['*']) {
    			foundEntry = true;
    		} else {
    			// None of the matching patterns exist, exit without permissions
    			return methodArr;
    		}

    		// Now we know that at least one of the permission rules matches the passed pattern
    		// so we loop through the permissions in order they are registered and add matching
    		// ones to the methodArr array.
    		for (i = 0; i < _accessOrder.length; i++) {
    			accessOrderItem = _accessOrder[i];

    			// Determine if the access data fits the query
    			if (accessOrderItem.dbName === '*' || accessOrderItem.dbName === dbName) {
    				if (accessOrderItem.objType === '*' || accessOrderItem.objType === objType) {
    					if (accessOrderItem.objName === '*' || accessOrderItem.objName === objName) {
    						if (accessOrderItem.methodName === '*' || accessOrderItem.methodName === methodName) {
    							methodArr.push(accessOrderItem.checkFunction);
    						}
    					}
    				}
    			}
    		}

    		return methodArr;
    	}

    	return [];
    };

    NodeApiServer.prototype._allowMethod = function defaultAllowMethod (dbName, objType, objName, httpMethod, req, callback) {
    	callback(false, dbName, objType, objName, httpMethod, req);
    };

    NodeApiServer.prototype._denyMethod = function defaultDenyMethod (dbName, objType, objName, httpMethod, req, callback) {
    	callback('Access forbidden', dbName, objType, objName, httpMethod, req);
    };

    /**
     * Creates the routes that express will expose to clients.
     * @private
     */
    NodeApiServer.prototype._defineRoutes = function () {
    	var self = this,
    		root = this._rootPath;

    	app.get(root + '/', function (req, res) {
    		res.send({
    			server: 'ForerunnerDB',
    			version: self._core.version()
    		});
    	});

    	// Handle sync routes
    	app.get(root + '/:dbName/:objType/:objName/_sync', function () { self.handleSyncRequest.apply(self, arguments); });

    	// Handle all other routes
    	app.get(root + '/:dbName/:objType/:objName', function () { self.handleRequest.apply(self, arguments); });
    	app.get(root + '/:dbName/:objType/:objName/:objId', function () { self.handleRequest.apply(self, arguments); });
    	app.get(root + '/:dbName/:objType/:objName/:objId/*', function () { self.handleRequest.apply(self, arguments); });
    	app.head(root + '/:dbName/:objType/:objName/:objId', function () { self.handleRequest.apply(self, arguments); });

    	app.post(root + '/:dbName/:objType/:objName', function () { self.handleRequest.apply(self, arguments); });
    	app.put(root + '/:dbName/:objType/:objName/:objId', function () { self.handleRequest.apply(self, arguments); });
    	app.patch(root + '/:dbName/:objType/:objName/:objId', function () { self.handleRequest.apply(self, arguments); });

    	app.delete(root + '/:dbName/:objType/:objName', function () { self.handleRequest.apply(self, arguments); });
    	app.delete(root + '/:dbName/:objType/:objName/:objId', function () { self.handleRequest.apply(self, arguments); });

    	app.get(root + '/:dbName/:objType/:objName/_sync', function () { self.handleRequest.apply(self, arguments); });
    	app.get(root + '/:dbName/:objType/:objName/:objId/_sync', function () { self.handleRequest.apply(self, arguments); });
    };

    /*
    NodeApiServer.prototype._generateCert = function () {
    	pem.createCertificate({
    		days: 365,
    		selfSigned: true
    	}, function(err, keys) {

    	});
    };
    */

    /**
     * Override the Core init to instantiate the plugin.
     * @returns {*}
     */
    Core.prototype.init = function () {
    	this.api = new NodeApiServer(this);
    	return CoreInit.apply(this, arguments);
    };

    Shared.finishModule('NodeApiServer');
    module.exports = NodeApiServer;