Package com.googlecode.jmxtrans

Source Code of com.googlecode.jmxtrans.JmxTransformer$ShutdownHook

package com.googlecode.jmxtrans;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Guice;
import com.google.inject.Injector;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.management.MBeanServer;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.googlecode.jmxtrans.cli.CliArgumentParser;
import com.googlecode.jmxtrans.cli.JmxTransConfiguration;
import com.googlecode.jmxtrans.exceptions.LifecycleException;
import com.googlecode.jmxtrans.guice.JmxTransModule;
import com.googlecode.jmxtrans.jobs.ServerJob;
import com.googlecode.jmxtrans.model.JmxProcess;
import com.googlecode.jmxtrans.model.OutputWriter;
import com.googlecode.jmxtrans.model.Query;
import com.googlecode.jmxtrans.model.Server;
import com.googlecode.jmxtrans.model.ValidationException;
import com.googlecode.jmxtrans.util.JsonUtils;
import com.googlecode.jmxtrans.util.WatchDir;
import com.googlecode.jmxtrans.util.WatchedCallback;

import static com.googlecode.jmxtrans.model.Server.mergeServerLists;

/**
* Main() class that takes an argument which is the directory to look in for
* files which contain json data that defines queries to run against JMX
* servers.
*
* @author jon
*/
public class JmxTransformer implements WatchedCallback {

  private static final Logger log = LoggerFactory.getLogger(JmxTransformer.class);

  private final Scheduler serverScheduler;

  private WatchDir watcher;

  private final JmxTransConfiguration configuration;

  private List<Server> masterServersList = new ArrayList<Server>();

  /**
   * The shutdown hook.
   */
  private Thread shutdownHook = new ShutdownHook();

  private volatile boolean isRunning = false;

  private final Injector injector;

  @Inject
  public JmxTransformer(Scheduler serverScheduler, JmxTransConfiguration configuration, Injector injector) {
    this.serverScheduler = serverScheduler;
    this.configuration = configuration;
    this.injector = injector;
  }

  public static void main(String[] args) throws Exception {
    JmxTransConfiguration configuration = new CliArgumentParser().parseOptions(args);
    if (configuration.isHelp()) {
      return;
    }

    Injector injector = Guice.createInjector(new JmxTransModule(configuration));

    JmxTransformer transformer = injector.getInstance(JmxTransformer.class);

    // Start the process
    transformer.doMain();
  }

  /**
   * The real main method.
   */
  private void doMain() throws Exception {
    ManagedJmxTransformerProcess mbean = new ManagedJmxTransformerProcess(this, configuration);
    ManagementFactory.getPlatformMBeanServer()
        .registerMBean(mbean, mbean.getObjectName());

    // Start the process
    this.start();

    while (true) {
      // look for some terminator
      // attempt to read off queue
      // process message
      // TODO : Make something here, maybe watch for files?
      try {
        Thread.sleep(5);
      } catch (Exception e) {
        break;
      }
    }

    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    mbs.unregisterMBean(mbean.getObjectName());
  }

