<commit_before>let CharacterClassInput = require('./CharacterClassInput'),
    NFAGraph = require('./NFAGraph');

class Regex {
	constructor() {
		this.source = "";
		this.sourceLength = 0;
		this.caseSensitive = false;
        this.ignoreWhitespace = true;
		this.accept = 0;
		this.index = -1;
		this.stack = [];
        this.nfa_graph = null;
        this.dfa_graph = null;
	}

    static parse(source) {
        let builder = new Regex();

        return builder.parse(source);
    }

    parse(regex, acceptState, cs, ignoreWhitespace) {
        acceptState = (acceptState === undefined) ? 0 : acceptState;
        cs = (cs === undefined) ? true : cs;
        ignoreWhitespace = (ignoreWhitespace === undefined) ? true : ignoreWhitespace;

        // reset current index into regex string
        this.source = regex;
        this.sourceLength = this.source.length;
        this.caseSensitive = cs;
        this.ignoreWhitespace = this.ignoreWhitespace;
        this.accept = acceptState;
        this.index = -1;
        this.stack = [];

        // prime the lexer
        this.advance();

        // parse the regex
        if (this.parseExpression() === false) {
            throw Error("Parse Error: index " + index + " of " + regex);
        }

        this.nfa_graph = (this.stack.length > 0) ? this.stack[0] : null;

        if (this.nfa_graph !== null) {
            let NFAGraphConverter = require('./NFAGraphConverter');
            let converter = new NFAGraphConverter();
            let result = converter.convert(this.nfa_graph);

            this.dfa_graph = converter.dfa_nodes;

            return result;
        }
        else {
            return null;
        }
    }

	advance() {
        // NOTE: we can't use hasText here since index starts at -1
		if (this.index < this.sourceLength) {
            // advance at least one character
            this.index++;

            // possibly skip whitespace
            if (this.ignoreWhitespace) {
                while (this.hasText && this.currentCharacter.match(/^\s/)) {
                    this.index++;
                }
            }
        }
	}

	get hasText() {
		return 0 <= this.index && this.index < this.sourceLength;
	}

    get currentCharacter() {
        return this.hasText ? this.source[this.index] : null;
    }

	isFirstInAndExpression() {
		var result = true;

        if (this.hasText) {
            switch (this.currentCharacter) {
                case '\0':
                case '|':
                case ')':
                case '*':
                case '+':
                case '?':
                case '^':
                case '$':
                    result = false;
                    break;
            }
        }
        else {
            result = false;
        }

        return result;
	}

	parseExpression() {
		var success = true;

        if (this.source.charAt(this.index) == "^") {
            // advance over '^'
            this.advance();

            // TODO: tag start-anchor
        }

        while (success && this.hasText) {
            success = this.parseOrExpression();

            if (this.index == this.sourceLength - 1 && this.source.charAt(this.index) == "$") {
                // advance over '$'
                this.advance();

                // TODO: tag end-anchor
            }
        }

        return success;
	}

