/*
 * 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 static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.any;
import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_JDBC_DDL_FILE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_ONLY;
import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_OR_EXTEND;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DDL_BOTH_GENERATION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DDL_GENERATION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DDL_GENERATION_MODE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DROP_AND_CREATE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DROP_JDBC_DDL_FILE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_DRIVER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_PASSWORD;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_USER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.NON_JTA_DATASOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.THROW_EXCEPTIONS;

import java.beans.PropertyVetoException;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.ambari.server.AmbariService;
import org.apache.ambari.server.EagerSingleton;
import org.apache.ambari.server.StaticallyInject;
import org.apache.ambari.server.actionmanager.ActionDBAccessor;
import org.apache.ambari.server.actionmanager.ActionDBAccessorImpl;
import org.apache.ambari.server.actionmanager.ExecutionCommandWrapperFactory;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactoryImpl;
import org.apache.ambari.server.actionmanager.RequestFactory;
import org.apache.ambari.server.actionmanager.StageFactory;
import org.apache.ambari.server.actionmanager.StageFactoryImpl;
import org.apache.ambari.server.checks.DatabaseConsistencyCheckHelper;
import org.apache.ambari.server.checks.UpgradeCheckRegistry;
import org.apache.ambari.server.checks.UpgradeCheckRegistryProvider;
import org.apache.ambari.server.cleanup.ClasspathScannerUtils;
import org.apache.ambari.server.configuration.AmbariServerConfiguration;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.configuration.Configuration.ConnectionPoolType;
import org.apache.ambari.server.configuration.Configuration.DatabaseType;
import org.apache.ambari.server.controller.internal.AlertTargetResourceProvider;
import org.apache.ambari.server.controller.internal.AuthResourceProvider;
import org.apache.ambari.server.controller.internal.ClusterStackVersionResourceProvider;
import org.apache.ambari.server.controller.internal.ComponentResourceProvider;
import org.apache.ambari.server.controller.internal.CredentialResourceProvider;
import org.apache.ambari.server.controller.internal.HostComponentResourceProvider;
import org.apache.ambari.server.controller.internal.HostKerberosIdentityResourceProvider;
import org.apache.ambari.server.controller.internal.HostResourceProvider;
import org.apache.ambari.server.controller.internal.KerberosDescriptorResourceProvider;
import org.apache.ambari.server.controller.internal.MemberResourceProvider;
import org.apache.ambari.server.controller.internal.RepositoryVersionResourceProvider;
import org.apache.ambari.server.controller.internal.RootServiceComponentConfigurationResourceProvider;
import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
import org.apache.ambari.server.controller.internal.UpgradeResourceProvider;
import org.apache.ambari.server.controller.internal.UserAuthenticationSourceResourceProvider;
import org.apache.ambari.server.controller.internal.UserResourceProvider;
import org.apache.ambari.server.controller.internal.ViewInstanceResourceProvider;
import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory;
import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactoryImpl;
import org.apache.ambari.server.controller.metrics.MetricPropertyProviderFactory;
import org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetricCacheEntryFactory;
import org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetricCacheProvider;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.controller.utilities.KerberosChecker;
import org.apache.ambari.server.events.AgentConfigsUpdateEvent;
import org.apache.ambari.server.events.AmbariEvent;
import org.apache.ambari.server.hooks.AmbariEventFactory;
import org.apache.ambari.server.hooks.HookContext;
import org.apache.ambari.server.hooks.HookContextFactory;
import org.apache.ambari.server.hooks.HookService;
import org.apache.ambari.server.hooks.users.PostUserCreationHookContext;
import org.apache.ambari.server.hooks.users.UserCreatedEvent;
import org.apache.ambari.server.hooks.users.UserHookService;
import org.apache.ambari.server.metadata.CachedRoleCommandOrderProvider;
import org.apache.ambari.server.metadata.RoleCommandOrderProvider;
import org.apache.ambari.server.metrics.system.MetricsService;
import org.apache.ambari.server.metrics.system.impl.MetricsServiceImpl;
import org.apache.ambari.server.mpack.MpackManagerFactory;
import org.apache.ambari.server.notifications.DispatchFactory;
import org.apache.ambari.server.notifications.NotificationDispatcher;
import org.apache.ambari.server.notifications.dispatchers.AlertScriptDispatcher;
import org.apache.ambari.server.notifications.dispatchers.AmbariSNMPDispatcher;
import org.apache.ambari.server.notifications.dispatchers.SNMPDispatcher;
import org.apache.ambari.server.orm.DBAccessor;
import org.apache.ambari.server.orm.DBAccessorImpl;
import org.apache.ambari.server.orm.PersistenceType;
import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
import org.apache.ambari.server.scheduler.ExecutionScheduler;
import org.apache.ambari.server.scheduler.ExecutionSchedulerImpl;
import org.apache.ambari.server.security.SecurityHelper;
import org.apache.ambari.server.security.SecurityHelperImpl;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
import org.apache.ambari.server.security.authorization.internal.InternalAuthenticationInterceptor;
import org.apache.ambari.server.security.authorization.internal.RunWithInternalSecurityContext;
import org.apache.ambari.server.security.encryption.AESEncryptionService;
import org.apache.ambari.server.security.encryption.AgentConfigUpdateEncryptor;
import org.apache.ambari.server.security.encryption.AmbariServerConfigurationEncryptor;
import org.apache.ambari.server.security.encryption.ConfigPropertiesEncryptor;
import org.apache.ambari.server.security.encryption.CredentialStoreService;
import org.apache.ambari.server.security.encryption.CredentialStoreServiceImpl;
import org.apache.ambari.server.security.encryption.EncryptionService;
import org.apache.ambari.server.security.encryption.Encryptor;
import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
import org.apache.ambari.server.serveraction.users.CollectionPersisterService;
import org.apache.ambari.server.serveraction.users.CollectionPersisterServiceFactory;
import org.apache.ambari.server.serveraction.users.CsvFilePersisterService;
import org.apache.ambari.server.stack.StackManagerFactory;
import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeContextFactory;
import org.apache.ambari.server.stageplanner.RoleGraphFactory;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.Config;
import org.apache.ambari.server.state.ConfigFactory;
import org.apache.ambari.server.state.ConfigImpl;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentFactory;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.ServiceComponentHostFactory;
import org.apache.ambari.server.state.ServiceComponentImpl;
import org.apache.ambari.server.state.ServiceFactory;
import org.apache.ambari.server.state.ServiceImpl;
import org.apache.ambari.server.state.cluster.ClusterFactory;
import org.apache.ambari.server.state.cluster.ClusterImpl;
import org.apache.ambari.server.state.cluster.ClustersImpl;
import org.apache.ambari.server.state.configgroup.ConfigGroup;
import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
import org.apache.ambari.server.state.configgroup.ConfigGroupImpl;
import org.apache.ambari.server.state.host.HostFactory;
import org.apache.ambari.server.state.host.HostImpl;
import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptorFactory;
import org.apache.ambari.server.state.scheduler.RequestExecution;
import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
import org.apache.ambari.server.state.scheduler.RequestExecutionImpl;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.state.svccomphost.ServiceComponentHostImpl;
import org.apache.ambari.server.topology.BlueprintFactory;
import org.apache.ambari.server.topology.PersistedState;
import org.apache.ambari.server.topology.PersistedStateImpl;
import org.apache.ambari.server.topology.SecurityConfigurationFactory;
import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
import org.apache.ambari.server.utils.PasswordUtils;
import org.apache.ambari.server.utils.ThreadPools;
import org.apache.ambari.server.view.ViewInstanceHandlerList;
import org.eclipse.jetty.server.session.SessionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.util.ClassUtils;
import org.springframework.web.filter.DelegatingFilterProxy;

import com.google.common.reflect.ClassPath;
import com.google.common.util.concurrent.ServiceManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Names;
import com.google.inject.persist.PersistModule;
import com.google.inject.persist.jpa.AmbariJpaPersistModule;
import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * Used for injection purposes.
 */
