/*
 * 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.controller;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.uima.UIMAException;
import org.apache.uima.UIMAFramework;
import org.apache.uima.aae.UIMAEE_Constants;
import org.apache.uima.aae.jmx.JmxManagement;
import org.apache.uima.aae.jmx.JmxManager;
import org.apache.uima.internal.util.JmxMBeanAgent;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.util.Level;
import org.springframework.beans.factory.DisposableBean;

/**
 * This bean functions as a proxy for a Uima C++ service. It starts the Uima C++ service given a
 * UIMA descriptor, the input queue name, and environment settings required for the specific
 * annotator and the Uima C++ runtime. On start up a socket connection is established between this
 * instance of the proxy and the service. This connection is used for routing log message from the
 * C++ service to the UIMA framework logger and to allow the proxy to send JMX and administrative
 * requests such as shutdown.
 * 
 */
public class UimacppServiceController extends AnalysisEngineControllerAdapter implements
        ControllerLifecycle, DisposableBean {
  private static final Class CLASS_NAME = UimacppServiceController.class;

  private static final String STARTING_DIRECTORY = "UIMACPP_STARTING_DIRECTORY";

  protected ServerSocket server;

  private int port;

  private Socket loggerConnection;

  private Socket commandConnection;

  protected Process uimacppProcess;

  private LoggerHandler loggerHandler;

  private StdoutHandler stdoutHandler;

  private StderrHandler stderrHandler;

  private ProcessBuilder builder;

  private String startingDirectory;

  private String aeDesc;

  private String queueName;

  private int numInstances;

  private int processCasErrorThreshhold;

  private int processCasErrorWindow;

  private boolean terminateOnCPCError;

  private org.apache.uima.util.Logger uimaLogger;

  protected UimacppServiceManagement mbean;

  private JmxManagement jmxMgmt;

  private UimacppShutdownHook shutdownHook;

  private int initialFsHeapSize;

  private ArrayList<ControllerCallbackListener> listeners = new ArrayList<ControllerCallbackListener>();

  private Boolean InitializedState = false;

  private Exception InitializedStatus = null;
  
  private Boolean isStopped = false;

  /**
   * Configure and start a Uima C++ service that connects to an ActiveMQ queue broker. This class
   * initializes the process environment and starts a process to deploy the C++ service.
   * Communication via sockets is established between this Controller and the C++ service through
   * which logging, JMX and administrative messages are transmitted.
   * 
   * @param aeDescriptorFileName
   *          - UIMA analysis engine descriptor file.
   * @param queueName
   *          - input queue name
   * @param brokerURL
   *          - queue broker URL
   * @param numInstances
   *          - number of instance of AEs to start in the service.
   * @param prefetchSize
   *          - number of message to prefetch
   * @param envVarMap
   *          - enviroment variables to be set. These settings are valid only for the new process in
   *          which C++ service will run.
   * @throws UIMAException
   */
  public UimacppServiceController(String aeDescriptorFileName, String queueName, String brokerURL,
          int numInstances, int prefetchSize, Map<String, String> envVarMap,
          int processCasErrorThreshhold, int processCasErrorWindow, boolean terminateOnCPCError,
          int initialFsHeapSize) throws ResourceInitializationException {

    try {
      this.uimaLogger = UIMAFramework.getLogger(this.getClass());
      this.aeDesc = aeDescriptorFileName;
      this.numInstances = numInstances;
      this.queueName = queueName;
      this.processCasErrorThreshhold = processCasErrorThreshhold;
      this.processCasErrorWindow = processCasErrorWindow;
      this.terminateOnCPCError = terminateOnCPCError;
      this.initialFsHeapSize = initialFsHeapSize;
      this.startingDirectory = envVarMap.get(STARTING_DIRECTORY);
      this.isStopped = false;

      /* start a listener */
      server = new ServerSocket(0);
      port = server.getLocalPort();
      server.setSoTimeout(10000);

      // System.out.println("Listening on port " + port);

      /* setup the command */
      ArrayList<String> commandArgs = new ArrayList<String>();
      buildCommandArgs(commandArgs, envVarMap, "deployCppService");

      if (brokerURL != null && brokerURL.length() > 0) {
        commandArgs.add("-b");
        commandArgs.add(brokerURL);
      }

      commandArgs.add("-p");
      commandArgs.add(Integer.toString(prefetchSize));

      /* construct the process builder */
      builder = new ProcessBuilder(commandArgs);
      setEnvironmentVariables(envVarMap);

      /* setup environment variables */

      // System.out.println("Uima C++ Service " + aeDesc + " configured.");
      // System.out.println("Listener started at port " + port + ".");
      // System.out.println(builder.command().toString());
      /* start the C++ service */
      // try {
      // start threads to accept two connections from the service

      /* start the service */
      this.uimaLogger.log(Level.INFO, "Starting C++ service: " + commandArgs.toString());
      this.uimaLogger.log(Level.INFO, " env params: " + envVarMap.toString());
      startService();

      /** register with JMX - for now register with the platform MBean server */
      mbean = new UimacppServiceManagement("org.apache.uima:type=ee.jms.services,",
              commandConnection, aeDesc, numInstances, brokerURL, queueName);
      JmxMBeanAgent.registerMBean(mbean, null);

      // Initialization looks good
      notifyInitializationStatus(null);

    } catch (IOException e) {
      notifyInitializationStatus(e);
      throw new ResourceInitializationException(e);
    } catch (UIMAException e) {
      notifyInitializationStatus(e);
      throw new ResourceInitializationException(e);
    }
  }

  private void notifyInitializationStatus(Exception e) {
    synchronized (this) {
      if (!this.InitializedState) {
        this.InitializedStatus = e;
        this.InitializedState = true;
      }

      for (int i = 0; i < this.listeners.size(); i++) {
        // If there is an exception, notify listener with failure
        if (e != null) {
          (this.listeners.get(i)).notifyOnInitializationFailure(e);
        }
        // else, Success!
        else {
          (this.listeners.get(i)).notifyOnInitializationSuccess();
        }
      }
    }
  }

  public UimacppServiceController(String aeDescriptorFileName, String queueName, String brokerURL,
          int numInstances, int prefetchSize, Map<String, String> envVarMap,
          int processCasErrorThreshhold, int processCasErrorWindow, boolean terminateOnCPCError)
          throws ResourceInitializationException {

    this(aeDescriptorFileName, queueName, brokerURL, numInstances, prefetchSize, envVarMap,
            processCasErrorThreshhold, processCasErrorWindow, terminateOnCPCError, 0);

  }

  /**
   * Configure and start a Uima C++ service that connects to an WebSphereMQ queue broker. This class
   * initializes the process environment and starts a process to deploy the C++ service.
   * Communication via sockets is established between this Controller and the C++ service through
   * which logging, JMX and administrative messages are transmitted.
   * 
   * @param uimaLogger
   * @param aeDescriptorFileName
   * @param mqQueueName
   * @param mqHostName
   * @param mqPort
   * @param mqChannel
   * @param mqQueueMgr
   * @param numInstances
   * @param envVarMap
   * @param processCasErrorThreshhold
   * @param processCasErrorWindow
   * @param terminateOnCPCError
   * @param mBeanServer
   * @throws ResourceInitializationException
   */
  public UimacppServiceController(org.apache.uima.util.Logger uimaLogger,
          String aeDescriptorFileName, String queueName, String mqHostName, int mqPort,
          String mqChannel, String mqQueueMgr, int numInstances, Map<String, String> envVarMap,
          int processCasErrorThreshhold, int processCasErrorWindow, boolean terminateOnCPCError,
          JmxManagement jmxManagement, int initialFsHeapSize)
          throws ResourceInitializationException {

    try {
      this.uimaLogger = UIMAFramework.getLogger(this.getClass());
      this.aeDesc = aeDescriptorFileName;
      this.numInstances = numInstances;
      this.queueName = queueName;
      this.processCasErrorThreshhold = processCasErrorThreshhold;
      this.processCasErrorWindow = processCasErrorWindow;
      this.terminateOnCPCError = terminateOnCPCError;
      this.initialFsHeapSize = initialFsHeapSize;
      this.startingDirectory = envVarMap.get(STARTING_DIRECTORY);
      this.jmxMgmt = jmxManagement;
      this.isStopped = false;
      
      /* start a listener */
      server = new ServerSocket(0);
      port = server.getLocalPort();
      server.setSoTimeout(10000);

      // System.out.println("Listening on port " + port);
      /* setup the command */
      ArrayList<String> commandArgs = new ArrayList<String>();
      buildCommandArgs(commandArgs, envVarMap, "deployWMQCppService");

      // add MQ specific args
      if (mqHostName != null && mqHostName.length() > 0) {
        commandArgs.add("-mqh");
        commandArgs.add(mqHostName);
      }

      commandArgs.add("-mqp");
      commandArgs.add(Integer.toString(mqPort));

      if (mqChannel != null && mqChannel.length() > 0) {
        commandArgs.add("-mqc");
        commandArgs.add(mqChannel);
      }

      if (mqQueueMgr != null && mqQueueMgr.length() > 0) {
        commandArgs.add("-mqm");
        commandArgs.add(mqQueueMgr);
      }

      /* construct the process builder */
      builder = new ProcessBuilder(commandArgs);
      setEnvironmentVariables(envVarMap);

      // System.out.println("Uima C++ Service " + aeDesc + " configured.");
      // System.out.println("Listener started at port " + port + ".");
      // System.out.println(builder.command().toString());

      /* start the service */
      this.uimaLogger.log(Level.INFO, "Starting C++ service: " + commandArgs.toString());
      this.uimaLogger.log(Level.INFO, " env params: " + envVarMap.toString());
      startService();

      mbean = new UimacppServiceManagement(null, commandConnection, aeDesc, numInstances,
              mqHostName + " " + mqPort + "//" + mqQueueMgr, "queue://" + /* mqChannel + */"/"
                      + queueName);

      /** register with JMX */

      if (jmxManagement == null) {
        throw new ResourceInitializationException(new IOException("JmxManagement object is null."));
      }

      mbean = new UimacppServiceManagement(jmxManagement.getJmxDomain(), commandConnection, aeDesc,
              numInstances, mqHostName + " " + mqPort + "//" + mqQueueMgr, queueName);
      ObjectName oname = new ObjectName(mbean.getUniqueMBeanName());
      jmxManagement.registerMBean(mbean, oname);

    } catch (IOException e) {
      throw new ResourceInitializationException(e);
    } catch (UIMAException e) {
      throw new ResourceInitializationException(e);
    } catch (MalformedObjectNameException e) {
      throw new ResourceInitializationException(e);
    } catch (NullPointerException e) {
      throw new ResourceInitializationException(e);
    } catch (Exception e) {
      throw new ResourceInitializationException(e);
    }
  }

  private void buildCommandArgs(ArrayList<String> commandArgs, Map<String, String> envVarMap,
          String exeName) throws ResourceInitializationException {

    String uimacppHome = (String) envVarMap.get("UIMACPP_HOME");
    if (uimacppHome == null) {
      uimacppHome = System.getenv("UIMACPP_HOME");
      if (uimacppHome == null) {
        uimacppHome = System.getenv("UIMA_HOME") + "/uimacpp";
      }
    }

    if (!(new File(uimacppHome)).exists()) {
      throw new ResourceInitializationException(new IOException("Invalid location of UIMACPP_HOME "
              + uimacppHome));
    }

    // the Uima C++ service wrapper exe
    String cmd = uimacppHome + System.getProperty("file.separator") + "bin"
            + System.getProperty("file.separator") + exeName;

    commandArgs.add(cmd);

    // arguments
    // UIMA AE descriptor
    // eclipse likes URL formats starting with file:, but C++ doesn't
    if (this.aeDesc.regionMatches(true, 0, "file:", 0, 5)) {
      this.aeDesc = this.aeDesc.substring(5);
    }
    Pattern mSlashDosDrive = Pattern.compile("/[a-zA-Z]:");
    Matcher matcher = mSlashDosDrive.matcher(this.aeDesc);
    if (matcher.find(0)) {
      // "/c:" doesn't work either
      this.aeDesc = this.aeDesc.substring(1);
    }

    commandArgs.add(aeDesc);
    if (!(new File(aeDesc)).exists()) {
      throw new ResourceInitializationException(new IOException(
              "Invalid location of AE descriptor " + aeDesc));
    }

    // input queue name
    commandArgs.add(queueName);

    // port this server is listening at
    commandArgs.add("-jport");
    commandArgs.add(Integer.toString(port));

    // number of instances of consumers the
    // service should start
    commandArgs.add("-n");
    if (numInstances < 1) {
      numInstances = 1;
    }
    commandArgs.add(Integer.toString(numInstances));

    // logging level setting obtained from the UIMA framework
    // translated to UIMA C++ logging levels.
    commandArgs.add("-l");
    if (uimaLogger.isLoggable(Level.FINE) || uimaLogger.isLoggable(Level.CONFIG)
            || uimaLogger.isLoggable(Level.FINER) || uimaLogger.isLoggable(Level.FINEST)
            || uimaLogger.isLoggable(Level.INFO)) {
      commandArgs.add(Integer.toString(0));
    } else if (uimaLogger.isLoggable(Level.WARNING)) {
      commandArgs.add(Integer.toString(1));
    } else if (uimaLogger.isLoggable(Level.SEVERE)) {
      commandArgs.add(Integer.toString(2));
    } else {
      commandArgs.add(Integer.toString(-1));
    }

    // translate logger level to trace level 0-4.
    // set based Level = CONFIG, INFO, FINE, FINER, FINEST...
    commandArgs.add("-t");
    if (uimaLogger.isLoggable(Level.FINEST)) {
      commandArgs.add(Integer.toString(3));
    } else if (uimaLogger.isLoggable(Level.FINER)) {
      commandArgs.add(Integer.toString(2));
    } else if (uimaLogger.isLoggable(Level.FINE)) {
      commandArgs.add(Integer.toString(1));
    } else if (uimaLogger.isLoggable(Level.CONFIG) || uimaLogger.isLoggable(Level.INFO)) {
      commandArgs.add(Integer.toString(0));
    } else {
      commandArgs.add(Integer.toString(-1));
    }
    // data directory used to resolve location of
    // files used by annotator.
    String uimacppDataPath = (String) envVarMap.get("UIMACPP_DATAPATH");
    if (uimacppDataPath != null && uimacppDataPath.length() != 0) {
      commandArgs.add("-d");
      commandArgs.add(uimacppDataPath);
    }

    if (processCasErrorThreshhold > 0) {
      commandArgs.add("-e");
      commandArgs.add(Integer.toString(processCasErrorThreshhold));
      if (processCasErrorWindow > 0) {
        commandArgs.add("-w");
        commandArgs.add(Integer.toString(processCasErrorWindow));
      }
    }

    if (terminateOnCPCError) {
      commandArgs.add("-a");
      commandArgs.add("true");
    }

    if (initialFsHeapSize > 0) {
      commandArgs.add("-fsheapsz");
      commandArgs.add(Integer.toString(initialFsHeapSize));
    }

  }

  public UimacppServiceController(org.apache.uima.util.Logger uimaLogger,
          String aeDescriptorFileName, String queueName, String mqHostName, int mqPort,
          String mqChannel, String mqQueueMgr, int numInstances, Map<String, String> envVarMap,
          int processCasErrorThreshhold, int processCasErrorWindow, boolean terminateOnCPCError,
          JmxManagement jmxManagement) throws ResourceInitializationException {

    this(uimaLogger, aeDescriptorFileName, queueName, mqHostName, mqPort, mqChannel, mqQueueMgr,
            numInstances, envVarMap, processCasErrorThreshhold, processCasErrorWindow,
            terminateOnCPCError, jmxManagement, 0);

  }

  private void setEnvironmentVariables(Map<String, String> envVarMap) {
    /* setup environment variables */
    String uimacppHome = (String) envVarMap.get("UIMACPP_HOME");
    String uimacppLibDir = uimacppHome + System.getProperty("file.separator") + "lib"
            + System.getProperty("path.separator") + uimacppHome
            + System.getProperty("file.separator") + "lib" + System.getProperty("file.separator")
            + "xms";

    Map<String, String> environment = builder.environment();

    // add uimacpp lib dir to the path
    String value = environment.get("PATH");
    if (value != null && value.length() > 0) {
      value = uimacppLibDir + System.getProperty("path.separator") + value;
    } else {
      value = uimacppLibDir;
    }
    environment.put("PATH", value);

    value = environment.get("LD_LIBRARY_PATH");
    if (value != null && value.length() > 0) {
      value = uimacppLibDir + System.getProperty("path.separator") + value;
    } else {
      value = uimacppLibDir;
    }
    environment.put("LD_LIBRARY_PATH", value);

    value = environment.get("DYLD_LIBRARY_PATH");

    value = environment.get("LD_LIBRARY_PATH");
    if (value != null && value.length() > 0) {
      value = uimacppLibDir + System.getProperty("path.separator") + value;
    } else {
      value = uimacppLibDir;
    }
    environment.put("DYLD_LIBRARY_PATH", value);

    // set user specified environment variables
    Set set = envVarMap.entrySet();

    for (Iterator it = set.iterator(); it.hasNext();) {
      Map.Entry entry = (Map.Entry) it.next();
      String key = (String) entry.getKey();
      value = (String) entry.getValue();

      if (value != null && value.length() > 0) {
        // special handling for PATH and LD_LIBRARY_PATH
        // and DYLD_LIBRARY_PATH
        // for these we prepend the values to the
        // existing values.
        if (key.equalsIgnoreCase("PATH") || key.equalsIgnoreCase("LD_LIBRARY_PATH")
                || key.equalsIgnoreCase("DYLD_LIBRARY_PATH")) {
          String origValue = environment.get(key);
          if (origValue != null) {
            value = value + System.getProperty("path.separator") + uimacppLibDir
                    + System.getProperty("path.separator") + origValue;
          }

        }
        // System.out.println(key+" "+value);
        environment.put(key, value);
      }
    }

  }

  private void startService() throws UIMAException {
    /* start the C++ service */
    try {
      // start threads to accept two connections from the service
      ConnectionHandler handler1 = new ConnectionHandler(this);
      Thread t1 = new Thread(handler1);
      t1.start();
      ConnectionHandler handler2 = new ConnectionHandler(this);
      Thread t2 = new Thread(handler2);
      t2.start();

      // setup starting directory if specified.
      if (this.startingDirectory != null && this.startingDirectory.length() > 0) {
        File startingDir = new File(this.startingDirectory);
        if (!startingDir.exists()) {
          throw new ResourceInitializationException(new IOException(this.startingDirectory
                  + " Uimacpp Starting Directory not found. + "));
        }
        builder.directory(startingDir);
      }
      /* add the shutdown hook */
      shutdownHook = new UimacppShutdownHook(this, commandConnection, uimaLogger);
      Runtime.getRuntime().addShutdownHook(shutdownHook);
      
      /* fork the C++ process */
      uimacppProcess = builder.start();
      
      if (uimacppProcess == null) {
        throw new UIMAException(new Throwable("Could not fork process."));
      }

      stdoutHandler = new StdoutHandler(uimacppProcess, uimaLogger);
      Thread t3 = new Thread(stdoutHandler);
      t3.start();

      stderrHandler = new StderrHandler(uimacppProcess, uimaLogger);
      Thread t4 = new Thread(stderrHandler);
      t4.start();

      // wait for connection handler threads to complete
      t1.join();
      t2.join();
      synchronized (this) {
        if (this.loggerConnection == null || this.loggerHandler == null
                || this.commandConnection == null) {
          throw new ResourceInitializationException(new IOException(
                  "Could not establish socket connection with C++ service."));
        }
      }

      if (uimacppProcess != null) {
        // wait for C++ process to report initialization status.
        System.out.println("Waiting for Uima C++ service to report init status...");
        BufferedReader in = new BufferedReader(new InputStreamReader(commandConnection
                .getInputStream()));

        StringBuffer sb = new StringBuffer();
        int c = in.read();
        while (c >= 0) {
          sb.append((char) c);
          c = in.read();
          if (c == '\n') {
            break;
          }
        }
        if (sb.toString().equalsIgnoreCase("0")) {
          System.out.println("Uima C++ service at " + queueName + " Ready to process...");
          WaitThread wt = new WaitThread(uimacppProcess, uimaLogger, this);
          Thread wThread = new Thread(wt);
          wThread.start();
        } else {
          System.out.println("UIMA C++ service at " + queueName + " failed to initialize.");
          System.out.println(sb.toString());
          uimacppProcess.destroy();
          throw new IOException(sb.toString());
        }
      } else {
        throw new ResourceInitializationException(new IOException(
                "Could not start the C++ service."));
      }
    } catch (IOException e) {
      throw new ResourceInitializationException(e);
    } catch (InterruptedException e) {
      throw new ResourceInitializationException(e);
    }

  }

  /**
   * Shuts down the UIMA C++ service process.
   * 
   * @param force
   *          - force or allow service to shutdown gracefully.
   * @throws IOException
   * @throws InterruptedException
   */
  public void shutdown() throws IOException, InterruptedException {
    mbean.shutdown();
    if (jmxMgmt != null) {
      try {
        this.jmxMgmt.destroy();
      } catch (Exception e) {
        throw new IOException(e.getMessage());
      }
    }
    if (listeners != null) {
      for (int i = 0; i < listeners.size(); i++) {
        ControllerCallbackListener listener = (ControllerCallbackListener) listeners.get(i);
        if (listener != null) {
          listener.notifyOnTermination("Uima C++ service shutdown.");
        }
      }
      listeners.clear();
    }
    synchronized (this) {
      loggerConnection.close();
    }
    commandConnection.close();
    server.close();
    this.isStopped = true;
  }

  public String getStatistics() throws IOException {
    return mbean.getStatisticsAsString();
  }

  public void resetStatistics() throws IOException {
    mbean.resetStats();
  }

  synchronized protected void handleConnection(Socket inSock) throws IOException {
    if (this.loggerConnection == null) {
      this.loggerConnection = inSock;

      loggerHandler = new LoggerHandler(loggerConnection, uimaLogger);
      (new Thread(loggerHandler)).start();
      // System.out.println("Accepted logger connection.");
    } else {
      this.commandConnection = inSock;
      // System.out.println("Accepted commands connection.");
    }
  }

  /**
   * test
   * 
   * @param args
   */
  public static void main(String[] args) {
    HashMap<String, String> envVarMap = new HashMap<String, String>();

    try {
      if (System.getProperty("os.name").startsWith("Windows")) {
        envVarMap.put("UIMACPP_HOME", "c:\\uimacpp2.0\\uimacpp");
        envVarMap.put("UIMACPP_LOGFILE", "c:\\temp\\uimacppcontroller.log");
        envVarMap.put("Path", "c:\\cppExamples2.0\\src");

        UimacppServiceController controller = new UimacppServiceController(
                "c:/cppExamples2.0/descriptors/DaveDetector.xml", // AE descriptor
                "davedetector", // input queue
                "tcp://localhost:61616", // activemq broker url
                1, // num instances
                0, // prefetch,
                envVarMap, 0, // processCAS error threshhold
                0, // processCAS error window
                false, 2000000);
      } else {
        envVarMap.put("UIMACPP_HOME", "/opt/IBM/uimacpp");
        // envVarMap.put("Path", "/opt/IBM/uimacpp/bin");
        envVarMap.put("UIMACPP_LOGFILE", "/tmp/bhavani.log");
        // envVarMap.put("LD_LIBRARY_PATH", "/opt/IBM/uimacpp/lib:/opt/IBM/uimacpp/lib/xms");
        UimacppServiceController controller = new UimacppServiceController(UIMAFramework
                .getLogger(), "/home/bsiyer/cppExamples/descriptors/DaveDetector.xml",
                "ORANGE.QUEUE", "sith07.watson.ibm.com", 1414, null, null, 1, envVarMap, 0, 0,
                false, null, 0);
      }

      /**
       * try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch
       * block e.printStackTrace(); }
       * 
       * System.out.println("MAIN getStats"); for (int i = 0; i < 3; i++) { String stats =
       * controller.getStatistics(); if (stats == null) System.out.println("NULL stats"); else
       * System.out.println(i + " " + stats); }
       * 
       * System.out.println("MAIN RESET STATS"); controller.resetStatistics(); String stats =
       * controller.getStatistics(); if (stats == null) System.out.println("NULL stats"); else
       * System.out.println("AFTER RESET " + stats);
       * 
       * try { controller.shutdown(); } catch (InterruptedException e1) { e1.printStackTrace(); }
       * catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
       **/

    } catch (ResourceInitializationException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, UimacppServiceController.class.getName(),
                "main", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    }

  }

  /**
   * 
   * Runs when UIMA EE client API undeploys this service.
   * 
   */
  public void terminate() {
    try {
      shutdown();
    } catch (IOException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "terminate", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    } catch (InterruptedException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "terminate", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    }
  }

  /**
   * 
   * Runs when spring undeploys this bean.
   * 
   */
  public void destroy() {
    try {
      shutdown();
    } catch (IOException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "destroy", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    } catch (InterruptedException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "destroy", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    }
  }

  public void addControllerCallbackListener(ControllerCallbackListener aListener) {
    synchronized (this) {
      this.listeners.add(aListener);
      // if already initialized, notify now
      if (this.InitializedState) {
        if (this.InitializedStatus == null) {
          aListener.notifyOnInitializationSuccess();
        } else {
          aListener.notifyOnInitializationFailure(this.InitializedStatus);
        }
      }
    }
  }

  public void removeControllerCallbackListener(ControllerCallbackListener aListener) {
    this.listeners.remove(aListener);
  }

  public void quiesceAndStop() {
		try {
		  String msg = this.mbean.quiesceAndStop();
		  //System.out.println("UimacppServiceController service reports QuiesceAndStop " + msg);
		  this.uimaLogger.log(Level.INFO, "Service reports QuiesceAndStop " + msg );
		} catch (IOException e ) {
		  this.uimaLogger.log(Level.SEVERE, e.getMessage());
		}
  }

  public boolean isStopped() {
    return this.isStopped;
  }
  
  public void setStopped() {
    this.isStopped = true;
  }
  
  public ArrayList<ControllerCallbackListener> getCallbackListeners() {
	  return this.listeners;
  }

  public String getKey() {
    return "";
  }
  public void dumpState(StringBuffer buffer, String lbl1) {
    // TODO Auto-generated method stub
    
  }
}

