package com.ctrip.framework.apollo.use.cases.dynamic.datasource.util;

import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.use.cases.dynamic.datasource.ds.DynamicDataSource;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class DataSourceRefresher implements ApplicationContextAware {

  private static final Logger logger = LoggerFactory.getLogger(DataSourceRefresher.class);

  private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

  @Autowired
  private DynamicDataSource dynamicDataSource;

  @Autowired
  private DataSourceManager dataSourceManager;

  @Autowired
  private ApplicationContext applicationContext;

  @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.datasource.")
  public void onChange(ConfigChangeEvent changeEvent) {
    refreshDataSource(changeEvent.changedKeys());
  }

  private synchronized void refreshDataSource(Set<String> changedKeys) {
    try {
      logger.info("Refreshing data source");

      /**
       * rebind configuration beans, e.g. DataSourceProperties
       * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
       */
      this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys));

      DataSource newDataSource = dataSourceManager.createAndTestDataSource();
      DataSource oldDataSource = dynamicDataSource.setDataSource(newDataSource);
      asyncTerminate(oldDataSource);

      logger.info("Finished refreshing data source");
    } catch (Throwable ex) {
      logger.error("Refreshing data source failed", ex);
    }
  }

  private void asyncTerminate(DataSource dataSource) {
    DataSourceTerminationTask task = new DataSourceTerminationTask(dataSource, scheduledExecutorService);

    //start now
    scheduledExecutorService.schedule(task, 0, TimeUnit.MILLISECONDS);
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}