public class ControllerModule extends AbstractModule {
  private static final Logger LOG = LoggerFactory.getLogger(ControllerModule.class);
  private static final String AMBARI_PACKAGE = "org.apache.ambari.server";

  private final Configuration configuration;
  private final OsFamily os_family;
  private final HostsMap hostsMap;
  private final ThreadPools threadPools;
  private boolean dbInitNeeded;
  private final Gson prettyGson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();


  // ----- Constructors ------------------------------------------------------

  public ControllerModule() throws Exception {
    configuration = new Configuration();
    hostsMap = new HostsMap(configuration);
    os_family = new OsFamily(configuration);
    threadPools = new ThreadPools(configuration);
  }

  public ControllerModule(Properties properties) throws Exception {
    configuration = new Configuration(properties);
    hostsMap = new HostsMap(configuration);
    os_family = new OsFamily(configuration);
    threadPools = new ThreadPools((configuration));
  }


  // ----- ControllerModule --------------------------------------------------

  /**
   * Get the common persistence related configuration properties.
   *
   * @return the configuration properties
   */
  public static Properties getPersistenceProperties(Configuration configuration) {
    Properties properties = new Properties();

    // log what database type has been calculated
    DatabaseType databaseType = configuration.getDatabaseType();
    LOG.info("Detected {} as the database type from the JDBC URL", databaseType);

    switch (configuration.getPersistenceType()) {
      case IN_MEMORY:
        properties.setProperty(JDBC_URL, Configuration.JDBC_IN_MEMORY_URL);
        properties.setProperty(JDBC_DRIVER, Configuration.JDBC_IN_MEMORY_DRIVER);
        properties.setProperty(JDBC_USER, Configuration.JDBC_IN_MEMORY_USER);
        properties.setProperty(JDBC_PASSWORD, Configuration.JDBC_IN_MEMORY_PASSWORD);
        properties.setProperty(DDL_GENERATION, CREATE_ONLY);
        properties.setProperty(THROW_EXCEPTIONS, "true");
        break;
      case REMOTE:
        properties.setProperty(JDBC_URL, configuration.getDatabaseUrl());
        properties.setProperty(JDBC_DRIVER, configuration.getDatabaseDriver());
        break;
      case LOCAL:
        properties.setProperty(JDBC_URL, configuration.getLocalDatabaseUrl());
        properties.setProperty(JDBC_DRIVER, Configuration.SERVER_JDBC_DRIVER.getDefaultValue());
        break;
    }

    //allow to override values above
    // custom jdbc driver properties
    Properties customDatabaseDriverProperties = configuration.getDatabaseCustomProperties();
    properties.putAll(customDatabaseDriverProperties);

    // custom persistence properties
    Properties customPersistenceProperties = configuration.getPersistenceCustomProperties();
    properties.putAll(customPersistenceProperties);

    // determine the type of pool to use
    boolean isConnectionPoolingExternal = false;
    ConnectionPoolType connectionPoolType = configuration.getConnectionPoolType();
    if (connectionPoolType == ConnectionPoolType.C3P0) {
      isConnectionPoolingExternal = true;
    }

    // force the use of c3p0 with MySQL
    if (databaseType == DatabaseType.MYSQL) {
      isConnectionPoolingExternal = true;
    }

    // use c3p0
    if (isConnectionPoolingExternal) {
      LOG.info("Using c3p0 {} as the EclipsLink DataSource",
          ComboPooledDataSource.class.getSimpleName());

      // Oracle requires a different validity query
      String testQuery = "SELECT 1";
      if (databaseType == DatabaseType.ORACLE) {
        testQuery = "SELECT 1 FROM DUAL";
      }

      ComboPooledDataSource dataSource = new ComboPooledDataSource();

      // attempt to load the driver; if this fails, warn and move on
      try {
        dataSource.setDriverClass(configuration.getDatabaseDriver());
      } catch (PropertyVetoException pve) {
        LOG.warn("Unable to initialize c3p0", pve);
        return properties;
      }

      // basic configuration stuff
      dataSource.setJdbcUrl(configuration.getDatabaseUrl());
      dataSource.setUser(configuration.getDatabaseUser());
      dataSource.setPassword(configuration.getDatabasePassword());

      // pooling
      dataSource.setMinPoolSize(configuration.getConnectionPoolMinimumSize());
      dataSource.setInitialPoolSize(configuration.getConnectionPoolMinimumSize());
      dataSource.setMaxPoolSize(configuration.getConnectionPoolMaximumSize());
      dataSource.setAcquireIncrement(configuration.getConnectionPoolAcquisitionSize());
      dataSource.setAcquireRetryAttempts(configuration.getConnectionPoolAcquisitionRetryAttempts());
      dataSource.setAcquireRetryDelay(configuration.getConnectionPoolAcquisitionRetryDelay());

      // validity
      dataSource.setMaxConnectionAge(configuration.getConnectionPoolMaximumAge());
      dataSource.setMaxIdleTime(configuration.getConnectionPoolMaximumIdle());
      dataSource.setMaxIdleTimeExcessConnections(configuration.getConnectionPoolMaximumExcessIdle());
      dataSource.setPreferredTestQuery(testQuery);
      dataSource.setIdleConnectionTestPeriod(configuration.getConnectionPoolIdleTestInternval());

      properties.put(NON_JTA_DATASOURCE, dataSource);
    }

    return properties;
  }