/**
 * Handles C++ service logging requests. It receives messages sent through the logger socket
 * connection and writes it to the UIMA logger.
 * 
 * 
 */
class LoggerHandler implements Runnable {
  Socket socket;

  BufferedReader in;

  org.apache.uima.util.Logger logger;

  public LoggerHandler(Socket sock, org.apache.uima.util.Logger uimaLogger) throws IOException {

    socket = sock;
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    logger = uimaLogger;
  }

  public void logMessage(String logMessage) {
    // determine the logging level of the message
    // translate uimacpp log level to java logger level
    Level level = Level.INFO; // default

    if (logMessage.startsWith("0")) {
      level = Level.INFO;
    } else if (logMessage.startsWith("1")) {
      level = Level.WARNING;
    } else if (logMessage.startsWith("2")) {
      level = Level.SEVERE;
    }

    logMessage = logMessage.trim();
    // log the message
    logger.log(level, logMessage);
    // System.out.println(logMessage);
  }

  public void run() {
    try {
      StringBuilder sb = new StringBuilder();

      int c = in.read();
      while (c >= 0) {
        sb.append((char) c);
        if (c == '\n') {
          logMessage(sb.toString());
          sb.delete(0, sb.length());
        }
        c = in.read();
      }
    } catch (IOException e) {
      logger.log(Level.WARNING, e.getMessage());
    }
  }
} // log handler