	parseOrExpression() {
		var success = true;

        if (this.parseAndExpression()) {
            while (this.source.charAt(this.index) == "|") {
                // advance over '|'
                this.advance();

                if (this.parseAndExpression()) {
                    var rhs = this.stack.pop();
                    var lhs = this.stack[this.stack.length - 1];

                    lhs.orMachines(rhs);
                } else {
                    success = false;
                    break;
                }
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseAndExpression() {
		var success = true;

        if (this.isFirstInAndExpression()) {
            // get lhs
            this.parseFactor();

            // get remaining rhs's
            while (this.isFirstInAndExpression()) {
                if (this.parseFactor()) {
                    var rhs = this.stack.pop();
                    var lhs = this.stack[this.stack.length - 1];

                    lhs.andMachines(rhs);
                } else {
                    success = false;
                    break;
                }
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseFactor() {
		var success = true;

        if (this.parseTerm()) {
            var node = this.stack[this.stack.length - 1];

            switch (this.currentCharacter) {
                case '*':
                    // advance over '*'
                    this.advance();
                    node.kleeneClosure();
                    break;

                case '+':
                    // advance over '+'
                    this.advance();
                    node.positiveClosure();
                    break;

                case '?':
                    // advance over '?'
                    this.advance();
                    node.option();
                    break;

                default:
                    break;
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseTerm() {
		var success = true;

        if (this.currentCharacter == '(') {
            success = this.parseSubExpression();
        }
        else {
            var node = new NFAGraph(this.accept);
            var cc;

            switch (this.currentCharacter) {
                case '.':
                    // advance over '.'
                    this.advance();
                    cc = new CharacterClassInput();
                    cc.addInputs("\r", "\n");
                    cc.complement = true;
                    node.add(cc);
                    break;

                case '\\':
                    node.add(this.parseEscapedTerm());
                    break;

                case '[':
                    node.add(this.parseCharacterClass());
                    break;

                default:
                    cc = new CharacterClassInput();
                    cc.addInput(this.currentCharacter);
                    node.add(cc);

                    // advance over character
                    this.advance();
            }

            if (success) {
                this.stack.push(node);
            }
        }

        return success;
	}

	parseCharacterClass() {
		// advance over '['
        this.advance();

        var cci = new CharacterClassInput();
        var last = '\0';

        if (this.currentCharacter == '^') {
            // advance over '^'
            this.advance();

            // find complement of character class
            cci.complement = true;
        }

        if (this.currentCharacter == '-') {
            // advance over '-'
            this.advance();

            cci.addInput('-');
        }

        while (this.hasText && this.currentCharacter != ']') {
            switch (this.currentCharacter) {
                case '-':
                    // advance over '-'
                    this.advance();

                    if (last != '\0') {
                        cci.addInputRange(last, this.currentCharacter);

                        // advance over character
                        this.advance();

                        last = '\0';
                    }
                    else {
                        // error
                        break;
                    }
                    break;

                case '\\':
                    var input = this.parseEscapedTerm();

                    cci.addInputs(input.getCharacters());
                    break;

                default:
                    last = this.currentCharacter;
                    cci.addInput(last);

                    // advance over character
                    this.advance();
            }
        }

        if (this.currentCharacter == ']') {
            // advance over ']'
            this.advance();
        }
        else {
            //success = false;
        }

        return cci;
	}

	parseEscapedTerm() {
		var result = null;

        // advance over '\\'
        // NOTE: we can't use advance here since it will skip the next
        // character if it is whitespace
        this.index++;

        switch (this.currentCharacter) {
            case 'd':
                result = new CharacterClassInput();
                result.addInputRange("0", "9");
                result.alias = "\\d";
                break;

            case 'D':
                result = new CharacterClassInput();
                result.complement = true;
                result.addInputRange("0", "9");
                result.alias = "\\D";
                break;

            case 'f':
                result = new CharacterClassInput();
                result.addInput("\f");
                break;

            case 'n':
                result = new CharacterClassInput();
                result.addInput("\n");
                break;

            case 'r':
                result = new CharacterClassInput();
                result.addInput("\r");
                break;

            case 's':
                result = new CharacterClassInput();
                result.addInput(' ', '\f', '\t', '\r', '\n', '\u000B');
                result.alias = "\\s";
                break;

            case 'S':
                result = new CharacterClassInput();
                result.complement = true;
                result.addInput(' ', '\f', '\t', '\r', '\n', '\u000B');
                result.alias = "\\S";
                break;

            case 't':
                result = new CharacterClassInput();
                result.addInput("\t");
                break;

            case 'v':
                result = new CharacterClassInput();
                result.addInput("\u000B");
                break;

            case 'w':
                result = new CharacterClassInput();
                result.addInputRange("a", "z");
                result.addInputRange("A", "Z");
                result.addInputRange("0", "9");
                result.addInput("_");
                result.alias = "\\w";
                break;

            case 'W':
                result = new CharacterClassInput();
                result.setComplement(true);
                result.addInputRange("a", "z");
                result.addInputRange("A", "Z");
                result.addInputRange("0", "9");
                result.addInput("_");
                result.alias = "\\W";
                break;

            case 'x':
                var hi = 0;
                var lo = 0;

                this.index++;

                if (this.currentCharacter.match(/[0-9a-f]/i)) {
                    throw Error("hex escape sequences not implemented");
                }
                break;

            default:
                result = new CharacterClassInput();
                result.addInput(this.currentCharacter);
                break;
        }

        // advance over term
        this.advance();

        return result;
	}

	parseSubExpression() {
		var success = true;

        // advance over '('
        this.advance();

        if (this.parseOrExpression()) {
            if (this.currentCharacter == ')') {
                // advance over ')'
                this.advance();
            }
            else {
                success = false;
            }
        }
        else {
            success = false;
        }

        return success;
	}
}

module.exports = Regex;
<commit_msg>Fix typos<commit_after>let CharacterClassInput = require('./CharacterClassInput'),
    NFAGraph = require('./NFAGraph');

class Regex {
	constructor() {
		this.source = "";
		this.sourceLength = 0;
		this.caseSensitive = false;
        this.ignoreWhitespace = true;
		this.accept = 0;
		this.index = -1;
		this.stack = [];
        this.nfa_graph = null;
        this.dfa_graph = null;
	}

    static parse(source) {
        let builder = new Regex();

        return builder.parse(source);
    }

    parse(regex, acceptState, cs, ignoreWhitespace) {
        acceptState = (acceptState === undefined) ? 0 : acceptState;
        cs = (cs === undefined) ? true : cs;
        ignoreWhitespace = (ignoreWhitespace === undefined) ? true : ignoreWhitespace;

        // reset current index into regex string
        this.source = regex;
        this.sourceLength = this.source.length;
        this.caseSensitive = cs;
        this.ignoreWhitespace = this.ignoreWhitespace;
        this.accept = acceptState;
        this.index = -1;
        this.stack = [];

        // prime the lexer
        this.advance();

        // parse the regex
        if (this.parseExpression() === false) {
            throw Error("Parse Error: index " + index + " of " + regex);
        }

        this.nfa_graph = (this.stack.length > 0) ? this.stack[0] : null;

        if (this.nfa_graph !== null) {
            let NFAGraphConverter = require('./NFAGraphConverter');
            let converter = new NFAGraphConverter();
            let result = converter.convert(this.nfa_graph);

            this.dfa_graph = converter.dfa_nodes;

            return result;
        }
        else {
            return null;
        }
    }

	advance() {
        // NOTE: we can't use hasText here since index starts at -1
		if (this.index < this.sourceLength) {
            // advance at least one character
            this.index++;

            // possibly skip whitespace
            if (this.ignoreWhitespace) {
                while (this.hasText && this.currentCharacter.match(/^\s/)) {
                    this.index++;
                }
            }
        }
	}

	get hasText() {
		return 0 <= this.index && this.index < this.sourceLength;
	}

    get currentCharacter() {
        return this.hasText ? this.source[this.index] : null;
    }

	isFirstInAndExpression() {
		var result = true;

        if (this.hasText) {
            switch (this.currentCharacter) {
                case '\0':
                case '|':
                case ')':
                case '*':
                case '+':
                case '?':
                case '^':
                case '$':
                    result = false;
                    break;
            }
        }
        else {
            result = false;
        }

        return result;
	}

	parseExpression() {
		var success = true;

        if (this.source.charAt(this.index) == "^") {
            // advance over '^'
            this.advance();

            // TODO: tag start-anchor
        }

        while (success && this.hasText) {
            success = this.parseOrExpression();

            if (this.index == this.sourceLength - 1 && this.source.charAt(this.index) == "$") {
                // advance over '$'
                this.advance();

                // TODO: tag end-anchor
            }
        }

        return success;
	}

	parseOrExpression() {
		var success = true;

        if (this.parseAndExpression()) {
            while (this.source.charAt(this.index) == "|") {
                // advance over '|'
                this.advance();

                if (this.parseAndExpression()) {
                    var rhs = this.stack.pop();
                    var lhs = this.stack[this.stack.length - 1];

                    lhs.orMachines(rhs);
                } else {
                    success = false;
                    break;
                }
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseAndExpression() {
		var success = true;

        if (this.isFirstInAndExpression()) {
            // get lhs
            this.parseFactor();

            // get remaining rhs's
            while (this.isFirstInAndExpression()) {
                if (this.parseFactor()) {
                    var rhs = this.stack.pop();
                    var lhs = this.stack[this.stack.length - 1];

                    lhs.andMachines(rhs);
                } else {
                    success = false;
                    break;
                }
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseFactor() {
		var success = true;

        if (this.parseTerm()) {
            var node = this.stack[this.stack.length - 1];

            switch (this.currentCharacter) {
                case '*':
                    // advance over '*'
                    this.advance();
                    node.kleeneClosure();
                    break;

                case '+':
                    // advance over '+'
                    this.advance();
                    node.positiveClosure();
                    break;

                case '?':
                    // advance over '?'
                    this.advance();
                    node.option();
                    break;

                default:
                    break;
            }
        }
        else {
            success = false;
        }

        return success;
	}

	parseTerm() {
		var success = true;

        if (this.currentCharacter == '(') {
            success = this.parseSubExpression();
        }
        else {
            var node = new NFAGraph(this.accept);
            var cc;

            switch (this.currentCharacter) {
                case '.':
                    // advance over '.'
                    this.advance();
                    cc = new CharacterClassInput();
                    cc.addInput("\r", "\n");
                    cc.complement = true;
                    node.add(cc);
                    break;

                case '\\':
                    node.add(this.parseEscapedTerm());
                    break;

                case '[':
                    node.add(this.parseCharacterClass());
                    break;

                default:
                    cc = new CharacterClassInput();
                    cc.addInput(this.currentCharacter);
                    node.add(cc);

                    // advance over character
                    this.advance();
            }

            if (success) {
                this.stack.push(node);
            }
        }

        return success;
	}

	parseCharacterClass() {
		// advance over '['
        this.advance();

        var cci = new CharacterClassInput();
        var last = '\0';

        if (this.currentCharacter == '^') {
            // advance over '^'
            this.advance();

            // find complement of character class
            cci.complement = true;
        }

        if (this.currentCharacter == '-') {
            // advance over '-'
            this.advance();

            cci.addInput('-');
        }

        while (this.hasText && this.currentCharacter != ']') {
            switch (this.currentCharacter) {
                case '-':
                    // advance over '-'
                    this.advance();

                    if (last != '\0') {
                        cci.addInputRange(last, this.currentCharacter);

                        // advance over character
                        this.advance();

                        last = '\0';
                    }
                    else {
                        // error
                        break;
                    }
                    break;

                case '\\':
                    var input = this.parseEscapedTerm();

                    cci.addInput(input.getCharacters());
                    break;

                default:
                    last = this.currentCharacter;
                    cci.addInput(last);

                    // advance over character
                    this.advance();
            }
        }

        if (this.currentCharacter == ']') {
            // advance over ']'
            this.advance();
        }
        else {
            //success = false;
        }

        return cci;
	}

	parseEscapedTerm() {
		var result = null;

        // advance over '\\'
        // NOTE: we can't use advance here since it will skip the next
        // character if it is whitespace
        this.index++;

        switch (this.currentCharacter) {
            case 'd':
                result = new CharacterClassInput();
                result.addInputRange("0", "9");
                result.alias = "\\d";
                break;

            case 'D':
                result = new CharacterClassInput();
                result.complement = true;
                result.addInputRange("0", "9");
                result.alias = "\\D";
                break;

            case 'f':
                result = new CharacterClassInput();
                result.addInput("\f");
                break;

            case 'n':
                result = new CharacterClassInput();
                result.addInput("\n");
                break;

            case 'r':
                result = new CharacterClassInput();
                result.addInput("\r");
                break;

            case 's':
                result = new CharacterClassInput();
                result.addInput(' ', '\f', '\t', '\r', '\n', '\u000B');
                result.alias = "\\s";
                break;

            case 'S':
                result = new CharacterClassInput();
                result.complement = true;
                result.addInput(' ', '\f', '\t', '\r', '\n', '\u000B');
                result.alias = "\\S";
                break;

            case 't':
                result = new CharacterClassInput();
                result.addInput("\t");
                break;

            case 'v':
                result = new CharacterClassInput();
                result.addInput("\u000B");
                break;

            case 'w':
                result = new CharacterClassInput();
                result.addInputRange("a", "z");
                result.addInputRange("A", "Z");
                result.addInputRange("0", "9");
                result.addInput("_");
                result.alias = "\\w";
                break;

            case 'W':
                result = new CharacterClassInput();
                result.setComplement(true);
                result.addInputRange("a", "z");
                result.addInputRange("A", "Z");
                result.addInputRange("0", "9");
                result.addInput("_");
                result.alias = "\\W";
                break;

            case 'x':
                var hi = 0;
                var lo = 0;

                this.index++;

                if (this.currentCharacter.match(/[0-9a-f]/i)) {
                    throw Error("hex escape sequences not implemented");
                }
                break;

            default:
                result = new CharacterClassInput();
                result.addInput(this.currentCharacter);
                break;
        }

        // advance over term
        this.advance();

        return result;
	}

	parseSubExpression() {
		var success = true;

        // advance over '('
        this.advance();

        if (this.parseOrExpression()) {
            if (this.currentCharacter == ')') {
                // advance over ')'
                this.advance();
            }
            else {
                success = false;
            }
        }
        else {
            success = false;
        }

        return success;
	}
}

module.exports = Regex;
<|endoftext|>


<commit_before>var Instance        = require("./Instance").Instance;
var Singleton       = require("./Singleton");
var OneAssociation  = require("./Associations/One");
var ManyAssociation = require("./Associations/Many");
var ChainFind       = require("./ChainFind");
var LazyLoad        = require("./LazyLoad");

exports.Model = Model;

function Model(opts) {
	opts = opts || {};
	opts.id = opts.id;

	var one_associations = [];
	var many_associations = [];
	var association_properties = [];
	var model_fields = null;

	var createInstance = function (data, inst_opts, cb) {
		if (!inst_opts) {
			inst_opts = {};
		}
		var instance = new Instance({
			id                     : opts.id,
			is_new                 : inst_opts.is_new || false,
			data                   : data,
			autoSave               : inst_opts.autoSave || false,
			extra                  : inst_opts.extra,
			extra_info             : inst_opts.extra_info,
			model                  : model,
			driver                 : opts.driver,
			table                  : opts.table,
			properties             : opts.properties,
			hooks                  : opts.hooks,
			methods                : opts.methods,
			validations            : opts.validations,
			association_properties : association_properties
		});
		if (model_fields !== null) {
			LazyLoad.extend(instance, model, opts.properties);
		}
		OneAssociation.extend(model, instance, opts.driver, one_associations, {
			autoFetch      : inst_opts.autoFetch || false,
			autoFetchLimit : inst_opts.autoFetchLimit,
			cascadeRemove  : inst_opts.cascadeRemove
		}, function () {
			ManyAssociation.extend(model, instance, opts.driver, many_associations, {
				autoFetch      : inst_opts.autoFetch || false,
				autoFetchLimit : inst_opts.autoFetchLimit,
				cascadeRemove  : inst_opts.cascadeRemove
			}, function () {
				if (typeof cb == "function") {
					return cb(instance);
				}
			});
		});
		return instance;
	};

	var model = function (data) {
		var instance;
		if (typeof data == "number") {
			var data2 = {};
			data2[opts.id] = data;

			return createInstance(data2);
		} else if (typeof data == "undefined") {
			data = {};
		}

		return createInstance(data, {
			is_new        : !data.hasOwnProperty(opts.id),
			autoSave      : opts.autoSave,
			cascadeRemove : opts.cascadeRemove
		});
	};

	for (var k in opts.properties) {
		if (opts.properties[k].lazyload === true) {
			model_fields = [ opts.id ];
			for (k in opts.properties) {
				if (opts.properties[k].lazyload !== true) {
					model_fields.push(k);
				}
			}
			break;
		}
	}

	OneAssociation.prepare(model, one_associations, association_properties);
	ManyAssociation.prepare(model, many_associations);

	model.settings = opts.settings;

	model.drop = function (cb) {
		if (arguments.length === 0) {
			cb = function () {};
		}
		if (typeof opts.driver.drop == "function") {
			opts.driver.drop({
				id                : opts.id,
				table             : opts.table,
				properties        : opts.properties,
				one_associations  : one_associations,
				many_associations : many_associations
			}, cb);

			return this;
		}

		return cb(new Error("Driver does not support Model.drop()"));
	};

	model.sync = function (cb) {
		if (arguments.length === 0) {
			cb = function () {};
		}
		if (typeof opts.driver.sync == "function") {
			opts.driver.sync({
				id                : opts.id,
				table             : opts.table,
				properties        : opts.properties,
				one_associations  : one_associations,
				many_associations : many_associations
			}, cb);

			return this;
		}

		return cb(new Error("Driver does not support Model.sync()"));
	};

	model.get = function (id, options, cb) {
		var conditions = {};
		conditions[opts.id] = id;

		if (typeof options == "function") {
			cb = options;
			options = {};
		} else {
			options = options || {};
		}

		if (typeof cb != "function") {
			throw new Error("Missing Model.get() callback");
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		opts.driver.find(model_fields, opts.table, conditions, { limit: 1 }, function (err, data) {
			if (err) {
				return cb(err);
			}
			if (data.length === 0) {
				return cb(new Error("Not found"));
			}
			Singleton.get(opts.driver.uid + "/" + opts.table + "/" + id, {
				cache      : options.cache,
				save_check : opts.settings.get("instance.cacheSaveCheck")
			}, function (cb) {
				return createInstance(data[0], {
					autoSave       : opts.autoSave,
					autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
					autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit,
					cascadeRemove  : options.cascadeRemove || opts.cascadeRemove
				}, cb);
			}, function (instance) {
				return cb(null, instance);
			});
		});

		return this;
	};

	model.find = function () {
		var options    = {};
		var conditions = null;
		var cb         = null;
		var order      = null;
		var merge      = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "number":
					options.limit = arguments[i];
					break;
				case "object":
					if (Array.isArray(arguments[i])) {
						if (arguments[i].length > 0) {
							order = arguments[i];
						}
					} else {
						if (conditions === null) {
							conditions = arguments[i];
						} else {
							options = arguments[i];
							if (options.hasOwnProperty("__merge")) {
								merge = options.__merge;
								delete options.__merge;
							}
							if (options.hasOwnProperty("order")) {
								order = options.order;
								delete options.order;
							}
						}
					}
					break;
				case "function":
					cb = arguments[i];
					break;
				case "string":
					order = [ arguments[i] ];
					break;
			}
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		var chain = new ChainFind({
			only       : options.only || model_fields,
			id         : opts.id,
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions,
			limit      : options.limit,
			order      : order,
			merge      : merge,
			offset     : options.offset,
			newInstance: function (data, cb) {
				Singleton.get(opts.driver.uid + "/" + opts.table + (merge ? "+" + merge.from.table : "") + "/" + data[opts.id], {
					cache      : options.cache,
					save_check : opts.settings.get("instance.cacheSaveCheck")
				}, function (cb) {
					return createInstance(data, {
						autoSave       : opts.autoSave,
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit,
						cascadeRemove  : options.cascadeRemove || opts.cascadeRemove,
						extra          : options.extra,
						extra_info     : options.extra_info
					}, cb);
				}, function (instance) {
					return cb(null, instance);
				});
			}
		});
		if (cb === null) {
			return chain;
		}

		chain.run(cb);

		return this;
	};

	model.count = function () {
		var conditions = null;
		var cb         = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "object":
					conditions = arguments[i];
					break;
				case "function":
					cb = arguments[i];
					break;
			}
		}

		if (typeof cb != "function") {
			throw new Error("Missing Model.count() callback");
		}

		opts.driver.count(opts.table, conditions, {}, function (err, data) {
			if (err || data.length === 0) {
				return cb(err);
			}
			return cb(null, data[0].c);
		});
		return this;
	};

	model.aggregate = function (conditions) {
		return new require("./AggregateFunctions")({
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions || {}
		});
	};

	model.exists = function (id, cb) {
		if (typeof cb != "function") {
			throw new Error("Missing Model.exists() callback");
		}

		var conditions = {};
		conditions[opts.id] = id;

		opts.driver.count(opts.table, conditions, {}, function (err, data) {
			if (err || data.length === 0) {
				return cb(err);
			}
			return cb(null, data[0].c > 0);
		});
		return this;
	};

	model.create = function () {
		var Instances = [];
		var options = {};
		var cb = null, idx = 0;
		var createNext = function () {
			if (idx >= Instances.length) {
				return cb(null, Instances);
			}

			Instances[idx] = createInstance(Instances[idx], {
				is_new    : true,
				autoSave  : opts.autoSave,
				autoFetch : false
			});
			Instances[idx].save(function (err) {
				if (err) {
					err.index = idx;
					err.instance = Instances[idx];

					return cb(err);
				}

				idx += 1;
				createNext();
			});
		};

		for (var i = 0; i < arguments.length; i++) {
			if (Array.isArray(arguments[i])) {
				Instances = Instances.concat(arguments[i]);
				continue;
			}

			switch (typeof arguments[i]) {
				case "object":
					options = arguments[i];
					break;
				case "function":
					cb = arguments[i];
					break;
			}
		}

		createNext();

		return this;
	};

	model.clear = function (cb) {
		opts.driver.clear(opts.table, function (err) {
			if (typeof cb == "function") cb(err);
		});

		return this;
	};

	Object.defineProperty(model, "table", {
		value: opts.table,
		enumerable: false
	});
	Object.defineProperty(model, "id", {
		value: opts.id,
		enumerable: false
	});

	return model;
}
<commit_msg>Avoids loosing previously set limit (if set) on Model.fin() (#93)<commit_after>var Instance        = require("./Instance").Instance;
var Singleton       = require("./Singleton");
var OneAssociation  = require("./Associations/One");
var ManyAssociation = require("./Associations/Many");
var ChainFind       = require("./ChainFind");
var LazyLoad        = require("./LazyLoad");

exports.Model = Model;

function Model(opts) {
	opts = opts || {};
	opts.id = opts.id;

	var one_associations = [];
	var many_associations = [];
	var association_properties = [];
	var model_fields = null;

	var createInstance = function (data, inst_opts, cb) {
		if (!inst_opts) {
			inst_opts = {};
		}
		var instance = new Instance({
			id                     : opts.id,
			is_new                 : inst_opts.is_new || false,
			data                   : data,
			autoSave               : inst_opts.autoSave || false,
			extra                  : inst_opts.extra,
			extra_info             : inst_opts.extra_info,
			model                  : model,
			driver                 : opts.driver,
			table                  : opts.table,
			properties             : opts.properties,
			hooks                  : opts.hooks,
			methods                : opts.methods,
			validations            : opts.validations,
			association_properties : association_properties
		});
		if (model_fields !== null) {
			LazyLoad.extend(instance, model, opts.properties);
		}
		OneAssociation.extend(model, instance, opts.driver, one_associations, {
			autoFetch      : inst_opts.autoFetch || false,
			autoFetchLimit : inst_opts.autoFetchLimit,
			cascadeRemove  : inst_opts.cascadeRemove
		}, function () {
			ManyAssociation.extend(model, instance, opts.driver, many_associations, {
				autoFetch      : inst_opts.autoFetch || false,
				autoFetchLimit : inst_opts.autoFetchLimit,
				cascadeRemove  : inst_opts.cascadeRemove
			}, function () {
				if (typeof cb == "function") {
					return cb(instance);
				}
			});
		});
		return instance;
	};

	var model = function (data) {
		var instance;
		if (typeof data == "number") {
			var data2 = {};
			data2[opts.id] = data;

			return createInstance(data2);
		} else if (typeof data == "undefined") {
			data = {};
		}

		return createInstance(data, {
			is_new        : !data.hasOwnProperty(opts.id),
			autoSave      : opts.autoSave,
			cascadeRemove : opts.cascadeRemove
		});
	};

	for (var k in opts.properties) {
		if (opts.properties[k].lazyload === true) {
			model_fields = [ opts.id ];
			for (k in opts.properties) {
				if (opts.properties[k].lazyload !== true) {
					model_fields.push(k);
				}
			}
			break;
		}
	}

	OneAssociation.prepare(model, one_associations, association_properties);
	ManyAssociation.prepare(model, many_associations);

	model.settings = opts.settings;

	model.drop = function (cb) {
		if (arguments.length === 0) {
			cb = function () {};
		}
		if (typeof opts.driver.drop == "function") {
			opts.driver.drop({
				id                : opts.id,
				table             : opts.table,
				properties        : opts.properties,
				one_associations  : one_associations,
				many_associations : many_associations
			}, cb);

			return this;
		}

		return cb(new Error("Driver does not support Model.drop()"));
	};

	model.sync = function (cb) {
		if (arguments.length === 0) {
			cb = function () {};
		}
		if (typeof opts.driver.sync == "function") {
			opts.driver.sync({
				id                : opts.id,
				table             : opts.table,
				properties        : opts.properties,
				one_associations  : one_associations,
				many_associations : many_associations
			}, cb);

			return this;
		}

		return cb(new Error("Driver does not support Model.sync()"));
	};

	model.get = function (id, options, cb) {
		var conditions = {};
		conditions[opts.id] = id;

		if (typeof options == "function") {
			cb = options;
			options = {};
		} else {
			options = options || {};
		}

		if (typeof cb != "function") {
			throw new Error("Missing Model.get() callback");
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		opts.driver.find(model_fields, opts.table, conditions, { limit: 1 }, function (err, data) {
			if (err) {
				return cb(err);
			}
			if (data.length === 0) {
				return cb(new Error("Not found"));
			}
			Singleton.get(opts.driver.uid + "/" + opts.table + "/" + id, {
				cache      : options.cache,
				save_check : opts.settings.get("instance.cacheSaveCheck")
			}, function (cb) {
				return createInstance(data[0], {
					autoSave       : opts.autoSave,
					autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
					autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit,
					cascadeRemove  : options.cascadeRemove || opts.cascadeRemove
				}, cb);
			}, function (instance) {
				return cb(null, instance);
			});
		});

		return this;
	};

	model.find = function () {
		var options    = {};
		var conditions = null;
		var cb         = null;
		var order      = null;
		var merge      = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "number":
					options.limit = arguments[i];
					break;
				case "object":
					if (Array.isArray(arguments[i])) {
						if (arguments[i].length > 0) {
							order = arguments[i];
						}
					} else {
						if (conditions === null) {
							conditions = arguments[i];
						} else {
							if (options.hasOwnProperty("limit")) {
								arguments[i].limit = options.limit;
							}
							options = arguments[i];

							if (options.hasOwnProperty("__merge")) {
								merge = options.__merge;
								delete options.__merge;
							}
							if (options.hasOwnProperty("order")) {
								order = options.order;
								delete options.order;
							}
						}
					}
					break;
				case "function":
					cb = arguments[i];
					break;
				case "string":
					order = [ arguments[i] ];
					break;
			}
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		var chain = new ChainFind({
			only       : options.only || model_fields,
			id         : opts.id,
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions,
			limit      : options.limit,
			order      : order,
			merge      : merge,
			offset     : options.offset,
			newInstance: function (data, cb) {
				Singleton.get(opts.driver.uid + "/" + opts.table + (merge ? "+" + merge.from.table : "") + "/" + data[opts.id], {
					cache      : options.cache,
					save_check : opts.settings.get("instance.cacheSaveCheck")
				}, function (cb) {
					return createInstance(data, {
						autoSave       : opts.autoSave,
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit,
						cascadeRemove  : options.cascadeRemove || opts.cascadeRemove,
						extra          : options.extra,
						extra_info     : options.extra_info
					}, cb);
				}, function (instance) {
					return cb(null, instance);
				});
			}
		});
		if (cb === null) {
			return chain;
		}

		chain.run(cb);

		return this;
	};

	model.count = function () {
		var conditions = null;
		var cb         = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "object":
					conditions = arguments[i];
					break;
				case "function":
					cb = arguments[i];
					break;
			}
		}

		if (typeof cb != "function") {
			throw new Error("Missing Model.count() callback");
		}

		opts.driver.count(opts.table, conditions, {}, function (err, data) {
			if (err || data.length === 0) {
				return cb(err);
			}
			return cb(null, data[0].c);
		});
		return this;
	};

	model.aggregate = function (conditions) {
		return new require("./AggregateFunctions")({
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions || {}
		});
	};

	model.exists = function (id, cb) {
		if (typeof cb != "function") {
			throw new Error("Missing Model.exists() callback");
		}

		var conditions = {};
		conditions[opts.id] = id;

		opts.driver.count(opts.table, conditions, {}, function (err, data) {
			if (err || data.length === 0) {
				return cb(err);
			}
			return cb(null, data[0].c > 0);
		});
		return this;
	};

	model.create = function () {
		var Instances = [];
		var options = {};
		var cb = null, idx = 0;
		var createNext = function () {
			if (idx >= Instances.length) {
				return cb(null, Instances);
			}

			Instances[idx] = createInstance(Instances[idx], {
				is_new    : true,
				autoSave  : opts.autoSave,
				autoFetch : false
			});
			Instances[idx].save(function (err) {
				if (err) {
					err.index = idx;
					err.instance = Instances[idx];

					return cb(err);
				}

				idx += 1;
				createNext();
			});
		};

		for (var i = 0; i < arguments.length; i++) {
			if (Array.isArray(arguments[i])) {
				Instances = Instances.concat(arguments[i]);
				continue;
			}

			switch (typeof arguments[i]) {
				case "object":
					options = arguments[i];
					break;
				case "function":
					cb = arguments[i];
					break;
			}
		}

		createNext();

		return this;
	};

	model.clear = function (cb) {
		opts.driver.clear(opts.table, function (err) {
			if (typeof cb == "function") cb(err);
		});

		return this;
	};

	Object.defineProperty(model, "table", {
		value: opts.table,
		enumerable: false
	});
	Object.defineProperty(model, "id", {
		value: opts.id,
		enumerable: false
	});

	return model;
}
<|endoftext|>


<commit_before>var Instance        = require("./Instance").Instance;
var Singleton       = require("./Singleton");
var OneAssociation  = require("./Associations/One");
var ManyAssociation = require("./Associations/Many");
var ChainFind       = require("./ChainFind");

exports.Model = Model;

function Model(opts) {
	opts = opts || {};
	opts.id = opts.id || "id";

	var one_associations = [];
	var many_associations = [];
	var association_properties = [];

	var model = function (data) {
		return new Instance({
			is_new                 : true,
			data                   : data,
			autoSave               : opts.autoSave,
			driver                 : opts.driver,
			table                  : opts.table,
			properties             : opts.properties,
			hooks                  : opts.hooks,
			methods                : opts.methods,
			validations            : opts.validations,
			association_properties : association_properties
		});
	};

	OneAssociation.prepare(model, one_associations, association_properties);
	ManyAssociation.prepare(model, many_associations);

	model.get = function (id, options, cb) {
		var conditions = {};
		conditions[opts.id] = id;

		if (typeof options == "function") {
			cb = options;
			options = {};
		} else {
			options = options || {};
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		opts.driver.find(null, opts.table, conditions, { limit: 1 }, function (err, data) {
			if (err) {
				return cb(err);
			}
			if (data.length === 0) {
				return cb(new Error("Not found"));
			}
			Singleton.get(opts.table + "/" + id, { cache: options.cache }, function (cb) {
				var instance = new Instance({
					data                   : data[0],
					autoSave               : opts.autoSave,
					driver                 : opts.driver,
					table                  : opts.table,
					properties             : opts.properties,
					hooks                  : opts.hooks,
					methods                : opts.methods,
					validations            : opts.validations,
					association_properties : association_properties
				});
				OneAssociation.extend(instance, opts.driver, one_associations, {
					autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
					autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit
				}, function () {
					ManyAssociation.extend(instance, opts.driver, many_associations, {
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit
					}, function () {
						return cb(instance);
					});
				});
			}, function (instance) {
				return cb(null, instance);
			});
		});

		return this;
	};

	model.find = function () {
		var options    = {};
		var conditions = null;
		var cb         = null;
		var limit      = null;
		var order      = null;
		var merge      = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "number":
					limit = arguments[i];
					break;
				case "object":
					if (Array.isArray(arguments[i])) {
						if (order.length > 0) {
							order = arguments[i];
						}
					} else {
						if (conditions === null) {
							conditions = arguments[i];
						} else {
							options = arguments[i];
							if (options.hasOwnProperty("__merge")) {
								merge = options.__merge;
								delete options.__merge;
							}
						}
					}
					break;
				case "function":
					cb = arguments[i];
					break;
				case "string":
					order = [ arguments[i] ];
					break;
			}
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		var chain = new ChainFind({
			only       : options.only || null,
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions,
			limit      : limit,
			order      : order,
			merge      : merge,
			offset     : options.offset,
			newInstance: function (data, cb) {
				Singleton.get(opts.table + (merge ? "+" + merge.from.table : "") + "/" + data[opts.id],
				              { cache: options.cache },
				function (cb) {
					var instance = new Instance({
						data                   : data,
						extra                  : options.extra,
						extra_info             : options.extra_info,
						autoSave               : opts.autoSave,
						driver                 : opts.driver,
						table                  : opts.table,
						properties             : opts.properties,
						hooks                  : opts.hooks,
						methods                : opts.methods,
						validations            : opts.validations,
						association_properties : association_properties
					});
					OneAssociation.extend(instance, opts.driver, one_associations, {
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit
					}, function () {
						ManyAssociation.extend(instance, opts.driver, many_associations, {
							autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
							autoFetchLimit : options.autoFetchLimit
						}, function () {
							return cb(instance);
						});
					});
				}, function (instance) {
					return cb(null, instance);
				});
			}
		});
		if (cb === null) {
			return chain;
		}

		chain.run(cb);

		return this;
	};

	model.clear = function (cb) {
		opts.driver.clear(opts.table, function (err) {
			if (typeof cb == "function") cb(err);
		});

		return this;
	};

	Object.defineProperty(model, "table", {
		value: opts.table,
		enumerable: false
	});
	Object.defineProperty(model, "id", {
		value: opts.id,
		enumerable: false
	});

	return model;
}
<commit_msg>Adds possibility to create a Model and use associations without fetching instances (#15)<commit_after>var Instance        = require("./Instance").Instance;
var Singleton       = require("./Singleton");
var OneAssociation  = require("./Associations/One");
var ManyAssociation = require("./Associations/Many");
var ChainFind       = require("./ChainFind");

exports.Model = Model;

function Model(opts) {
	opts = opts || {};
	opts.id = opts.id || "id";

	var one_associations = [];
	var many_associations = [];
	var association_properties = [];

	var model = function (data) {
		if (typeof data == "number") {
			var data2 = {};
			data2[opts.id] = data;

			var instance = new Instance({
				data                   : data2,
				autoSave               : false,
				driver                 : opts.driver,
				table                  : opts.table,
				properties             : opts.properties,
				hooks                  : opts.hooks,
				methods                : opts.methods,
				validations            : opts.validations,
				association_properties : association_properties
			});
			OneAssociation.extend(instance, opts.driver, one_associations, {
				autoFetch      : false
			}, function () {
				ManyAssociation.extend(instance, opts.driver, many_associations, {
					autoFetch      : false
				}, function () {
					// ..
				});
			});
			return instance;
		}
		return new Instance({
			is_new                 : !data.hasOwnProperty(opts.id),
			data                   : data,
			autoSave               : opts.autoSave,
			driver                 : opts.driver,
			table                  : opts.table,
			properties             : opts.properties,
			hooks                  : opts.hooks,
			methods                : opts.methods,
			validations            : opts.validations,
			association_properties : association_properties
		});
	};

	OneAssociation.prepare(model, one_associations, association_properties);
	ManyAssociation.prepare(model, many_associations);

	model.get = function (id, options, cb) {
		var conditions = {};
		conditions[opts.id] = id;

		if (typeof options == "function") {
			cb = options;
			options = {};
		} else {
			options = options || {};
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		opts.driver.find(null, opts.table, conditions, { limit: 1 }, function (err, data) {
			if (err) {
				return cb(err);
			}
			if (data.length === 0) {
				return cb(new Error("Not found"));
			}
			Singleton.get(opts.table + "/" + id, { cache: options.cache }, function (cb) {
				var instance = new Instance({
					data                   : data[0],
					autoSave               : opts.autoSave,
					driver                 : opts.driver,
					table                  : opts.table,
					properties             : opts.properties,
					hooks                  : opts.hooks,
					methods                : opts.methods,
					validations            : opts.validations,
					association_properties : association_properties
				});
				OneAssociation.extend(instance, opts.driver, one_associations, {
					autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
					autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit
				}, function () {
					ManyAssociation.extend(instance, opts.driver, many_associations, {
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit || opts.autoFetchLimit
					}, function () {
						return cb(instance);
					});
				});
			}, function (instance) {
				return cb(null, instance);
			});
		});

		return this;
	};

	model.find = function () {
		var options    = {};
		var conditions = null;
		var cb         = null;
		var limit      = null;
		var order      = null;
		var merge      = null;

		for (var i = 0; i < arguments.length; i++) {
			switch (typeof arguments[i]) {
				case "number":
					limit = arguments[i];
					break;
				case "object":
					if (Array.isArray(arguments[i])) {
						if (order.length > 0) {
							order = arguments[i];
						}
					} else {
						if (conditions === null) {
							conditions = arguments[i];
						} else {
							options = arguments[i];
							if (options.hasOwnProperty("__merge")) {
								merge = options.__merge;
								delete options.__merge;
							}
						}
					}
					break;
				case "function":
					cb = arguments[i];
					break;
				case "string":
					order = [ arguments[i] ];
					break;
			}
		}

		if (!options.hasOwnProperty("cache")) {
			options.cache = opts.cache;
		}

		var chain = new ChainFind({
			only       : options.only || null,
			table      : opts.table,
			driver     : opts.driver,
			conditions : conditions,
			limit      : limit,
			order      : order,
			merge      : merge,
			offset     : options.offset,
			newInstance: function (data, cb) {
				Singleton.get(opts.table + (merge ? "+" + merge.from.table : "") + "/" + data[opts.id],
				              { cache: options.cache },
				function (cb) {
					var instance = new Instance({
						data                   : data,
						extra                  : options.extra,
						extra_info             : options.extra_info,
						autoSave               : opts.autoSave,
						driver                 : opts.driver,
						table                  : opts.table,
						properties             : opts.properties,
						hooks                  : opts.hooks,
						methods                : opts.methods,
						validations            : opts.validations,
						association_properties : association_properties
					});
					OneAssociation.extend(instance, opts.driver, one_associations, {
						autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
						autoFetchLimit : options.autoFetchLimit
					}, function () {
						ManyAssociation.extend(instance, opts.driver, many_associations, {
							autoFetch      : (options.autoFetchLimit === 0 ? false : opts.autoFetch),
							autoFetchLimit : options.autoFetchLimit
						}, function () {
							return cb(instance);
						});
					});
				}, function (instance) {
					return cb(null, instance);
				});
			}
		});
		if (cb === null) {
			return chain;
		}

		chain.run(cb);

		return this;
	};

	model.clear = function (cb) {
		opts.driver.clear(opts.table, function (err) {
			if (typeof cb == "function") cb(err);
		});

		return this;
	};

	Object.defineProperty(model, "table", {
		value: opts.table,
		enumerable: false
	});
	Object.defineProperty(model, "id", {
		value: opts.id,
		enumerable: false
	});

	return model;
}
<|endoftext|>


<commit_before>var CHECK_STORED_DATA = true;

var cassandraTypes = require('cassandra-driver').types;
var Joi = require('joi');
var Q = require("q");

var IoC = require('electrolyte');
var log = IoC.create('logger').createLogger("Model");

var Model = function (parameters) {
    this._name = parameters.name;
    this._schema = parameters.schema;
    this._joiSchema = parameters.joiSchema;
    this._queryBuilder = parameters.queryBuilder;
    this._preWriteTransform = parameters.preWriteTransform;
    log.info("MODEL READY!");
};

Model.prototype.getName = function () {
    return this._name;
};

Model.prototype.save = function (o) {
    if (this._preWriteTransform) {
        o = this._preWriteTransform(o);
    }
    o = tranformCommon(o);
    this.validate(o);
    var cql = this._queryBuilder().insert(o).into(this._name);
    var res = this.exec(cql);

    if (CHECK_STORED_DATA) {
        var self = this;
        res
            .then(function () {
                return self.find({timeuuid: o.timeuuid});
            })
            .then(function (v) {
                assert.deepEqual(v[0], o);
            })
            .catch(log.error.bind(log));
    }

    return res.then(function (r) {
        r.insertedObject = o;
        return r;
    });
};

Model.prototype.find = function (params) {
    var cql = this._queryBuilder().select('*').from(this._name);
    var i = 0;
    Object.keys(params).forEach(function (k) {
        if (i == 0) {
            cql.where(k, "=", params[k]);
        } else {
            cql.andWhere(k, "=", params[k]);
        }
        i++;
    });
    var self = this;
    return this.exec(cql)
        .then(function (res) {
            return res.rows
                .map(mapToJs)
                .map(mapFieldNamesToJs.bind(null, self._schema))
                .map(tranformCommon);
        });
};

var mapToJs = function (obj) {
    if (obj instanceof cassandraTypes.Long) {
        return parseInt(obj.toString());
    } else if (obj instanceof cassandraTypes.Uuid) {
        return obj.toString();
    } else if (obj instanceof Date) {
        return obj.getTime();
    } else if (obj instanceof Buffer) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(function (v) {
            return mapToJs(v);
        });
    } else if (obj === null) {
        return obj;
    } else if (typeof obj === "object") {
        var res = {};
        Object.keys(obj).forEach(function (k) {
            res[k] = mapToJs(obj[k]);
        });
        return res;
    } else {
        return obj;
    }
};

var mapFieldNamesToJs = function (rootSchema, obj) {

    var meta = rootSchema.meta[rootSchema.meta.length - 1];
    if (Array.isArray(obj)) {
        return obj.map(mapFieldNamesToJs.bind(null, rootSchema.items[0]));
    } else if (meta.mappings.indexOf("map") >= 0) {
        var r = {};
        Object.keys(obj).forEach(function (k) {
            r[k] = mapFieldNamesToJs(meta.vMeta, obj[k]);
        });
        return r;
    } else if (meta.mappings.indexOf("nested") >= 0 || meta.mappings.indexOf("schema") >= 0) {
        var res = {};
        Object.keys(rootSchema.children).forEach(function (schemaFieldName) {
            var field = rootSchema.children[schemaFieldName];
            //var fieldMeta = field.meta[field.meta.length - 1];
            if (obj[schemaFieldName] != null) {
                res[schemaFieldName] = mapFieldNamesToJs(field, obj[schemaFieldName]);
            } else {
                res[schemaFieldName] = null;
            }
        });
        return res;
    } else {
        return obj;
    }
};

Model.prototype.validate = function (obj) {
    Joi.assert(obj, this._joiSchema);
};

Model.prototype.exec = function (cql) {
    return Q.nbind(cql.exec, cql, {prepare: true})();
};

function tranformCommon(obj) {
    if (obj == null) {
        return null;
    }

    if (typeof obj !== "object") {
        return obj;
    }

    if (obj instanceof Buffer) {
        return obj;
    }

    return Object.keys(obj).reduce(function (acc, k) {
        var v = obj[k];
        if (Array.isArray(v)) {
            if (v.length == 0) {
                acc[k] = null;
            } else {
                acc[k] = v.map(tranformCommon);
            }
        } else {
            if (typeof v === "object") {
                acc[k] = tranformCommon(v);
            } else {
                acc[k] = v;
            }
        }
        return acc;
    }, {});
}

module.exports = Model;<commit_msg>CHECK_STORED_DATA<commit_after>var assert = require('assert');

var CHECK_STORED_DATA = true;

var cassandraTypes = require('cassandra-driver').types;
var Joi = require('joi');
var Q = require("q");

var IoC = require('electrolyte');
var log = IoC.create('logger').createLogger("Model");

var Model = function (parameters) {
    this._name = parameters.name;
    this._schema = parameters.schema;
    this._joiSchema = parameters.joiSchema;
    this._queryBuilder = parameters.queryBuilder;
    this._preWriteTransform = parameters.preWriteTransform;
    log.info("MODEL READY!");
};

Model.prototype.getName = function () {
    return this._name;
};

Model.prototype.save = function (o) {
    if (this._preWriteTransform) {
        o = this._preWriteTransform(o);
    }
    o = tranformCommon(o);
    this.validate(o);
    var cql = this._queryBuilder().insert(o).into(this._name);
    var res = this.exec(cql);

    if (CHECK_STORED_DATA) {
        var self = this;
        res
            .then(function () {
                return self.find({timeuuid: o.timeuuid});
            })
            .then(function (v) {
                assert.deepEqual(v[0], o);
            })
            .catch(log.error.bind(log));
    }

    return res.then(function (r) {
        r.insertedObject = o;
        return r;
    });
};

Model.prototype.find = function (params) {
    var cql = this._queryBuilder().select('*').from(this._name);
    var i = 0;
    Object.keys(params).forEach(function (k) {
        if (i == 0) {
            cql.where(k, "=", params[k]);
        } else {
            cql.andWhere(k, "=", params[k]);
        }
        i++;
    });
    var self = this;
    return this.exec(cql)
        .then(function (res) {
            return res.rows
                .map(mapToJs)
                .map(mapFieldNamesToJs.bind(null, self._schema))
                .map(tranformCommon);
        });
};

var mapToJs = function (obj) {
    if (obj instanceof cassandraTypes.Long) {
        return parseInt(obj.toString());
    } else if (obj instanceof cassandraTypes.Uuid) {
        return obj.toString();
    } else if (obj instanceof Date) {
        return obj.getTime();
    } else if (obj instanceof Buffer) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(function (v) {
            return mapToJs(v);
        });
    } else if (obj === null) {
        return obj;
    } else if (typeof obj === "object") {
        var res = {};
        Object.keys(obj).forEach(function (k) {
            res[k] = mapToJs(obj[k]);
        });
        return res;
    } else {
        return obj;
    }
};

var mapFieldNamesToJs = function (rootSchema, obj) {

    var meta = rootSchema.meta[rootSchema.meta.length - 1];
    if (Array.isArray(obj)) {
        return obj.map(mapFieldNamesToJs.bind(null, rootSchema.items[0]));
    } else if (meta.mappings.indexOf("map") >= 0) {
        var r = {};
        Object.keys(obj).forEach(function (k) {
            r[k] = mapFieldNamesToJs(meta.vMeta, obj[k]);
        });
        return r;
    } else if (meta.mappings.indexOf("nested") >= 0 || meta.mappings.indexOf("schema") >= 0) {
        var res = {};
        Object.keys(rootSchema.children).forEach(function (schemaFieldName) {
            var field = rootSchema.children[schemaFieldName];
            //var fieldMeta = field.meta[field.meta.length - 1];
            if (obj[schemaFieldName] != null) {
                res[schemaFieldName] = mapFieldNamesToJs(field, obj[schemaFieldName]);
            } else {
                res[schemaFieldName] = null;
            }
        });
        return res;
    } else {
        return obj;
    }
};

Model.prototype.validate = function (obj) {
    Joi.assert(obj, this._joiSchema);
};

Model.prototype.exec = function (cql) {
    return Q.nbind(cql.exec, cql, {prepare: true})();
};

function tranformCommon(obj) {
    if (obj == null) {
        return null;
    }

    if (typeof obj !== "object") {
        return obj;
    }

    if (obj instanceof Buffer) {
        return obj;
    }

    return Object.keys(obj).reduce(function (acc, k) {
        var v = obj[k];
        if (Array.isArray(v)) {
            if (v.length == 0) {
                acc[k] = null;
            } else {
                acc[k] = v.map(tranformCommon);
            }
        } else {
            if (typeof v === "object") {
                acc[k] = tranformCommon(v);
            } else {
                acc[k] = v;
            }
        }
        return acc;
    }, {});
}

module.exports = Model;<|endoftext|>


<commit_before>
const { Deferral } = require('./deferral');
const systemMap = require('./system-map');
const { ActorReference, TemporaryReference, Nobody } = require('./references');
const Queue = require('denque');
const assert = require('assert');
const { stop } = require('./functions');
const { defaultSupervisionPolicy, SupervisionActions } = require('./supervision');

class Actor {
  constructor (parent, name, system, f, { shutdownAfter, onCrash } = {}) {
    this.parent = parent;
    if (!name) {
      name = `anonymous-${Math.abs(Math.random() * Number.MAX_SAFE_INTEGER) | 0}`;
    }
    if (parent.children.has(name)) {
      throw new Error(`child actor of name ${name} already exists`);
    }
    this.name = name;
    this.path = parent.path.createChildPath(this.name);
    this.system = system;
    this.reference = new ActorReference(this.system.name, this.parent.reference, this.path, this.name);
    this.log = this.system.createLogger(this.reference);
    this.f = f;
    this.state = undefined;
    this.stopped = false;
    this.children = new Map();
    this.childReferences = new Map();
    this.busy = false;
    this.mailbox = new Queue();
    this.immediate = undefined;
    this.parent.childSpawned(this);
    this.onCrash = onCrash || defaultSupervisionPolicy;

    if (shutdownAfter) {
      if (typeof (shutdownAfter) !== 'number') {
        throw new Error('Shutdown should be specified as a number in milliseconds');
      }
      this.shutdownPeriod = Actor.getSafeTimeout(shutdownAfter);
      this.setTimeout();
    } else {
      this.setTimeout = () => {};
    }
  }

  setTimeout () {
    this.timeout = setTimeout(() => this.stop(), this.shutdownPeriod);
  }

  reset () {
    this.state = undefined;
    [...this.children.values()].forEach(x => x.stop());
    this.resume();
  }

  clearTimeout () {
    clearTimeout(this.timeout);
  }

  clearImmediate () {
    clearImmediate(this.immediate);
  }

  static getSafeTimeout (timeoutDuration) {
    timeoutDuration = timeoutDuration | 0;
    const MAX_TIMEOUT = 2147483647;
    return Math.min(MAX_TIMEOUT, timeoutDuration);
  }

  assertNotStopped () { assert(!this.stopped); return true; }
  postMessage () { }

  dispatch (message, sender = new Nobody()) {
    this.assertNotStopped();
    this.clearTimeout();
    if (!this.busy) {
      this.handleMessage(message, sender);
    } else {
      this.mailbox.push({ message, sender });
    }
  }

  query (message, timeout) {
    this.assertNotStopped();
    assert(timeout !== undefined && timeout !== null);
    const deffered = new Deferral();

    timeout = Actor.getSafeTimeout(timeout);
    const timeoutHandle = setTimeout(() => { deffered.reject(new Error('Query Timeout')); }, timeout);
    const tempReference = new TemporaryReference(this.system.name);
    this.system.addTempReference(tempReference, deffered);
    deffered.promise.then(() => {
      clearTimeout(timeoutHandle);
      this.system.removeTempReference(tempReference);
    }).catch(() => {
      this.system.removeTempReference(tempReference);
    });

    if (typeof (message) === 'function') {
      message = message(tempReference);
    }
    this.dispatch(message, tempReference);
    return deffered.promise;
  }

  childStopped (child) {
    this.children.delete(child.name);
    this.childReferences.delete(child.name);
  }

  childSpawned (child) {
    this.children.set(child.name, child);
    this.childReferences.set(child.name, child.reference);
  }

  stop () {
    this.clearImmediate();
    this.clearTimeout();
    this.parent && this.parent.childStopped(this);
    delete this.parent;
    [...this.children.values()].forEach(stop);
    this.stopped = true;
  }

  processNext () {
    if (!this.stopped) {
      const nextMsg = this.mailbox.shift();
      if (nextMsg) {
        this.handleMessage(nextMsg.message, nextMsg.sender);
      } else {
        this.busy = false;
          // Counter is now ticking until actor is killed
        this.setTimeout();
      }
    }
  }

  async handleFault (msg, sender, error) {
    const ctx = this.createSupervisionContext(msg, sender, error);
    const decision = await Promise.resolve(this.onCrash(msg, error, ctx));
    switch (decision) {
      case SupervisionActions.stop:
        this.stop();
        break;
      case SupervisionActions.stopAll:
        [...this.parent.children.values()].forEach(x => x.stop());
        break;
      case SupervisionActions.resume:
        this.resume();
        break;
      case SupervisionActions.reset:
        this.reset();
        break;
      case SupervisionActions.resetAll:
        [...this.parent.children.values()].forEach(x => x.reset());
        break;
      case SupervisionActions.escalate:
      default:
        this.parent.handleFault(this, msg, sender, error);
        break;
    }
  }

  resume () {
    this.processNext();
  }

  createSupervisionContext (msg, sender, error) {
    const ctx = this.createContext(this);
    return { ...ctx, ...SupervisionActions };
  }

  createContext (sender) {
    return {
      parent: this.parent.reference,
      path: this.path,
      self: this.reference,
      name: this.name,
      children: new Map(this.childReferences),
      sender,
      log: this.log
    };
  }

  handleMessage (message, sender) {
    this.busy = true;
    this.immediate = setImmediate(async () => {
      try {
        let ctx = this.createContext(sender);
        let next = await Promise.resolve(this.f.call(ctx, this.state, message, ctx));
        this.state = next;
        this.postMessage();
        this.processNext();
      } catch (e) {
        this.handleFault(this, message, sender, e);
      }
    });
  }
}

const spawn = (parent, f, name, properties) =>
  systemMap.applyOrThrowIfStopped(parent, p => p.assertNotStopped() && new Actor(p, name, p.system, f, properties).reference);

const spawnStateless = (parent, f, name, properties) =>
  spawn(parent, function (state, msg, ctx) {
    try {
      return Promise.resolve(f.call(ctx, msg, ctx)).then((_) => true);
    } catch (e) {
      console.error(e);
      return true;
    }
  }, name, properties);

module.exports.spawn = spawn;
module.exports.spawnStateless = spawnStateless;
module.exports.Actor = Actor;
<commit_msg>Simplified stateless actors<commit_after>
const { Deferral } = require('./deferral');
const systemMap = require('./system-map');
const { ActorReference, TemporaryReference, Nobody } = require('./references');
const Queue = require('denque');
const assert = require('assert');
const { stop } = require('./functions');
const { defaultSupervisionPolicy, SupervisionActions } = require('./supervision');

class Actor {
  constructor (parent, name, system, f, { shutdownAfter, onCrash } = {}) {
    this.parent = parent;
    if (!name) {
      name = `anonymous-${Math.abs(Math.random() * Number.MAX_SAFE_INTEGER) | 0}`;
    }
    if (parent.children.has(name)) {
      throw new Error(`child actor of name ${name} already exists`);
    }
    this.name = name;
    this.path = parent.path.createChildPath(this.name);
    this.system = system;
    this.reference = new ActorReference(this.system.name, this.parent.reference, this.path, this.name);
    this.log = this.system.createLogger(this.reference);
    this.f = f;
    this.state = undefined;
    this.stopped = false;
    this.children = new Map();
    this.childReferences = new Map();
    this.busy = false;
    this.mailbox = new Queue();
    this.immediate = undefined;
    this.parent.childSpawned(this);
    this.onCrash = onCrash || defaultSupervisionPolicy;

    if (shutdownAfter) {
      if (typeof (shutdownAfter) !== 'number') {
        throw new Error('Shutdown should be specified as a number in milliseconds');
      }
      this.shutdownPeriod = Actor.getSafeTimeout(shutdownAfter);
      this.setTimeout();
    } else {
      this.setTimeout = () => {};
    }
  }

  setTimeout () {
    this.timeout = setTimeout(() => this.stop(), this.shutdownPeriod);
  }

  reset () {
    this.state = undefined;
    [...this.children.values()].forEach(x => x.stop());
    this.resume();
  }

  clearTimeout () {
    clearTimeout(this.timeout);
  }

  clearImmediate () {
    clearImmediate(this.immediate);
  }

  static getSafeTimeout (timeoutDuration) {
    timeoutDuration = timeoutDuration | 0;
    const MAX_TIMEOUT = 2147483647;
    return Math.min(MAX_TIMEOUT, timeoutDuration);
  }

  assertNotStopped () { assert(!this.stopped); return true; }
  postMessage () { }

  dispatch (message, sender = new Nobody()) {
    this.assertNotStopped();
    this.clearTimeout();
    if (!this.busy) {
      this.handleMessage(message, sender);
    } else {
      this.mailbox.push({ message, sender });
    }
  }

  query (message, timeout) {
    this.assertNotStopped();
    assert(timeout !== undefined && timeout !== null);
    const deffered = new Deferral();

    timeout = Actor.getSafeTimeout(timeout);
    const timeoutHandle = setTimeout(() => { deffered.reject(new Error('Query Timeout')); }, timeout);
    const tempReference = new TemporaryReference(this.system.name);
    this.system.addTempReference(tempReference, deffered);
    deffered.promise.then(() => {
      clearTimeout(timeoutHandle);
      this.system.removeTempReference(tempReference);
    }).catch(() => {
      this.system.removeTempReference(tempReference);
    });

    if (typeof (message) === 'function') {
      message = message(tempReference);
    }
    this.dispatch(message, tempReference);
    return deffered.promise;
  }

  childStopped (child) {
    this.children.delete(child.name);
    this.childReferences.delete(child.name);
  }

  childSpawned (child) {
    this.children.set(child.name, child);
    this.childReferences.set(child.name, child.reference);
  }

  stop () {
    this.clearImmediate();
    this.clearTimeout();
    this.parent && this.parent.childStopped(this);
    delete this.parent;
    [...this.children.values()].forEach(stop);
    this.stopped = true;
  }

  processNext () {
    if (!this.stopped) {
      const nextMsg = this.mailbox.shift();
      if (nextMsg) {
        this.handleMessage(nextMsg.message, nextMsg.sender);
      } else {
        this.busy = false;
          // Counter is now ticking until actor is killed
        this.setTimeout();
      }
    }
  }

  async handleFault (msg, sender, error) {
    const ctx = this.createSupervisionContext(msg, sender, error);
    const decision = await Promise.resolve(this.onCrash(msg, error, ctx));
    switch (decision) {
      case SupervisionActions.stop:
        this.stop();
        break;
      case SupervisionActions.stopAll:
        [...this.parent.children.values()].forEach(x => x.stop());
        break;
      case SupervisionActions.resume:
        this.resume();
        break;
      case SupervisionActions.reset:
        this.reset();
        break;
      case SupervisionActions.resetAll:
        [...this.parent.children.values()].forEach(x => x.reset());
        break;
      case SupervisionActions.escalate:
      default:
        this.parent.handleFault(this, msg, sender, error);
        break;
    }
  }

  resume () {
    this.processNext();
  }

  createSupervisionContext (msg, sender, error) {
    const ctx = this.createContext(this);
    return { ...ctx, ...SupervisionActions };
  }

  createContext (sender) {
    return {
      parent: this.parent.reference,
      path: this.path,
      self: this.reference,
      name: this.name,
      children: new Map(this.childReferences),
      sender,
      log: this.log
    };
  }

  handleMessage (message, sender) {
    this.busy = true;
    this.immediate = setImmediate(async () => {
      try {
        let ctx = this.createContext(sender);
        let next = await Promise.resolve(this.f.call(ctx, this.state, message, ctx));
        this.state = next;
        this.postMessage();
        this.processNext();
      } catch (e) {
        this.handleFault(this, message, sender, e);
      }
    });
  }
}

const spawn = (parent, f, name, properties) =>
  systemMap.applyOrThrowIfStopped(parent, p => p.assertNotStopped() && new Actor(p, name, p.system, f, properties).reference);

const spawnStateless = (parent, f, name, properties) =>
  spawn(parent, function (state, msg, ctx) {
    try {
      return f.call(ctx, msg, ctx);
    } catch (e) {
      console.error(e);
    }
  }, name, properties);

module.exports.spawn = spawn;
module.exports.spawnStateless = spawnStateless;
module.exports.Actor = Actor;
<|endoftext|>


<commit_before>/*
 * @copyright Copyright (c) Sematext Group, Inc. - All Rights Reserved
 *
 * @licence SPM for NodeJS is free-to-use, proprietary software.
 * THIS IS PROPRIETARY SOURCE CODE OF Sematext Group, Inc. (Sematext)
 * This source code may not be copied, reverse engineered, or altered for any purpose.
 * This source code is to be used exclusively by users and customers of Sematext.
 * Please see the full license (found in LICENSE in this distribution) for details on its license and the licenses of its dependencies.
 */

'use strict'
var moment = require('moment')
var logger = require('./util/logger.js')

function formatArray (a) {
  if (a instanceof Array) {
    return a.join('\t')
  } else {
    return a + ''
  }
}

/**
 * Agent super class that provides an EventEmitter for 'metric' event.
 * In addition it fires for each added metric an event named by the property metric.name
 *
 * @param {function} plugin -  Function providing a plugin having start(agent) and stop method. The plugin can use agent.addMetric function.
 * @returns {function} an Agent having start,stop, addMetric function
 */
function Agent (plugin) {
  var events = require('events')
  var util = require('util')
  var eventEmitter = new events.EventEmitter()
  var cluster = require('cluster')
  var workerId = 0 + '-' + process.pid // 0 == Master, default
  var config = require('./util/spmconfig.js')
  if (!cluster.isMaster) {
    workerId = cluster.worker.id + '-' + process.pid
  }
  var agentSuperClass = {
    plugin: plugin,
    defaultType: 'njs',
    defaultFilters: function () {
      return [workerId || 0, process.pid || 0]
    },
    metricBuffer: [],
    /**
     * Adds a metric value and emits event to listeners on 'metric' or metric.name
     * @param metric
     */
    addMetrics: function (metric) {
      if (metric.measurement && metric.fields) {
        return this.addInfluxMetrics(metric)
      }
      metric.workerId = workerId
      metric.pid = process.pid
      if (!metric.sct) {
        if (metric.name && /collectd/.test(metric.name)) {
          metric.sct = 'OS'
        } else {
          metric.sct = 'APP'
        }
      }
      if (!metric.ts) {
        metric.ts = new Date().getTime()
      }
      if (!metric.type) {
        metric.type = this.defaultType
      }
      if (!metric.filters && metric.type === 'njs') {
        metric.filters = this.defaultFilters()
      }
      metric.spmLine = this.formatLine(metric)
      this.emit('metric', metric)
      if (metric.name) {
        this.emit(metric.name, metric)
      }
    },

    addInfluxMetrics: function (metric) {
      if (!metric) {
        return
      }

      if (!metric.timestamp) {
        metric.timestamp = new Date()
      }
      // numeric values depend on influx-client precision settings
      // while Date objects are correctly converted by influx-client
      if (typeof metric.timestamp === 'number') {
        metric.timestamp = new Date(metric.timestamp)
      }
      this.addInfluxTags(metric)
      this.emit('metric', metric)
      if (metric.measurement) {
        this.emit(metric.measurement, metric)
      }
    },

    addInfluxTags: function (metric) {
      if (!metric.tags) {
        metric.tags = {}
      }
      // Set pid, ppid tags only for nodejs and process metrics
      if (
        metric.measurement.startsWith('nodejs') ||
        metric.measurement.startsWith('process') ||
        metric.measurement.startsWith('container')
      ) {
        // don't set default tags, when already provided
        if (!metric.tags['nodejs.worker.id']) {
          metric.tags['nodejs.worker.id'] = workerId
        }
        if (!metric.tags['process.pid']) {
          metric.tags['process.pid'] = process.pid
        }
        if (!metric.tags['process.ppid']) {
          metric.tags['process.ppid'] = process.ppid
        }

        // Set container tags only for container metrics if running in Docker
        if (process.env.isDocker === true) {
          // don't set default tags, when already provided
          if (!metric.tags['container.id']) {
            metric.tags['container.id'] = process.env.containerId
          }
          if (!metric.tags['container.name']) {
            metric.tags['container.name'] = process.env.containerName
          }
          if (!metric.tags['container.image']) {
            metric.tags['container.image'] = process.env.containerImage
          }
        }
      }

      // save the metric token tag, in case global tags set 'token'
      let token = metric.tags.token
      // add global tags to metrics Object
      Object.assign(metric.tags, this.globalTags || {})
      // set metric specific token, e.g. in case of infra-app token
      if (token) {
        metric.tags.token = token
      }
    },

    collectdFormatLine: function (metric) {
      var now = new Date().getTime().toFixed(0)
      var metricsTs = metric.ts.toFixed(0)
      var metricsTs2 = (metric.ts / 1000).toFixed(0)
      var dateString = moment(metric.ts).format('YYYY-MM-DD')
      var line = ''
      if (/collectd.*-/.test(metric.name)) {
        if (
          /collectd-cpu/.test(metric.name) ||
          /collectd-io-octets/.test(metric.name)
        ) {
          line =
            now +
            '\t' +
            metric.name +
            '-' +
            dateString +
            ',' +
            metricsTs2 +
            ',' +
            metric.value
        } else if (/disk/.test(metric.name)) {
          line =
            now +
            '\t' +
            metric.name +
            '-' +
            dateString +
            ',' +
            metricsTs2 +
            ',' +
            metric.value.toFixed(6)
        } else {
          line =
            now + '\t' + metric.name + '\t' + metricsTs2 + ',' + metric.value
        }
      } else {
        line = now + '\t' + metric.name + '\t' + metricsTs + ',' + metric.value
      }
      return line
    },

    defaultFormatLine: function (metric) {
      var now = new Date().getTime().toFixed(0)
      var line = null
      if (metric.sct === 'OS') {
        line = this.collectdFormatLine(metric)
      } else {
        line = util.format(
          '%d\t%s\t%d\t%s\t%s',
          now,
          (metric.type || this.defaultType) + '-' + metric.name,
          metric.ts,
          formatArray(metric.filters),
          formatArray(metric.value)
        )
        logger.log(line)
      }
      return line
    },
    /**
     * formats a line for SPM sender, if the plugin does not support this method the default formatter for nodejs and OP metrics is used
     * @maram metric to format
     */
    formatLine: function (metric) {
      if (!this.plugin.formatLine) {
        return this.defaultFormatLine(metric)
      } else {
        return this.plugin.formatLine(metric)
      }
    },
    generateGlobalTags: function (tagNames, tagObject) {
      if (tagNames) {
        if (typeof tagNames === 'string') {
          // convert comma separated String list to array
          tagNames = tagNames.replace(/\s/g, '').split(',')
        }
        if (tagNames instanceof Array) {
          tagNames.forEach(tagName => {
            if (!tagName || tagName === '') {
              return
            }
            if (tagName.indexOf(':') > -1) {
              // static tag a=b
              let keyValue = tagName.split(':')
              if (keyValue.length === 2) {
                tagObject[keyValue[0]] = keyValue[1]
              }
            } else {
              tagObject[tagName] = process.env[tagName]
            }
          })
        }
      }
    },

    /**
     * Starts the agent - typically agents create listeners or interval checks for metrics,
     * and use 'addMetrics' to inform listeners
     */
    start: function () {
      if (!this.globalTags) {
        this.globalTags = {}
      }
      let tagNames =
        process.env.MONITORING_TAGS_FROM_ENV || config.influx.tagsFromEnv
      this.generateGlobalTags(tagNames, this.globalTags)
      if (this.plugin && this.plugin.start) {
        // this is the place to register for external events that return metrics
        this.plugin.start(this)
      } else {
        console.log('NOT called start')
      }
    },
    /**
     * Stops the plugin - timers could be pushed to plugins 'timers' Array, if timers exists it tries to clearInterval(timerId)
     * and tries to call the stop() method of the agent-plugin
     */
    stop: function () {
      if (this.plugin.timers) {
        this.plugin.timers.forEach(function (tid) {
          clearInterval(tid)
        })
      }
      if (this.plugin.stop) {
        this.plugin.stop()
      } else {
        logger.warn('Plugin ' + plugin.name + ' has no stop() function')
      }
    }
  }
  return Object.assign(eventEmitter, agentSuperClass)
}

module.exports = Agent
<commit_msg>added docker inspect to agent.js<commit_after>/*
 * @copyright Copyright (c) Sematext Group, Inc. - All Rights Reserved
 *
 * @licence SPM for NodeJS is free-to-use, proprietary software.
 * THIS IS PROPRIETARY SOURCE CODE OF Sematext Group, Inc. (Sematext)
 * This source code may not be copied, reverse engineered, or altered for any purpose.
 * This source code is to be used exclusively by users and customers of Sematext.
 * Please see the full license (found in LICENSE in this distribution) for details on its license and the licenses of its dependencies.
 */

'use strict'
var moment = require('moment')
var logger = require('./util/logger.js')

function formatArray (a) {
  if (a instanceof Array) {
    return a.join('\t')
  } else {
    return a + ''
  }
}

/**
 * Agent super class that provides an EventEmitter for 'metric' event.
 * In addition it fires for each added metric an event named by the property metric.name
 *
 * @param {function} plugin -  Function providing a plugin having start(agent) and stop method. The plugin can use agent.addMetric function.
 * @returns {function} an Agent having start,stop, addMetric function
 */
function Agent (plugin) {
  var events = require('events')
  var util = require('util')
  var eventEmitter = new events.EventEmitter()
  var cluster = require('cluster')
  var workerId = 0 + '-' + process.pid // 0 == Master, default
  var config = require('./util/spmconfig.js')
  require('./util/dockerInspect.js')
  if (!cluster.isMaster) {
    workerId = cluster.worker.id + '-' + process.pid
  }
  var agentSuperClass = {
    plugin: plugin,
    defaultType: 'njs',
    defaultFilters: function () {
      return [workerId || 0, process.pid || 0]
    },
    metricBuffer: [],
    /**
     * Adds a metric value and emits event to listeners on 'metric' or metric.name
     * @param metric
     */
    addMetrics: function (metric) {
      if (metric.measurement && metric.fields) {
        return this.addInfluxMetrics(metric)
      }
      metric.workerId = workerId
      metric.pid = process.pid
      if (!metric.sct) {
        if (metric.name && /collectd/.test(metric.name)) {
          metric.sct = 'OS'
        } else {
          metric.sct = 'APP'
        }
      }
      if (!metric.ts) {
        metric.ts = new Date().getTime()
      }
      if (!metric.type) {
        metric.type = this.defaultType
      }
      if (!metric.filters && metric.type === 'njs') {
        metric.filters = this.defaultFilters()
      }
      metric.spmLine = this.formatLine(metric)
      this.emit('metric', metric)
      if (metric.name) {
        this.emit(metric.name, metric)
      }
    },

    addInfluxMetrics: function (metric) {
      if (!metric) {
        return
      }

      if (!metric.timestamp) {
        metric.timestamp = new Date()
      }
      // numeric values depend on influx-client precision settings
      // while Date objects are correctly converted by influx-client
      if (typeof metric.timestamp === 'number') {
        metric.timestamp = new Date(metric.timestamp)
      }
      this.addInfluxTags(metric)
      this.emit('metric', metric)
      if (metric.measurement) {
        this.emit(metric.measurement, metric)
      }
    },

    addInfluxTags: function (metric) {
      if (!metric.tags) {
        metric.tags = {}
      }
      // Set pid, ppid tags only for nodejs and process metrics
      if (
        metric.measurement.startsWith('nodejs') ||
        metric.measurement.startsWith('process') ||
        metric.measurement.startsWith('container')
      ) {
        // don't set default tags, when already provided
        if (!metric.tags['nodejs.worker.id']) {
          metric.tags['nodejs.worker.id'] = workerId
        }
        if (!metric.tags['process.pid']) {
          metric.tags['process.pid'] = process.pid
        }
        if (!metric.tags['process.ppid']) {
          metric.tags['process.ppid'] = process.ppid
        }

        // Set container tags only for container metrics if running in Docker
        if (process.env.isDocker === true) {
          // don't set default tags, when already provided
          if (!metric.tags['container.id']) {
            metric.tags['container.id'] = process.env.containerId
          }
          if (!metric.tags['container.name']) {
            metric.tags['container.name'] = process.env.containerName
          }
          if (!metric.tags['container.image']) {
            metric.tags['container.image'] = process.env.containerImage
          }
        }
      }

      // save the metric token tag, in case global tags set 'token'
      let token = metric.tags.token
      // add global tags to metrics Object
      Object.assign(metric.tags, this.globalTags || {})
      // set metric specific token, e.g. in case of infra-app token
      if (token) {
        metric.tags.token = token
      }
    },

    collectdFormatLine: function (metric) {
      var now = new Date().getTime().toFixed(0)
      var metricsTs = metric.ts.toFixed(0)
      var metricsTs2 = (metric.ts / 1000).toFixed(0)
      var dateString = moment(metric.ts).format('YYYY-MM-DD')
      var line = ''
      if (/collectd.*-/.test(metric.name)) {
        if (
          /collectd-cpu/.test(metric.name) ||
          /collectd-io-octets/.test(metric.name)
        ) {
          line =
            now +
            '\t' +
            metric.name +
            '-' +
            dateString +
            ',' +
            metricsTs2 +
            ',' +
            metric.value
        } else if (/disk/.test(metric.name)) {
          line =
            now +
            '\t' +
            metric.name +
            '-' +
            dateString +
            ',' +
            metricsTs2 +
            ',' +
            metric.value.toFixed(6)
        } else {
          line =
            now + '\t' + metric.name + '\t' + metricsTs2 + ',' + metric.value
        }
      } else {
        line = now + '\t' + metric.name + '\t' + metricsTs + ',' + metric.value
      }
      return line
    },

    defaultFormatLine: function (metric) {
      var now = new Date().getTime().toFixed(0)
      var line = null
      if (metric.sct === 'OS') {
        line = this.collectdFormatLine(metric)
      } else {
        line = util.format(
          '%d\t%s\t%d\t%s\t%s',
          now,
          (metric.type || this.defaultType) + '-' + metric.name,
          metric.ts,
          formatArray(metric.filters),
          formatArray(metric.value)
        )
        logger.log(line)
      }
      return line
    },
    /**
     * formats a line for SPM sender, if the plugin does not support this method the default formatter for nodejs and OP metrics is used
     * @maram metric to format
     */
    formatLine: function (metric) {
      if (!this.plugin.formatLine) {
        return this.defaultFormatLine(metric)
      } else {
        return this.plugin.formatLine(metric)
      }
    },
    generateGlobalTags: function (tagNames, tagObject) {
      if (tagNames) {
        if (typeof tagNames === 'string') {
          // convert comma separated String list to array
          tagNames = tagNames.replace(/\s/g, '').split(',')
        }
        if (tagNames instanceof Array) {
          tagNames.forEach(tagName => {
            if (!tagName || tagName === '') {
              return
            }
            if (tagName.indexOf(':') > -1) {
              // static tag a=b
              let keyValue = tagName.split(':')
              if (keyValue.length === 2) {
                tagObject[keyValue[0]] = keyValue[1]
              }
            } else {
              tagObject[tagName] = process.env[tagName]
            }
          })
        }
      }
    },

    /**
     * Starts the agent - typically agents create listeners or interval checks for metrics,
     * and use 'addMetrics' to inform listeners
     */
    start: function () {
      if (!this.globalTags) {
        this.globalTags = {}
      }
      let tagNames =
        process.env.MONITORING_TAGS_FROM_ENV || config.influx.tagsFromEnv
      this.generateGlobalTags(tagNames, this.globalTags)
      if (this.plugin && this.plugin.start) {
        // this is the place to register for external events that return metrics
        this.plugin.start(this)
      } else {
        console.log('NOT called start')
      }
    },
    /**
     * Stops the plugin - timers could be pushed to plugins 'timers' Array, if timers exists it tries to clearInterval(timerId)
     * and tries to call the stop() method of the agent-plugin
     */
    stop: function () {
      if (this.plugin.timers) {
        this.plugin.timers.forEach(function (tid) {
          clearInterval(tid)
        })
      }
      if (this.plugin.stop) {
        this.plugin.stop()
      } else {
        logger.warn('Plugin ' + plugin.name + ' has no stop() function')
      }
    }
  }
  return Object.assign(eventEmitter, agentSuperClass)
}

module.exports = Agent
<|endoftext|>


<commit_before>'use strict';

var express = require('express');

var path = require('path'),
    debug = require('../lib/functions/debug');

let symbols = {
  prefix: Symbol('route'),
  options: Symbol('options')
}

let defaults = {
  prefix: '/',
  baseDir: path.join(__dirname, '..')
}

module.exports = class Route {

  constructor(prefix, app, options) {

    debug.log('Initializing new Route', prefix);

    this.options = options;
    this.parent_app = app;
    this.app = express();
    this.prefix = (typeof(prefix) === 'string') ? prefix : defaults.prefix;
    app.use(this.prefix, this.app);

    this._loadRoute();

  }

  get baseDir() {
    return (typeof(this.options.baseDir) === 'string') ? this.options.baseDir : path.join(defaults.baseDir, this.prefix);
  }

  follow(suffix) {
    var options = {};
    Object.keys(this.options).forEach((key) => {
      options[key] = this.options[key];
    });
    options.baseDir = path.join(this.path, suffix);
    const _suffix = (suffix.startsWith('/') ? '' : '/') + suffix;
    return new Route(_suffix, this.app, options);
  }

  get() {
    return this.app.get.apply(this.app, arguments);
  }

  post() {
    return this.app.post.apply(this.app, arguments);
  }

  all() {
    return this.app.all.apply(this.app, arguments);
  }

  _loadRoute() {
    debug.info('Load Route from', this.path);
    require(this.path).apply(this);
  }

  set prefix(value) {
    this[symbols.prefix] = value;
  }

  get prefix() {
    return this[symbols.prefix];
  }

  set options(value) {
    this[symbols.options] = value || {};
  }

  get options() {
    return this[symbols.options] || {};
  }

  get path() {
    return this.baseDir;
  }

}
<commit_msg>express 4 Router<commit_after>'use strict';

var express = require('express');

var path = require('path'),
    debug = require('../lib/functions/debug');

let symbols = {
  prefix: Symbol('route'),
  options: Symbol('options')
}

let defaults = {
  prefix: '/',
  baseDir: path.join(__dirname, '..')
}

module.exports = class Route {

  constructor(prefix, app, options) {

    debug.log('Initializing new Route', prefix);

    this.options = options;
    this.parent_app = app;
    this.app = express.Router();
    this.prefix = (typeof(prefix) === 'string') ? prefix : defaults.prefix;
    app.use(this.prefix, this.app);

    this._loadRoute();

  }

  get baseDir() {
    return (typeof(this.options.baseDir) === 'string') ? this.options.baseDir : path.join(defaults.baseDir, this.prefix);
  }

  follow(suffix) {
    var options = {};
    Object.keys(this.options).forEach((key) => {
      options[key] = this.options[key];
    });
    options.baseDir = path.join(this.path, suffix);
    const _suffix = (suffix.startsWith('/') ? '' : '/') + suffix;
    return new Route(_suffix, this.app, options);
  }

  get() {
    return this.app.get.apply(this.app, arguments);
  }

  post() {
    return this.app.post.apply(this.app, arguments);
  }

  all() {
    return this.app.all.apply(this.app, arguments);
  }

  _loadRoute() {
    debug.info('Load Route from', this.path);
    require(this.path).apply(this);
  }

  set prefix(value) {
    this[symbols.prefix] = value;
  }

  get prefix() {
    return this[symbols.prefix];
  }

  set options(value) {
    this[symbols.options] = value || {};
  }

  get options() {
    return this[symbols.options] || {};
  }

  get path() {
    return this.baseDir;
  }

}
<|endoftext|>