  // ----- AbstractModule ----------------------------------------------------

  @Override
  protected void configure() {
    installFactories();

    final SessionHandler sessionHandler = new SessionHandler();
    bind(SessionHandler.class).toInstance(sessionHandler);

    bind(KerberosOperationHandlerFactory.class);
    bind(KerberosDescriptorFactory.class);
    bind(KerberosServiceDescriptorFactory.class);
    bind(KerberosHelper.class).to(KerberosHelperImpl.class);

    bind(CredentialStoreService.class).to(CredentialStoreServiceImpl.class);
    bind(EncryptionService.class).to(AESEncryptionService.class);
    //to support different Encryptor implementation we have to annotate them by their name and use them as @Named injects
    if (configuration.shouldEncryptSensitiveData()) {
      bind(new TypeLiteral<Encryptor<Config>>() {}).annotatedWith(Names.named("ConfigPropertiesEncryptor")).to(ConfigPropertiesEncryptor.class);
      bind(new TypeLiteral<Encryptor<AgentConfigsUpdateEvent>>() {}).annotatedWith(Names.named("AgentConfigEncryptor")).to(AgentConfigUpdateEncryptor.class);
      bind(new TypeLiteral<Encryptor<AmbariServerConfiguration>>() {}).annotatedWith(Names.named("AmbariServerConfigurationEncryptor")).to(AmbariServerConfigurationEncryptor.class);
    } else {
      bind(new TypeLiteral<Encryptor<Config>>() {}).annotatedWith(Names.named("ConfigPropertiesEncryptor")).toInstance(Encryptor.NONE);
      bind(new TypeLiteral<Encryptor<AgentConfigsUpdateEvent>>() {}).annotatedWith(Names.named("AgentConfigEncryptor")).toInstance(Encryptor.NONE);
      bind(new TypeLiteral<Encryptor<AmbariServerConfiguration>>() {}).annotatedWith(Names.named("AmbariServerConfigurationEncryptor")).toInstance(Encryptor.NONE);
    }

    bind(Configuration.class).toInstance(configuration);
    bind(OsFamily.class).toInstance(os_family);
    bind(ThreadPools.class).toInstance(threadPools);
    bind(HostsMap.class).toInstance(hostsMap);
    bind(PasswordEncoder.class).toInstance(new StandardPasswordEncoder());
    bind(DelegatingFilterProxy.class).toInstance(new DelegatingFilterProxy() {
      {
        setTargetBeanName("springSecurityFilterChain");
      }
    });

    bind(Gson.class).annotatedWith(Names.named("prettyGson")).toInstance(prettyGson);

    install(buildJpaPersistModule());

    bind(Gson.class).in(Scopes.SINGLETON);
    bind(SecureRandom.class).in(Scopes.SINGLETON);

    bind(Clusters.class).to(ClustersImpl.class);
    bind(AmbariCustomCommandExecutionHelper.class);
    bind(ActionDBAccessor.class).to(ActionDBAccessorImpl.class);
    bindConstant().annotatedWith(Names.named("schedulerSleeptime")).to(
        configuration.getExecutionSchedulerWait());

    // This time is added to summary timeout time of all tasks in stage
    // So it's an "additional time", given to stage to finish execution before
    // it is considered as timed out
    bindConstant().annotatedWith(Names.named("actionTimeout")).to(600000L);
    bindConstant().annotatedWith(Names.named("alertServiceCorePoolSize")).to(configuration.getAlertServiceCorePoolSize());

    bindConstant().annotatedWith(Names.named("dbInitNeeded")).to(dbInitNeeded);
    bindConstant().annotatedWith(Names.named("statusCheckInterval")).to(5000L);

    //ExecutionCommands cache size

    bindConstant().annotatedWith(Names.named("executionCommandCacheSize")).
        to(configuration.getExecutionCommandsCacheSize());


    // Host role commands status summary max cache enable/disable
    bindConstant().annotatedWith(Names.named(HostRoleCommandDAO.HRC_STATUS_SUMMARY_CACHE_ENABLED)).
        to(configuration.getHostRoleCommandStatusSummaryCacheEnabled());

    // Host role commands status summary max cache size
    bindConstant().annotatedWith(Names.named(HostRoleCommandDAO.HRC_STATUS_SUMMARY_CACHE_SIZE)).
        to(configuration.getHostRoleCommandStatusSummaryCacheSize());
    // Host role command status summary cache expiry duration in minutes
    bindConstant().annotatedWith(Names.named(HostRoleCommandDAO.HRC_STATUS_SUMMARY_CACHE_EXPIRY_DURATION_MINUTES)).
        to(configuration.getHostRoleCommandStatusSummaryCacheExpiryDuration());

    bind(AmbariManagementController.class).to(
        AmbariManagementControllerImpl.class);
    bind(AbstractRootServiceResponseFactory.class).to(RootServiceResponseFactory.class);
    bind(ExecutionScheduler.class).to(ExecutionSchedulerImpl.class);
    bind(DBAccessor.class).to(DBAccessorImpl.class);
    bind(ViewInstanceHandlerList.class).to(AmbariHandlerList.class);
    bind(TimelineMetricCacheProvider.class);
    bind(TimelineMetricCacheEntryFactory.class);
    bind(SecurityConfigurationFactory.class).in(Scopes.SINGLETON);

    bind(PersistedState.class).to(PersistedStateImpl.class);

    // factory to create LoggingRequestHelper instances for LogSearch integration
    bind(LoggingRequestHelperFactory.class).to(LoggingRequestHelperFactoryImpl.class);

    bind(MetricsService.class).to(MetricsServiceImpl.class).in(Scopes.SINGLETON);

    requestStaticInjection(DatabaseConsistencyCheckHelper.class);
    requestStaticInjection(KerberosChecker.class);
    requestStaticInjection(AuthorizationHelper.class);
    requestStaticInjection(PasswordUtils.class);

    bindByAnnotation(null);
    bindNotificationDispatchers(null);
    
    bind(UpgradeCheckRegistry.class).toProvider(UpgradeCheckRegistryProvider.class).in(Singleton.class);
    bind(HookService.class).to(UserHookService.class);

    InternalAuthenticationInterceptor ambariAuthenticationInterceptor = new InternalAuthenticationInterceptor();
    requestInjection(ambariAuthenticationInterceptor);
    bindInterceptor(any(), annotatedWith(RunWithInternalSecurityContext.class), ambariAuthenticationInterceptor);
  }