/**
 * accept socket connection from C++ service.
 */
class ConnectionHandler implements Runnable {
  private static final Class CLASS_NAME = ConnectionHandler.class;

  UimacppServiceController controller;

  ConnectionHandler(UimacppServiceController controller) {
    this.controller = controller;
  }

  public void run() {

    Socket aSocket;
    try {
      System.out.println("Waiting for Uima C++ service to connect...");
      aSocket = controller.server.accept();
      controller.handleConnection(aSocket);
    } catch (SocketTimeoutException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "run", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    } catch (IOException e) {
      if (UIMAFramework.getLogger(CLASS_NAME).isLoggable(Level.WARNING)) {
        UIMAFramework.getLogger(CLASS_NAME).logrb(Level.WARNING, getClass().getName(),
                "run", UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
                "UIMAEE_exception__WARNING", e);
      }
    }

  }

}

/**
 * Logs messages written to stdout by the C++ service.
 * 
 * 
 */
class StdoutHandler implements Runnable {

  BufferedReader in;

  org.apache.uima.util.Logger logger;

  public StdoutHandler(Process aprocess, org.apache.uima.util.Logger uimaLogger) throws IOException {

    in = new BufferedReader(new InputStreamReader(aprocess.getInputStream()));
    logger = uimaLogger;
  }

