/**
 * server.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var path = require('path');
var mysql = require('mysql');
var async = require('async');
var fs = require('fs');
var _ = require('underscore');
var generic_pool = require('generic-pool');

var dibs = require('../../core/dibs.js');
var DError = require('../../core/exception.js');
var migrateSteps = require('./migrate.js');
var ProjectManager = require('./project-manager.js');
var Project = require('../../plugins/dibs.model.common/project.js');
var User = require('../../plugins/dibs.model.common/user.js');
var Group = require('../../plugins/dibs.model.common/group.js');
var Environment = require('../../plugins/dibs.model.common/environment.js');

var Job = require('../../plugins/dibs.model.common/job.js');
var Approval = require('../../plugins/dibs.model.common/approval.js');
var Notification = require('../../plugins/dibs.model.common/notification.js');
var Trigger = require('../../plugins/dibs.model.common/trigger.js');
var Server = require('../../plugins/dibs.model.common/server.js');
var Snapshot = require('../../plugins/dibs.model.common/snapshot.js');
var Artifact = require('../../plugins/dibs.model.common/artifact.js');


/**
 * @module servers/data-manager/server
 */

/**
 * @constructor
 * @memberOf module:servers/data-manager/server
 */
function DataManagerServer() {
    var self = this;
    /** type {string} */
    this.name = 'data manager';

    var db = null;
    /**
     * @constant {number} CURRENT_DB_VERSION
     * @default 1
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    var CURRENT_DB_VERSION = migrateSteps.length;
    /** type {number} */
    var dbRetryCount = 0;
    /** type {object} */
    var prjmgr = null;
    var statistics = {};

    /**
     * @typedef configuration
     * @property {string} db_host
     * @property {number} db_port
     * @property {string} db_user
     * @property {string} db_password
     * @property {string} db_name
     * @memberOf module:servers/data-manager/server
     */

    /**
     * @method getDefaultConfiguration
     * @return {module:servers/data-manager/server.configuration}
     * : returns for creating server's default configuration
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    // CALLBACK: for creating server's default configuration
    this.getDefaultConfiguration = function (baseConfig) {
        var defaultConfig = baseConfig;
        defaultConfig.db_host = '127.0.0.1';
        defaultConfig.db_port = '3306';
        defaultConfig.db_user = 'user';
        defaultConfig.db_password = 'pass';
        defaultConfig.db_name = 'buildsystem';
        return defaultConfig;
    };


    /**
     * @method OnServerStarting
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.OnServerStarting = function (callback) {
        async.series([
            // connect to DB
            function (cb) {
                self.log.info('Connecting to DB...');
                connectToDB(function (err, dbVersion) {
                    if (err) {
                        cb(err);
                    }

                    db = getNewConnectionPool();
                    // add periodic action handler for checking DB connection
                    addConnectionChecker(db);

                    if (dbVersion < CURRENT_DB_VERSION) {
                        migrateDB(dbVersion, function (err) {
                            if (err) {
                                cb(err);
                            } else {
                                self.log.info('Migrating DB succeeded!');
                                cb(null);
                            }
                        });
                    } else {
                        self.log.info('DB is ready!');
                        cb(null);
                    }
                });
            }
        ],
        function (err) {
            if (err) {
                self.log.error(err);
            }
            callback(err);
        });
    };

    /**
     * @method OnServerStarted
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.OnServerStarted = function (callback) {
        async.series([
            function (cb) {
                var exts = dibs.plugin.getExtensions('dibs.base.environment');
                initEnvironments(exts, function (err) {
                    if (err) {
                        self.log.error(err);
                    }
                    cb(null);
                });
            },
            function (cb) {
                // create project manager
                prjmgr = ProjectManager.create(self);
                // initialize project manager
                prjmgr.initialize(function (err) {
                    cb(err);
                });
            },
            function (cb) {
                // create statistics
                var exts = dibs.plugin.getExtensions('dibs.data.statistics.type');
                for (var i = 0; i < exts.length; i++) {
                    var ext = exts[i];
                    statistics[ext.type] = ext.module.create(self);
                }
                cb(null);
            }
        ],
            function (err) {
                callback(err);
            });
    };


    this.addImageFromFileInternal = function (ipath, name, callback) {
        var contents = null;
        async.waterfall([
            function (wcb) {
                fs.exists(ipath, function (truth) {
                    if (truth) {
                        wcb(null);
                    } else {
                        wcb(new DError('DATAMGR006', {
                            file: ipath
                        }));
                    }
                });
            },
            function (wcb) {
                fs.readFile(ipath, function (err, data) {
                    wcb(err, data);
                });
            },
            function (data, wcb) {
                contents = {
                    name: path.basename(ipath),
                    size: fs.statSync(ipath).size,
                    contents: data
                };
                self.log.info('Adding image...');
                db.getConnection(function (err, conn) {
                    if (err) {
                        self.log.info('Adding image failed!');
                        wcb(err);
                    } else {
                        conn.query('INSERT INTO images SET ?', contents, function (err, result) {
                            conn.release();
                            wcb(err, result.insertId);
                        });
                    }
                });

            }
        ], callback);
    };

    this.searchImageInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                var where = _.map(_.pairs(condition), function (cond) {
                    return cond[0] + ' = ' + cond[1];
                }).join(' AND ');
                self.log.info(where);
                conn.query('SELECT * FROM images WHERE ' + where, function (err, data) {
                    conn.release();
                    if (err) {
                        self.log.info('Search Image failed!');
                        callback(err, null);
                    } else {
                        var image = data[0];
                        if (Buffer.isBuffer(image.contents)) {
                            image.contents = image.contents.toString('base64');
                        }
                        callback(null, image);
                    }
                });
            }
        });
    };

    this.addNotificationInternal = function (event, method, type, target, groupId, description, callback) {
        var notification = Notification.create(null, groupId, event, method, type, target, description);

        self.log.info('Adding notification...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Notification.insert(conn, notification, function (err, notification) {
                    conn.release();
                    if (err) {
                        self.log.info('Adding notification failed!');
                        callback(err, null);
                    } else {
                        self.log.info('Added notification succeeded! ID: ' + notification.id);
                        callback(null, null);
                    }
                });
            }
        });
    };

    this.updateNotificationInternal = function (notification, callback) {
        self.log.info('Updating notification...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Notification.update(conn, notification, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('updadting notification failed! ' + err);
                        callback(err, null);
                    } else {
                        callback(null, notification);
                    }
                });
            }
        });
    };

    this.searchNotificationInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Notification.select(conn, condition, function (err, notifications) {
                    conn.release();
                    if (err) {
                        self.log.info('Search notification failed!');
                        callback(err, null);
                    } else {
                        callback(null, notifications);
                    }
                });
            }
        });
    };

    this.removeNotificationInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Notification.delete(conn, condition, function (err, notifications) {
                    conn.release();
                    if (err) {
                        self.log.info('Remove notification failed!');
                        callback(err, null);
                    } else {
                        callback(null, notifications);
                    }
                });
            }
        });
    };

    /**
     * @method addUserInternal
     * @param {string} email
     * @param {string} name
     * @param {string} password_hash
     * @param {string} password_salt
     * @param {module:models/user.options} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    //User
    this.addUserInternal = function (email, name, password_hash, password_salt, options, callback) {
        var user = User.create(email, name, password_hash, password_salt, 2, options);

        self.log.info('Adding user...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.insert(conn, user, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('Adding user failed!');
                        callback(err, null);
                    } else {
                        self.log.info('Added user succeeded! ID: ' + id);
                        callback(null, id);
                    }
                });
            }
        });
    };


    this.updateUserInternal = function (user, callback) {
        self.log.info('Updating user...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.update(conn, user, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('updadting user failed! ' + err);
                        callback(err, null);
                    } else {
                        callback(null, user);
                    }
                });
            }
        });
    };


    this.removeUserInternal = function (userId, callback) {
        self.log.info('Removing user...' + userId);
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.delete(conn, {
                    id: userId
                }, function (err) {
                    conn.release();
                    if (err) {
                        self.log.info('Removing user failed!...' + userId);
                        callback(err, null);
                    } else {
                        self.log.info('Removing user succeeded!...' + userId);
                        callback(null, null);
                    }
                });
            }
        });
    };


    this.resetUserPasswordInternal = function (userId, callback) {
        self.log.info('Resetting user password...' + userId);
        var conn = null;
        var user = null;
        async.waterfall([
            function (cb) {
                db.getConnection(function (err, conn) {
                    if (err) {
                        self.log.error('Connect DB fail: ' + err);
                    }
                    cb(err, conn);
                });
            },
            function (c, cb) {
                conn = c;
                User.select(conn, {
                    id: userId
                }, cb);
            },
            function (users, cb) {
                if (users.length <= 0) {
                    return cb(new Error('User not found!'));
                }
                user = users[0];
                user.password_hash = '$2a$10$H.w3ssI9KfuvNEXXp1qxD.b3Wm8alJG.HXviUofe4nErDn.TKUAka';
                user.password_salt = '$2a$10$H.w3ssI9KfuvNEXXp1qxD.';
                User.update(conn, user, cb);
            }], function (err) {
            if (conn) {
                conn.release();
            }
            if (err) {
                self.log.info('Resetting user password failed!...' + userId);
                callback(err, null);
            } else {
                self.log.info('Resetting user password succeeded!...' + userId);
                callback(err, user);
            }
        });
    };


    /**
     * @method searchUsersInternal
     * @param {module:models/user.userQuerySql} condition
     * @param {module:models/user.callback_err_user} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchUsersInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.select(conn, condition, function (err, users) {
                    conn.release();
                    if (err) {
                        self.log.info('Search user failed!');
                        callback(err, null);
                    } else {
                        callback(null, users);
                    }
                });
            }
        });
    };

    /**
     * @method searchUsersInternal
     * @param {module:models/user.userQuerySql} condition
     * @param {module:models/user.callback_err_user} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchUsersByGroupNameInternal = function (groupName, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.selectUsersByGroupName(conn, groupName, function (err, users) {
                    conn.release();
                    if (err) {
                        self.log.info('Search user failed!');
                        callback(err, null);
                    } else {
                        callback(null, users);
                    }
                });
            }
        });
    };

    /**
     * @method addUserGroupInternal
     * @param {string} name
     * @param {string} type
     * @param {module:models/user.options} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    //User
    this.addUserGroupInternal = function (name, type, description, options, callback) {
        var group = Group.create(name, type, description, options, null);

        self.log.info('Adding user group...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Group.insert(conn, group, function (err, id) {
                    group.id = id;
                    conn.release();
                    if (err) {
                        self.log.info('Adding user group failed!');
                        callback(err, null);
                    } else {
                        self.log.info('Added user group succeeded! ID: ' + id);
                        callback(null, group);
                    }
                });
            }
        });
    };


    this.removeUserGroupInternal = function (groupId, callback) {
        self.log.info('Removing user group...' + groupId);
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Group.delete(conn, {
                    id: groupId
                }, function (err) {
                    conn.release();
                    if (err) {
                        self.log.info('Removing user group failed!...' + groupId);
                        callback(err, null);
                    } else {
                        self.log.info('Removing user group succeeded!...' + groupId);
                        callback(null, null);
                    }
                });
            }
        });
    };


    this.updateUserGroupInternal = function (group, callback) {
        self.log.info('Updating User Group...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Group.update(conn, group, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('Updadting user group failed! ' + err);
                        callback(err, null);
                    } else {
                        callback(null, group);
                    }
                });
            }
        });
    };

    /**
     * @method searchUsersInternal
     * @param {module:models/user.userQuerySql} condition
     * @param {module:models/user.callback_err_user} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchUserGroupsInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Group.select(conn, condition, function (err, groups) {
                    conn.release();
                    if (err) {
                        self.log.error('Search user group failed!');
                        callback(err, null);
                    } else {
                        callback(null, groups);
                    }
                });
            }
        });
    };

    /**
     * @method searchUsersInfoInternal
     * @param {string} email
     * @param {module:models/user.callback_err_user} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchUserGroupsInfoInternal = function (email, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                User.selectGroup(conn, email, function (err, user) {
                    conn.release();
                    if (err) {
                        self.log.error('Search user group info failed!');
                        callback(err, null);
                    } else {
                        callback(null, user);
                    }
                });
            }
        });
    };


    /**
     * @method addEnvironmentInternal
     * @param {string} name
     * @param {string} description
     * @param {module:models/user.options} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    //Environment
    function addEnv(id, name, description, options, callback) {
        var env = Environment.create(id, name, description, options);

        self.log.info('Adding environment...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Environment.insert(conn, env, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('Adding environment failed! ' + err);
                        callback(err, null);
                    } else {
                        self.log.info('Added environment succeeded! ID: ' + id);
                        callback(null, null);
                    }
                });
            }
        });
    }
    this.addEnvironmentInternal = addEnv;

    /**
     * @method updateEnvironment entInternal
     * @param {module:models/environment.Environment} environment
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateEnvironmentInternal = function (environment, callback) {
        self.log.info('Updating environment...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Environment.update(conn, environment, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('updadting environment failed! ' + err);
                        callback(err, null);
                    } else {
                        callback(null, null);
                    }
                });
            }
        });
    };

    /**
     * @method searchEnvironmentsInternal
     * @param {module:models/environment.environmentQuerySql} condition
     * @param {module:models/environment.callback_err_environment} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    function searchEnvs(condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Environment.select(conn, condition, function (err, envs) {
                    conn.release();
                    if (err) {
                        self.log.info('Search environment failed! ' + err);
                        callback(err, null);
                    } else {
                        callback(null, envs);
                    }
                });
            }
        });
    }
    this.searchEnvironmentsInternal = searchEnvs;

    /**
     * @method deleteEnvironmentsInternal
     * @param {module:models/environment.environmentQuerySql} condition
     * @param {module:models/environment.callback_err_environment} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteEnvironmentsInternal = function (condition, callback) {
        self.log.info('Deleting environment...');
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Environment.delete(conn, condition, function (err, env) {
                    conn.release();
                    if (err) {
                        self.log.info('Delete environment failed! ' + err);
                        callback(err, null);
                    } else {
                        self.log.info('Deleted environment succeeded! ID: ' + env.id);
                        callback(null, env);
                    }
                });
            }
        });
    };

    /**
     * @method addProjectInternal
     * @param {string} name
     * @param {string} type
     * @param {module:models/user.options} options
     * @param {string} distName
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addProjectInternal = function (name, type, options, environments, triggers, notifications, distName, callback) {
        prjmgr.addProject(name, type, options, environments, triggers, notifications, distName, callback);
    };


    /**
     * @method removeProjectInternal
     * @param {string} name
     * @param {string} distName
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.removeProjectInternal = function (name, distName, callback) {
        prjmgr.removeProject(name, distName, callback);
    };


    /**
     * @method updateProjectInternal
     * @param {object} project
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateProjectInternal = function (project, callback) {
        prjmgr.updateProject(project, callback);
    };

    /**
     * @method searchProjectsInternal
     * @param {module:models/project.projectQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchProjectsInternal = function (cond, callback) {
        async.waterfall([
            function (wcb) {
                prjmgr.searchProjects(cond, wcb);
            },
            function (projects, wcb) {
                if (cond.queryPrivilege) {
                    async.mapLimit(projects, 20,
                        function (project, mcb) {
                            queryProjectPrivilege(project.distName, project.name, cond.email, function (err, privileges) {
                                project.privileges = privileges;
                                mcb(err, project);
                            });
                        },
                        function (err, newProjects) {
                            wcb(err, newProjects);
                        });
                } else {
                    wcb(null, projects);
                }
            }
        ], function (err, projects) {
            callback(err, projects);
        });
    };

    /**
     * @method searchProjectsWithLastJobInternal
     * @param {module:models/project.projectQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchProjectsWithTreeModelInternal = function (cond, callback) {
        async.waterfall([
            function (wcb) {
                db.getConnection(function (err, conn) {
                    if (err) {
                        self.log.error('Connect DB fail: ' + err);
                        wcb(err, null);
                    } else {
                        Project.selectAllWithTreeModel(conn, cond, function (err, projects) {
                            conn.release();
                            if (err) {
                                self.log.info('Search projects failed! ' + err);
                                wcb(err, null);
                            } else {
                                wcb(null, projects);
                            }
                        });
                    }
                });
            },
            function (projects, wcb) {
                if (cond.privilege) {
                    var privilegeProjects = [];
                    async.mapSeries(projects,
                        function (project, ecb) {
                            checkProjectPrivilege(cond.privilege, {
                                distName: project.distName,
                                prjName: project.name
                            }, cond.email, function (err, allowed) {
                                if (!err && allowed) {
                                    privilegeProjects.push(project);
                                }
                                ecb(err);
                            });
                        },
                        function (err) {
                            if (err) {
                                wcb(err, []);
                            } else {
                                wcb(err, privilegeProjects);
                            }
                        });
                } else {
                    wcb(null, projects);
                }
            },
            function (projects, wcb) {
                if (cond.queryPrivilege) {
                    async.mapSeries(projects,
                        function (project, mcb) {
                            queryProjectPrivilege(project.distName, project.name, cond.email, function (err, privileges) {
                                project.privileges = privileges;
                                mcb(err, project);
                            });
                        },
                        function (err, newProjects) {
                            wcb(err, newProjects);
                        });
                } else {
                    wcb(null, projects);
                }
            }
        ], function (err, projects) {
            callback(err, projects);
        });
    };

    /**
     * @method searchProjectPrivilegeInternal
     * @param {module:models/project.projectQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchProjectPrivilegeInternal = function (cond, callback) {
        prjmgr.searchProjects(cond, function (err, projects) {
            if (projects.length === 0) {
                callback(null, null);
            }
            db.getConnection(function (err, conn) {
                if (err) {
                    self.log.error('Connect DB fail: ' + err);
                    callback(err, null);
                } else {
                    Group.selectByProjectPrivilege(conn, cond, function (err, groups) {
                        conn.release();
                        if (err) {
                            self.log.info('Search projects failed! ' + err);
                            callback(err, null);
                        } else {
                            callback(null, groups);
                        }
                    });
                }
            });
        });
    };


    /**
     * @method addJobInternal
     * @param {string} prjName
     * @param {string} distName
     * @param {module:models/job.options} options
     * @param {module:models/job.callback_err_job} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    //Job
    this.addJobInternal = function (userEmail, distName, prjName, environmentName, parentId, options, callback) {
        // get distribution
        var distribution = prjmgr.getDistribution(distName);
        if (distribution === null) {
            var error = new DError('DATAMGR005', {
                distName: distName
            });
            self.log.error(error.message);
            callback(error, null);
            return;
        }

        // get project
        var prj = prjmgr.getProject(prjName, distName);
        if (prj === null) {
            var error = new DError('DATAMGR001', {
                prjName: prjName
            });
            self.log.info(error.message);
            callback(error, null);
            return;
        }

        self.log.info('Adding job...');
        var newJob = null;
        async.waterfall([
            // check parent job exists if specified
            function (cb) {
                if (dibs.getServersByType('master')[0].status === 'RUNNING') {
                    cb(null);
                } else {
                    cb(new DError('DATAMGR020', {
                        status: dibs.getServersByType('master')[0].status
                    }));
                }
            },
            function (cb) {
                if (parentId) {
                    self.searchJobsInternal({
                        id: parentId
                    }, function (err, jobs) {
                        if (!err && jobs.length === 0) {
                            cb(new DError('DATAMGR004', {
                                jobId: parentId
                            }));
                        } else {
                            cb(err);
                        }
                    });
                } else {
                    cb(null);
                }
            },
            // create job and insert it into DB
            function (cb) {
                // add notifications info to job-options
                var newOpt = _.extend(options, {
                    NOTIFICATIONS: prj.notifications
                });
                prj.createJob(userEmail, environmentName, parentId, distribution.type, newOpt, cb);
            },
            function (newjob, cb) {
                newJob = newjob;

                // add job environment
                newJob.options['JOB_ENV'] = distribution.options['JOB_ENV'];

                insertNewJobIntoDB(newJob, cb);
            }], function (err) {
            callback(err, newJob);
        });
    };


    /**
     * @method searchJobsInternal
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchJobsInternal = function (cond, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Job.select(conn, cond, function (err, jobs) {
                    conn.release();
                    if (err) {
                        callback(err, null);
                    } else {
                        callback(err, jobs);
                    }
                });
            }
        });
    };
    /**
     * @method searchJobCountInternal
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchJobCountInternal = function (cond, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Job.selectCount(conn, cond, function (err, jobs) {
                    conn.release();
                    if (err) {
                        callback(err, null);
                    } else {
                        callback(err, jobs);
                    }
                });
            }
        });
    };


    /**
     * @method updateJobInternal
     * @param {module:models/job.jobQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateJobInternal = function (job, callback) {
        self.log.info('Update job #' + job.id + ' status...' + job.status);
        async.waterfall([
            function (wcb) {
                // check job exists
                self.searchJobsInternal({
                    id: job.id
                }, wcb);
            },
            function (results, wcb) {
                if (results.length === 1) {
                    db.getConnection(function (err, conn) {
                        if (err) {
                            self.log.error('Connect DB fail: ' + err);
                            wcb(err);
                        } else {
                            // update
                            Job.updateStatus(conn, job, function (err) {
                                conn.release();
                                wcb(err);
                            });
                        }
                    });
                } else {
                    var error = DError('DATAMGR003', {
                        jobId: job.id
                    });
                    wcb(error);
                }
            }
        ], function (err) {
            callback(err);
        });
    };


    /**
     * @method checkPrivilegeInternal
     * @param {module:models/job.jobQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.checkPrivilegeInternal = function (resource, method, options, userEmail, callback) {
        switch (resource) {
        case 'distribution':
            checkDistributionPrivilege(method, options, userEmail, callback);
            break;
        case 'project':
            checkProjectPrivilege(method, options, userEmail, callback);
            break;
        default:
            callback(new DError('DATAMGR017', {
                resource: resource
            }));
        }
    };


    /**
     * @method checkJobPrivilegeInternal
     * @param {module:models/job.jobQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.checkJobPrivilegeInternal = function (userEmail, distName, prjName, options, callback) {
        var conn;
        var user;
        var groups = [];
        var distribution;
        var project;

        async.series([
            function (scb) {
                db.getConnection(function (err, connection) {
                    if (err) {
                        self.log.error('Connect DB fail: ' + err);
                        scb(err);
                        return;
                    }
                    conn = connection;
                    scb(null);
                });
            },
            function (scb) {
                User.select(conn, {
                    email: userEmail
                }, function (err, users) {
                    if (err) {
                        scb(err);
                    } else if (users.length === 0) {
                        scb(new DError('DATAMGR012'), {
                            email: userEmail
                        });
                    } else {
                        user = users[0];
                        scb(null);
                    }
                });
            },
            function (scb) {
                async.eachSeries(user.groups, function (groupName, ecb) {
                    Group.select(conn, {
                        name: groupName
                    }, function (err, results) {
                        if (err) {
                            ecb(err);
                        } else {
                            groups.push(results[0]);
                            ecb(null);
                        }
                    });
                }, function (err) {
                    scb(err);
                });
            },
            function (scb) {
                distribution = prjmgr.getDistribution(distName);
                if (!distribution) {
                    scb(new DError('DATAMGR013'), {
                        distName: distName
                    });
                } else {
                    scb(null);
                }
            },
            function (scb) {
                project = prjmgr.getProject(prjName, distName);
                if (!project) {
                    scb(new DError('DATAMGR014'), {
                        prjName: prjName
                    });
                } else {
                    scb(null);
                }
            }],
            function (err) {
                conn.release();
                if (err) {
                    callback(err);
                    return;
                }
                project.checkJobPrivilege(user, groups, distribution, options, function (err) {
                    callback(err);
                });
            });
    };

    /**
     * @method addTriggerInternal
     * @param {string} user_email
     * @param {string} subject
     * @param {string} contents
     * @param {string} summary
     * @param {list} trigger_routes
     * @param {string} type
     * @param {module:lib/utils.callback_error} callback
     * @return {string} trigger_id
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addTriggerInternal = function (name, type, status, serverType, data, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                self.log.info('Adding trigger');
                Trigger.create(null, name, type, status, serverType, data, function (err, trigger) {
                    Trigger.insert(connection, trigger, function (err, id) {
                        connection.release();
                        if (err) {
                            self.log.info('Adding trigger failed!');
                            self.log.info(err);
                            callback(err, null);
                        } else {
                            self.log.info('Added trigger succeeded! ID: ' + id);
                            callback(err, id);
                        }
                    });
                });
            }
        });
    };

    /**
     * @method updateTriggerInternal
     * @param {object} trigger
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateTriggerInternal = function (trigger, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Trigger.update(connection, trigger, function (err) {
                    connection.release();
                    if (err) {
                        self.log.info('Updating trigger failed!');
                        self.log.info(err);
                        callback(err);
                    } else {
                        callback(err);
                    }
                });
            }
        });
    };

    /**
     * @method searchTriggersInternal
     * @param {string} condition
     * @param {module:lib/utils.callback_error} callback
     * @return {list} triggers
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchTriggersInternal = function (condition, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Trigger.select(connection, condition, function (err, triggers) {
                    connection.release();
                    if (err) {
                        self.log.info('Search trigger failed!');
                        self.log.info(err);
                        callback(err, null);
                    } else {
                        callback(err, triggers);
                    }
                });
            }
        });
    };

    /**
     * @method deleteTriggerInternal
     * @param {string} trigger id
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteTriggerInternal = function (triggerId, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Trigger.delete(connection, triggerId, function (err) {
                    if (err) {
                        self.log.info('Removing trigger failed!...' + triggerId);
                        self.log.info(err);
                        callback(err);
                    } else {
                        self.log.info('Removing succeeded!...' + triggerId);
                        callback(err);
                    }
                });
            }
        });
    };
    /**
     * @method addApprovalInternal
     * @param {string} user_email
     * @param {string} subject
     * @param {string} contents
     * @param {string} summary
     * @param {list} approval_routes
     * @param {string} type
     * @param {module:lib/utils.callback_error} callback
     * @return {string} approval_id
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addApprovalInternal = function (email, subject, contents, summary, approvalRoutes, type, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                self.log.info('Adding approval');
                var approval = Approval.create(email, subject, contents, summary, approvalRoutes, type);
                Approval.insert(connection, approval, function (err, id) {
                    connection.release();
                    if (err) {
                        self.log.info('Adding approval failed!');
                        self.log.info(err);
                        callback(err, null);
                    } else {
                        self.log.info('Added approval succeeded! ID: ' + id);
                        callback(err, id);
                    }
                });
            }
        });
    };

    /**
     * @method updateApprovalInternal
     * @param {object} approval
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateApprovalInternal = function (approval, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Approval.update(connection, approval, function (err) {
                    connection.release();
                    if (err) {
                        self.log.info('Updating approval failed!');
                        self.log.info(err);
                        callback(err);
                    } else {
                        callback(err);
                    }
                });
            }
        });
    };

    /**
     * @method searchApprovalsInternal
     * @param {string} condition
     * @param {module:lib/utils.callback_error} callback
     * @return {list} approvals
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchApprovalsInternal = function (condition, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Approval.select(connection, condition, function (err, approvals) {
                    connection.release();
                    if (err) {
                        self.log.info('Search approval failed!');
                        self.log.info(err);
                        callback(err, null);
                    } else {
                        callback(err, approvals);
                    }
                });
            }
        });
    };

    /**
     * @method deleteApprovalInternal
     * @param {string} approval id
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteApprovalInternal = function (approvalId, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Approval.delete(connection, approvalId, function (err) {
                    if (err) {
                        self.log.info('Removing approval failed!...' + approvalId);
                        self.log.info(err);
                        callback(err);
                    } else {
                        self.log.info('Removing succeeded!...' + approvalId);
                        callback(err);
                    }
                });
            }
        });
    };

    /**
     * @method addServerInternal
     * @param {string} type
     * @param {string} parentId
     * @param {string} osType
     * @param {string} accessIp
     * @param {string} accessPort
     * @param {string} domainName
     * @param {string} description
     * @param {list} information
     * @param {module:lib/utils.callback_error} callback
     * @return {object} server
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addServerInternal = function (id, type, parentId, osType, host, port, domainName, description, information, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                self.log.info('Adding server');
                Server.create(id, type, parentId, osType, host, port, domainName, description, information, function (err1, server) {
                    if (err1) {
                        self.log.error('Create fail: ' + err1);
                        callback(err1, null);
                        return;
                    }
                    Server.insert(connection, server, function (err, result) {
                        connection.release();
                        if (err) {
                            self.log.info('Adding server failed!');
                            self.log.info(err);
                            callback(err, null);
                        } else {
                            self.log.info('Added server succeeded! ID: ' + result.id);
                            callback(err, result);
                        }
                    });
                });
            }
        });
    };

    /**
     * @method updateServerInternal
     * @param {object} server
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateServerInternal = function (server, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Server.update(connection, server, function (err) {
                    connection.release();
                    if (err) {
                        self.log.info('Updating server failed!');
                        self.log.info(err);
                        callback(err);
                    } else {
                        callback(err);
                    }
                });
            }
        });
    };

    /**
     * @method searchServersInternal
     * @param {string} condition
     * @param {module:lib/utils.callback_error} callback
     * @return {list} servers
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchServersInternal = function (condition, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Server.select(connection, condition, function (err, servers) {
                    connection.release();
                    if (err) {
                        self.log.info('Search servers failed!');
                        self.log.info(err);
                        callback(err, null);
                    } else {
                        callback(err, servers);
                    }
                });
            }
        });
    };

    /**
     * @method searchAllServersInternal
     * @param {module:lib/utils.callback_error} callback
     * @return {list} servers
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchAllServersInternal = function (callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Server.selectAll(connection, function (err, servers) {
                    connection.release();
                    if (err) {
                        self.log.info('Search servers failed!');
                        self.log.info(err);
                        callback(err, null);
                    } else {
                        callback(err, servers);
                    }
                });
            }
        });
    };

    /**
     * @method deleteServerInternal
     * @param {string} server id
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteServerInternal = function (id, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Server.delete(connection, id, function (err) {
                    if (err) {
                        self.log.info('Removing server failed!...' + id);
                        self.log.info(err);
                        callback(err);
                    } else {
                        self.log.info('Removing succeeded!...' + id);
                        callback(err);
                    }
                });
            }
        });
    };


    /**
     * @method addDistributionInternal
     * @param {string} distName
     * @param {module:models/distribution.options} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addDistributionInternal = function (groupName, distName, type, status, access, sync, description, options, callback) {

        if (!groupName) {
            callback(new DError('DATAMGR007'), null);
            return;
        }
        if (!distName) {
            callback(new DError('DATAMGR008'), null);
            return;
        }
        if (!type) {
            callback(new DError('DATAMGR009'), null);
            return;
        }
        if (!_.contains(['OPEN', 'CLOSE'], status)) {
            callback(new DError('DATAMGR010', {
                status: status
            }), null);
            return;
        }
        if (!_.contains(['PUBLIC', 'PRIVATE'], access)) {
            callback(new DError('DATAMGR011', {
                access: access
            }), null);
            return;
        }
        if (!_.isObject(options)) {
            options = {};
        }

        var distInfo = null;
        async.waterfall([
            // create distribution in DB
            function (cb) {
                prjmgr.addDistribution(groupName, distName, type, status, access, sync, description, options,
                    function (err, dist) {
                        distInfo = dist;
                        cb(err);
                    });
            },
            // create distribution in repo
            function (cb) {
                dibs.rpc.repo.searchDistributions({
                    distName: distName,
                    repoType: type
                }, cb);
            },
            function (dists, cb) {
                if (dists.length === 0) {
                    var repo_options = {};
                    if (options) {
                        repo_options = _.clone(options);
                    }

                    if (sync) {
                        repo_options = _.extend(repo_options, {
                            SYNC_METHOD: sync
                        });
                    }

                     dibs.rpc.repo.createDistribution(distName, type, repo_options,
                        function (err) {
                            cb(err);
                        });
                } else {
                    cb(null);
                }
            },
            function (cb) {
                if (options.cloneProject) {
                    prjmgr.loadProjects(cb);
                } else {
                    cb(null);
                }
            }], function (err) {
            callback(err, distInfo);
        });
    };


    /**
     * @method removeDistributionInternal
     * @param {string} distName
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.removeDistributionInternal = function (distName, callback) {

        async.waterfall([
            // query distribution
            function (cb) {
                var dist = prjmgr.getDistribution(distName);
                if (dist) {
                    cb(null, dist.type);
                } else {
                    cb(new DError('DATAMGR005', {
                        distName: distName
                    }), null);
                }
            },
            // remove from repo if exist
            function (repoType, cb) {
                dibs.rpc.repo.removeDistribution(distName, {
                    repoType: repoType
                }, cb);
            },
            // update db model
            function (cb) {
                prjmgr.removeDistribution(distName, cb);
            }], function (err) {
            callback(err);
        });
    };


    /**
     * @method updateDistributionInternal
     * @param {string} dist
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateDistributionInternal = function (dist, callback) {
        self.log.info('Updating distribution...');
        async.waterfall([
            function (cb) {
                var newOpts = {};
                for (var key in dist.options) {
                    newOpts[key] = dist.options[key];
                }
                newOpts.repoType = dist.type;
                newOpts.SYNC_METHOD = dist.sync;
                dibs.rpc.repo.updateDistributionConfig(dist.name, newOpts, cb);

            }], function (err) {
            if (err) {
                callback(err, null);
            } else {
                // update model
                prjmgr.updateDistribution(dist, callback);
            }
        });
    };


    /**
     * @method searchDistributionsInternal
     * @param {module:models/distribution.distributionQuerySql} cond
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchDistributionsInternal = function (cond, callback) {
        async.waterfall([
            function (cb) {
                prjmgr.searchDistributions(cond, function (err, distributions) {
                    if (err) {
                        cb(err, []);
                    } else {
                        cb(null, distributions);
                    }
                });
            },
            function (dists, cb) {
                if (cond && cond.privilege) {
                    var privilegeDistributions = [];

                    // async.eachSeries(dists,
                    async.each(dists,
                        function (dist, cb1) {
                            checkDistributionPrivilege(cond.privilege, {
                                distName: dist.name
                            }, cond.email, function (err1, allowed) {
                                if (!err1 && allowed) {
                                    privilegeDistributions.push(dist);
                                }
                                cb1(err1);
                            });
                        },
                        function (err) {
                            if (err) {
                                cb(err, []);
                            } else {
                                cb(err, privilegeDistributions);
                            }
                        });
                } else {
                    cb(null, dists);
                }
            },
            function (dists, cb) {
                if (cond.queryPrivilege) {
                    // update privileges into each distribution
                    async.map(dists,
                        function (distribution, mcb) {
                            queryDistributionPrivilege(distribution.name, cond.email, function (err, privileges) {
                                distribution.privileges = privileges;
                                mcb(err, distribution);
                            });
                        },
                        function (err, newDistributions) {
                            newDistributions.projects = {};
                            cb(err, newDistributions);
                        });
                } else {
                    cb(null, dists);
                }
            }
        ],
        function (err, distributions) {
            callback(err, distributions);
        });
    };

    /**
     * @method addProjectInternal
     * @param {string} name: snapshot name
     * @param {string} distName: distribution name
     * @param {string} type: distribution type
     * @param {string} time: snapshot creation time
     * @param {string} attribute:
     * @param {string} path: snapshot path
     * @param {string} description: snapshot description
     * @param {module:models/snapshot} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addSnapshotInternal = function (name, distName, type, time, status,
        attribute, path, description, options, callback) {

        var conn = null;

        async.waterfall([
            function (cb) {
                db.getConnection(function (err1, connection) {
                    if (err1) {
                        self.log.error('Failed to connect DB: ' + err1);
                    } else {
                        conn = connection;
                    }
                    cb(err1);
                });
            },
            function (cb) {
                Snapshot.create(name, distName, type, time, status,
                    attribute, path, description, options,
                    function (err1, snapshot) {
                        if (err1) {
                            cb(err1, []);
                        } else {
                            if (options['ARTIFACTS']) {
                                snapshot.artifacts = options['ARTIFACTS'];
                                delete options['ARTIFACTS'];
                            }
                            cb(null, snapshot);
                        }
                    });
            },
            function (snapshot, cb) {
                Snapshot.insert(conn, snapshot, function (err1, result) {
                    cb(err1, result);
                });
            }
        ],
        function (err, result) {
            conn.release();
            callback(err, result);
        });
    };


    /**
     * @method searchSnapshotsInternal
     * @param {object} condition: snapshot search conditions
     *  name: snapshot name
     *  distName: distribution name
     *  type: snapshot type
     *  status: snapshot status (OPEN, CLOSE)
     *  attribute: snapshot attribute (auto, manual, etc.)
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchSnapshotsInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Snapshot.select(conn, condition, function (err1, snapshots) {
                    conn.release();
                    if (err1) {
                        callback(err1, null);
                    } else {
                        callback(err1, snapshots);
                    }
                });
            }
        });
    };


    /**
     * @method updateSnapshotInternal
     * @param {object} snapshot: snapshot object for update
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateSnapshotInternal = function (snapshot, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Snapshot.update(conn, snapshot, function (err1) {
                    conn.release();
                    if (err1) {
                        self.log.error(err1);
                    }
                    callback(err1);
                });
            }
        });
    };


    /**
     * @method updateSnapshotStatusInternal
     * @param {object} snapshot: snapshot object for update
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateSnapshotStatusInternal = function (snapshot, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Snapshot.updateStatus(conn, snapshot, function (err1) {
                    conn.release();
                    if (err1) {
                        self.log.error(err1);
                    }
                    callback(err1);
                });
            }
        });
    };


    /**
     * @method deleteSnapshotInternal
     * @param {number} id: snapshot id
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteSnapshotInternal = function (id, callback) {
        var conn = null;

        async.waterfall([
            function (cb) {
                db.getConnection(function (err1, connection) {
                    conn = connection;
                    cb(err1);
                });
            },
            function (cb) {
                Snapshot.delete(conn, id, function (err1, results) {
                    if (err1) {
                        self.log.error(err1);
                    }
                    cb(err1, results);
                });
            }
        ],
        function (err, results) {
            if (conn) {
                conn.release();
            }
            callback(err, results);
        });
    };


    /**
     * @method addArtifactInternal
     * @param {string} name: artifact name
     * @param {string} distName: distribution name
     * @param {string} type: artifact type ex) binary, archive
     * @param {string} version: artifact version
     * @param {string} environment: target os
     * @param {string} attribute: artifact attribute ex) root, extra, null
     * @param {string} fileName: artifact file name
     * @param {string} path: artifact path
     * @param {string} size: artifact file size
     * @param {string} checksum: artifact shasum value
     * @param {string} description: artifact description
     * @param {module:models/snapshot} options
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.addArtifactInternal = function (name, distName, type, version,
        environment, attribute, status, fileName, path,
        size, checksum, description, options, callback) {

        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Artifact.create(name, distName, type, version, environment, attribute, status,
                    fileName, path, size, checksum, description, options,
                    function (err, artifact) {
                        if (err) {
                            conn.release();
                            self.log.error(err);
                            callback(err, null);
                        } else {
                            Artifact.insert(conn, artifact, function (err1, result) {
                                conn.release();
                                if (err1) {
                                    callback(err1, null);
                                } else {
                                    callback(err1, result);
                                }
                            });
                        }
                    });
            }
        });
    };


    /**
     * @method searchArtifactsInternal
     * @param {object} condition: artifact search conditions
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.searchArtifactsInternal = function (condition, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Artifact.select(conn, condition, function (err1, artifacts) {
                    conn.release();
                    if (err1) {
                        callback(err1, null);
                    } else {
                        callback(err1, artifacts);
                    }
                });
            }
        });
    };


    /**
     * @method updateArtifactInternal
     * @param {object} artifact: artifact object for update
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.updateArtifactInternal = function (artifact, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Artifact.update(conn, artifact, function (err1) {
                    conn.release();
                    if (err1) {
                        self.log.error(err1);
                    }
                    callback(err1);
                });
            }
        });
    };


    /**
     * @method deleteArtifactInternal
     * @param {number} id: artifact id
     * @param {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.deleteArtifactInternal = function (id, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Failed to connect DB: ' + err);
                callback(err, null);
            } else {
                Artifact.delete(conn, id, function (err1) {
                    conn.release();
                    if (err1) {
                        self.log.error(err1);
                    }
                    callback(err1);
                });
            }
        });
    };


    /**
     * @method getDBHandle
     * @returns {object} db - db pool
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    this.getDBHandle = function () {
        return db;
    };


    this._queryStatistics = function (stype, options, callback) {
        if (statistics[stype]) {
            statistics[stype].queryStatistics(options, callback);
        } else {
            callback(new DError('DATAMGR016', {
                stype: stype
            }));
        }
    };


    // PRIVATE
    //

    // Try to connect to DB and it will return its DB schema version
    function connectToDB(callback) {
        var connection = mysql.createConnection({
            host: self.config.get('db_host'),
            port: self.config.get('db_port'),
            user: self.config.get('db_user'),
            password: self.config.get('db_password'),
            database: self.config.get('db_name')
        });

        connection.query('SELECT * FROM DB_VERSION', function (err, results) {
            connection.end();
            if (err) {
                if (err.code === 'ER_BAD_DB_ERROR' || err.code === 'ER_NO_SUCH_TABLE') {
                    createDB(function (err1) {
                        if (err1) {
                            callback(err1, null);
                        } else {
                            callback(null, 0);
                        }
                    });
                } else {
                    self.log.error('DB connection failed! ' + err);
                    callback(err, null);
                }
            } else {
                var version = results[0].version;

                if ((typeof version === 'number') && version <= CURRENT_DB_VERSION) {
                    self.log.info('DB version: ' + version.toString());
                    callback(null, version);
                } else {
                    var error = new DError('DATAMGR002', {
                        version: version.toString()
                    });
                    callback(error, null);
                }
            }
        });
    }


    function createDB(callback) {

        var connection = mysql.createConnection({
            host: self.config.get('db_host'),
            port: self.config.get('db_port'),
            user: self.config.get('db_user'),
            password: self.config.get('db_password')
        });

        var dbName = self.config.get('db_name');

        connection.query('CREATE DATABASE ' + dbName, function (err, results) {
            connection.end();
            if (err) {
                callback(err);
            } else {
                self.log.info('Creating new DB succeeded!');
                callback(null);
            }
        });
    }


    function createNewConnection(config, retryCnt, prevError, callback) {
        if (retryCnt > 3) {
            return callback(prevError, null);
        }

        var client = mysql.createConnection(config);
        client.connect(function (err) {
            if (err) {
                self.log.error(err);
                setTimeout(function () {
                    self.log.info('Retrying to connect...');
                    createNewConnection(config, retryCnt + 1, err, callback);
                }, 2000);
            } else {
                callback(err, client);
            }
        });
    }


    function getNewConnectionPool() {
        var pool = generic_pool.Pool({
            name: 'mysql',
            create: function (callback) {
                var config = {
                    host: self.config.get('db_host'),
                    port: self.config.get('db_port'),
                    user: self.config.get('db_user'),
                    password: self.config.get('db_password'),
                    database: self.config.get('db_name')
                };
                createNewConnection(config, 0, null, function (err, conn) {
                    conn.release = function () {
                        pool.release(conn);
                    };

                    conn.on('error', function (err) {
                        if (err.code === 'PROTOCOL_CONNECTION_LOST') {
                            pool.release(conn);
                            conn.end();
                        } else {
                            throw err;
                        }
                    });
                    callback(err, conn);
                });
            },

            destroy: function (client) {
                client.end();
            },

            max: 50,
            idleTimeoutMillis: 60000,

            log: function (str, level) {
                if (level === 'error') {
                    self.log.error(str);
                } else {
                    //self.log.info( str );
                }
            }
        });

        pool.getConnection = function (callback) {
            pool.acquire(function (err, conn) {
                callback(err, conn);
            });
        };

        return pool;
    /*
    var pool = mysql.createPool({
        host: self.config.get("db_host"),
        port: self.config.get("db_port"),
        user: self.config.get("db_user"),
        password: self.config.get("db_password"),
        database: self.config.get("db_name"),
        connectionLimit: 100,
        queueLimit:10
    });

    var cnt = 0;
    pool.on('connection', function(conn) {
        cnt++;
        self.log.info( "New connection: "+cnt);

        conn.on('error', function(err) {
            self.log.error( "Connection error:"+ err.code);
            self.log.error( "Remove connection in the pool");
            pool.releaseConnection(conn);
        });
    });

    pool.on('enqueue', function () {
        self.log.info('Waiting for available connection slot');
    });

    return pool;
    */
    }

    /**
     * @method initEnvironments
     * @params {Array.<module:core/plugin/extension.Extension>} exts - extension list
     * @params {module:lib/utils.callback_error} callback
     * @memberOf module:servers/data-manager/server.DataManagerServer
     */
    function initEnvironments(exts, callback) {

        async.eachSeries(exts, function (ext, ecb) {
            searchEnvs({
                id: ext.id
            }, function (err, env) {
                if (err) {
                    ecb(err, null);
                    return;
                }

                if (env.length === 0) {
                    addEnv(ext.id, ext.name, ext.description, ext.spec, function (err, data) {
                        ecb(err, null);
                    });
                } else {
                    //TO-DO UPDATE
                    ecb(err, null);
                }
            });
        }, function (err, result) {
            callback(err);
        });
    }

    function migrateDB(oldVersion, callback) {
        db.getConnection(function (err, connection) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err);
                return;
            }

            var i;
            var j;
            var steps = [];
            for (i = oldVersion; i < CURRENT_DB_VERSION; i++) {
                var msteps = migrateSteps[i];
                for (j = 0; j < msteps.length; j++) {
                    var step = msteps[j];
                    steps.push(step);
                }
            }

            // if updates exists, update its version
            if (steps.length > 0) {
                steps.push('UPDATE DB_VERSION SET version = ' + CURRENT_DB_VERSION);
            }

            self.log.info('Migrating DB...');
            async.reduce(steps, null,
                function (oldRows, step, rcb) {
                    var sql = '';
                    var options = [];
                    if (typeof step === 'string') {
                        self.log.info('Executing SQL: ' + step);
                        sql = step;
                    } else {
                        sql = step.sql;
                        options = step.genObj(oldRows);
                        self.log.info('Executing SQL: ' + step.sql + ' << ' + JSON.stringify(options));
                    }
                    connection.query(sql, options, function (err, newRows) {
                        rcb(err, newRows);
                    });
                }, function (err) {
                    connection.release();
                    callback(err);
                });
        });
    }


    function insertNewJobIntoDB(newJob, callback) {
        db.getConnection(function (err, conn) {
            if (err) {
                self.log.error('Connect DB fail: ' + err);
                callback(err, null);
            } else {
                Job.insert(conn, newJob, function (err, id) {
                    conn.release();
                    if (err) {
                        self.log.info('Adding job failed!');
                        callback(err, null);
                    } else {
                        newJob.id = id;
                        self.log.info('Added job succeeded! :#' + id);
                        callback(null, newJob);
                    }
                });
            }
        });
    }


    function addConnectionChecker(conn) {
        self.addScheduledAction('keep-connection', {
            startTime: new Date((new Date()).getTime() + 1000),
            period: 10 * 60 * 1000, // 10 minutes
            timeout: 30000 //30 sec
        }, function (cb) {

            // skip checking when launching server
            if (dibs.thisServer.status !== 'RUNNING') {
                cb(null); return;
            }

            var currConn = null;
            async.waterfall([
                function (cb1) {
                    conn.getConnection(function (err, conn) {
                        if (err) {
                            self.log.error('Connect DB fail: ' + err);
                        }
                        cb1(err, conn);
                    });
                },
                function (conn, cb1) {
                    currConn = conn;
                    conn.query('SELECT 1', cb1);
                }],
                function (err) {
                    // release connection if needed
                    if (currConn) {
                        currConn.release();
                    }

                    if (err) {
                        self.log.error('Checking DB Connection failed!: ' + err);
                        if (err.toString() === 'Error: This socket has been ended by the other party') {
                            self.log.info('Recovery DB connection');
                            db = getNewConnectionPool();
                        }
                    } else {
                        self.log.info('Checking DB Connection OK!');
                    }
                    cb(err);
                });

        });
    }


    function queryDistributionPrivilege(distName, userEmail, callback) {
        var hasPrivileges = [];
        async.series([
            function (scb) {
                isAdministrator(userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('CREATE');
                        hasPrivileges.push('DELETE');
                    }
                    scb(null);
                });
            },
            function (scb) {
                isDistributionVisible(distName, userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('READ');
                    }
                    scb(null);
                });
            },
            function (scb) {
                isDistributionModifiable(distName, userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('UPDATE');
                    }
                    scb(null);
                });
            }
        ], function (err) {
            callback(err, hasPrivileges);
        });
    }

    function checkDistributionPrivilege(method, options, userEmail, callback) {
        switch (method) {
        case 'CREATE':
            isAdministrator(userEmail, callback);
            break;
        case 'READ':
            if (options.distName) {
                isDistributionVisible(options.distName, userEmail, callback);
            } else {
                 // getting dist list is always possible!
                callback(null, true);
            }
            break;
        case 'UPDATE':
            isDistributionModifiable(options.distName, userEmail, callback);
            break;
        case 'DELETE':
            isAdministrator(userEmail, callback);
            break;
        default:
            callback(new DError('DATAMGR018', {
                method: method
            }));
            break;
        }
    }

    function queryProjectPrivilege(distName, prjName, userEmail, callback) {
        var hasPrivileges = [];
        async.series([
            function (scb) {
                isDistributionVisible(distName, userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('READ');
                    }
                    scb(null);
                });
            },
            function (scb) {
                isProjectExecutable(distName, prjName, userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('EXECUTE');
                    }
                    scb(null);
                });
            },
            function (scb) {
                isDistributionModifiable(distName, userEmail, function (err, allowed) {
                    if (allowed) {
                        hasPrivileges.push('CREATE');
                        hasPrivileges.push('UPDATE');
                        hasPrivileges.push('DELETE');
                    }
                    scb(null);
                });
            }
        ], function (err) {
            callback(err, hasPrivileges);
        });
    }

    function checkProjectPrivilege(method, options, userEmail, callback) {
        switch (method) {
        case 'CREATE':
            isDistributionModifiable(options.distName, userEmail, callback);
            break;
        case 'READ':
            isDistributionVisible(options.distName, userEmail, callback);
            break;
        case 'UPDATE':
            isDistributionModifiable(options.distName, userEmail, callback);
            break;
        case 'DELETE':
            isDistributionModifiable(options.distName, userEmail, callback);
            break;
        case 'EXECUTE':
            isProjectExecutable(options.distName, options.prjName, userEmail, callback);
            break;
        default:
            callback(new DError('DATAMGR018', {
                method: method
            }));
            break;
        }
    }


    function isAdministrator(userEmail, callback) {
        var conn = null;

        async.waterfall([
            function (cb) {
                db.getConnection(cb);
            },
            function (c, cb) {
                conn = c;
                User.select(conn, {
                    email: userEmail
                }, function (err, users) {
                    if (err) {
                        cb(err, null);
                    } else if (users.length === 0) {
                        cb(new DError('DATAMGR012', {
                            email: userEmail
                        }), null);
                    } else {
                        cb(null, users[0]);
                    }
                });
            },
            function (user, cb) {
                async.map(user.groups, function (groupName, cb1) {
                    Group.select(conn, {
                        name: groupName
                    }, function (err, results) {
                        if (err) {
                            cb1(err, null);
                        } else if (results.length === 0) {
                            cb1(new DError('DATAMGR019', {
                                group: groupName
                            }), null);
                        } else {
                            cb1(null, results[0]);
                        }
                    });
                }, function (err, results) {
                    if (err) {
                        cb(err, false);
                    } else {
                        // check if my groups are admin
                        cb(null, results.filter(function (e) {
                            return e.id === 1;
                        }).length >= 1);
                    }
                });
            }], function (err, result) {
            if (conn) {
                conn.release();
            }

            if (err) {
                callback(err, false);
            } else {
                callback(err, result);
            }
        });
    }


    function isDistributionVisible(distName, userEmail, callback) {
        var conn = null;

        if (!distName) {
            callback(new DError('DATAMGR013', {
                distName: distName
            }), null);
            return;
        }

        var dist = prjmgr.getDistribution(distName);

        if (dist && dist.access === 'PUBLIC') {
            return callback(null, true);
        }

        async.waterfall([
            function (cb) {
                db.getConnection(cb);
            },
            function (c, cb) {
                conn = c;
                User.select(conn, {
                    email: userEmail
                }, function (err, users) {
                    if (err) {
                        cb(err, null);
                    } else if (users.length === 0) {
                        cb(new DError('DATAMGR012', {
                            email: userEmail
                        }), null);
                    } else {
                        cb(null, users[0]);
                    }
                });
            },
            function (user, cb) {
                async.map(user.groups, function (groupName, cb1) {
                    Group.select(conn, {
                        name: groupName
                    }, function (err, results) {
                        if (err) {
                            cb1(err, null);
                        } else if (results.length === 0) {
                            cb1(new DError('DATAMGR019', {
                                group: groupName
                            }), null);
                        } else {
                            cb1(null, results[0]);
                        }
                    });
                }, cb);
            },
            function (groups, cb) {
                // check my groups includes admin
                if (groups.filter(function (e) {
                    return e.id === 1;
                }).length >= 1) {
                    cb(null, true);
                } else {
                    if (dist && groups.filter(function (e) {
                        return e.name === dist.groupName;
                    }).length >= 1) {
                        cb(null, true);
                    } else {
                        cb(null, false);
                    }
                }
            }
        ], function (err, result) {
            if (conn) {
                conn.release();
            }

            if (err) {
                callback(err, false);
            } else {
                callback(err, result);
            }
        });
    }


    function isDistributionModifiable(distName, userEmail, callback) {
        var conn = null;

        if (!distName) {
            callback(new DError('DATAMGR013', {
                distName: distName
            }), null);
            return;
        }

        async.waterfall([
            function (cb) {
                db.getConnection(cb);
            },
            function (c, cb) {
                conn = c;
                User.select(conn, {
                    email: userEmail
                }, function (err, users) {
                    if (err) {
                        cb(err, null);
                    } else if (users.length === 0) {
                        cb(new DError('DATAMGR012', {
                            email: userEmail
                        }), null);
                    } else {
                        cb(null, users[0]);
                    }
                });
            },
            function (user, cb) {
                async.map(user.groups, function (groupName, cb1) {
                    Group.select(conn, {
                        name: groupName
                    }, function (err, results) {
                        if (err) {
                            cb1(err, null);
                        } else if (results.length === 0) {
                            cb1(new DError('DATAMGR019', {
                                group: groupName
                            }), null);
                        } else {
                            cb1(null, results[0]);
                        }
                    });
                }, cb);
            },
            function (groups, cb) {
                // check my groups includes admin
                if (groups.filter(function (e) { return e.id === 1; }).length >= 1) {
                    cb(null, true);
                } else {
                    var dist = prjmgr.getDistribution(distName);
                    if (dist && groups.filter(function (e) { return e.name === dist.groupName; }).length >= 1) {
                        cb(null, true);
                    } else {
                        cb(null, false);
                    }
                }
            }
        ], function (err, result) {
            if (conn) {
                conn.release();
            }

            if (err) {
                callback(err, false);
            } else {
                callback(err, result);
            }
        });
    }

    function isProjectExecutable(distName, projectName, userEmail, callback) {
        var conn = null;

        if (!distName) {
            callback(new DError('DATAMGR013', {
                distName: distName
            }), null);
            return;
        }

        async.waterfall([
            function (cb) {
                db.getConnection(cb);
            },
            function (c, cb) {
                conn = c;
                User.select(conn, {
                    email: userEmail
                }, function (err, users) {
                    if (err) {
                        cb(err, null);
                    } else if (users.length === 0) {
                        cb(new DError('DATAMGR012', {
                            email: userEmail
                        }), null);
                    } else {
                        cb(null, users[0]);
                    }
                });
            },
            function (user, cb) {
                async.map(user.groups, function (groupName, cb1) {
                    Group.select(conn, {
                        name: groupName
                    }, function (err, results) {
                        if (err) {
                            cb1(err, null);
                        } else if (results.length === 0) {
                            cb1(new DError('DATAMGR019', {
                                group: groupName
                            }), null);
                        } else {
                            cb1(null, results[0]);
                        }
                    });
                }, cb);
            },
            function (groups, cb) {
                var prj = prjmgr.getProject(projectName, distName);
                var allowed = false;
                _.each(groups, function (group) {
                    if (_.contains(group.projects, prj.id)) {
                        allowed = true;
                    }
                });
                cb(null, allowed);
            }
        ], function (err, result) {
            if (conn) {
                conn.release();
            }

            if (err) {
                callback(err, false);
            } else {
                callback(err, result);
            }
        });
    }
}


/**
 * @function createServer
 * @param {string} serverId - serverId
 * @returns {module:servers/data-manager/server.DataManagerServer}
 * @memberOf module:servers/data-manager/server
 */


module.exports.createServer = function (serverId) {
    DataManagerServer.prototype = dibs.BaseServer.createServer(serverId, 'datamgr');

    return new DataManagerServer();
};
