@Getter
public enum  AgentConfiguration {

    INSTANCE;

    //版本不能大于 x.9
    public static final float VERSION = (float) 11.8;

    /**
     * quartz配置文件路径
     */
    private String quartzConfPath = null;
    /**
     * push到falcon的地址
     */
    private String agentPushUrl = null;

    /**
     * 插件配置的目录
     */
    private String pluginConfPath;

    /**
     * 授权配置文件路径
     */
    private String authorizationConfPath;

    /**
     * 集成的Falcon文件夹路径
     */
    private String falconDir;

    /**
     * Falcon的配置文件目录路径
     */
    private String falconConfDir;

    /**
     * 服务配置文件路径
     */
    private String agentConfPath = null;
    private String jmxCommonMetricsConfPath = null;

    /**
     * agent监控指标的主体说明
     */
    private String agentEndpoint = "";

    /**
     * agent启动端口
     */
    private int agentPort = 4518;

    /**
     * mock的有效时间
     */
    private int mockValidTime = 10800;

    /**
     * agent web 监听端口
     */
    private int agentWebPort = 4519;

    /**
     * 是否启用web服务
     */
    private boolean webEnable = true;

    /**
     * agent自动发现服务刷新周期
     */
    private int agentFlushTime = 300;

    /**
     * agent最大线程数
     */
    private int agentMaxThreadCount = 200;

    /**
     * JMX连接是否支持本地连接
     */
    private boolean agentJMXLocalConnect = false;

    private String agentUpdateUrl = null;

    private String agentHomeDir;


    private static final String CONF_AGENT_ENDPOINT = "agent.endpoint";
    private static final String CONF_AGENT_HOME = "agent.home.dir";
    private static final String CONF_AGENT_FLUSH_TIME = "agent.flush.time";
    private static final String CONF_AGENT_MAX_THREAD = "agent.thread.maxCount";

    private static final String CONF_AGENT_FALCON_PUSH_URL = "agent.falcon.push.url";
    private static final String CONF_AGENT_PORT = "agent.port";
    private static final String CONF_AGENT_WEB_PORT = "agent.web.port";
    private static final String CONF_AGENT_WEB_ENABLE = "agent.web.enable";
    private static final String CONF_AGENT_MOCK_VALID_TIME = "agent.mock.valid.time";
    private static final String AUTHORIZATION_CONF_PATH = "authorization.conf.path";
    private static final String FALCON_DIR_PATH = "agent.falcon.dir";
    private static final String FALCON_CONF_DIR_PATH = "agent.falcon.conf.dir";
    private static final String CONF_AGENT_JMX_LOCAL_CONNECT = "agent.jmx.localConnectSupport";
    private static final String CONF_AGENT_UPDATE_URL = "agent.update.pack.url";

    private Properties agentConf = null;

    /**
     * 初始化agent配置
     */
    AgentConfiguration() {
        instance();
    }

    private void instance(){
        if(StringUtils.isEmpty(System.getProperty("agent.conf.path"))){
            System.err.println("agent agent.properties 配置文件位置读取失败,请确定系统配置项:" + "agent.conf.path");
            System.exit(0);
        }else{
            this.agentConfPath = System.getProperty("agent.conf.path");
        }

        if(StringUtils.isEmpty(System.getProperty(CONF_AGENT_HOME))){
            System.err.println("agent home dir 读取失败,请确定系统配置项:" + CONF_AGENT_HOME);
            System.exit(0);
        }else{
            this.agentHomeDir = System.getProperty(CONF_AGENT_HOME);
        }

        if(StringUtils.isEmpty(System.getProperty(AUTHORIZATION_CONF_PATH))){
            System.err.println("agent authorization.properties 配置文件位置读取失败,请确定系统配置项:" + AUTHORIZATION_CONF_PATH);
            System.exit(0);
        }else{
            this.authorizationConfPath = System.getProperty(AUTHORIZATION_CONF_PATH);
        }

        if(StringUtils.isEmpty(System.getProperty(FALCON_DIR_PATH))){
            System.err.println("falcon 目录位置读取失败,请确定系统配置项:" + FALCON_DIR_PATH);
            System.exit(0);
        }else{
            this.falconDir = System.getProperty(FALCON_DIR_PATH);
        }

        if(StringUtils.isEmpty(System.getProperty(FALCON_CONF_DIR_PATH))){
            System.err.println("falcon conf 目录位置读取失败,请确定系统配置项:" + FALCON_CONF_DIR_PATH);
            System.exit(0);
        }else{
            this.falconConfDir = System.getProperty(FALCON_CONF_DIR_PATH);
        }

        if(StringUtils.isEmpty(System.getProperty("agent.plugin.conf.dir"))){
            System.err.println("agent 配置文件位置读取失败,请确定系统配置项:" + "agent.plugin.conf.dir");
            System.exit(0);
        }else{
            this.pluginConfPath = System.getProperty("agent.plugin.conf.dir");
        }

        if(StringUtils.isEmpty(System.getProperty("agent.quartz.conf.path"))){
            System.err.println("quartz 配置文件位置读取失败,请确定系统配置项:" + "agent.quartz.conf.path");
            System.exit(0);
        }else{
            this.quartzConfPath = System.getProperty("agent.quartz.conf.path");
        }

        agentConf = new Properties();
        try(FileInputStream in = new FileInputStream(this.agentConfPath)){
            agentConf.load(in);
        }catch (IOException e) {
            System.err.println(this.agentConfPath + " 配置文件读取失败 Agent启动失败");
            System.exit(0);
        }
        init();
        initJMXCommon();
    }

