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


import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.BindException;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Map;
import java.util.logging.LogManager;

import javax.crypto.BadPaddingException;

import jakarta.servlet.DispatcherType;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.StateRecoveryManager;
import org.apache.ambari.server.StaticallyInject;
import org.apache.ambari.server.actionmanager.ActionManager;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
import org.apache.ambari.server.agent.HeartBeatHandler;
import org.apache.ambari.server.api.AmbariErrorHandler;
import org.apache.ambari.server.api.AmbariPersistFilter;
import org.apache.ambari.server.api.ContentTypeOverrideFilter;
import org.apache.ambari.server.api.MethodOverrideFilter;
import org.apache.ambari.server.api.UserNameOverrideFilter;
import org.apache.ambari.server.api.rest.BootStrapResource;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.api.services.BaseService;
import org.apache.ambari.server.api.services.KeyService;
import org.apache.ambari.server.api.services.PersistKeyValueImpl;
import org.apache.ambari.server.api.services.PersistKeyValueService;
import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor;
import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
import org.apache.ambari.server.audit.AuditLoggerModule;
import org.apache.ambari.server.audit.request.RequestAuditLogger;
import org.apache.ambari.server.bootstrap.BootStrapImpl;
import org.apache.ambari.server.checks.DatabaseConsistencyCheckHelper;
import org.apache.ambari.server.checks.DatabaseConsistencyCheckResult;
import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.configuration.SingleFileWatch;
import org.apache.ambari.server.configuration.spring.AgentStompConfig;
import org.apache.ambari.server.configuration.spring.ApiSecurityConfig;
import org.apache.ambari.server.configuration.spring.ApiStompConfig;
import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider;
import org.apache.ambari.server.controller.internal.AmbariPrivilegeResourceProvider;
import org.apache.ambari.server.controller.internal.BaseClusterRequest;
import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
import org.apache.ambari.server.controller.internal.ClusterPrivilegeResourceProvider;
import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
import org.apache.ambari.server.controller.internal.HostResourceProvider;
import org.apache.ambari.server.controller.internal.PermissionResourceProvider;
import org.apache.ambari.server.controller.internal.PrivilegeResourceProvider;
import org.apache.ambari.server.controller.internal.StackAdvisorResourceProvider;
import org.apache.ambari.server.controller.internal.StackDefinedPropertyProvider;
import org.apache.ambari.server.controller.internal.StackDependencyResourceProvider;
import org.apache.ambari.server.controller.internal.UserPrivilegeResourceProvider;
import org.apache.ambari.server.controller.internal.ViewPermissionResourceProvider;
import org.apache.ambari.server.controller.metrics.ThreadPoolEnabledPropertyProvider;
import org.apache.ambari.server.controller.utilities.KerberosChecker;
import org.apache.ambari.server.controller.utilities.KerberosIdentityCleaner;
import org.apache.ambari.server.events.AmbariPropertiesChangedEvent;
import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
import org.apache.ambari.server.ldap.LdapModule;
import org.apache.ambari.server.listeners.WebSocketInitializerListener;
import org.apache.ambari.server.metrics.system.MetricsService;
import org.apache.ambari.server.metrics.system.impl.MetricsConfiguration;
import org.apache.ambari.server.metrics.system.impl.MetricsServiceImpl;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.PersistenceType;
import org.apache.ambari.server.orm.dao.BlueprintDAO;
import org.apache.ambari.server.orm.dao.ClusterDAO;
import org.apache.ambari.server.orm.dao.GroupDAO;
import org.apache.ambari.server.orm.dao.MetainfoDAO;
import org.apache.ambari.server.orm.dao.PermissionDAO;
import org.apache.ambari.server.orm.dao.PrincipalDAO;
import org.apache.ambari.server.orm.dao.PrivilegeDAO;
import org.apache.ambari.server.orm.dao.ResourceDAO;
import org.apache.ambari.server.orm.dao.TopologyRequestDAO;
import org.apache.ambari.server.orm.dao.UserDAO;
import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
import org.apache.ambari.server.orm.entities.MetainfoEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import org.apache.ambari.server.resources.ResourceManager;
import org.apache.ambari.server.resources.api.rest.GetResource;
import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
import org.apache.ambari.server.security.AmbariServerSecurityHeaderFilter;
import org.apache.ambari.server.security.AmbariViewsSecurityHeaderFilter;
import org.apache.ambari.server.security.CertificateManager;
import org.apache.ambari.server.security.SecurityFilter;
import org.apache.ambari.server.security.authorization.Users;
import org.apache.ambari.server.security.unsecured.rest.CertificateDownload;
import org.apache.ambari.server.security.unsecured.rest.CertificateSign;
import org.apache.ambari.server.security.unsecured.rest.ConnectionInfo;
import org.apache.ambari.server.serveraction.kerberos.stageutils.KerberosKeytabController;
import org.apache.ambari.server.stack.UpdateActiveRepoVersionOnStartup;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.topology.AmbariContext;
import org.apache.ambari.server.topology.BlueprintFactory;
import org.apache.ambari.server.topology.SecurityConfigurationFactory;
import org.apache.ambari.server.topology.TopologyManager;
import org.apache.ambari.server.topology.TopologyRequestFactoryImpl;
import org.apache.ambari.server.utils.AmbariPath;
import org.apache.ambari.server.utils.RetryHelper;
import org.apache.ambari.server.utils.StageUtils;
import org.apache.ambari.server.utils.VersionUtils;
import org.apache.ambari.server.view.AmbariViewsMDCLoggingFilter;
import org.apache.ambari.server.view.ViewDirectoryWatcher;
import org.apache.ambari.server.view.ViewRegistry;
import org.apache.ambari.server.view.ViewThrottleFilter;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpVersion;
import org.apache.log4j.PropertyConfigurator;
import org.apache.velocity.app.Velocity;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.socket.config.WebSocketMessageBrokerStats;

