/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.uima.aae.jmx.monitor;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.apache.activemq.broker.jmx.QueueViewMBean;
import org.apache.uima.UIMAFramework;
import org.apache.uima.aae.jmx.ServiceInfoMBean;
import org.apache.uima.aae.jmx.ServicePerformanceMBean;
import org.apache.uima.aae.spi.transport.vm.UimaVmQueueMBean;
import org.apache.uima.adapter.jms.JmsConstants;
import org.apache.uima.util.Level;
import org.apache.uima.util.impl.CasPoolManagementImplMBean;

/**
 * Collects metrics from UIMA-AS Service MBeans at defined intervals and passes the metrics for
 * formatting to the registered {@link JmxMonitorListener}
 * 
 */
public class JmxMonitor implements Runnable {
  private static final Class CLASS_NAME = JmxMonitor.class;

  public static final String SamplingInterval = "uima.jmx.monitor.interval";

  public static final String FormatterListener = "uima.jmx.monitor.formatter";

  private boolean running = false;

  private MBeanServerConnection mbsc;

  private ObjectName uimaServicePattern;

  private ObjectName uimaServiceQueuePattern;

  private ObjectName uimaServiceTempQueuePattern;

  private Set<ObjectName> servicePerformanceNames;

  private long interval;

  private ConcurrentHashMap<ObjectName, StatEntry> stats = new ConcurrentHashMap<ObjectName, StatEntry>();

  private long startTime = System.nanoTime();

  private List<JmxMonitorListener> listeners = new ArrayList<JmxMonitorListener>();

  private int maxNameLength = 0;

  private boolean verbose = false;

  private ThreadMXBean threads = null;

  private RuntimeMXBean runtime = null;

  private OperatingSystemMXBean os = null;

  private int serviceCount = 0;

  /**
   * Creates a connection to an MBean Server identified by <code>remoteServerURI</code>
   * 
   * @param remoteServerURI
   *          - URI to MBeanServer
   * @return - connection to MBeanServer
   * 
   * @throws Exception
   */
  private MBeanServerConnection getServerConnection(String remoteServerURI) throws Exception {
    JMXServiceURL url = new JMXServiceURL(remoteServerURI);
    JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
    MBeanServerConnection server = jmxc.getMBeanServerConnection();
    return server;
  }

  /**
   * Registers custom {@link JmxMonitorListener}
   * 
   * @param listener
   *          - listener to receive metrics for formatting
   */
  public void addJmxMonitorListener(JmxMonitorListener listener) {
    listeners.add(listener);
  }

  private void showServerEnvironment(RuntimeMXBean runtime) {
    echo("\nRemote JVM Info: \n\tJVM::" + runtime.getVmName() + "\n\tJVM Vendor::"
            + runtime.getVmVendor() + "\n\tJVM Version::" + runtime.getVmVersion() + "\n\n");
  }

  /**
   * Passes metrics to all registered {@link JmxMonitorListener} objects
   * 
   * @param uptime
   *          - time when the metrics were collected
   * @param metrics
   *          - current metrics
   */
  public void notifyListeners(long uptime, ServiceMetrics[] metrics) {
    for (JmxMonitorListener listener : listeners) {
      listener.onNewMetrics(uptime, metrics);
    }
  }

  public boolean isVerbose() {
    return verbose;
  }

  public void setVerbose() {
    verbose = true;
  }

  // Should be called after initialize()
  public int getMaxServiceNameLength() {
    return maxNameLength;
  }

  private CasPoolManagementImplMBean getServiceCasPoolMBean(String labelToMatch,
          Set<ObjectName> names) {
    for (ObjectName name : names) {
      // Check if the current name is the Service Performance MBean
      if (name.toString().endsWith(labelToMatch.trim())) {
        return (CasPoolManagementImplMBean) MBeanServerInvocationHandler.newProxyInstance(mbsc,
                name, CasPoolManagementImplMBean.class, true);
      }
    }
    return null; // not found
  }

