Source: session/RequestHandler.js

/** 
 * @version  1.2
 * @author Boris GBAHOUE
 * @file Generic request handler
*/

// =======================================================================
// 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('../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
 *
 * @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 module:session~RequestHandler
 */
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;