import com.google.common.base.Joiner;
import com.google.common.util.concurrent.ServiceManager;
import com.google.gson.Gson;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.persist.Transactional;


@Singleton
public class AmbariServer {
  public static final String VIEWS_URL_PATTERN = "/api/v1/views/*";
  private static final Logger LOG = LoggerFactory.getLogger(AmbariServer.class);

  /**
   * The thread name prefix for threads handling agent requests.
   */
  private static final String AGENT_THREAD_POOL_NAME = "qtp-ambari-agent";

  /**
   * The thread name prefix for threads handling REST API requests.
   */
  private static final String CLIENT_THREAD_POOL_NAME = "ambari-client-thread";

  // Set velocity logger
  protected static final String VELOCITY_LOG_CATEGORY = "VelocityLogger";

  /**
   * Dispatcher types for webAppContext.addFilter.
   */
  public static final EnumSet<DispatcherType> DISPATCHER_TYPES = EnumSet.of(DispatcherType.REQUEST);
  private static final int DEFAULT_ACCEPTORS_COUNT = 1;
  private static final String[] DEPRECATED_SSL_PROTOCOLS = new String[] {"TLSv1"};

  static {
    Velocity.setProperty("runtime.log.logsystem.log4j.logger", VELOCITY_LOG_CATEGORY);
  }