  // ----- helper methods ----------------------------------------------------

  private PersistModule buildJpaPersistModule() {
    PersistenceType persistenceType = configuration.getPersistenceType();
    AmbariJpaPersistModule jpaPersistModule = new AmbariJpaPersistModule(Configuration.JDBC_UNIT_NAME);

    Properties persistenceProperties = ControllerModule.getPersistenceProperties(configuration);

    if (!persistenceType.equals(PersistenceType.IN_MEMORY)) {
      persistenceProperties.setProperty(JDBC_USER, configuration.getDatabaseUser());
      persistenceProperties.setProperty(JDBC_PASSWORD, configuration.getDatabasePassword());

      switch (configuration.getJPATableGenerationStrategy()) {
        case CREATE:
          persistenceProperties.setProperty(DDL_GENERATION, CREATE_ONLY);
          dbInitNeeded = true;
          break;
        case DROP_AND_CREATE:
          persistenceProperties.setProperty(DDL_GENERATION, DROP_AND_CREATE);
          dbInitNeeded = true;
          break;
        case CREATE_OR_EXTEND:
          persistenceProperties.setProperty(DDL_GENERATION, CREATE_OR_EXTEND);
          break;
        default:
          break;
      }

      persistenceProperties.setProperty(DDL_GENERATION_MODE, DDL_BOTH_GENERATION);
      persistenceProperties.setProperty(CREATE_JDBC_DDL_FILE, "DDL-create.jdbc");
      persistenceProperties.setProperty(DROP_JDBC_DDL_FILE, "DDL-drop.jdbc");
    }

    jpaPersistModule.properties(persistenceProperties);
    return jpaPersistModule;
  }