    private void init(){

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_ENDPOINT))){
            this.agentEndpoint = agentConf.getProperty(CONF_AGENT_ENDPOINT);
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_WEB_PORT))){
            try {
                this.agentWebPort = Integer.parseInt(agentConf.getProperty(CONF_AGENT_WEB_PORT));
            } catch (Exception e) {
                System.err.println(String.format("Agent启动失败,端口配置%s无效:%s", CONF_AGENT_WEB_PORT, agentConf.getProperty(CONF_AGENT_WEB_PORT)));
                System.exit(0);
            }
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_WEB_ENABLE))){
            this.webEnable = "true".equals(agentConf.getProperty(CONF_AGENT_WEB_ENABLE));
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_MOCK_VALID_TIME))){
            try {
                this.mockValidTime = Integer.parseInt(agentConf.getProperty(CONF_AGENT_MOCK_VALID_TIME));
            } catch (NumberFormatException e) {
                System.err.println(String.format("Agent启动失败,mock有效时间配置 %s 无效: %s", CONF_AGENT_MOCK_VALID_TIME, agentConf.getProperty(CONF_AGENT_MOCK_VALID_TIME)));
                System.exit(0);
            }
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_JMX_LOCAL_CONNECT))){
            this.agentJMXLocalConnect = "true".equals(agentConf.getProperty(CONF_AGENT_JMX_LOCAL_CONNECT));
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_UPDATE_URL))){
            this.agentUpdateUrl = agentConf.getProperty(CONF_AGENT_UPDATE_URL);
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_PORT))){
            try {
                this.agentPort = Integer.parseInt(agentConf.getProperty(CONF_AGENT_PORT));
            } catch (NumberFormatException e) {
                System.err.println(String.format("Agent启动失败,端口配置%s无效:%s", CONF_AGENT_PORT, agentConf.getProperty(CONF_AGENT_PORT)));
                System.exit(0);
            }
        }

        if(StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_FALCON_PUSH_URL))){
            System.err.println("Agent启动失败,未定义falcon push地址配置:" + CONF_AGENT_FALCON_PUSH_URL);
            System.exit(0);
        }
        this.agentPushUrl = agentConf.getProperty(CONF_AGENT_FALCON_PUSH_URL);

        if(StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_FLUSH_TIME))){
            System.err.println("Agent启动失败,未指定配置:" + CONF_AGENT_FLUSH_TIME);
            System.exit(0);
        }
        try {
            this.agentFlushTime = Integer.parseInt(agentConf.getProperty(CONF_AGENT_FLUSH_TIME));
            if(this.agentFlushTime <= 0){
                System.err.println(String.format("Agent启动失败,自动发现服务的刷新周期配置 %s 必须大于0: %s",CONF_AGENT_FLUSH_TIME,agentConf.getProperty(CONF_AGENT_FLUSH_TIME)));
                System.exit(0);
            }
        } catch (NumberFormatException e) {
            System.err.println(String.format("Agent启动失败,自动发现服务的刷新周期配置%s无效:%s",CONF_AGENT_FLUSH_TIME,agentConf.getProperty(CONF_AGENT_FLUSH_TIME)));
            System.exit(0);
        }

        if(!StringUtils.isEmpty(agentConf.getProperty(CONF_AGENT_MAX_THREAD))){
            try {
                this.agentMaxThreadCount = Integer.parseInt(agentConf.getProperty(CONF_AGENT_MAX_THREAD));
                if(this.agentMaxThreadCount <= 5){
                    System.err.println(String.format("Agent启动失败,最大线程数 %s 必须大于5: %s",CONF_AGENT_MAX_THREAD,agentConf.getProperty(CONF_AGENT_MAX_THREAD)));
                    System.exit(0);
                }
            } catch (NumberFormatException e) {
                System.err.println(String.format("Agent启动失败,最大线程数%s无效:%s",CONF_AGENT_MAX_THREAD,agentConf.getProperty(CONF_AGENT_MAX_THREAD)));
                System.exit(0);
            }
        }

    }

    private void initJMXCommon(){
        String property = System.getProperty("agent.jmx.metrics.common.path");
        if(StringUtils.isEmpty(property)){
            System.err.println("jmx 的公共配置系统属性文件未定义:agent.jmx.metrics.common.path");
            System.exit(0);
        }
        this.jmxCommonMetricsConfPath = property;
    }

} @Slf4j
public class JMXConnection {
    private static final Map<String,JMXConnectionInfo> connectCacheLibrary = new HashMap<>();//JMX的连接缓存
    private static final Map<String,Integer> serverConnectCount = new HashMap<>();//记录服务应有的JMX连接数

    private String serverName;

    public JMXConnection(String serverName) {
        this.serverName = serverName;
    }

    /**
     * 删除JMX连接池连接
     * @param serverName
     * JMX服务名
     * @param pid
     * 进程id
     */
    public static void removeConnectCache(String serverName,int pid){
        String key = serverName + pid;
        if(connectCacheLibrary.remove(key) != null){
            //删除成功,更新serverConnectCount
            int count = serverConnectCount.get(serverName);
            serverConnectCount.put(serverName,count - 1);
            log.info("已清除JMX监控: {} , pid: {}",serverName,pid);
        }
    }

    /**
     * 根据服务名,返回该服务应有的JMX连接数
     * @param serverName
     * @return
     */
    public static int getServerConnectCount(String serverName){
        return serverConnectCount.get(serverName);
    }

