/**
   * Send the request message asynchronously to the given EPR
   *
   * @param epr the destination EPR for the message
   * @param msgContext the message being sent
   * @throws AxisFault on error
   */
  private void sendAsyncRequest(EndpointReference epr, MessageContext msgContext) throws AxisFault {
    try {
      URL url = new URL(epr.getAddress());
      String scheme = url.getProtocol() != null ? url.getProtocol() : "http";
      String hostname = url.getHost();
      int port = url.getPort();
      if (port == -1) {
        // use default
        if ("http".equals(scheme)) {
          port = 80;
        } else if ("https".equals(scheme)) {
          port = 443;
        }
      }
      HttpHost target = new HttpHost(hostname, port, scheme);
      boolean secure = "https".equalsIgnoreCase(target.getSchemeName());

      HttpHost proxy = proxyConfig.selectProxy(target);

      HttpRoute route;
      if (proxy != null) {
        route = new HttpRoute(target, null, proxy, secure);
      } else {
        route = new HttpRoute(target, null, secure);
      }
      Axis2HttpRequest axis2Req = new Axis2HttpRequest(epr, route, msgContext);
      Object timeout = msgContext.getProperty(NhttpConstants.SEND_TIMEOUT);
      if (timeout != null && timeout instanceof Long) {
        axis2Req.setTimeout((int) ((Long) timeout).longValue());
      }

      NHttpClientConnection conn = connpool.getConnection(route);

      // Ensure MessageContext has a ClientConnectionDebug attached before we start streaming
      ServerConnectionDebug scd =
          (ServerConnectionDebug) msgContext.getProperty(ServerHandler.SERVER_CONNECTION_DEBUG);

      ClientConnectionDebug ccd;
      if (scd != null) {
        ccd = scd.getClientConnectionDebug();
        if (ccd == null) {
          ccd = new ClientConnectionDebug(scd);
          scd.setClientConnectionDebug(ccd);
        }
        ccd.recordRequestStartTime(conn, axis2Req);
        msgContext.setProperty(ClientHandler.CLIENT_CONNECTION_DEBUG, ccd);
      }

      if (conn == null) {
        HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
        ioReactor.connect(
            new InetSocketAddress(host.getHostName(), host.getPort()),
            null,
            axis2Req,
            sessionRequestCallback);
        if (log.isDebugEnabled()) {
          log.debug("A new connection established to : " + route);
        }
      } else {
        conn.setSocketTimeout(socketTimeout); // reinitialize timeouts for the pooled connection
        try {
          handler.submitRequest(conn, axis2Req);
          if (log.isDebugEnabled()) {
            log.debug("An existing connection reused to : " + hostname + ":" + port);
          }
        } catch (ConnectionClosedException e) {
          ioReactor.connect(
              new InetSocketAddress(hostname, port), null, axis2Req, sessionRequestCallback);
          if (log.isDebugEnabled()) {
            log.debug("A new connection established to : " + hostname + ":" + port);
          }
        }
      }
      try {
        axis2Req.streamMessageContents();
      } catch (AxisFault af) {
        throw af;
      }

    } catch (MalformedURLException e) {
      handleException("Malformed destination EPR : " + epr.getAddress(), e);
    }
  }
  /**
   * Initialize the transport sender, and execute reactor in new separate thread
   *
   * @param cfgCtx the Axis2 configuration context
   * @param transportOut the description of the http/s transport from Axis2 configuration
   * @throws AxisFault thrown on an error
   */
  public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut)
      throws AxisFault {
    this.configurationContext = cfgCtx;

    cfg = NHttpConfiguration.getInstance();
    params = new BasicHttpParams();
    params
        .setIntParameter(
            CoreConnectionPNames.SO_TIMEOUT,
            cfg.getProperty(NhttpConstants.SO_TIMEOUT_SENDER, 60000))
        .setIntParameter(
            CoreConnectionPNames.CONNECTION_TIMEOUT,
            cfg.getProperty(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000))
        .setIntParameter(
            CoreConnectionPNames.SOCKET_BUFFER_SIZE,
            cfg.getProperty(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024))
        .setParameter(CoreProtocolPNames.USER_AGENT, "Synapse-HttpComponents-NIO");
    //                .setParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET,
    //
    // cfg.getStringValue(CoreProtocolPNames.HTTP_ELEMENT_CHARSET,HTTP.DEFAULT_PROTOCOL_CHARSET));
    // //TODO:This does not works with HTTPCore 4.3

    name = transportOut.getName().toUpperCase(Locale.US) + " Sender";

    ClientConnFactoryBuilder contextBuilder = initConnFactoryBuilder(transportOut);
    connFactory = contextBuilder.createConnFactory(params);

    connpool = new ConnectionPool();

    proxyConfig = new ProxyConfigBuilder().build(transportOut);
    log.info(proxyConfig.logProxyConfig());

    Parameter param = transportOut.getParameter("warnOnHTTP500");
    if (param != null) {
      String[] warnOnHttp500 = ((String) param.getValue()).split("\\|");
      cfgCtx.setNonReplicableProperty("warnOnHTTP500", warnOnHttp500);
    }

    IOReactorConfig ioReactorConfig = new IOReactorConfig();
    ioReactorConfig.setIoThreadCount(NHttpConfiguration.getInstance().getClientIOWorkers());
    ioReactorConfig.setSoTimeout(cfg.getProperty(NhttpConstants.SO_TIMEOUT_RECEIVER, 60000));
    ioReactorConfig.setConnectTimeout(
        cfg.getProperty(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000));
    ioReactorConfig.setTcpNoDelay(cfg.getProperty(CoreConnectionPNames.TCP_NODELAY, 1) == 1);
    if (cfg.getBooleanValue("http.nio.interest-ops-queueing", false)) {
      ioReactorConfig.setInterestOpQueued(true);
    }

    try {
      String prefix = name + " I/O dispatcher";
      ioReactor =
          new DefaultConnectingIOReactor(
              ioReactorConfig,
              new NativeThreadFactory(new ThreadGroup(prefix + " thread group"), prefix));
      ioReactor.setExceptionHandler(
          new IOReactorExceptionHandler() {
            public boolean handle(IOException ioException) {
              log.warn(
                  "System may be unstable: IOReactor encountered a checked exception : "
                      + ioException.getMessage(),
                  ioException);
              return true;
            }

            public boolean handle(RuntimeException runtimeException) {
              log.warn(
                  "System may be unstable: IOReactor encountered a runtime exception : "
                      + runtimeException.getMessage(),
                  runtimeException);
              return true;
            }
          });
    } catch (IOException e) {
      log.error("Error starting the IOReactor", e);
      throw new AxisFault(e.getMessage(), e);
    }

    metrics = new NhttpMetricsCollector(false, transportOut.getName());
    handler = new ClientHandler(connpool, connFactory, proxyConfig, cfgCtx, params, metrics);
    iodispatch = new ClientIODispatch(handler, connFactory);
    final IOEventDispatch ioEventDispatch = iodispatch;

    // start the Sender in a new seperate thread
    Thread t =
        new Thread(
            new Runnable() {
              public void run() {
                try {
                  ioReactor.execute(ioEventDispatch);
                } catch (InterruptedIOException ex) {
                  log.fatal("Reactor Interrupted");
                } catch (IOException e) {
                  log.fatal("Encountered an I/O error: " + e.getMessage(), e);
                }
                log.info(name + " Shutdown");
              }
            },
            "HttpCoreNIOSender");
    t.start();
    log.info(name + " starting");

    // register with JMX
    mbeanSupport = new TransportMBeanSupport(this, "nio-" + transportOut.getName());
    mbeanSupport.register();

    state = BaseConstants.STARTED;
  }