  /**
   * Bind classes to their Factories, which can be built on-the-fly.
   * Often, will also have to edit AgentResourceTest.java
   */
  private void installFactories() {
    install(new FactoryModuleBuilder().implement(
        Cluster.class, ClusterImpl.class).build(ClusterFactory.class));
    install(new FactoryModuleBuilder().implement(
        Host.class, HostImpl.class).build(HostFactory.class));
    install(new FactoryModuleBuilder().implement(
        Service.class, ServiceImpl.class).build(ServiceFactory.class));

    install(new FactoryModuleBuilder()
        .implement(ResourceProvider.class, Names.named("host"), HostResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("hostComponent"), HostComponentResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("service"), ServiceResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("component"), ComponentResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("member"), MemberResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("repositoryVersion"), RepositoryVersionResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("hostKerberosIdentity"), HostKerberosIdentityResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("user"), UserResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("userAuthenticationSource"), UserAuthenticationSourceResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("credential"), CredentialResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("kerberosDescriptor"), KerberosDescriptorResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("upgrade"), UpgradeResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("clusterStackVersion"), ClusterStackVersionResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("alertTarget"), AlertTargetResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("viewInstance"), ViewInstanceResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("rootServiceHostComponentConfiguration"), RootServiceComponentConfigurationResourceProvider.class)
        .implement(ResourceProvider.class, Names.named("auth"), AuthResourceProvider.class)
        .build(ResourceProviderFactory.class));

    install(new FactoryModuleBuilder().implement(
      ServiceComponent.class, ServiceComponentImpl.class).build(
      ServiceComponentFactory.class));
    install(new FactoryModuleBuilder().implement(
        ServiceComponentHost.class, ServiceComponentHostImpl.class).build(
        ServiceComponentHostFactory.class));
    install(new FactoryModuleBuilder().implement(
        Config.class, ConfigImpl.class).build(ConfigFactory.class));
    install(new FactoryModuleBuilder().implement(
        ConfigGroup.class, ConfigGroupImpl.class).build(ConfigGroupFactory.class));
    install(new FactoryModuleBuilder().implement(RequestExecution.class,
        RequestExecutionImpl.class).build(RequestExecutionFactory.class));

    bind(StageFactory.class).to(StageFactoryImpl.class);
    bind(RoleCommandOrderProvider.class).to(CachedRoleCommandOrderProvider.class);

    install(new FactoryModuleBuilder().build(RoleGraphFactory.class));

    install(new FactoryModuleBuilder().build(RequestFactory.class));
    install(new FactoryModuleBuilder().build(StackManagerFactory.class));
    install(new FactoryModuleBuilder().build(ExecutionCommandWrapperFactory.class));
    install(new FactoryModuleBuilder().build(MetricPropertyProviderFactory.class));
    install(new FactoryModuleBuilder().build(UpgradeContextFactory.class));
    install(new FactoryModuleBuilder().build(MpackManagerFactory.class));
    install(new FactoryModuleBuilder().build(org.apache.ambari.server.topology.addservice.RequestValidatorFactory.class));

    bind(HostRoleCommandFactory.class).to(HostRoleCommandFactoryImpl.class);
    bind(SecurityHelper.class).toInstance(SecurityHelperImpl.getInstance());
    bind(org.apache.ambari.server.topology.StackFactory.class).to(org.apache.ambari.server.topology.DefaultStackFactory.class);
    bind(BlueprintFactory.class);

    install(new FactoryModuleBuilder().implement(AmbariEvent.class, Names.named("userCreated"), UserCreatedEvent.class).build(AmbariEventFactory.class));
    install(new FactoryModuleBuilder().implement(HookContext.class, PostUserCreationHookContext.class).build(HookContextFactory.class));
    install(new FactoryModuleBuilder().implement(CollectionPersisterService.class, CsvFilePersisterService.class).build(CollectionPersisterServiceFactory.class));

    install(new FactoryModuleBuilder().build(ConfigureClusterTaskFactory.class));

  }

