/**
   * Waits until it is safe to stop or the the specified end time has been reached. A delay of
   * <code>waitIntervalMillis</code> milliseconds is used between each subsequent check. If the
   * state "safeToStop" is reached before the specified <code>endTime</code>, the return value is
   * true.
   *
   * @param waitIntervalMillis the pause time (delay) in milliseconds between subsequent checks
   * @param endTime the time until which the checks need to finish successfully
   * @return true, if a safe state is reached before the specified <code>endTime</code>, otherwise
   *     false (forceful stop required)
   */
  public boolean waitUntilSafeToStop(long waitIntervalMillis, long endTime) {

    boolean safeToStop = false;
    boolean forcefulStop = false;
    Axis2TransportHelper transportHelper = new Axis2TransportHelper(configurationContext);

    // wait until it is safe to shutdown (listeners and tasks are idle, no callbacks)
    while (!safeToStop && !forcefulStop) {

      int pendingListenerThreads = transportHelper.getPendingListenerThreadCount();
      if (pendingListenerThreads > 0) {
        log.info(
            new StringBuilder("Waiting for: ")
                .append(pendingListenerThreads)
                .append(" listener threads to complete")
                .toString());
      }
      int pendingSenderThreads = transportHelper.getPendingSenderThreadCount();
      if (pendingSenderThreads > 0) {
        log.info(
            new StringBuilder("Waiting for: ")
                .append(pendingSenderThreads)
                .append(" listener threads to complete")
                .toString());
      }
      int activeConnections = transportHelper.getActiveConnectionsCount();
      if (activeConnections > 0) {
        log.info("Waiting for: " + activeConnections + " active connections to be closed..");
      }
      int pendingTransportThreads = pendingListenerThreads + pendingSenderThreads;

      int pendingCallbacks = serverContextInformation.getCallbackCount();
      if (pendingCallbacks > 0) {
        log.info("Waiting for: " + pendingCallbacks + " callbacks/replies..");
      }

      int runningTasks = 0;
      SynapseTaskManager synapseTaskManager = synapseEnvironment.getTaskManager();
      if (synapseTaskManager.isInitialized()) {
        runningTasks = synapseTaskManager.getTaskScheduler().getRunningTaskCount();
        if (runningTasks > 0) {
          log.info("Waiting for : " + runningTasks + " tasks to complete..");
        }
      }

      // it is safe to stop if all used listener threads, callbacks and tasks are zero
      safeToStop = ((pendingTransportThreads + pendingCallbacks + runningTasks) == 0);

      if (safeToStop) {
        log.info("All transport threads and tasks are idle and no pending callbacks..");
      } else {
        if (System.currentTimeMillis() < endTime) {
          log.info(
              new StringBuilder("Waiting for a maximum of another ")
                  .append((endTime - System.currentTimeMillis()) / 1000)
                  .append(" seconds until transport threads and tasks become idle, ")
                  .append("active connections to get closed,")
                  .append(" and callbacks to be completed..")
                  .toString());
          try {
            Thread.sleep(waitIntervalMillis);
          } catch (InterruptedException ignore) {
            // nothing to do here
          }
        } else {
          // maximum time to wait is over, do a forceful stop
          forcefulStop = true;
        }
      }
    }

    return !forcefulStop;
  }
  /**
   * {@inheritDoc}
   *
   * @param serverConfigurationInformation ServerConfigurationInformation Instance
   * @param serverContextInformation Server Context if the Axis2 Based Server Environment has been
   *     already set up.
   */
  public void init(
      ServerConfigurationInformation serverConfigurationInformation,
      ServerContextInformation serverContextInformation) {

    log.info("Initializing Synapse at : " + new Date());
    if (serverConfigurationInformation == null) {
      throw new IllegalArgumentException("ServerConfigurationInformation cannot be null");
    }

    if (serverContextInformation == null) {
      throw new IllegalArgumentException("ServerContextInformation cannot be null");
    }

    this.serverConfigurationInformation = serverConfigurationInformation;
    this.serverContextInformation = serverContextInformation;
    /* If no system property for the JMX agent is specified from outside, use a default one
    to show all MBeans (including the Axis2-MBeans) within the Synapse tree */
    if (System.getProperty(JMX_AGENT_NAME) == null) {
      System.setProperty(JMX_AGENT_NAME, "org.apache.synapse");
    }

    if (serverContextInformation.getServerContext() == null
        || serverConfigurationInformation.isCreateNewInstance()) {

      if (log.isDebugEnabled()) {
        log.debug("Initializing Synapse in a new axis2 server environment instance");
      }
      createNewInstance(serverConfigurationInformation);
    } else {
      Object context = serverContextInformation.getServerContext();
      if (context instanceof ConfigurationContext) {
        if (log.isDebugEnabled()) {
          log.debug(
              "Initializing Synapse in an already existing " + "axis2 server environment instance");
        }
        configurationContext = (ConfigurationContext) context;
        configurationContext.setProperty(AddressingConstants.ADDR_VALIDATE_ACTION, Boolean.FALSE);
      } else {
        handleFatal(
            "Synapse startup initialization failed : Provided server context is"
                + " invalid, expected an Axis2 ConfigurationContext instance");
      }
    }
    // set the configuration context
    serverContextInformation.setServerContext(configurationContext);

    // set the ServerContextInformation as a parameter
    Parameter serverContextParameter =
        new Parameter(SynapseConstants.SYNAPSE_SERVER_CTX_INFO, serverContextInformation);
    // set the ServerConfiguration as a parameter
    Parameter serverConfigParameter =
        new Parameter(SynapseConstants.SYNAPSE_SERVER_CONFIG_INFO, serverConfigurationInformation);
    try {
      configurationContext.getAxisConfiguration().addParameter(serverContextParameter);
      configurationContext.getAxisConfiguration().addParameter(serverConfigParameter);
    } catch (AxisFault ignored) {
      log.fatal("Error adding the parameter to the Axis Configuration");
    }

    // we retrieve these properties to initialize the task scheduler in the environment
    Object repo = serverContextInformation.getProperty(TaskConstants.TASK_DESCRIPTION_REPOSITORY);
    Object taskScheduler = serverContextInformation.getProperty(TaskConstants.TASK_SCHEDULER);

    if (repo != null && (repo instanceof TaskDescriptionRepository)) {
      this.taskDescriptionRepository = (TaskDescriptionRepository) repo;
    }

    if (taskScheduler != null && (taskScheduler instanceof TaskScheduler)) {
      this.taskScheduler = (TaskScheduler) taskScheduler;
    }

    addDefaultBuildersAndFormatters(configurationContext.getAxisConfiguration());
    initDataSourceHelper(serverContextInformation);
    initSharedSecretCallbackHandlerCache(serverContextInformation);
    initEnterpriseBeanstalkHolder(serverContextInformation);
    initialized = true;
  }