package am.ik.categolj2.config;
import am.ik.aws.apa.AwsApaRequester;
import am.ik.aws.apa.AwsApaRequesterImpl;
import am.ik.categolj2.domain.AuditAwareBean;
import am.ik.categolj2.infra.db.ConnectionValidator;
import am.ik.categolj2.infra.db.UrlStringDevider;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import lombok.extern.slf4j.Slf4j;
import net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy;
import org.flywaydb.core.Flyway;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import javax.inject.Inject;
import javax.sql.DataSource;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableJpaAuditing(setDates = false)
@Slf4j
public class InfraConfig {
@Bean
AuditAwareBean auditAwareBean() {
return new AuditAwareBean();
}
static void setValidationQuery(DataSource dataSource) {
if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) {
org.apache.tomcat.jdbc.pool.DataSource ds = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
ds.setValidationQuery("SELECT 1");
ds.setTestOnBorrow(true);
ds.setTestWhileIdle(true);
ds.setValidationInterval(TimeUnit.MINUTES.toMillis(1));
ds.setTimeBetweenEvictionRunsMillis((int) TimeUnit.MINUTES.toMillis(5));
ds.setValidatorClassName(ConnectionValidator.class.getName());
}
}
@Configuration
@Profile("db.property")
public static class PropertyDbConfiguration {
@Inject
DataSourceProperties dataSourceProperties;
@Bean
DataSourceBuilder realDataSourceBuilder() throws URISyntaxException {
String url = this.dataSourceProperties.getUrl();
String username = this.dataSourceProperties.getUsername();
String password = this.dataSourceProperties.getPassword();
DataSourceBuilder factory = DataSourceBuilder
.create(this.dataSourceProperties.getClassLoader())
.url(url)
.username(username)
.password(password);
return factory;
}
@Bean
DataSource dataSource(DataSourceBuilder factory) {
DataSource dataSource = factory.build();
setValidationQuery(dataSource);
return new DataSourceSpy(dataSource);
}
}
@Configuration
@Profile("db.docker")
public static class DockerDbConfiguration {
@Value("#{systemEnvironment['MYSQL_PORT_3306_TCP_ADDR']}")
String mysqlHost;
@Value("#{systemEnvironment['MYSQL_PORT_3306_TCP_PORT']}")
int mysqlPort;
@Value("${spring.datasource.database:categolj2}")
String database;
@Inject
DataSourceProperties dataSourceProperties;
@Bean
DataSourceBuilder realDataSourceBuilder() throws URISyntaxException {
String url = "jdbc:mysql://" + mysqlHost + ":" + mysqlPort + "/" + database + "?zeroDateTimeBehavior=convertToNull";
String username = this.dataSourceProperties.getUsername();
String password = this.dataSourceProperties.getPassword();
DataSourceBuilder factory = DataSourceBuilder
.create()
.url(url)
.username(username)
.password(password);
return factory;
}
@Bean
DataSource dataSource(DataSourceBuilder factory) {
DataSource dataSource = factory.build();
setValidationQuery(dataSource);
checkDbAvailiability(dataSource);
return new DataSourceSpy(dataSource);
}
void checkDbAvailiability(DataSource dataSource) {
// ** Hack for Docker Compose **
// Check Availability because DB could not start yet by Docker Compose
log.info("Check DB availability... ({}:{})", mysqlHost, mysqlPort);
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
Level currentLevel = context.getLogger("org.apache.tomcat.jdbc.pool.ConnectionPool").getLevel();
context.getLogger("org.apache.tomcat.jdbc.pool.ConnectionPool").setLevel(Level.OFF);
int retryMax = 10;
for (int i = 0; i < retryMax; i++) {
if (i > 0) {
long sleep = 3 * i;
log.info("Retry after {} sec ({}/{})", sleep, i + 1, retryMax);
try {
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException e) {
log.info("Interrupted!");
Thread.currentThread().interrupt();
}
}
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.execute("SELECT 1");
log.info("OK");
break;
} catch (SQLException e) {
String message = e.getMessage();
if (message != null) {
message = message.replace("\n", "").replace("\r", "");
}
log.info("NG ({})", message);
if (i + 1 == retryMax) {
throw new IllegalStateException("Failed checking DB availability", e);
}
}
}
context.getLogger("org.apache.tomcat.jdbc.pool.ConnectionPool").setLevel(currentLevel);
}
}
@Configuration
@Profile("db.urlstring")
public static class UrlStringDbConfiguration {
DataSource dataSource;
@Bean
DataSourceBuilder realDataSourceBuilder() throws URISyntaxException {
UrlStringDevider urlStringDevider = new UrlStringDevider(
System.getenv("CLEARDB_DATABASE_URL"),
"zeroDateTimeBehavior=convertToNull");
String url = urlStringDevider.getUrl();
String username = urlStringDevider.getUsername();
String password = urlStringDevider.getPassword();
DataSourceBuilder factory = DataSourceBuilder
.create()
.url(url)
.username(username)
.password(password);
return factory;
}
@Bean
DataSource dataSource(DataSourceBuilder factory) {
DataSource dataSource = factory.build();
setValidationQuery(dataSource);
return new DataSourceSpy(dataSource);
}
}
@Bean
BeanPostProcessor flywayBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Flyway) {
((Flyway) bean).setValidateOnMigrate(false);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
};
}
@Bean
AwsApaRequester apaRequester(@Value("${aws.accesskey.id}") String accessKeyId,
@Value("${aws.secret.accesskey}") String secretAccessKey,
@Value("${aws.endpoint}") String endPoint,
@Value("${aws.associate.tag}") String associateTag) {
return new AwsApaRequesterImpl(endPoint, accessKeyId, secretAccessKey, associateTag);
}
}