  /**
   * Connects to a local MBeanServer identified by given <code>serverUri</code>. Creates
   * proxies for all UIMA AS ServicePerformance MBeans found in the JMX server registry. If UIMA AS
   * service MBean indicates a remote service, this method connects to a remote Broker and creates a
   * proxy to an input queue for that service.
   * 
   * 
   * @param serverUri
   * @param samplingInterval
   * @throws Exception
   */
  public void initialize(String serverUri, long samplingInterval) throws Exception {
    interval = samplingInterval;
    try {
      mbsc = getServerConnection(serverUri);
    } catch (Exception e) {
      System.out.println("Unable to Connect To Jmx Server. URL:" + serverUri);
      throw e;
    }
    System.out.println(">>> Connected To Jmx Server. URL:" + serverUri);
    // Fetch remote JVM's MXBeans
    ObjectName runtimeObjName = new ObjectName(ManagementFactory.RUNTIME_MXBEAN_NAME);
    ObjectName threadObjName = new ObjectName(ManagementFactory.THREAD_MXBEAN_NAME);
    ObjectName osObjName = new ObjectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME);
    Set<ObjectName> mbeans = mbsc.queryNames(threadObjName, null);

    mbeans = mbsc.queryNames(runtimeObjName, null);
    for (ObjectName name : mbeans) {
      runtime = (RuntimeMXBean) MBeanServerInvocationHandler.newProxyInstance(mbsc, name,
              RuntimeMXBean.class, true);
    }

    mbeans = mbsc.queryNames(osObjName, null);
    for (ObjectName name : mbeans) {
      os = (OperatingSystemMXBean) MBeanServerInvocationHandler.newProxyInstance(mbsc, name,
              OperatingSystemMXBean.class, true);
    }

    System.out.println(runtime.getVmName() + "::" + runtime.getVmVendor() + "::"
            + runtime.getVmVersion());
    // Construct query string to fetch UIMA-AS MBean names from the JMX Server registry
    uimaServicePattern = new ObjectName("org.apache.uima:type=ee.jms.services,*");
    // Construct query string to fetch Queue MBean names from the JMX Server registry
    uimaServiceQueuePattern = new ObjectName(
            "org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Queue,*");

    uimaServiceTempQueuePattern = new ObjectName(
            "org.apache.activemq:type=Broker,brokerName=localhost,destinationType=TempQueue,*");
    // Fetch UIMA AS MBean names from the JMX Server that match the name pattern
    Set<ObjectName> names = new HashSet<ObjectName>(mbsc.queryNames(uimaServicePattern, null));
    String key = "";
    if (verbose)
      System.out.println("\nFound UIMA AS Services Managed by JMX Server:" + serverUri);

