/**
* @version 1.2
* @author Boris GBAHOUE
* @file Generic request handler
* @module amiwo/rest
*/
// =======================================================================
// BASE SETUP
// =======================================================================
// call the packages we need
var debug = require('debug')('amiwo:session');
var u = require('../util');
var util = require('util');
var assert = require('assert');
// load our objects
var DBObjectFactory = require('../db/DBObjectFactory');
var GenericError = require('../error/GenericError');
var ResponseJSON = require('../rest/ResponseJSON');
var Session = require('./Session');
// =======================================================================
// CONSTRUCTOR
// =======================================================================
/**
* Variables
* - {Object} route JSON containing the routes to use
* - {String} key session key to be used to store interim results
*
* @constructor
* @param {String} application Session's application name
* @param {Object} options
* @param {Boolean} options.useMiddlewareErrorHandler if true, uses the Middleware error handler, else will redirect to the error route ("/error" by default)
* @param {String} options.route.error path to the default error route ('/error' by default)
* @class
*/
function RequestHandler(application, key, options) {
// application
this._application = application || "amiwo";
// key
if (key == null) {
this._key = this.constructor.name;
} else {
this._key = key;
}
// Options processing
if (options == null) options = {};
if (u.isEmpty(options.route)) options.route = {};
this._route = {};
if (u.isNotEmpty(options.route)) {
this._route.error = options.route.error || "/error";
delete options.route;
}
if (options.useMiddlewareErrorHandler != true) {
options.useMiddlewareErrorHandler = false;
}
this._options = options;
}
// =======================================================================
// PUBLIC METHODS
// =======================================================================
/**
* Returns the Handler for a request
* Default implementation returns a middleware that does nothing and calls next()
*
* @param {String} [action] to be used to redirect the request
* @param {Object} [options] to be passed to the requested middleware
*
* @public
*/
RequestHandler.prototype.getMiddleware = function(action, options) {
return function(req, res, next) {
next();
}
}
/**
* Get this RequestHandler's key
*
* @returns {String}
*
* @public
*/
RequestHandler.prototype.getKey = function() {
return this._key;
}
/**
* Check the status of the debug flag
*
* @param {Request} req
*
* @returns {boolean}
*
* @public
*/
RequestHandler.prototype.debug = function(req) {
return this._getDebug(req).activated;
}
// =======================================================================
// PROTECTED METHODS
// =======================================================================
/**
* Get the full debug object
*
* @param {Request} req
*
* @returns {Object} structured as {activated, count}; object is never null and activated always set (to false if "common.debug" didn't exist)
*
* @protected
*/
RequestHandler.prototype._getDebug = function(req) {
var debug = Session.getSession(this._application).getRequest(req, "common.debug", false);
return ((debug == null) ? {activated: false} : debug);
}
/**
* Create a DBObject, Event if 'type' is nully or of type 'type' otherwise
*
* @param {String} [type] classname of the object to create (with or without an ending "Mongo")
* @param {Object} [object] to pass to the constructor
*
* @protected
*/
RequestHandler.prototype._createObject = function(type, object) {
if ((object === undefined) && (u.typeOf(type) !== 'string')) {
object = type;
type = null;
}
if (u.isEmpty(type)) type = this._defaultDBObject;
assert(type != null, "::AMIWO::"+this.constructor.name.toUpperCase()+"::$CREATEOBJECT::ERROR Invalid state, _defaultDBObject is probably not set");
return DBObjectFactory.createObject(type, object);
}
/**
* Set 'obj' to 'value' if 'value' is !== undefined
*
* @param {Object} obj
* @param {String} property
* @param {any} value
* @param {String} [type] type of the value being set (date)
*
* @protected
*/
RequestHandler.prototype._setParamValue = function(obj, property, value, type) {
if (property == null) return;
if (value === undefined) {
return;
} else {
if (type && (type.toLowerCase() == "date")) {
var time = Date.parse(value);
if (isNaN(time)) return;
value = new Date(time);
}
obj[property] = value;
}
}
/**
* Add 'elements' to 'set' only if they aren't already present
*
* @param {Array} set (possibly modifed after this method gets executed)
* @param {Array|any} elements : Do nothing if 'elements' is null or an empty Array
* @param {String} property: name of the property to add (dotted name ok)
*
* @returns {Array} the elements which were added into 'set' (empty array if none)
* @protected
*/
RequestHandler.prototype._addToSet = function(set, elements, property) {
function $push(array, item, prop) {
if (prop) {
array.push(u.getProperty(item, prop));
} else {
array.push(item);
}
}
if (u.isEmpty(elements)) return [];
if (u.isEmpty(set)) {
// Fill 'set' keeping the referent to the original object => can't use concat
elements = Array.isArray(elements) ? elements : [elements];
for (var i=0; i < elements.length; i++) {
$push(set, elements[i], property);
}
return elements;
}
if (Array.isArray(elements)) {
var addedElements = [];
for (var i=0; i < elements.length; i++) {
if (elements[i] != null) {
addedElements = addedElements.concat(this._addToSet(set, elements[i], property));
}
}
return addedElements;
} else {
// Non array ...
var element = elements; // for readability
if (u.isObject(element)) {
var alreadyExists = false;
var hasId = element.hasOwnProperty("_id");
for (var i=0; i < set.length; i++) {
if (hasId) {
// Compare only _id as they are unique
if (set[i] == null) continue;
if (hasId) {
if (set[i]._id === element._id) {
alreadyExists = true;
break;
}
} else if (u.smartDeepEqual(set[i], element)) {
alreadyExists = true;
break;
}
}
} // end of 'for' loop
if (!alreadyExists) {
$push(set, element, property);
return [element];
} else {
return [];
}
} else {
// Non array, non Object
if (set.indexOf(element) == -1) {
$push(set, element, property);
return [element];
} else {
return [];
}
}
}
}
/**
* Process an error and create a GenericError
*
* @param {String} message
* @param {Error} err
* @param {Express.Request} req
*
* @returns {GenericError}
* @protected
*/
RequestHandler.prototype._processAndCreateError = function(message, err, req) {
if (this.debug(req)) debug("::AMIWO::%s::PROCESSANDCREATEERROR::ERROR %s => err=%s, trace=%s", this.constructor.name.toUpperCase(), err.message || message, err, err.stack);
if (err instanceof GenericError) {
err.object = ResponseJSON.processError(err.message, req);
return err;
} else {
return new GenericError(message, ResponseJSON.processError(message, req), err);
}
}
/**
* Safe transform of a String into an Array
*
* @param {String} input
*
* @returns {Array} null if 'input == null', [] if 'input == ""', 'input' if its content didn't allow to create an Array
* @protected
*/
RequestHandler.prototype._stringToArray = function(input) {
if (input == null) return input;
if (input == "") return [];
if (Array.isArray(input)) return input;
if (input instanceof Object) return input; // can't convert an Object into an Array
var array = null;
try {
array = JSON.parse(input);
if (Array.isArray(array)) {
return array;
} else {
// input is not a stringified array
return input;
}
} catch(err) {
return input;
}
}
/**
* Safe transform of a String into an Object
*
* @param {String} input
*
* @returns {Array} null if 'input == null', {} if 'input == ""', 'input' if its content didn't allow to create an Object
* @protected
*/
RequestHandler.prototype._stringToObject = function(input) {
if (input == null) return input;
if (input == "") return {};
if (input instanceof Object) return input; // Array go here => can't convert an Array into an Object
var obj = null;
try {
obj = JSON.parse(input);
if (u.isObject(obj)) {
return obj;
} else {
// input is not a stringified array
return input;
}
} catch(err) {
return input;
}
}
/**
* Safe generic getter of session response or request object
*
* @protected
*/
RequestHandler.prototype.getSession = function(session, req, property, deleteEntry) {
if (session.toLowerCase() === "response") {
return Session.getSession(this._application).getResponse(req, property, deleteEntry);
} else {
return Session.getSession(this._application).getRequest(req, property, deleteEntry);
}
}
/**
* Safe generic setter of session response or request object
*
* @protected
*/
RequestHandler.prototype.setSession = function(session, req, property, value) {
if (session.toLowerCase() === "response") {
return Session.getSession(this._application).setResponse(req, property, value);
} else {
return Session.getSession(this._application).setRequest(req, property, value);
}
}
/**
* Safe getter of session response object
*
* @protected
*/
RequestHandler.prototype.getSessionResponse = function(req, property, deleteEntry) {
return Session.getSession(this._application).getResponse(req, property, deleteEntry);
}
/**
* Safe setter of session response object
*
* @protected
*/
RequestHandler.prototype.setSessionResponse = function(req, property, value) {
Session.getSession(this._application).setResponse(req, property, value);
}
/**
* Safe getter of session request object
*
* @protected
*/
RequestHandler.prototype.getSessionRequest = function(req, property, deleteEntry) {
return Session.getSession(this._application).getRequest(req, property, deleteEntry);
}
/**
* Safe setter of session request object
*
* @protected
*/
RequestHandler.prototype.setSessionRequest = function(req, property, value) {
return Session.getSession(this._application).setRequest(req, property, value);
}
/**
* Create the objects to hold flash messages if needed.
*
* @protected
*/
RequestHandler.prototype.initMessages = function(req) {
Session.getSession(this._application).initMessages(req);
}
/**
* Saves a message in the Session message holder
*
* @protected
*/
RequestHandler.prototype.setMessage = function(req, type, message) {
Session.getSession(this._application).setMessage(req, type, message);
}
/**
* Saves an info message
*
* @protected
*/
RequestHandler.prototype.setInfoMessage = function(req, message) {
this.setMessage(req, "info", message);
}
/**
* Saves a warning message
*
* @protected
*/
RequestHandler.prototype.setWarningMessage = function(req, message) {
this.setMessage(req, "warning", message);
}
/**
* Saves an error message
*
* @protected
*/
RequestHandler.prototype.setErrorMessage = function(req, message) {
this.setMessage(req, "error", message);
}
/**
* Saves a success message
*
* @protected
*/
RequestHandler.prototype.setSuccessMessage = function(req, message) {
this.setMessage(req, "success", message);
}
module.exports = RequestHandler;