    /**
     * 获取本地是否已开启指定的JMX服务
     * @param serverName
     * @return
     */
    public static boolean hasJMXServerInLocal(String serverName){
        if(!StringUtils.isEmpty(serverName)){
            List<VirtualMachineDescriptor> vms = VirtualMachine.list();
            for (VirtualMachineDescriptor desc : vms) {
                File file = new File(desc.displayName());
                if(file.exists()){
                    //java -jar 形式启动的Java应用
                    if(file.toPath().getFileName().toString().equals(serverName)){
                        return true;
                    }
                }else if(desc.displayName().contains(serverName)){
                    return true;
                }

            }
        }
        return false;
    }

    /**
     *
     * @throws IOException
     */
    public static void closeAll() {
        for (JMXConnectionInfo jmxConnectionInfo : connectCacheLibrary.values()) {
            jmxConnectionInfo.closeJMXConnector();
        }
    }

    /**
     * 获取指定服务名的本地JMX VM 描述对象
     * @param serverName
     * @return
     */
    public static List<VirtualMachineDescriptor> getVmDescByServerName(String serverName){
        List<VirtualMachineDescriptor> vmDescList = new ArrayList<>();
        List<VirtualMachineDescriptor> vms = VirtualMachine.list();
        for (VirtualMachineDescriptor desc : vms) {
            File file = new File(desc.displayName());
            if(file.exists()){
                //java -jar 形式启动的Java应用
                if(file.toPath().getFileName().toString().equals(serverName)){
                    vmDescList.add(desc);
                }
            }else if(desc.displayName().contains(serverName)){
                vmDescList.add(desc);
            }
        }
        return vmDescList;
    }



    /**
     * 获取JMX连接
     * @return
     * @throws IOException
     */
    public synchronized List<JMXConnectionInfo> getMBeanConnection(){
        if(StringUtils.isEmpty(serverName)){
            log.error("获取JMX连接的serverName不能为空");
            return new ArrayList<>();
        }

        List<VirtualMachineDescriptor> vmDescList = getVmDescByServerName(serverName);

        List<JMXConnectionInfo> connections = connectCacheLibrary.entrySet().
                stream().
                filter(entry -> NumberUtils.isNumber(entry.getKey().replace(serverName,""))).
                map(Map.Entry::getValue).
                collect(Collectors.toList());

        if(connections.isEmpty() || connections.size() < vmDescList.size()){ //JMX连接池为空,进行连接获取
            int count = 0;
            connections.clear();
            clearCache();
            for (VirtualMachineDescriptor desc : vmDescList) {
                JMXConnectUrlInfo jmxConnectUrlInfo = getConnectorAddress(desc);
                if (jmxConnectUrlInfo == null) {
                    log.error("应用 {} 的JMX连接URL获取失败",desc.displayName());
                    //对应的ServerName的JMX连接获取失败，返回该服务JMX连接失败，用于上报不可用记录
                    connections.add(initBadJMXConnect(desc));
                    count++;
                    continue;
                }

                try {
                    connections.add(initJMXConnectionInfo(getJMXConnector(jmxConnectUrlInfo),desc));
                    log.debug("应用 {} JMX 连接已建立",serverName);

                } catch (Exception e) {
                    log.error("JMX 连接获取异常:{}",e.getMessage());
                    //JMX连接获取失败，添加该服务JMX的不可用记录，用于上报不可用记录
                    connections.add(initBadJMXConnect(desc));
                }
                //该服务应有的数量++
                count++;
            }

            if(count > 0){
                serverConnectCount.put(serverName,count);
            }else{
                //对应的ServerName的JMX连接获取失败，返回该服务JMX连接失败，用于上报不可用记录
                JMXConnectionInfo jmxConnectionInfo = new JMXConnectionInfo();
                jmxConnectionInfo.setValid(false, JMXUnavailabilityType.connectionFailed);
                connections.add(jmxConnectionInfo);
                serverConnectCount.put(serverName,1);
            }
        }

        //若当前应有的服务实例记录值比获取到的记录值小，重新设置
        if(getServerConnectCount(serverName) < vmDescList.size()){
            serverConnectCount.put(serverName,vmDescList.size());
        }

        return connections;
    }

    private JMXConnector getJMXConnector(JMXConnectUrlInfo jmxConnectUrlInfo) throws Exception {
        JMXServiceURL url = new JMXServiceURL(jmxConnectUrlInfo.getRemoteUrl());
        JMXConnector connector;
        if(jmxConnectUrlInfo.isAuthentication()){
            connector = JMXConnectWithTimeout.connectWithTimeout(url,jmxConnectUrlInfo.getJmxUser()
                    ,jmxConnectUrlInfo.getJmxPassword(),10, TimeUnit.SECONDS);
        }else{
            connector = JMXConnectWithTimeout.connectWithTimeout(url,null,null,10, TimeUnit.SECONDS);
        }
        return connector;
    }

    /**
     * 清楚连接缓存
     */
    private void clearCache(){
        //清除当前连接池中的连接
        List<String> removeKey = connectCacheLibrary.keySet().stream().filter(key -> NumberUtils.isNumber(key.replace(serverName,""))).collect(Collectors.toList());
        removeKey.forEach(key -> {
            connectCacheLibrary.get(key).closeJMXConnector();
            connectCacheLibrary.remove(key);
        });
    }

