/**
   * Remove unwanted headers from the given header map.
   *
   * @param headers Header map
   * @param nHttpConfiguration NHttp transporter base configurations
   */
  private void removeUnwantedHeadersFromHeaderMap(
      Map headers, NHttpConfiguration nHttpConfiguration) {

    Iterator iter = headers.keySet().iterator();
    while (iter.hasNext()) {
      String headerName = (String) iter.next();
      if (HTTP.CONN_DIRECTIVE.equalsIgnoreCase(headerName)
          || HTTP.TRANSFER_ENCODING.equalsIgnoreCase(headerName)
          || HTTP.CONTENT_TYPE.equalsIgnoreCase(headerName)
          || HTTP.CONTENT_LEN.equalsIgnoreCase(headerName)) {
        iter.remove();
      }

      if (HTTP.SERVER_HEADER.equalsIgnoreCase(headerName)
          && !nHttpConfiguration.isPreserveHttpHeader(HTTP.SERVER_HEADER)) {
        iter.remove();
      }

      if (HTTP.USER_AGENT.equalsIgnoreCase(headerName)
          && !nHttpConfiguration.isPreserveHttpHeader(HTTP.USER_AGENT)) {
        iter.remove();
      }

      if (HTTP.DATE_HEADER.equalsIgnoreCase(headerName)
          && !nHttpConfiguration.isPreserveHttpHeader(HTTP.DATE_HEADER)) {
        iter.remove();
      }
    }
  }
  /**
   * 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;
  }
  /** Process the incoming request */
  @SuppressWarnings({"unchecked"})
  public void run() {

    String method = request.getRequestLine().getMethod().toUpperCase();
    msgContext.setProperty(
        Constants.Configuration.HTTP_METHOD, request.getRequestLine().getMethod());

    if (NHttpConfiguration.getInstance().isHttpMethodDisabled(method)) {
      handleException("Unsupported method : " + method, null);
    }

    // String uri = request.getRequestLine().getUri();
    String oriUri = request.getRequestLine().getUri();
    String restUrlPostfix = NhttpUtil.getRestUrlPostfix(oriUri, cfgCtx.getServicePath());

    msgContext.setProperty(NhttpConstants.REST_URL_POSTFIX, restUrlPostfix);
    String servicePrefix = oriUri.substring(0, oriUri.indexOf(restUrlPostfix));
    if (servicePrefix.indexOf("://") == -1) {
      HttpInetConnection inetConn = (HttpInetConnection) conn;
      InetAddress localAddr = inetConn.getLocalAddress();
      if (localAddr != null) {
        servicePrefix =
            schemeName
                + "://"
                + localAddr.getHostName()
                + ":"
                + inetConn.getLocalPort()
                + servicePrefix;
      }

      if (inetConn.getLocalPort() > 0) {
        msgContext.setProperty(NhttpConstants.SERVER_PORT, inetConn.getLocalPort());
      }
    }
    msgContext.setProperty(NhttpConstants.SERVICE_PREFIX, servicePrefix);

    if ("GET".equals(method)) {
      httpGetRequestProcessor.process(request, response, msgContext, conn, os, isRestDispatching);
    } else if ("POST".equals(method)) {
      processEntityEnclosingMethod();
    } else if ("PUT".equals(method)) {
      processEntityEnclosingMethod();
    } else if ("HEAD".equals(method)) {
      processNonEntityEnclosingMethod();
    } else if ("OPTIONS".equals(method)) {
      processNonEntityEnclosingMethod();
    } else if ("DELETE".equals(method)) {
      processGetAndDelete("DELETE");
    } else if ("TRACE".equals(method)) {
      processNonEntityEnclosingMethod();
    } else {
      handleException("Unsupported method : " + method, null);
    }

    // here the RequestResponseTransport plays an important role when it comes to
    // dual channel invocation. This is becasue we need to ACK to the request once the request
    // is received to synapse. Otherwise we will not be able to support the single channel
    // invocation within the actual service and synapse for a dual channel request from the
    // client.
    if (isAckRequired()) {
      String respWritten = "";
      if (msgContext.getOperationContext() != null) {
        respWritten =
            (String) msgContext.getOperationContext().getProperty(Constants.RESPONSE_WRITTEN);
      }
      boolean respWillFollow =
          !Constants.VALUE_TRUE.equals(respWritten) && !"SKIP".equals(respWritten);
      boolean acked =
          (((RequestResponseTransport)
                      msgContext.getProperty(RequestResponseTransport.TRANSPORT_CONTROL))
                  .getStatus()
              == RequestResponseTransport.RequestResponseTransportStatus.ACKED);
      boolean forced = msgContext.isPropertyTrue(NhttpConstants.FORCE_SC_ACCEPTED);
      boolean nioAck = msgContext.isPropertyTrue("NIO-ACK-Requested", false);

      if (respWillFollow || acked || forced || nioAck) {

        if (!nioAck) {
          if (log.isDebugEnabled()) {
            log.debug(
                "Sending 202 Accepted response for MessageID : "
                    + msgContext.getMessageID()
                    + " response written : "
                    + respWritten
                    + " response will follow : "
                    + respWillFollow
                    + " acked : "
                    + acked
                    + " forced ack : "
                    + forced);
          }
          response.setStatusCode(HttpStatus.SC_ACCEPTED);
        } else {
          if (log.isDebugEnabled()) {
            log.debug(
                "Sending ACK response with status "
                    + msgContext.getProperty(NhttpConstants.HTTP_SC)
                    + ", for MessageID : "
                    + msgContext.getMessageID());
          }
          response.setStatusCode(
              Integer.parseInt(msgContext.getProperty(NhttpConstants.HTTP_SC).toString()));
          Map<String, String> responseHeaders =
              (Map<String, String>) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
          if (responseHeaders != null) {
            for (String headerName : responseHeaders.keySet()) {
              response.addHeader(headerName, responseHeaders.get(headerName));

              String excessProp = NhttpConstants.EXCESS_TRANSPORT_HEADERS;

              Map map = (Map) msgContext.getProperty(excessProp);
              if (map != null) {
                log.debug(
                    "Number of excess values for "
                        + headerName
                        + " header is : "
                        + ((Collection) (map.get(headerName))).size());

                for (Iterator iterator = map.keySet().iterator(); iterator.hasNext(); ) {
                  String key = (String) iterator.next();

                  for (String excessVal : (Collection<String>) map.get(key)) {
                    response.addHeader(headerName, (String) excessVal);
                  }
                }
              }
            }
          }
        }

        if (metrics != null) {
          metrics.incrementMessagesSent();
        }

        try {

          /*
           * Remove Content-Length and Transfer-Encoding headers, if already present.
           * */
          response.removeHeaders(HTTP.TRANSFER_ENCODING);
          response.removeHeaders(HTTP.CONTENT_LEN);

          serverHandler.commitResponse(conn, response);

        } catch (HttpException e) {
          if (metrics != null) {
            metrics.incrementFaultsSending();
          }
          handleException("Unexpected HTTP protocol error : " + e.getMessage(), e);
        } catch (ConnectionClosedException e) {
          if (metrics != null) {
            metrics.incrementFaultsSending();
          }
          log.warn("Connection closed by client (Connection closed)");
        } catch (IllegalStateException e) {
          if (metrics != null) {
            metrics.incrementFaultsSending();
          }
          log.warn("Connection closed by client (Buffer closed)");
        } catch (IOException e) {
          if (metrics != null) {
            metrics.incrementFaultsSending();
          }
          handleException("IO Error sending response message", e);
        } catch (Exception e) {
          if (metrics != null) {
            metrics.incrementFaultsSending();
          }
          handleException("General Error sending response message", e);
        }

        if (is != null) {
          try {
            is.close();
          } catch (IOException ignore) {
          }
        }

        // make sure that the output stream is flushed and closed properly
        try {
          os.flush();
          os.close();
        } catch (IOException ignore) {
        }
      }
    }
  }