    // Find all Service Performance MBeans
    for (ObjectName name : names) {

      // Set up a key for matching Service Performance MBean names
      String perfKey = "_Service Performance";
      // Check if the current name is the Service Performance MBean
      if (name.toString().endsWith(perfKey)) {
        if (servicePerformanceNames == null) {
          servicePerformanceNames = new HashSet<ObjectName>();
        }
        // Reduce the Set to Service Performance MBeans only
        servicePerformanceNames.add(name);
        // Create a proxy object for the Service Performance MBean
        ServicePerformanceMBean perfMBeanProxy = (ServicePerformanceMBean) MBeanServerInvocationHandler
                .newProxyInstance(mbsc, name, ServicePerformanceMBean.class, true);
        // Extract the service name from the MBean name
        int beginIndx = name.toString().indexOf(perfKey);
        key = name.toString().substring(0, beginIndx);

        int nameLen = name.getKeyProperty("name").length();
        if (nameLen > maxNameLength) {
          maxNameLength = nameLen;
        }

        // Create a proxy object to the Service Info MBean
        ServiceInfoMBean infoMBeanProxy = getServiceInfoMBean(names, key);
        key = key.substring(key.indexOf(",name=") + ",name=".length());
        // Create a proxy to the service CasPool object.
        CasPoolManagementImplMBean casPoolMBeanProxy = null;
        //	Get Service CAS Pool MBean Proxy. This only make sense for top level service.
        //	This is a Cas Pool for deserializing CASes from remote clients
        if (infoMBeanProxy.isTopLevel()) {
          if (infoMBeanProxy.isAggregate()) {
            casPoolMBeanProxy = getServiceCasPoolMBean("AggregateContext", names);
          } else {
            casPoolMBeanProxy = getServiceCasPoolMBean("PrimitiveAEService", names);
          }
        }
        StatEntry entry = null;
        if (casPoolMBeanProxy != null) {
          entry = new StatEntry(perfMBeanProxy, infoMBeanProxy, casPoolMBeanProxy);
        } else {
          entry = new StatEntry(perfMBeanProxy, infoMBeanProxy);
        }

        String location = "Collocated";
        // If a service is co-located in the same JVM fetch the service queue proxy
        if (infoMBeanProxy.getBrokerURL().startsWith("Embedded Broker")) {
          // Get Co-located CM Cas Pool
          if (infoMBeanProxy.isCASMultiplier()) {
            // Create a proxy to the Cas Multiplier CasPool object.
            CasPoolManagementImplMBean casPoolMBean = 
            	getServiceCasPoolMBean(infoMBeanProxy.getCmRegisteredName(), names);
            if (casPoolMBean != null) {
              entry.setCasPoolMBean(casPoolMBean);
            }
          }

          // Create a proxy to the service queue MBean
          UimaVmQueueMBean queueProxy = getQueueMBean(mbsc, infoMBeanProxy.getInputQueueName());
          if (queueProxy != null) {
            entry.setInputQueueInfo(queueProxy);
          }
          UimaVmQueueMBean replyQueueProxy = getQueueMBean(mbsc, infoMBeanProxy.getReplyQueueName());
          if (replyQueueProxy != null) {
            entry.setReplyQueueInfo(replyQueueProxy);
          }

        } else {
          // The MBean is for a remote service. Connect to this service Broker and create a proxy
          // to an input queue for the service. Assumption: the broker registers JMX server on
          // port 1099.
          location = "Remote";
          int spos = infoMBeanProxy.getBrokerURL().indexOf("//");
          int endpos = infoMBeanProxy.getBrokerURL().lastIndexOf(":");
          
          String remoteHostname = infoMBeanProxy.getBrokerURL().substring(spos + 2, endpos);
          if (verbose)
            System.out.println("Connecting to a remote JMX Server: " + remoteHostname + " key:"
                    + key);
          String remoteJMX = "service:jmx:rmi:///jndi/rmi://" + remoteHostname + ":1099/jmxrmi";
          MBeanServerConnection remoteServer = getServerConnection(remoteJMX);

          QueueViewMBean inputQueueProxy = getQueueMBean(remoteServer, infoMBeanProxy
                  .getInputQueueName(), uimaServiceQueuePattern);
          if (inputQueueProxy != null) {
            entry.setInputQueueInfo(inputQueueProxy);
            ObjectName on = new ObjectName("org.apache.activemq:type=Broker,brokerName=localhost,destinationType=Queue,destinationName="+infoMBeanProxy.getInputQueueName());
            long qsize = (Long)remoteServer.getAttribute(on, "QueueSize");
            entry.setQueueSize(qsize);

          } else {
            System.out.println("Unable to find Input Queue:" + infoMBeanProxy.getInputQueueName()
                    + " In JMX Registry:" + remoteJMX);
          }
          QueueViewMBean replyQueueProxy = null;
          if (infoMBeanProxy.isAggregate()) {
            replyQueueProxy = getQueueMBean(mbsc, infoMBeanProxy.getReplyQueueName(),
                    uimaServiceQueuePattern);
          } else {
            replyQueueProxy = getQueueMBean(remoteServer, infoMBeanProxy.getReplyQueueName(),
                    uimaServiceTempQueuePattern);
            
          }
          if (replyQueueProxy != null) {
            entry.setReplyQueueInfo(replyQueueProxy);
          } else {
            System.out.println("Unable to find Reply Queue:" + infoMBeanProxy.getReplyQueueName()
                    + " In JMX Registry:" + remoteJMX);
          }
        }
        if (verbose)
          System.out.println("\nUIMA AS Service:" + key + "[>>> " + location
                  + " <<<]\n\tService Broker:" + infoMBeanProxy.getBrokerURL() + "\n\tQueue Name:"
                  + infoMBeanProxy.getInputQueueName());
        serviceCount++;
        stats.put(name, entry);

      }

    }
  }

  protected int getServiceCount() {
    return serviceCount;
  }

  /**
   * Returns a proxy object to an UIMA-AS Service Info MBean identified by a <code>key</code>
   * 
   * @param names
   *          - list of MBean names
   * @param key
   *          - target name to find in the list of MBeans
   * @return - proxy to MBean identified by <code>key</code>
   */
  private ServiceInfoMBean getServiceInfoMBean(Set<ObjectName> names, String key) {
    String target = key + "_Service Info";
    for (ObjectName name : names) {
      if (name.toString().equals(target)) {
        return (ServiceInfoMBean) MBeanServerInvocationHandler.newProxyInstance(mbsc, name,
                ServiceInfoMBean.class, true);
      }
    }
    return null;
  }

  private CasPoolManagementImplMBean getCasPoolMBean(Set<ObjectName> names, String target) {
    for (ObjectName name : names) {
      if (name.toString().endsWith(target)) {
        return (CasPoolManagementImplMBean) MBeanServerInvocationHandler.newProxyInstance(mbsc,
                name, CasPoolManagementImplMBean.class, true);
      }
    }
    return null;
  }

  private QueueViewMBean getQueueMBean(MBeanServerConnection server, String key,
          ObjectName matchPattern) throws Exception {
    Set<ObjectName> queues = new HashSet<ObjectName>(server.queryNames(matchPattern, null));
    String target = "destinationName=" + key;
    for (ObjectName name : queues) {

      if (name.toString().contains(target)) {
        System.out.println("Creating Proxy for Queue:" + name.toString());

        return (QueueViewMBean) MBeanServerInvocationHandler.newProxyInstance(server, name,
                QueueViewMBean.class, true);
      }
    }
    return null;
  }

  private UimaVmQueueMBean getQueueMBean(MBeanServerConnection server, String aPattern)
          throws Exception {
    ObjectName name = new ObjectName(aPattern);
    System.out.println("Creating Proxy for Queue:" + name.toString());
    return (UimaVmQueueMBean) MBeanServerInvocationHandler.newProxyInstance(server, name,
            UimaVmQueueMBean.class, true);
  }

  protected ServiceMetrics[] collectStats(boolean initial, long uptime) {
    int cmFreeCasInstanceCount = 0;
    ServiceMetrics[] metrics = null;
    
    List<ServiceMetrics> metricsList = new ArrayList<ServiceMetrics>();
    // iterate over all Performance MBeans to retrieve current metrics
    for (ObjectName name : servicePerformanceNames) {
      try {
        // Fetch previous metrics for service identified by 'name'
        StatEntry entry = stats.get(name);
        if (entry == null) {
          continue;
        }
        ServiceInfoMBean serviceInfo = entry.getServiceInfoMBeanProxy();
        CasPoolManagementImplMBean getServiceCasPoolMBeanProxy = entry
                .getServiceCasPoolMBeanProxy();
        boolean isRemote = serviceInfo.getBrokerURL().startsWith("tcp:");
        boolean topLevel = serviceInfo.isTopLevel();

        // Get the current reading from MBeans
        double idleTime = entry.getServicePerformanceMBeanProxy().getIdleTime();
        double casPoolWaitTime = entry.getServicePerformanceMBeanProxy().getCasPoolWaitTime();
        double shadowCasPoolWaitTime = entry.getServicePerformanceMBeanProxy()
                .getShadowCasPoolWaitTime();
        double analysisTime = entry.getServicePerformanceMBeanProxy().getAnalysisTime();
        long processCount = entry.getServicePerformanceMBeanProxy().getNumberOfCASesProcessed();
        QueueViewMBean inputQueueInfo = entry.getInputQueueInfo();
        QueueViewMBean replyQueueInfo = entry.getReplyQueueInfo();

		cmFreeCasInstanceCount = -1;
        if (serviceInfo.isCASMultiplier() && !isRemote && entry.getCasPoolMBean() != null) {
        	try {
        		cmFreeCasInstanceCount = entry.getCasPoolMBean().getAvailableInstances();
        	} catch( Exception e) {
        		//swallow this for now. 
        	}
        }
        long inputQueueDepth = -1;
        UimaVmQueueMBean vmInputQueueInfo = null;

        // if the service is colocated, get the bean object for the internal (non-jms) queue
        if (entry.isVmQueue) {
          vmInputQueueInfo = entry.getVmInputQueueInfo();
          if (vmInputQueueInfo != null) {
            inputQueueDepth = vmInputQueueInfo.getQueueSize();
          }
        } else {
          // service is top level and uses JMS queue
          inputQueueInfo = entry.getInputQueueInfo();
          if (inputQueueInfo != null) {
        	//String [] queueAttributes = {"QueueSize"};
            inputQueueDepth = entry.getQueueSize(); //inputQueueInfo.getQueueSize();
            
            
          }
        }
        long replyQueueDepth = -1; // -1 means not available
        UimaVmQueueMBean vmReplyQueueInfo = null;
        if (entry.isVmQueue) {
          vmReplyQueueInfo = entry.getVmReplyQueueInfo();
          if (vmReplyQueueInfo != null) {
            replyQueueDepth = vmReplyQueueInfo.getQueueSize();
          }
        } else {
          replyQueueInfo = entry.getReplyQueueInfo();
          if (replyQueueInfo != null) {
            replyQueueDepth = replyQueueInfo.getQueueSize();
          }
        }
        // compute the delta idle time by subtracting previously reported idle time from the
        // current idle time
        double deltaIdleTime = 0;
        if (initial == false) {
          deltaIdleTime = idleTime - entry.getIdleTime();
        }

        double deltaAnalysisTime = 0;
        if (analysisTime > 0) {
          deltaAnalysisTime = analysisTime - entry.getAnalysisTime();
        }

        ServiceMetrics serviceMetrics = new ServiceMetrics();
        serviceMetrics.setCasMultiplier(entry.getServiceInfoMBeanProxy().isCASMultiplier());
        serviceMetrics.setServiceRemote(isRemote);
        serviceMetrics.setTopLevelService(topLevel);
        serviceMetrics.setTimestamp((double) uptime / 1000000);
        serviceMetrics.setIdleTime(deltaIdleTime);
        serviceMetrics.setServiceName(name.getKeyProperty("name"));
        serviceMetrics.setProcessCount(processCount - entry.getLastCASCount());
        serviceMetrics.setInputQueueDepth(inputQueueDepth);
        serviceMetrics.setReplyQueueDepth(replyQueueDepth);
        serviceMetrics.setProcessThreadCount(entry.getServicePerformanceMBeanProxy()
                .getProcessThreadCount());
        serviceMetrics.setAnalysisTime(deltaAnalysisTime);
        serviceMetrics.setCmFreeCasInstanceCount(cmFreeCasInstanceCount);
        // The service cas pool proxy is only valid for aggregates and top level primitives
		serviceMetrics.setSvcFreeCasInstanceCount(-1);
        if (getServiceCasPoolMBeanProxy != null) {
        	try {
                serviceMetrics.setSvcFreeCasInstanceCount(getServiceCasPoolMBeanProxy
                        .getAvailableInstances());
        	} catch( Exception e) {
        	}
        }
        // populate shadow CAS pool metric for remote CAS multiplier. Filter out the top level
        // service
        if (entry.getServiceInfoMBeanProxy().isCASMultiplier() && isRemote && !topLevel) {
          serviceMetrics.setShadowCasPoolWaitTime(shadowCasPoolWaitTime
                  - entry.getTotalSCPWaitTime());
          entry.setSCPWaitTime(shadowCasPoolWaitTime);
        } else {
          // populate CAS pool metric
          serviceMetrics.setCasPoolWaitTime(casPoolWaitTime - entry.getTotalCPWaitTime());
          entry.setCPWaitTime(casPoolWaitTime);
        }
        // Add metrics collected from the service to the array of metrics
        // in the current sampling (interval). The metrics array will
        // be provided to all listeners plugged into this monitor.
//        metrics[index++] = serviceMetrics;
        metricsList.add(serviceMetrics);
        // Save current metrics for the next delta
        entry.setIdleTime(idleTime);
        entry.incrementCASCount(processCount);
        entry.incrementAnalysisTime(analysisTime);

      } catch (Exception e) {
        if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
          UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, CLASS_NAME.getName(),
                  "collectStats", JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
                  "UIMAJMS_exception__WARNING", e);
        }
      }
    } // for
    if ( metricsList.size() > 0 ) {
      metrics = new ServiceMetrics[metricsList.size()];
      metricsList.toArray(metrics);
    }
    return metrics;
  }

  /**
   * Retrieves metrics from UIMA-AS MBeans at defined interval.
   * 
   */
  public void run() {
    running = true;
    boolean initial = true;
    while (running) {
      long sampleStart = System.nanoTime();
      long uptime = sampleStart - startTime;
      try {
        if (mbsc != null) {
          mbsc.getMBeanCount(); // test the server
        }
      } catch (Exception e) {
        if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
          UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, CLASS_NAME.getName(),
                  "run", JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
                  "UIMAJMS_exception__WARNING", e);
        }
      }
      ServiceMetrics[] metrics = collectStats(initial, uptime);
      if ( metrics != null ) {
        initial = false;
        // Notify listeners with current metrics collected from MBeans
        notifyListeners(uptime, metrics);
      }

      // compute wait time till next sample
      long sampleEnd = System.nanoTime();
      long time2wait;
      long timeLost = (200000 + sampleEnd - sampleStart) / 1000000;
      if (interval > timeLost) {
        time2wait = interval - timeLost;
      } else {
        // Increase interval to a multiple of the requested interval
        long newInterval = interval * (1 + (timeLost / interval));
        time2wait = newInterval - timeLost;
      }

      synchronized (this) {
        try {
          wait(time2wait);
        } catch (InterruptedException e) {
          running = false;
        }
      }
    }

    running = false;
  }

  public boolean isRunning() {
    return running;
  }

  public void doStop() {
    running = false;
  }

  /*
   * For simplicity, we declare "throws Exception". Real programs will usually want finer-grained
   * exception handling.
   */
  public static void main(String[] args) throws Exception {

    JmxMonitor monitor = new JmxMonitor();
    if (System.getProperty("verbose") != null) {
      // Shows a list of UIMA-AS services found in a given JMX server
      monitor.setVerbose();
    }
    long samplingFrequency = 1000; // default sampling every 1000 ms

    if (args.length == 0 || args.length > 2) {
      printUsage();
      System.exit(-1);
    }

    if (args.length == 3) {
      // The second parameter is the sampling frequency
      samplingFrequency = Long.parseLong(args[2]);
    }

    echo("\nJMX Sampling Frequency:" + samplingFrequency);
    // Use the URI provided in the first arg to connect to the JMX server.
    // Also define sampling frequency. The monitor will collect the metrics
    // at this interval.
    monitor.initialize(args[0], samplingFrequency);
    // Create listener
    BasicUimaJmxMonitorListener listener = new BasicUimaJmxMonitorListener(monitor
            .getMaxServiceNameLength());
    // Plug in the monitor listener
    monitor.addJmxMonitorListener(listener);
    // Create and start the monitor thread
    Thread t = new Thread(monitor);
    t.start();

  }

  private static void printUsage() {
    System.out
            .println("Usage: \njava -cp %UIMA_HOME%/lib/*;%UIMA_HOME%/apache-activemq/* -Djava.util.logging.config.file=%UIMA_HOME%/config/MonitorLogger.properties    org.apache.uima.aae.jmx.monitor.JmxMonitor uri <frequency>\nuri - jmx server URI- required\nfrequency - how often the checkpoint is done. Default: 1000ms - optional");
  }

  private static void echo(String msg) {
    System.out.println(msg);
  }

  private static class StatEntry {
    ServicePerformanceMBean servicePerformanceMBeanProxy;

    ServiceInfoMBean serviceInfoMBeanProxy;

    QueueViewMBean inputQueueInfo;

    UimaVmQueueMBean vmInputQueueInfo;

    QueueViewMBean replyQueueInfo;

    UimaVmQueueMBean vmReplyQueueInfo;

    CasPoolManagementImplMBean casPoolMBeanProxy;

    CasPoolManagementImplMBean serviceCasPoolMBeanProxy;

    String name = "";

    long queueSize;
    
    public long getQueueSize() {
		return queueSize;
	}

	public void setQueueSize(long queueSize) {
		this.queueSize = queueSize;
	}

	boolean isVmQueue = true;

    double lastIdleTime = 0;

    double waitTimeOnCMGetNext = 0;

    long casProcessedCount = 0;

    double cpWaitTime;

    double scpWaitTime;

    double analysisTime = 0.0;

    public StatEntry(ServicePerformanceMBean perfBean, ServiceInfoMBean infoBean) {
      this(perfBean, infoBean, null);
    }

    public StatEntry(ServicePerformanceMBean perfBean, ServiceInfoMBean infoBean,
            CasPoolManagementImplMBean casPoolMBeanProxy) {
      servicePerformanceMBeanProxy = perfBean;
      serviceCasPoolMBeanProxy = casPoolMBeanProxy;
      serviceInfoMBeanProxy = infoBean;
      if (!infoBean.getBrokerURL().startsWith("Embedded Broker")) {
        isVmQueue = false; // This is JMS queue
      }
    }

    public CasPoolManagementImplMBean getServiceCasPoolMBeanProxy() {
      return serviceCasPoolMBeanProxy;
    }

    public void setInputQueueInfo(QueueViewMBean queueView) {
      inputQueueInfo = queueView;
    }

    public void setInputQueueInfo(UimaVmQueueMBean queueView) {
      vmInputQueueInfo = queueView;
    }

    public QueueViewMBean getInputQueueInfo() {
      return inputQueueInfo;
    }

    public UimaVmQueueMBean getVmInputQueueInfo() {
      return vmInputQueueInfo;
    }

    public void setReplyQueueInfo(QueueViewMBean queueView) {
      replyQueueInfo = queueView;
    }

    public void setReplyQueueInfo(UimaVmQueueMBean queueView) {
      vmReplyQueueInfo = queueView;
    }

    public QueueViewMBean getReplyQueueInfo() {
      return replyQueueInfo;
    }

    public UimaVmQueueMBean getVmReplyQueueInfo() {
      return vmReplyQueueInfo;
    }

    public void setCasPoolMBean(CasPoolManagementImplMBean anMBean) {
      casPoolMBeanProxy = anMBean;
    }

    public CasPoolManagementImplMBean getCasPoolMBean() {
      return casPoolMBeanProxy;
    }

    public double getIdleTime() {
      return lastIdleTime;
    }

    public void incrementCASCount(long aCASCount) {
      casProcessedCount = +aCASCount;
    }

    public long getLastCASCount() {
      return casProcessedCount;
    }

    public void setIdleTime(double lastIdleTime) {
      this.lastIdleTime = lastIdleTime;
    }

    public ServicePerformanceMBean getServicePerformanceMBeanProxy() {
      return servicePerformanceMBeanProxy;
    }

    public ServiceInfoMBean getServiceInfoMBeanProxy() {
      return serviceInfoMBeanProxy;
    }

    public void setName(String aName) {
      name = aName;
    }

    public String getName() {
      return name;
    }

    public void incrementWaitTimeOnCMGetNext(double aWaitTimeOnCMGetNext) {
      waitTimeOnCMGetNext = +aWaitTimeOnCMGetNext;
    }

    public double getWaitTimeOnCMGetNext() {
      return waitTimeOnCMGetNext;
    }

    public double getTotalCPWaitTime() {
      return cpWaitTime;
    }

    public void setCPWaitTime(double aWaitTime) {
      cpWaitTime = aWaitTime;
    }

    public double getTotalSCPWaitTime() {
      return scpWaitTime;
    }

    public void setSCPWaitTime(double aWaitTime) {
      scpWaitTime = aWaitTime;
    }

    public double getAnalysisTime() {
      return analysisTime;
    }

    public void incrementAnalysisTime(double anAnalysisTime) {
      analysisTime = +anAnalysisTime;
    }
  }

}