    /**
     * 重置jmx连接
     * @throws IOException
     */
    public synchronized void resetMBeanConnection() {
        if(StringUtils.isEmpty(serverName)){
            log.error("获取JMX连接的serverName不能为空");
        }

        //本地JMX连接中根据指定的服务名命中的VirtualMachineDescriptor
        List<VirtualMachineDescriptor> targetDesc = getVmDescByServerName(serverName);

        //若命中的target数量大于或等于该服务要求的JMX连接数,则进行重置连接池中的连接
        if(targetDesc.size() >= getServerConnectCount(serverName)){

            //清除当前连接池中的连接
            clearCache();

            //重新设置服务应有连接数
            int count = 0;
            //重新构建连接
            for (VirtualMachineDescriptor desc : targetDesc) {

                JMXConnectUrlInfo jmxConnectUrlInfo = getConnectorAddress(desc);
                if (jmxConnectUrlInfo == null) {
                    log.error("应用{}的JMX连接URL获取失败",serverName);
                    //对应的ServerName的JMX连接获取失败，返回该服务JMX连接失败，用于上报不可用记录
                    initBadJMXConnect(desc);
                    count++;
                    continue;
                }
                try {
                    initJMXConnectionInfo(getJMXConnector(jmxConnectUrlInfo),desc);
                    log.info("应用 {} JMX 连接已建立,将在下一周期获取Metrics值时生效",serverName);
                } catch (Exception e) {
                    log.error("JMX 连接获取异常:{}",e);
                    //JMX连接获取失败，添加该服务JMX的不可用记录，用于上报不可用记录
                    initBadJMXConnect(desc);
                }
                count++;
            }
            serverConnectCount.put(serverName,count);
        }

        //若当前应有的服务实例记录值比获取到的记录值小，重新设置
        if(getServerConnectCount(serverName) < targetDesc.size()){
            serverConnectCount.put(serverName,targetDesc.size());
        }

    }



    private JMXConnectUrlInfo getConnectorAddress(VirtualMachineDescriptor desc){
        if(AgentConfiguration.INSTANCE.isAgentJMXLocalConnect()){
            String connectorAddress = AbstractJmxCommand.findJMXLocalUrlByProcessId(Integer.parseInt(desc.id()));
            if(connectorAddress != null){
                return new JMXConnectUrlInfo(connectorAddress);
            }
        }

        JMXConnectUrlInfo jmxConnectUrlInfo = null;
        try {
            jmxConnectUrlInfo = AbstractJmxCommand.findJMXRemoteUrlByProcessId(Integer.parseInt(desc.id()), InetAddress.getLocalHost().getHostAddress());
            if(jmxConnectUrlInfo != null){
                log.info("JMX Remote URL:{}",jmxConnectUrlInfo);
            }else if(!AgentConfiguration.INSTANCE.isAgentJMXLocalConnect()){
                log.warn("应用未配置JMX Remote功能,请给应用配置JMX Remote");
            }
        } catch (UnknownHostException e) {
            log.error("JMX连接本机地址获取失败",e);
        }
        return jmxConnectUrlInfo;
    }

    /**
     * JMXConnectionInfo的初始化动作
     * @param connector
     * @param desc
     * @return
     * @throws IOException
     */
    private JMXConnectionInfo initJMXConnectionInfo(JMXConnector connector,VirtualMachineDescriptor desc) throws IOException {
        JMXConnectionInfo jmxConnectionInfo = new JMXConnectionInfo();
        jmxConnectionInfo.setJmxConnector(connector);
        jmxConnectionInfo.setCacheKeyId(desc.id());
        jmxConnectionInfo.setConnectionServerName(serverName);
        jmxConnectionInfo.setConnectionQualifiedServerName(desc.displayName());
        jmxConnectionInfo.setMBeanServerConnection(connector.getMBeanServerConnection());
        jmxConnectionInfo.setValid(true,null);
        jmxConnectionInfo.setPid(Integer.parseInt(desc.id()));

        connectCacheLibrary.put(serverName + desc.id(),jmxConnectionInfo);
        return jmxConnectionInfo;
    }