  /**
   * Initializes specially-marked interfaces that require injection.
   * <p/>
   * An example of where this is needed is with a singleton that is headless; in
   * other words, it doesn't have any injections but still needs to be part of
   * the Guice framework.
   * <p/>
   * A second example of where this is needed is when classes require static
   * members that are available via injection.
   * <p/>
   * Although Spring has a nicer API for performing this search, it's dreadfully
   * slow on annotation processing. This class will use
   * {@link ClasspathScannerUtils} which in turn uses {@link ClassPath} to
   * perform the scan.
   * <p/>
   * If {@code matchedClasses} is null this will scan
   * {@code org.apache.ambari.server} (currently) for any {@link EagerSingleton}
   * or {@link StaticallyInject} or {@link AmbariService} instances.
   *
   * @param matchedClasses
   *          the set of previously found classes, or {@code null} to invoke
   *          scanning.
   * @return the set of classes that was found.
   */
  @SuppressWarnings("unchecked")
  protected Set<Class<?>> bindByAnnotation(Set<Class<?>> matchedClasses) {
    // only search if necessary
    if (null == matchedClasses) {
      List<Class<?>> classes = new ArrayList<>();
      classes.add(EagerSingleton.class);
      classes.add(StaticallyInject.class);
      classes.add(AmbariService.class);

      LOG.info("Searching package {} for annotations matching {}", AMBARI_PACKAGE, classes);

      matchedClasses = ClasspathScannerUtils.findOnClassPath(AMBARI_PACKAGE, new ArrayList<>(), classes);

      if (null == matchedClasses || matchedClasses.size() == 0) {
        LOG.warn("No instances of {} found to register", classes);
        return matchedClasses;
      }
    }

    Set<com.google.common.util.concurrent.Service> services = new HashSet<>();

    for (Class<?> clazz : matchedClasses) {
      if (null != clazz.getAnnotation(EagerSingleton.class)) {
        bind(clazz).asEagerSingleton();
        LOG.debug("Eagerly binding singleton {}", clazz);
      }

      if (null != clazz.getAnnotation(StaticallyInject.class)) {
        requestStaticInjection(clazz);
        LOG.debug("Statically injecting {} ", clazz);
      }

      // Ambari services are registered with Guava
      if (null != clazz.getAnnotation(AmbariService.class)) {
        // safety check to ensure it's actually a Guava service
        if (!com.google.common.util.concurrent.Service.class.isAssignableFrom(clazz)) {
          String message = MessageFormat.format(
              "Unable to register service {0} because it is not a Service which can be scheduled",
              clazz);

          LOG.error(message);
          throw new RuntimeException(message);
        }

        // instantiate the service, register as singleton via toInstance()
        com.google.common.util.concurrent.Service service = null;
        try {
          service = (com.google.common.util.concurrent.Service) clazz.newInstance();