  public void run() {
    try {
      StringBuilder sb = new StringBuilder();

      int c = in.read();
      while (c >= 0) {
        sb.append((char) c);
        if (c == '\n') {
          logger.log(Level.INFO, sb.toString());
          sb.delete(0, sb.length());
        }
        c = in.read();
      }
    } catch (IOException e) {
      logger.log(Level.WARNING, e.getMessage());
    }

  }
} // stdoutHandler

/**
 * Logs messages written to stderr by the C++ service.
 * 
 * 
 */
class StderrHandler implements Runnable {

  BufferedReader in;

  org.apache.uima.util.Logger logger;

  public StderrHandler(Process aprocess, org.apache.uima.util.Logger uimaLogger) throws IOException {

    in = new BufferedReader(new InputStreamReader(aprocess.getErrorStream()));
    logger = uimaLogger;
  }

  public void run() {
    try {
      StringBuilder sb = new StringBuilder();

      int c = in.read();
      while (c >= 0) {
        sb.append((char) c);
        if (c == '\n') {
          System.out.println(sb.toString());
          logger.log(Level.INFO, sb.toString());
          sb.delete(0, sb.length());
        }
        c = in.read();
      }
    } catch (IOException e) {
      logger.log(Level.WARNING, e.getMessage());
    }
  }
} // stderrHandler