    /**
     * 连接失败的JMX的初始化动作
     * @param desc
     */
    private JMXConnectionInfo initBadJMXConnect(VirtualMachineDescriptor desc){
        JMXConnectionInfo jmxConnectionInfo = new JMXConnectionInfo();
        jmxConnectionInfo.setValid(false,JMXUnavailabilityType.connectionFailed);
        jmxConnectionInfo.setConnectionServerName(serverName);
        jmxConnectionInfo.setConnectionQualifiedServerName(desc.displayName());
        jmxConnectionInfo.setPid(Integer.parseInt(desc.id()));
        connectCacheLibrary.put(serverName + desc.id(),jmxConnectionInfo);
        return jmxConnectionInfo;
    }

} public interface DetectPlugin extends Plugin {

    /**
     * 监控的具体服务的agentSignName tag值
     * @param address
     * 被监控的探测地址
     * @return
     * 根据地址提炼的标识,如域名等
     */
    String agentSignName(String address);

    /**
     * 一次地址的探测结果
     * @param address
     * 被探测的地址,地址来源于方法 {@link com.yiji.falcon.agent.plugins.DetectPlugin#detectAddressCollection()}
     * @return
     * 返回被探测的地址的探测结果,将用于上报监控状态
     */
    DetectResult detectResult(String address);

    /**
     * 被探测的地址集合
     * @return
     * 只要该集合不为空,就会触发监控
     * pluginActivateType属性将不起作用
     */
    Collection<String> detectAddressCollection();

    /**
     * 无需配置该属性,此类插件pluginActivateType属性将不起作用
     * 运行方式见 {@link com.yiji.falcon.agent.plugins.DetectPlugin#detectAddressCollection()}
     *
     * 插件运行方式
     *
     * @return
     */
    @Override
    default PluginActivateType activateType(){
        return PluginActivateType.AUTO;
    }

    /**
     * 转换配置地址为地址集合工具方法
     * @param address
     * 配置的地址
     * @param split
     * 分隔的字符串
     * null代表不分隔
     * @return
     */
    default Collection<String> helpTransformAddressCollection(String address,String split){
        Set<String> addresses = new HashSet<>();
        if(!StringUtils.isEmpty(address)){
            if(split != null){
                Collections.addAll(addresses, address.split(split));
            }else{
                if(!StringUtils.isEmpty(address)){
                    addresses.add(address);
                }
            }
        }
        return addresses;
    }

    /**
     * 自动探测地址的实现
     * 若配置文件已配置地址,将不会调用此方法
     * 若配置文件未配置探测地址的情况下,将会调用此方法,若该方法返回非null且有元素的集合,则启动运行插件,使用该方法返回的探测地址进行监控
     * @return
     */
    default Collection<String> autoDetectAddress(){
        return null;
    }
} public interface JDBCPlugin extends Plugin{

    /**
     * 数据库的JDBC连接驱动名称
     * @return
     */
    String getJDBCDriveName();

    /**
     * 数据库的连接对象集合
     * 系统将根据此对象建立数据库连接
     * @return
     */
    Collection<JDBCConnectionInfo> getConnectionInfos();

    /**
     * 数据库监控语句的配置文件
     * 默认值 插件的简单类名第一个字母小写 加 MetricsConf.properties
     * @return
     * 若不需要语句配置文件,则设置其返回null
     */
    default String metricsConfName(){
        String className = this.getClass().getSimpleName();
        return className.substring(0,1).toLowerCase() + className.substring(1) + "MetricsConf.properties";
    }

    /**
     * 配置的数据库连接地址
     *
     * @return
     * 返回与配置文件中配置的地址一样即可,用于启动判断
     */
    String jdbcConfig();

    /**
     * 该插件监控的服务标记名称,目的是为能够在操作系统中准确定位该插件监控的是哪个具体服务
     * 如该服务运行的端口号等
     * 若不需要指定则可返回null
     * @return
     */
    String agentSignName();

    /**
     * 插件监控的服务正常运行时的內建监控报告
     * 若有些特殊的监控值无法用配置文件进行配置监控,可利用此方法进行硬编码形式进行获取
     * 注:此方法只有在监控对象可用时,才会调用,并加入到监控值报告中,一并上传
     * @param connection
     * 数据库连接 不需在方法内关闭连接
     * @return
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    Collection<FalconReportObject> inbuiltReportObjectsForValid(Connection connection) throws SQLException, ClassNotFoundException;

} public interface SNMPV3Plugin extends Plugin{

    /**
     * 通过SNMPV3协议获取设备监控信息的登陆用户信息
     * @return
     * list
     * 一个 {@link com.yiji.falcon.agent.vo.snmp.SNMPV3UserInfo} 对象就是一个设备的监控
     */
    Collection<SNMPV3UserInfo> userInfo();

    /**
     * 当设备链接可用时的插件内置报告,如该插件适配的不同设备和品牌的SNMP监控报告
     * Agent会自动采集一些公共的MIB数据,但是设备私有的MIB信息,将由不同的插件自己提供
     * @param session
     * 连接设备的SNMP Session,插件可通过此对象进行设备间的SNMP通信,以获取监控数据
     * @return
     */
    Collection<FalconReportObject> inbuiltReportObjectsForValid(SNMPV3Session session);

    /**
     * 是否需要进行接口数据采集
     * @return
     */
    boolean hasIfCollect();

    /**
     * 允许采集哪些接口数据，用map的形式返回，key为接口metrics名称，value为是否允许。
     * 若对应key的metrics获取为null或false，均不采集。key的集合为：
     *
     * if.HCInBroadcastPkts
     * if.HCInMulticastPkts
     * if.HCInOctets
     * if.HCInUcastPkts
     * if.HCOutBroadcastPkts
     * if.HCOutMulticastPkts
     * if.getIfHCOutUcastPkts
     * if.OperStatus
     * if.HCOutOctets
     *
     * @return
     */
    Map<String,Boolean> ifCollectMetricsEnable();

} @Slf4j
public class JMXPluginJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        String pluginName = jobDataMap.getString("pluginName");
        try {
            JMXPlugin jmxPlugin = (JMXPlugin) jobDataMap.get("pluginObject");
            String jmxServerName = jobDataMap.getString("jmxServerName");

            List<JMXMetricsValueInfo> jmxMetricsValueInfos = JMXManager.getJmxMetricValue(jmxServerName,jmxPlugin);

            //设置agentSignName
            for (JMXMetricsValueInfo jmxMetricsValueInfo : jmxMetricsValueInfos) {
                String agentSignName = jmxPlugin.agentSignName(jmxMetricsValueInfo,
                        jmxMetricsValueInfo.getJmxConnectionInfo().getPid());
                if ("{jmxServerName}".equals(agentSignName)) {
                    //设置变量
                    jmxMetricsValueInfo.getJmxConnectionInfo().setName(jmxServerName);
                }else{
                    jmxMetricsValueInfo.getJmxConnectionInfo().setName(agentSignName);
                }
            }

            MetricsCommon jmxMetricsValue = new JMXMetricsValue(jmxPlugin,jmxMetricsValueInfos);
            ReportMetrics.push(jmxMetricsValue.getReportObjects());
        } catch (Exception e) {
            log.error("插件 {} 运行异常",pluginName,e);
        }
    }
} public enum  PluginActivateType {
    /**
     * 插件不运行
     */
    DISABLED("不启用"),
    /**
     * 无论服务是否运行,都启动插件
     */
    FORCE("强制启动"),
    /**
     * 当监控服务可用时,自动进行插件启动
     */
    AUTO("自动运行");

    /**
     * 活动描述
     */
    private String desc;

    PluginActivateType(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
} public class CronUtil {

    /**
     * 获取以指定秒数为单位的定时调度
     * @param second
     * 最小不能小于0
     * 最大不能超过 24 * 60 * 60 秒
     * @return
     * null 获取失败
     */
    public static String getCronBySecondScheduler(int second){
        if(second >= (24 * 60 * 60) || second == 0){
            return null;
        }
        int hh = 0;
        if(second >= 3600){
            hh = second / 3600;
            second = second - ( hh * 3600);
        }
        int mm = second/60;
        int ss = second%60;
        return (ss == 0 ? "0" : ("*/" + ss)) + (mm == 0 ? (ss == 0 ? (hh == 0 ? (" *") : " 0") : (hh == 0 ? " *" : " 0")) : (" */" + mm)) + (hh == 0 ? " *" : (" */" + hh)) + " * * ?";
    }

    /**
     * 获取传入日期，时间的cron表达式
     * @param date 传入的日期
     * @param time 传入的时间
     * @return
     */
    public static String getCron(String date,String time){
        Calendar calendar = DateUtil.getDatetime(date,time);
        Date d = calendar.getTime();
        String dateStr = DateUtil.getFormatDateTime(d);
        String[] str = dateStr.split(" ");

        int year = Integer.parseInt(str[0].split("-")[0]);
        int month = Integer.parseInt(str[0].split("-")[1]);
        int day = Integer.parseInt(str[0].split("-")[2]);

        int HH = Integer.parseInt(str[1].split(":")[0]);
        int mm = Integer.parseInt(str[1].split(":")[1]);
        int ss = Integer.parseInt(str[1].split(":")[2]);
        return ss + " " + mm + " " + HH + " " + day + " " + month + " " + "?" + " " + year;
    }

    /**
     * 获取传入日期，时间前多少分钟的cron表达式
     * @param date 传入的日期，如："yyyy-MM-dd"
     * @param time 传入的时间，如：'hh:mm:ss'
     * @param minutes 需要减的分钟
     * @return
     */
    public static String getCronExMinutes(String date,String time,int minutes){
        Calendar calendar = DateUtil.getDatetime(date,time);
        calendar.add(Calendar.MINUTE,-minutes);
        Date d = calendar.getTime();
        String dateStr = DateUtil.getFormatDateTime(d);
        String[] str = dateStr.split(" ");

        int year = Integer.parseInt(str[0].split("-")[0]);
        int month = Integer.parseInt(str[0].split("-")[1]);
        int day = Integer.parseInt(str[0].split("-")[2]);

        int HH = Integer.parseInt(str[1].split(":")[0]);
        int mm = Integer.parseInt(str[1].split(":")[1]);
        int ss = Integer.parseInt(str[1].split(":")[2]);
        return ss + " " + mm + " " + HH + " " + day + " " + month + " " + "?" + " " + year;
    }

    /**
     * 获取传入日期，时间后多少分钟的cron表达式
     * @param date 传入的日期，如："yyyy-MM-dd"
     * @param time 传入的时间，如：'hh:mm:ss'
     * @param minutes 需要加的分钟
     * @return cron表达式
     */
    public static String getCronDoMinutes(String date,String time,int minutes){
        Calendar calendar = DateUtil.getDatetime(date,time);
        calendar.add(Calendar.MINUTE,minutes);
        Date d = calendar.getTime();
        String dateStr = DateUtil.getFormatDateTime(d);
        String[] str = dateStr.split(" ");

        int year = Integer.parseInt(str[0].split("-")[0]);
        int month = Integer.parseInt(str[0].split("-")[1]);
        int day = Integer.parseInt(str[0].split("-")[2]);

        int HH = Integer.parseInt(str[1].split(":")[0]);
        int mm = Integer.parseInt(str[1].split(":")[1]);
        int ss = Integer.parseInt(str[1].split(":")[2]);
        return ss + " " + mm + " " + HH + " " + day + " " + month + " " + "?" + " " + year;
    }

    /**
     * 获取传入日期与时间后几天的cron表达式
     * @param date 传入的日期，如："yyyy-MM-dd"
     * @param time 传入的时间，如："HH:mm:ss"
     * @param day  需要加的天数
     * @return
     */
    public static String getCronAfDay(String date,String time,int day){
        Calendar calendar = DateUtil.getDatetime(date,time);
        calendar.add(Calendar.DATE,day);
        Date d = calendar.getTime();
        String dateStr = DateUtil.getFormatDateTime(d);
        String[] str = dateStr.split(" ");

        int year = Integer.parseInt(str[0].split("-")[0]);
        int month = Integer.parseInt(str[0].split("-")[1]);
        int toDay = Integer.parseInt(str[0].split("-")[2]);

        int HH = Integer.parseInt(str[1].split(":")[0]);
        int mm = Integer.parseInt(str[1].split(":")[1]);
        int ss = Integer.parseInt(str[1].split(":")[2]);

        return ss + " " + mm + " " + HH + " " + toDay + " " + month + " " + "?" + " " + year;
    }


} @Slf4j
public class SchedulerUtil {

    /**
     * 执行计划任务
     * @param job
     * @param trigger
     * @return
     * @throws SchedulerException
     */
    public static ScheduleJobResult executeScheduleJob(JobDetail job, Trigger trigger) throws SchedulerException {
        ScheduleJobResult scheduleJobResult = new ScheduleJobResult();
        //判断是否满足计划任务的创建条件
        if(job.getKey() == null || trigger.getKey() == null || job.getJobDataMap() == null){
            scheduleJobResult.setScheduleJobStatus(ScheduleJobStatus.FAILED);
            //不满足计划任务的创建条件，返回scheduleJobResult值类
            return scheduleJobResult;
        }
        scheduleJobResult.setJobDetail(job);
        scheduleJobResult.setTrigger(trigger);
        //开始分配计划任务
        Scheduler scheduler  = SchedulerFactory.getScheduler();
        //开始判断是否存在相同的计划任务
        if(scheduler.checkExists(job.getKey())){
            log.info("存在相同的计划任务:{}",job.getKey());
            scheduler.deleteJob(job.getKey());
            scheduleJobResult.setJobKey(job.getKey());
            scheduleJobResult.setTriggerKey(trigger.getKey());
            scheduleJobResult.setScheduleJobStatus(ScheduleJobStatus.ISEXIST);
            scheduler.scheduleJob(job,trigger);
            scheduler.start();
        }else{
            scheduler.scheduleJob(job,trigger);
            scheduler.start();
            scheduleJobResult.setJobKey(job.getKey());
            scheduleJobResult.setTriggerKey(trigger.getKey());
            scheduleJobResult.setScheduleJobStatus(ScheduleJobStatus.SUCCESS);
        }
        //计划任务分配成功
        return scheduleJobResult;
    }

} public class StringUtils {

    /**
     * 判断传入的String是否为空('' or null)
     * @param str
     * @return
     */
    public static boolean isEmpty(String str){
        return str == null || str.length() == 0;
    }

    /**
     * 返回指定数字的字符串形式
     * @param value
     * @return
     */
    public static String getStringByInt(int value){
        return String.valueOf(value).intern();
    }

} @Data
public class JDBCConnectionInfo {
    private String url;
    private String username;
    private String password;

    public JDBCConnectionInfo(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }
} @Data
public class ScheduleJobResult implements Serializable {

    private TriggerKey triggerKey;
    private JobKey jobKey;
    private JobDetail jobDetail;
    private Trigger trigger;
    private ScheduleJobStatus scheduleJobStatus;

} public enum ScheduleJobStatus {

    /**
     * 任务创建成功
     */
    SUCCESS,
    /**
     * 已存在相同的任务(key)
     */
    ISEXIST,
    /**
     * 传入的对象条不满足任务的创建条件
     */
    DISCONTENT,
    /**
     * 创建任务失败
     */
    FAILED;

} @Data
public class SNMPV3UserInfo {

    /**
     * 网络协议 如udp、tcp
     */
    private String protocol = "udp";
    /**
     * 网络设备的IP地址
     */
    private String address = "";
    /**
     * 网络设备的连接端口号
     */
    private String port = "161";
    /**
     * 用户名
     */
    private String username = "";
    /**
     * 认证算法,如 none,MD5,SHA
     */
    private String aythType = "";
    /**
     * 认证密码
     */
    private String authPswd = "";
    /**
     * 加密算法,如 none,DES,3DES,AES128,AES,AES192,AES256
     */
    private String privType = "";
    /**
     * 加密密码
     */
    private String privPswd = "";
    /**
     * endPoint
     */
    private String endPoint = "";

    /**
     * 允许采集哪些名称的接口数据
     * 注：只采集集合中的接口名称的数据。若为空，则不会采集接口数据
     */
    private List<String> ifCollectNameEnables;

}
import com.yiji.falcon.agent.config.AgentConfiguration;
import com.yiji.falcon.agent.jmx.JMXConnection;
import com.yiji.falcon.agent.plugins.DetectPlugin;
import com.yiji.falcon.agent.plugins.JDBCPlugin;
import com.yiji.falcon.agent.plugins.SNMPV3Plugin;
import com.yiji.falcon.agent.plugins.job.JMXPluginJob;
import com.yiji.falcon.agent.plugins.util.PluginActivateType;
import com.yiji.falcon.agent.util.CronUtil;
import com.yiji.falcon.agent.util.SchedulerUtil;
import com.yiji.falcon.agent.util.StringUtils;
import com.yiji.falcon.agent.vo.jdbc.JDBCConnectionInfo;
import com.yiji.falcon.agent.vo.sceduler.ScheduleJobResult;
import com.yiji.falcon.agent.vo.sceduler.ScheduleJobStatus;
import com.yiji.falcon.agent.vo.snmp.SNMPV3UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import java.util.Collection;
import java.util.concurrent.ConcurrentSkipListSet;
import static org.quartz.TriggerBuilder.newTrigger;
/*
 * www.yiji.com Inc.
 * Copyright (c) 2016 All Rights Reserved
 */
package com.yiji.falcon.agent.common;




/*
 * 修订记录:
 * guqiu@yiji.com 2016-06-22 17:48 创建
 */

/**
 * @author guqiu@yiji.com
 */
@Slf4j
public class AgentJobHelper {

    //正在work的job记录
    private static final ConcurrentSkipListSet<String> worked = new ConcurrentSkipListSet<>();

    /**
     * 添加work记录
     * @param serverName
     */
    private static void addWorkJob(String serverName){
        if(!StringUtils.isEmpty(serverName)){
            worked.add(serverName);
        }
    }

    /**
     * 判断指定job是否已经work
     * @param serverName
     * @return
     */
    private static boolean isHasWorked(String serverName){
        return worked.contains(serverName);
    }

    /**
     * Agent监控服务自动发现定时刷新功能
     * @throws SchedulerException
     */
    public static void agentFlush() throws SchedulerException {
        String agentFlush = "AgentFlush";
        if(AgentConfiguration.INSTANCE.getAgentFlushTime() != 0 &&
                !isHasWorked(agentFlush)){
            //开启服务自动发现
            JobDetail job = getJobDetail(AgentFlushJob.class,agentFlush,"Agent监控服务自动发现定时刷新功能Job",new JobDataMap());

            Trigger trigger = getTrigger(AgentConfiguration.INSTANCE.getAgentFlushTime(),agentFlush,"Agent监控服务自动发现定时刷新push调度任务");
            ScheduleJobResult scheduleJobResult = SchedulerUtil.executeScheduleJob(job,trigger);
            workResult(scheduleJobResult,agentFlush);
        }else{
            log.info("Agent监控服务自动发现定时刷新功能未开启");
        }
    }

    /**
     * JMX服务的监控启动
     * @param pluginName
     * @param pluginActivateType
     * @param step
     * @param desc
     * @param serverName
     * @param jmxServerName
     * @param jobDataMap
     * @throws SchedulerException
     */
    public synchronized static void pluginWorkForJMX(String pluginName, PluginActivateType pluginActivateType, int step, String desc, String serverName,String jmxServerName, JobDataMap jobDataMap) throws SchedulerException {
        //只有指定job未启动过的情况下才进行work开启
        if(!isHasWorked(serverName)){
            if(pluginActivateType == PluginActivateType.AUTO){
                if(JMXConnection.hasJMXServerInLocal(jmxServerName)){
                    //开启服务监控
                    log.info("发现服务 {} , 启动插件 {} ",serverName,pluginName);
                    doJob(JMXPluginJob.class,desc,step,jobDataMap,serverName);
                }
            }else if(pluginActivateType == PluginActivateType.FORCE){
                doJob(JMXPluginJob.class,desc,step,jobDataMap,serverName);
            }
        }
    }

    /**
     * JDBC服务的监控启动
     * @param jdbcPlugin
     * @param pluginName
     * @param jobClazz
     * @param serverName
     * @param desc
     * @param jobDataMap
     * @throws SchedulerException
     */
    public synchronized static void pluginWorkForJDBC(JDBCPlugin jdbcPlugin ,String pluginName, Class<? extends Job> jobClazz, String serverName,String desc, JobDataMap jobDataMap) throws SchedulerException {
        PluginActivateType pluginActivateType = jdbcPlugin.activateType();
        int step = jdbcPlugin.step();
        //只有指定job未启动过的情况下才进行work开启
        if(!isHasWorked(serverName)){
            if(pluginActivateType == PluginActivateType.AUTO){
                try {
                    Collection<JDBCConnectionInfo> connectionInfos = jdbcPlugin.getConnectionInfos();
                    if(connectionInfos != null && !connectionInfos.isEmpty()){
                        //无异常且连接正常,代表连接获取成功,开启服务监控
                        log.info("发现服务 {} , 启动插件 {} ",serverName,pluginName);
                        doJob(jobClazz,desc,step,jobDataMap,serverName);
                    }
                } catch (Exception ignored) {
                }
            }else if(!StringUtils.isEmpty(jdbcPlugin.jdbcConfig()) && pluginActivateType == PluginActivateType.FORCE){
                doJob(jobClazz,desc,step,jobDataMap,serverName);
            }
        }
    }

    /**
     * 探测服务的监控启动
     * @param plugin
     * @param pluginName
     * @param jobClazz
     * @param jobDataMap
     * @throws SchedulerException
     */
    public synchronized static void pluginWorkForDetect(DetectPlugin plugin , String pluginName, Class<? extends Job> jobClazz, JobDataMap jobDataMap) throws SchedulerException {
        String serverName = plugin.serverName();
        //只有指定job未启动过的情况下才进行work开启
        if(!isHasWorked(serverName)){
            //只要配置了监控地址,就启动监控
            Collection<String> addresses = plugin.detectAddressCollection();
            boolean start = false;
            if(addresses != null && !addresses.isEmpty()){
                start = true;
            }else{
                //未配置探测地址,尝试自动探测地址
                addresses = plugin.autoDetectAddress();
                if(addresses != null && !addresses.isEmpty()){
                    start = true;
                }
            }
            if(start){
                //开启监控服务
                log.info("发现服务 {} , 启动插件 {} ",serverName,pluginName);
                doJob(jobClazz,pluginName,plugin.step(),jobDataMap,serverName);
            }
        }
    }

    /**
     * SNMPV3服务的监控启动
     * @param snmpv3Plugin
     * @param pluginName
     * @param jobClazz
     * @param serverName
     * @param desc
     * @param jobDataMap
     * @throws SchedulerException
     */
    public synchronized static void pluginWorkForSNMPV3(SNMPV3Plugin snmpv3Plugin , String pluginName, Class<? extends Job> jobClazz, String serverName, String desc, JobDataMap jobDataMap) throws SchedulerException {