/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 * 
 * Please see distribution for license.
 */
package com.opengamma.engine.view.calcnode;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.xml.sax.InputSource;

/**
 * Starts a calculation node and joins a job dispatcher.
 */
public final class CalculationNodeProcess {

  private static final Logger s_logger = LoggerFactory.getLogger(CalculationNodeProcess.class);

  private static final int CONFIGURATION_RETRY = 3;
  private static final int CONFIGURATION_POLL_PERIOD = 5;

  private static HttpClient s_httpClient;

  private CalculationNodeProcess() {
  }

  private static void sleep(int period) {
    try {
      Thread.sleep(1000 * period);
    } catch (InterruptedException e) {
    }
  }

  private static String getConfigurationXml(final String url) {
    if (s_httpClient == null) {
      s_httpClient = new DefaultHttpClient();
    }
    s_logger.debug("Fetching {}", url);
    final HttpResponse resp;
    try {
      resp = s_httpClient.execute(new HttpGet(url));
    } catch (Exception e) {
      s_logger.warn("Error fetching {} - {}", url, e.getMessage());
      return null;
    }
    s_logger.debug("HTTP result {}", resp.getStatusLine());
    if (resp.getStatusLine().getStatusCode() != 200) {
      s_logger.warn("No configuration available (HTTP {})", resp.getStatusLine().getStatusCode());
      return null;
    }
    try {
      final Reader in = new InputStreamReader(resp.getEntity().getContent());
      final StringBuilder sb = new StringBuilder();
      final char[] buf = new char[1024];
      int i = in.read(buf);
      while (i > 0) {
        sb.append(buf, 0, i);
        i = in.read(buf);
      }
      s_logger.debug("Configuration document received - {} characters", sb.length());
      return sb.toString();
    } catch (IOException e) {
      s_logger.warn("Error retrieving response from {} - {}", url, e.getMessage());
      return null;
    }
  }

  private static boolean startContext(final String configuration) {
    try {
      final GenericApplicationContext context = new GenericApplicationContext();
      final XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(context);
      beanReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
      s_logger.debug("Loading configuration");
      beanReader.loadBeanDefinitions(new InputSource(new StringReader(configuration)));
      s_logger.debug("Instantiating beans");
      context.refresh();
      s_logger.debug("Starting node");
      context.start();
      return true;
    } catch (RuntimeException e) {
      s_logger.warn("Spring initialisation error", e);
      return false;
    }
  }

  private static String getBaseUrl(final String url) {
    final int slash = url.lastIndexOf('/');
    return url.substring(0, slash + 1);
  }

  private static void setConnectionDefaults(final String url) {
    try {
      final URI uri = new URI(url);
      if (uri.getHost() != null) {
        System.setProperty("opengamma.engine.calcnode.host", uri.getHost());
      }
      if (uri.getPort() != -1) {
        System.setProperty("opengamma.engine.calcnode.port", Integer.toString(uri.getPort()));
      }
    } catch (URISyntaxException e) {
      s_logger.warn("Couldn't set connection defaults", e);
    }
  }

  /**
   * Starts a calculation node, retrieving configuration from the given URL
   * 
   * @param url The URL to use
   */
  public static void main(final String url) {
    s_logger.info("Using configuration URL {}", url);
    String configuration = getConfigurationXml(url);
    if (configuration == null) {
      for (int i = 0; i < CONFIGURATION_RETRY; i++) {
        s_logger.warn("Failed to retrieve configuration - retrying");
        sleep(1);
        configuration = getConfigurationXml(url);
        if (configuration != null) {
          break;
        }
      }
      if (configuration == null) {
        s_logger.error("No response from {}", url);
        System.exit(1);
      }
    }
    // Create and start the spring config
    System.setProperty("opengamma.engine.calcnode.baseurl", getBaseUrl(url));
    setConnectionDefaults(url);
    if (startContext(configuration)) {
      s_logger.info("Calculation node started");
    } else {
      s_logger.error("Couldn't start calculation node");
      System.exit(1);
    }
    // Terminate if the configuration changes - the O/S will restart us
    do {
      sleep(CONFIGURATION_POLL_PERIOD);
      final String newConfiguration = getConfigurationXml(url);
      if (newConfiguration != null) {
        if (!configuration.equals(newConfiguration)) {
          s_logger.info("Configuration at {} has changed", url);
          System.exit(0);
        }
      }
      s_logger.info("Free memory = {}Mb, total memory = {}Mb", (double) Runtime.getRuntime().freeMemory() / (1024d * 1024d), (double) Runtime.getRuntime().totalMemory() / (1024d * 1024d));
    } while (true);
  }

  /**
   * Starts a calculation node
   * @param args the arguments, should contain one parameter - the configuration URL to use 
   */
  public static void main(String[] args) { // CSIGNORE
    if (args.length != 1) {
      s_logger.error("Configuration URL not specified");
      System.exit(1);
    }
    main(args[0]);
  }

}

@@ -4950,16 +4950,35 @@
 tart us%0A
+    int retry = 0;%0A
     do %7B
@@ -5268,32 +5268,694 @@
 t(0);%0A        %7D%0A
+        retry = 0;%0A      %7D else %7B%0A        switch (++retry) %7B%0A          case 1:%0A            s_logger.debug(%22No response from configuration at %7B%7D%22, url);%0A            break;%0A          case 2:%0A            s_logger.info(%22No response from configuration at %7B%7D%22, url);%0A            break;%0A          case 3:%0A            s_logger.warn(%22No response from configuration at %7B%7D%22, url);%0A            break;%0A          case 4:%0A            s_logger.error(%22No response from configuration at %7B%7D%22, url);%0A            // TODO: be a bit more graceful - stop accepting jobs, allow current ones to run to completion, then disconnect %0A            System.exit(0);%0A            break;%0A        %7D%0A
       %7D%0A      s_

Terminate a calculation node if the configuration host stops responding.