  private static final String CLASSPATH_CHECK_CLASS = "org/apache/ambari/server/controller/AmbariServer.class";
  private static final String CLASSPATH_SANITY_CHECK_FAILURE_MESSAGE = "%s class is found in multiple jar files. Possible reasons include multiple ambari server jar files in the ambari classpath.\n" +
      String.format("Check for additional ambari server jar files and check that %s matches only one file.", AmbariPath.getPath("/usr/lib/ambari-server/ambari-server*.jar"));
  static {
    Enumeration<URL> ambariServerClassUrls;
    try {
      ambariServerClassUrls = AmbariServer.class.getClassLoader().getResources(CLASSPATH_CHECK_CLASS);

      int ambariServerClassUrlsSize = 0;
      while (ambariServerClassUrls.hasMoreElements()) {
        ambariServerClassUrlsSize++;
        URL url = ambariServerClassUrls.nextElement();
        LOG.info(String.format("Found %s class in %s", CLASSPATH_CHECK_CLASS, url.getPath()));
      }
      if (ambariServerClassUrlsSize > 1) {
        throw new RuntimeException(String.format(CLASSPATH_SANITY_CHECK_FAILURE_MESSAGE, CLASSPATH_CHECK_CLASS));
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private Server server = null;

  public volatile boolean running = true; // true while controller runs

  final String CONTEXT_PATH = "/";
  final String DISABLED_ENTRIES_SPLITTER = "\\|";

  @Inject
  Configuration configs;

  @Inject
  CertificateManager certMan;

  @Inject
  Injector injector;

  @Inject
  AmbariMetaInfo ambariMetaInfo;

  @Inject
  MetainfoDAO metainfoDAO;

  @Inject
  @Named("dbInitNeeded")
  boolean dbInitNeeded;

  /**
   * Guava service manager singleton (bound with {@link Scopes#SINGLETON}).
   */
  @Inject
  private ServiceManager serviceManager;

  /**
   * The singleton view registry.
   */
  @Inject
  ViewRegistry viewRegistry;

  /**
   * The handler list for deployed web apps.
   */
  @Inject
  AmbariHandlerList handlerList;

  /**
   * Session manager.
   */
  @Inject
  SessionHandler sessionHandler;

  @Inject
  DelegatingFilterProxy springSecurityFilter;

  @Inject
  ViewDirectoryWatcher viewDirectoryWatcher;

  @Inject
  SessionHandlerConfigurer sessionHandlerConfigurer;

  public String getServerOsType() {
    return configs.getServerOsType();
  }

  private static AmbariManagementController clusterController = null;

  /**
   * Alters system variables on base of Ambari configuration
   */
  static void setSystemProperties(Configuration configs) {
    // modify location of temporary dir to avoid using default /tmp dir
    System.setProperty("java.io.tmpdir", configs.getServerTempDir());
    if (configs.getJavaVersion() >= 8) {
      System.setProperty("jdk.tls.ephemeralDHKeySize", String.valueOf(configs.getTlsEphemeralDhKeySize()));
    }
  }

  public static AmbariManagementController getController() {
    return clusterController;
  }

  public static void setController(AmbariManagementController controller) {
    clusterController = controller;
  }

  @SuppressWarnings("deprecation")
  public void run() throws Exception {
    setupJulLogging();


    performStaticInjection();
    initDB();
    // Client Jetty thread pool - widen the thread pool if needed !
    Integer clientAcceptors = configs.getClientApiAcceptors() != null ? configs
      .getClientApiAcceptors() : DEFAULT_ACCEPTORS_COUNT;

    server = configureJettyThreadPool(clientAcceptors, CLIENT_THREAD_POOL_NAME,
      configs.getClientThreadPoolSize());

    final SessionIdManager sessionIdManager = new DefaultSessionIdManager(server);
    sessionHandler.setSessionIdManager(sessionIdManager);
    server.setSessionIdManager(sessionIdManager);

    // Agent Jetty thread pool - widen the thread pool if needed !
    Integer agentAcceptors = configs.getAgentApiAcceptors() != null ?
      configs.getAgentApiAcceptors() : DEFAULT_ACCEPTORS_COUNT;

    Server serverForAgent = configureJettyThreadPool(agentAcceptors * 2,
      AGENT_THREAD_POOL_NAME, configs.getAgentThreadPoolSize());

    setSystemProperties(configs);

    runDatabaseConsistencyCheck();

    try {
      ClassPathXmlApplicationContext parentSpringAppContext =
          new ClassPathXmlApplicationContext();
      parentSpringAppContext.refresh();
      ConfigurableListableBeanFactory factory = parentSpringAppContext.
          getBeanFactory();

      factory.registerSingleton("injector", injector);
      //todo unable to register Users class as Spring @Bean since it tries to process @Inject annotations, investigate
      factory.registerSingleton("ambariUsers", injector.getInstance(Users.class));

      //create spring context for web api
      AnnotationConfigWebApplicationContext apiContext = new AnnotationConfigWebApplicationContext();
      apiContext.setParent(parentSpringAppContext);
      apiContext.register(ApiSecurityConfig.class);
      //refresh will be called in ContextLoaderListener

      AnnotationConfigWebApplicationContext apiDispatcherContext = new AnnotationConfigWebApplicationContext();
      apiDispatcherContext.register(ApiStompConfig.class);
      DispatcherServlet apiDispatcherServlet = new DispatcherServlet(apiDispatcherContext);

      AnnotationConfigWebApplicationContext agentDispatcherContext = new AnnotationConfigWebApplicationContext();
      agentDispatcherContext.register(AgentStompConfig.class);
      DispatcherServlet agentDispatcherServlet = new DispatcherServlet(agentDispatcherContext);

      ServletContextHandler root = new ServletContextHandler(
          ServletContextHandler.SECURITY | ServletContextHandler.SESSIONS);

      configureRootHandler(root);
      sessionHandlerConfigurer.configureSessionHandler(sessionHandler);
      root.setSessionHandler(sessionHandler);

      //ContextLoaderListener handles all work on registration in servlet container
      root.addEventListener(new ContextLoaderListener(apiContext));

      certMan.initRootCert();

      // the agent communication (heartbeats, registration, etc) is stateless
      // and does not use sessions.
      ServletContextHandler agentroot = new ServletContextHandler(
          serverForAgent, "/", ServletContextHandler.NO_SESSIONS);

      AnnotationConfigWebApplicationContext agentApiContext = new AnnotationConfigWebApplicationContext();
      agentApiContext.setParent(parentSpringAppContext);

      if (configs.isAgentApiGzipped()) {
        configureHandlerCompression(agentroot);
      }

      JettyWebSocketServletContainerInitializer initializerForAgentroot = new JettyWebSocketServletContainerInitializer((context, jettyContainer) -> {
        jettyContainer.setMaxTextMessageSize(configs.getStompMaxIncomingMessageSize());
        LOG.info("Configured WebSocket container max text message size: {}", configs.getStompMaxIncomingMessageSize());
      });

      agentroot.addEventListener(new ContextLoaderListener(agentApiContext));
      agentroot.addEventListener(new WebSocketInitializerListener(initializerForAgentroot));

      ServletHolder rootServlet = root.addServlet(DefaultServlet.class, "/");
      rootServlet.setInitParameter("dirAllowed", "false");
      rootServlet.setInitParameter("precompressed", "gzip=.gz");
      rootServlet.setInitOrder(1);

      /* Configure default servlet for agent server */
      rootServlet = agentroot.addServlet(DefaultServlet.class, "/");
      rootServlet.setInitOrder(1);

      // Conditionally adds security-related headers to all HTTP responses.
      root.addFilter(new FilterHolder(injector.getInstance(AmbariServerSecurityHeaderFilter.class)), "/*", DISPATCHER_TYPES);

      // The security header filter - conditionally adds security-related headers to the HTTP response for Ambari Views
      // requests.
      root.addFilter(new FilterHolder(injector.getInstance(AmbariViewsSecurityHeaderFilter.class)), VIEWS_URL_PATTERN,
          DISPATCHER_TYPES);

      // since views share the REST API threadpool, a misbehaving view could
      // consume all of the available threads and effectively cause a loss of
      // service for Ambari
      root.addFilter(new FilterHolder(injector.getInstance(ViewThrottleFilter.class)),
        VIEWS_URL_PATTERN, DISPATCHER_TYPES);

      // adds MDC info for views logging
      root.addFilter(new FilterHolder(injector.getInstance(AmbariViewsMDCLoggingFilter.class)),
        VIEWS_URL_PATTERN, DISPATCHER_TYPES);

      // session-per-request strategy for api
      root.addFilter(new FilterHolder(injector.getInstance(AmbariPersistFilter.class)), "/api/*", DISPATCHER_TYPES);
      root.addFilter(new FilterHolder(new MethodOverrideFilter()), "/api/*", DISPATCHER_TYPES);
      root.addFilter(new FilterHolder(new ContentTypeOverrideFilter()), "/api/*", DISPATCHER_TYPES);

      JettyWebSocketServletContainerInitializer initializerForRoot = new JettyWebSocketServletContainerInitializer((context, jettyContainer) -> {
        jettyContainer.setMaxTextMessageSize(configs.getStompMaxIncomingMessageSize());
        LOG.info("Configured WebSocket container max text message size: {}", configs.getStompMaxIncomingMessageSize());
      });

      // register listener to capture request context
      root.addEventListener(new RequestContextListener());
      root.addEventListener(new WebSocketInitializerListener(initializerForRoot));
      root.addFilter(new FilterHolder(springSecurityFilter), "/api/*", DISPATCHER_TYPES);
      root.addFilter(new FilterHolder(new UserNameOverrideFilter()), "/api/v1/users/*", DISPATCHER_TYPES);

      // session-per-request strategy for agents
      agentroot.addFilter(new FilterHolder(injector.getInstance(AmbariPersistFilter.class)), "/agent/*", DISPATCHER_TYPES);
      agentroot.addFilter(SecurityFilter.class, "/*", DISPATCHER_TYPES);

      Map<String, String> configsMap = configs.getConfigsMap();

      // Agents download cert on on-way connector but always communicate on
      // two-way connector for server-agent communication
      ServerConnector agentOneWayConnector = createSelectChannelConnectorForAgent(serverForAgent, configs.getOneWayAuthPort(), false, agentAcceptors);
      ServerConnector agentTwoWayConnector = createSelectChannelConnectorForAgent(serverForAgent, configs.getTwoWayAuthPort(), configs.isTwoWaySsl(), agentAcceptors);

      serverForAgent.addConnector(agentOneWayConnector);
      serverForAgent.addConnector(agentTwoWayConnector);

      ServletHolder sh = new ServletHolder(new ServletContainer(new ResourceConfig().packages(
              "org.apache.ambari.server.api.rest",
              "org.apache.ambari.server.api.services",
              "org.apache.ambari.eventdb.webservice",
              "org.apache.ambari.server.api").register(org.glassfish.jersey.jackson.JacksonFeature.class)));
      root.addServlet(sh, "/api/v1/*");
      sh.setInitOrder(2);

      ServletHolder springDispatcherServlet = new ServletHolder("springDispatcherServlet", apiDispatcherServlet);
      springDispatcherServlet.setInitOrder(3);
      root.addServlet(springDispatcherServlet, "/api/stomp/*");

      ServletHolder agentSpringDispatcherServlet =
          new ServletHolder("agentSpringDispatcherServlet", agentDispatcherServlet);
      agentSpringDispatcherServlet.setInitOrder(2);
      agentroot.addServlet(agentSpringDispatcherServlet, "/agent/stomp/*");

      SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

      viewRegistry.readViewArchives();

      //Check and load requestlog handler.
      loadRequestlogHandler(handlerList, serverForAgent, configsMap);

      enableLog4jMonitor(configsMap);

      if (configs.isGzipHandlerEnabledForJetty()) {
        GzipHandler gzipHandler = new GzipHandler();
        gzipHandler.setHandler(root);
        //TODO minimal set, perhaps is needed to add some other mime types
        gzipHandler.setIncludedMimeTypes("text/html", "text/plain", "text/xml", "text/css", "application/javascript",
          "application/x-javascript", "application/xml", "application/x-www-form-urlencoded", "application/json");
        handlerList.addHandler(gzipHandler);
      } else {
        handlerList.addHandler(root);
      }

      server.setHandler(handlerList);

      ServletHolder agent = new ServletHolder(new ServletContainer(new ResourceConfig()
              .packages("org.apache.ambari.server.agent.rest", "org.apache.ambari.server.api")
              .register(org.glassfish.jersey.jackson.JacksonFeature.class)));
      agentroot.addServlet(agent, "/agent/v1/*");
      agent.setInitOrder(3);

      injector.getInstance(HeartBeatHandler.class).start();
      LOG.info("********** Started Heartbeat handler **********");

      ServletHolder cert = new ServletHolder(new ServletContainer(new ResourceConfig()
              .packages("org.apache.ambari.server.security.unsecured.rest", "org.apache.ambari.server.api")
              .register(org.glassfish.jersey.jackson.JacksonFeature.class)));
      agentroot.addServlet(cert, "/*");
      cert.setInitOrder(4);

      File resourcesDirectory = new File(configs.getResourceDirPath());
      ServletHolder resources = new ServletHolder(DefaultServlet.class);
      resources.setInitParameter("resourceBase", resourcesDirectory.getParent());
      resources.setInitParameter("dirAllowed", "false");
      root.addServlet(resources, "/resources/*");
      resources.setInitOrder(5);

      // Allow symlinked files in Jetty 11
      Resource baseResource = Resource.newResource(resourcesDirectory.getParentFile().getAbsolutePath());
      root.addAliasCheck(new SymlinkAllowedResourceAliasChecker(root, baseResource));

      if (configs.csrfProtectionEnabled()) {
        sh.setInitParameter("org.glassfish.jersey.server.ContainerRequestFilter",
            "org.apache.ambari.server.api.AmbariCsrfProtectionFilter");
      }

      /* Configure the API server to use the NIO connectors */
      ServerConnector apiConnector = createSelectChannelConnectorForClient(server, clientAcceptors);

      server.addConnector(apiConnector);

      server.setStopAtShutdown(true);
      serverForAgent.setStopAtShutdown(true);
//todo remove      springAppContext.start();

      String osType = getServerOsType();
      if (osType == null || osType.isEmpty()) {
        throw new RuntimeException(Configuration.OS_VERSION.getKey() + " is not "
            + " set in the ambari.properties file");
      }

      //Start action scheduler
      LOG.info("********* Initializing Clusters **********");
      Clusters clusters = injector.getInstance(Clusters.class);
      StringBuilder clusterDump = new StringBuilder();
      //TODO temporally commented because takes a lot of time on 5k cluster
      //clusters.debugDump(clusterDump);

      LOG.info("********* Current Clusters State *********");
      LOG.info(clusterDump.toString());

      LOG.info("********* Reconciling Alert Definitions **********");
      ambariMetaInfo.reconcileAlertDefinitions(clusters, false);

      LOG.info("********* Initializing ActionManager **********");
      ActionManager manager = injector.getInstance(ActionManager.class);

      LOG.info("********* Initializing Controller **********");
      AmbariManagementController controller = injector.getInstance(
          AmbariManagementController.class);

      LOG.info("********* Initializing Scheduled Request Manager **********");
      ExecutionScheduleManager executionScheduleManager = injector
          .getInstance(ExecutionScheduleManager.class);

      MetricsService metricsService = injector.getInstance(
        MetricsService.class);

      clusterController = controller;

      StateRecoveryManager recoveryManager = injector.getInstance(
          StateRecoveryManager.class);

      recoveryManager.doWork();
      /*
       * Start the server after controller state is recovered.
       */
      server.start();
      handlerList.shareSessionCacheToViews(sessionHandler.getSessionCache());

      //views initialization will reset inactive interval with default value, so we should set it after
      sessionHandlerConfigurer.configureMaxInactiveInterval(sessionHandler);

      serverForAgent.start();
      LOG.info("********* Started Server **********");

      if( !configs.isViewDirectoryWatcherServiceDisabled()) {
        LOG.info("Starting View Directory Watcher");
        viewDirectoryWatcher.start();
      }

      manager.start();
      LOG.info("********* Started ActionManager **********");

      executionScheduleManager.start();
      LOG.info("********* Started Scheduled Request Manager **********");

      serviceManager.startAsync();
      LOG.info("********* Started Services **********");

      if (!configs.isMetricsServiceDisabled()) {
        if (MetricsConfiguration.isStompStatMetricsConfigured() && metricsService instanceof MetricsServiceImpl) {
          WebSocketMessageBrokerStats apiStompStats = apiDispatcherContext.getBean(WebSocketMessageBrokerStats.class);
          ((MetricsServiceImpl) metricsService).setApiStompStats(apiStompStats);
          WebSocketMessageBrokerStats agentStompStats = agentDispatcherContext.getBean(WebSocketMessageBrokerStats.class);
          ((MetricsServiceImpl) metricsService).setAgentStompStats(agentStompStats);
        }
        metricsService.start();
      } else {
        LOG.info("AmbariServer Metrics disabled.");
      }

      server.join();
      LOG.info("Joined the Server");
    } catch (BadPaddingException bpe) {
      LOG.error("Bad keystore or private key password. " +
          "HTTPS certificate re-importing may be required.");
      throw bpe;
    } catch (BindException bindException) {
      LOG.error("Could not bind to server port - instance may already be running. " +
          "Terminating this instance.", bindException);
      throw bindException;
    }
  }

  /**
   * Create org.eclipse.jetty.server.nio.SelectChannelConnector
   * implementation for SSL or non-SSL based on configuration.
   *
   * @param port connector port
   * @param needClientAuth one-way / two-way SSL
   * @return org.eclipse.jetty.server.nio.SelectChannelConnector
   */
  @SuppressWarnings("deprecation")
  private ServerConnector createSelectChannelConnectorForAgent(Server server, int port, boolean needClientAuth, int acceptors) {
    Map<String, String> configsMap = configs.getConfigsMap();
    ServerConnector agentConnector;

    if (configs.getAgentSSLAuthentication()) {
      String keystore = configsMap.get(Configuration.SRVR_KSTR_DIR.getKey()) + File.separator
        + configsMap.get(Configuration.KSTR_NAME.getKey());

      String truststore = configsMap.get(Configuration.SRVR_KSTR_DIR.getKey()) + File.separator
        + configsMap.get(Configuration.TSTR_NAME.getKey());

      String srvrCrtPass = configsMap.get(Configuration.SRVR_CRT_PASS.getKey());

      SecureRequestCustomizer src = new SecureRequestCustomizer();
      src.setSniHostCheck(false);
      src.setSniRequired(false);
      HttpConfiguration https_config = new HttpConfiguration();
      https_config.addCustomizer(src);
      https_config.setRequestHeaderSize(configs.getHttpRequestHeaderSize());
      https_config.setResponseHeaderSize(configs.getHttpResponseHeaderSize());
      https_config.setSendServerVersion(false);

      // Secured connector - default constructor sets trustAll = true for certs
      SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
      disableInsecureProtocols(sslContextFactory);
      sslContextFactory.setKeyStorePath(keystore);
      sslContextFactory.setTrustStorePath(truststore);
      sslContextFactory.setKeyStorePassword(srvrCrtPass);
      sslContextFactory.setKeyManagerPassword(srvrCrtPass);
      sslContextFactory.setTrustStorePassword(srvrCrtPass);
      sslContextFactory.setKeyStoreType(configsMap.get(Configuration.KSTR_TYPE.getKey()));
      sslContextFactory.setTrustStoreType(configsMap.get(Configuration.TSTR_TYPE.getKey()));
      sslContextFactory.setNeedClientAuth(needClientAuth);
      sslContextFactory.setSniRequired(false);

      ServerConnector agentSslConnector = new ServerConnector(server, acceptors, -1,
        new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.toString()),
        new HttpConnectionFactory(https_config));
      agentConnector = agentSslConnector;
    } else {
      agentConnector = new ServerConnector(server, acceptors, -1);
      agentConnector.setIdleTimeout(configs.getConnectionMaxIdleTime());
    }

    agentConnector.setPort(port);

    return agentConnector;
  }

  @SuppressWarnings("deprecation")
  private ServerConnector createSelectChannelConnectorForClient(Server server, int acceptors) {
    Map<String, String> configsMap = configs.getConfigsMap();
    ServerConnector apiConnector;

    HttpConfiguration http_config = new HttpConfiguration();
    http_config.setRequestHeaderSize(configs.getHttpRequestHeaderSize());
    http_config.setResponseHeaderSize(configs.getHttpResponseHeaderSize());
    http_config.setSendServerVersion(false);
    if (configs.getApiSSLAuthentication()) {
      String httpsKeystore = configsMap.get(Configuration.CLIENT_API_SSL_KSTR_DIR_NAME.getKey()) +
        File.separator + configsMap.get(Configuration.CLIENT_API_SSL_KSTR_NAME.getKey());
      String httpsTruststore = configsMap.get(Configuration.CLIENT_API_SSL_KSTR_DIR_NAME.getKey()) +
        File.separator + configsMap.get(Configuration.CLIENT_API_SSL_TSTR_NAME.getKey());
      LOG.info("API SSL Authentication is turned on. Keystore - " + httpsKeystore);

      String httpsCrtPass = configsMap.get(Configuration.CLIENT_API_SSL_CRT_PASS.getKey());