/**
 * 
 * Waits on C++ process.
 * 
 */
class WaitThread implements Runnable {

  public Process uimacppProcess;

  private org.apache.uima.util.Logger uimaLogger;

  private List<ControllerCallbackListener> listeners;
  
  private UimacppServiceController controller;

  public WaitThread(Process aprocess, org.apache.uima.util.Logger logger,
         // List<ControllerCallbackListener> llist) throws IOException {
	      UimacppServiceController cntlr) throws IOException {
    this.uimacppProcess = aprocess;
    this.uimaLogger = logger;
    this.controller = cntlr;
    this.listeners = this.controller.getCallbackListeners();
  }

  public void run() {
    int rc;
    String message = "WaitThread calling UIMA C++ service shutdown.";
    try {
      rc = uimacppProcess.waitFor();
      message += "rc=" + rc;
      controller.setStopped();
      if (listeners != null) {
        for (int i = 0; i < listeners.size(); i++) {
          ControllerCallbackListener listener = (ControllerCallbackListener) listeners.get(i);
          if (listener != null) {
            listener.notifyOnTermination(message);
          }
        }
        listeners.clear();
      }
    } catch (InterruptedException e) {
      this.uimaLogger.log(Level.INFO, e.getMessage());
      message += e.getMessage();
      if (listeners != null) {
        for (int i = 0; i < listeners.size(); i++) {
          ControllerCallbackListener listener = (ControllerCallbackListener) listeners.get(i);
          if (listener != null) {
            listener.notifyOnTermination(message);
          }
        }
        listeners.clear();
      }
    }

  }
} // WaitThread

/**
 * 
 * Runs when JVM termintes.
 * 
 */
class UimacppShutdownHook extends Thread {

  public UimacppServiceController controller;
  
  private org.apache.uima.util.Logger uimaLogger;
  

  public UimacppShutdownHook(UimacppServiceController controller, Socket socket, org.apache.uima.util.Logger logger)
          throws IOException {
    this.controller = controller;
    this.uimaLogger = logger;
  }

  public void run() {
    uimaLogger.log(Level.INFO, "UimacppShutdownHook sending shutdown message.");
    if (controller.mbean != null)
		try {
			System.out.println("UimacppShutdownHook sending quiesce message");
			controller.mbean.quiesceAndShutdown();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
    if (controller.uimacppProcess != null) {
      System.out.println("UimacppShutdownHook destroy C++ process");
      controller.uimacppProcess.destroy();
    }
  }
} // shutdownhook