  /**
   * Start.
   *
   * @throws LifecycleException the lifecycle exception
   */
  public synchronized void start() throws LifecycleException {
    if (isRunning) {
      throw new LifecycleException("Process already started");
    } else {
      log.info("Starting Jmxtrans on : " + this.configuration.getJsonDirOrFile().toString());
      try {
        this.serverScheduler.start();

        this.startupWatchdir();

        this.startupSystem();

      } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new LifecycleException(e);
      }

      // Ensure resources are free
      Runtime.getRuntime().addShutdownHook(shutdownHook);
      isRunning = true;
    }
  }

  /**
   * Stop.
   *
   * @throws LifecycleException the lifecycle exception
   */
  public synchronized void stop() throws LifecycleException {
    if (!isRunning) {
      throw new LifecycleException("Process already stoped");
    } else {
      try {
        log.info("Stopping Jmxtrans");

        // Remove hook to not call twice
        if (shutdownHook != null) {
          Runtime.getRuntime().removeShutdownHook(shutdownHook);
        }

        this.stopServices();
        isRunning = false;
      } catch (LifecycleException e) {
        log.error(e.getMessage(), e);
        throw new LifecycleException(e);
      }
    }
  }

  /**
   * Stop services.
   *
   * @throws LifecycleException the lifecycle exception
   */
  // There is a sleep to work around a Quartz issue. The issue is marked to be
  // fixed, but will require further analysis. This should not be reported by
  // Findbugs, but as a more complex issue.
  @SuppressFBWarnings(value = "SWL_SLEEP_WITH_LOCK_HELD", justification = "Workaround for Quartz issue")
  private synchronized void stopServices() throws LifecycleException {
    try {
      // Shutdown the scheduler
      if (this.serverScheduler.isStarted()) {
        this.serverScheduler.shutdown(true);
        log.debug("Shutdown server scheduler");
        try {
          // FIXME: Quartz issue, need to sleep
          Thread.sleep(1500);
        } catch (InterruptedException e) {
          log.error(e.getMessage(), e);
        }
      }

      // Shutdown the file watch service
      if (this.watcher != null) {
        this.watcher.stopService();
        this.watcher = null;
        log.debug("Shutdown watch service");
      }

      // Shutdown the outputwriters
      this.stopWriterAndClearMasterServerList();

    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw new LifecycleException(e);
    }
  }

  /**
   * Shut down the output writers and clear the master server list
   * Used both during shutdown and when re-reading config files
   */
  private void stopWriterAndClearMasterServerList() {
    for (Server server : this.masterServersList) {
      for (Query query : server.getQueries()) {
        for (OutputWriter writer : query.getOutputWriters()) {
          try {
            writer.stop();
            log.debug("Stopped writer: " + writer.getClass().getSimpleName() + " for query: " + query);
          } catch (LifecycleException ex) {
            log.error("Error stopping writer: " + writer.getClass().getSimpleName() + " for query: " + query);
          }
        }
      }
    }
    this.masterServersList.clear();
  }

  /**
   * Startup the watchdir service.
   */
  private void startupWatchdir() throws Exception {
    File dirToWatch;
    if (this.configuration.getJsonDirOrFile().isFile()) {
      dirToWatch = new File(FilenameUtils.getFullPath(this.configuration.getJsonDirOrFile().getAbsolutePath()));
    } else {
      dirToWatch = this.configuration.getJsonDirOrFile();
    }

    // start the watcher
    this.watcher = new WatchDir(dirToWatch, this);
    this.watcher.start();
  }

  /**
   * Handy method which runs the JmxProcess
   */
  public void executeStandalone(JmxProcess process) throws Exception {
    this.masterServersList = process.getServers();

    this.serverScheduler.start();

    this.processServersIntoJobs();

    // Sleep for 10 seconds to wait for jobs to complete.
    // There should be a better way, but it seems that way isn't working
    // right now.
    Thread.sleep(10 * 1000);
  }

  /**
   * Processes files into Server objects and then processesServers into jobs
   */
  private void startupSystem() throws LifecycleException {
    // process all the json files into Server objects
    this.processFilesIntoServers();

    // process the servers into jobs
    this.processServersIntoJobs();
  }

  private void validateSetup(Server server, ImmutableSet<Query> queries) throws ValidationException {
    for (Query q : queries) {
      this.validateSetup(server, q);
    }
  }

  private void validateSetup(Server server, Query query) throws ValidationException {
    List<OutputWriter> writers = query.getOutputWriters();
    for (OutputWriter w : writers) {
      injector.injectMembers(w);
      w.validateSetup(server, query);
    }
  }

  /**
   * Processes all the json files and manages the dedup process
   */
  private void processFilesIntoServers() throws LifecycleException {
    // Shutdown the outputwriters and clear the current server list - this gives us a clean
    // start when re-reading the json config files
    try {
      this.stopWriterAndClearMasterServerList();
    } catch (Exception e) {
      log.error("Error while clearing master server list: " + e.getMessage(), e);
      throw new LifecycleException(e);
    }

    for (File jsonFile : getJsonFiles()) {
      JmxProcess process;
      try {
        process = JsonUtils.getJmxProcess(jsonFile);
        if (log.isDebugEnabled()) {
          log.debug("Loaded file: " + jsonFile.getAbsolutePath());
        }
        this.masterServersList = mergeServerLists(this.masterServersList, process.getServers());
      } catch (Exception ex) {
        if (configuration.isContinueOnJsonError()) {
          throw new LifecycleException("Error parsing json: " + jsonFile, ex);
        } else {
          // error parsing one file should not prevent the startup of JMXTrans
          log.error("Error parsing json: " + jsonFile, ex);
        }
      }
    }

  }

  /**
   * Processes all the Servers into Job's
   * <p/>
   * Needs to be called after processFiles()
   */
  private void processServersIntoJobs() throws LifecycleException {
    for (Server server : this.masterServersList) {
      try {

        // need to inject the poolMap
        for (Query query : server.getQueries()) {
          for (OutputWriter writer : query.getOutputWriters()) {
            writer.start();
          }
        }

        // Now validate the setup of each of the OutputWriter's per
        // query.
        this.validateSetup(server, server.getQueries());

        // Now schedule the jobs for execution.
        this.scheduleJob(server);
      } catch (ParseException ex) {
        throw new LifecycleException("Error parsing cron expression: " + server.getCronExpression(), ex);
      } catch (SchedulerException ex) {
        throw new LifecycleException("Error scheduling job for server: " + server, ex);
      } catch (ValidationException ex) {
        throw new LifecycleException("Error validating json setup for query", ex);
      }
    }
  }

  /**
   * Schedules an individual job.
   */
  private void scheduleJob(Server server) throws ParseException, SchedulerException {

    String name = server.getHost() + ":" + server.getPort() + "-" + System.currentTimeMillis() + "-" + RandomStringUtils.randomNumeric(10);
    JobDetail jd = new JobDetail(name, "ServerJob", ServerJob.class);

    JobDataMap map = new JobDataMap();
    map.put(Server.class.getName(), server);
    jd.setJobDataMap(map);

    Trigger trigger;

    if ((server.getCronExpression() != null) && CronExpression.isValidExpression(server.getCronExpression())) {
      trigger = new CronTrigger();
      ((CronTrigger) trigger).setCronExpression(server.getCronExpression());
      trigger.setName(server.getHost() + ":" + server.getPort() + "-" + Long.valueOf(System.currentTimeMillis()).toString());
      trigger.setStartTime(new Date());
    } else {
      Trigger minuteTrigger = TriggerUtils.makeSecondlyTrigger(configuration.getRunPeriod());
      minuteTrigger.setName(server.getHost() + ":" + server.getPort() + "-" + Long.valueOf(System.currentTimeMillis()).toString());
      minuteTrigger.setStartTime(new Date());

      trigger = minuteTrigger;
    }

    serverScheduler.scheduleJob(jd, trigger);
    if (log.isDebugEnabled()) {
      log.debug("Scheduled job: " + jd.getName() + " for server: " + server);
    }
  }

  /**
   * Deletes all of the Jobs
   */
  private void deleteAllJobs() throws Exception {
    List<JobDetail> allJobs = new ArrayList<JobDetail>();
    String[] jobGroups = serverScheduler.getJobGroupNames();
    for (String jobGroup : jobGroups) {
      String[] jobNames = serverScheduler.getJobNames(jobGroup);
      for (String jobName : jobNames) {
        allJobs.add(serverScheduler.getJobDetail(jobName, jobGroup));
      }
    }

    for (JobDetail jd : allJobs) {
      serverScheduler.deleteJob(jd.getName(), jd.getGroup());
      if (log.isDebugEnabled()) {
        log.debug("Deleted scheduled job: " + jd.getName() + " group: " + jd.getGroup());
      }
    }
  }

  /**
   * If getJsonFile() is a file, then that is all we load. Otherwise, look in
   * the jsonDir for files.
   * <p/>
   * Files must end with .json as the suffix.
   */
  private List<File> getJsonFiles() {
    // TODO : should use a FileVisitor (Once we update to Java 7)
    File[] files;
    if ((this.configuration.getJsonDirOrFile() != null) && this.configuration.getJsonDirOrFile().isFile()) {
      files = new File[1];
      files[0] = this.configuration.getJsonDirOrFile();
    } else {
      files = this.configuration.getJsonDirOrFile().listFiles();
    }

    List<File> result = new ArrayList<File>();
    for (File file : files) {
      if (this.isJsonFile(file)) {
        result.add(file);
      }
    }
    return result;
  }

  /**
   * Are we a file and a json file?
   */
  private boolean isJsonFile(File file) {
    if (this.configuration.getJsonDirOrFile().isFile()) {
      return file.equals(this.configuration.getJsonDirOrFile());
    }

    return file.isFile() && file.getName().endsWith(".json");
  }

  @Override
  public void fileModified(File file) throws Exception {
    if (this.isJsonFile(file)) {
      Thread.sleep(1000);
      log.info("Configuration file modified: " + file);
      this.deleteAllJobs();
      this.startupSystem();
    }
  }

  @Override
  public void fileDeleted(File file) throws Exception {
    log.info("Configuration file deleted: " + file);
    Thread.sleep(1000);
    this.deleteAllJobs();
    this.startupSystem();
  }

  @Override
  public void fileAdded(File file) throws Exception {
    if (this.isJsonFile(file)) {
      Thread.sleep(1000);
      log.info("Configuration file added: " + file);
      this.deleteAllJobs();
      this.startupSystem();
    }
  }

  protected class ShutdownHook extends Thread {
    public void run() {
      try {
        JmxTransformer.this.stopServices();
      } catch (LifecycleException e) {
        log.error("Error shutdown hook", e);
      }
    }
  }
}
TOP

Related Classes of com.googlecode.jmxtrans.JmxTransformer$ShutdownHook

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.