package de.rwth.idsg.steve.config; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.mysql.cj.core.conf.PropertyDefinitions; import com.mysql.cj.jdbc.MysqlDataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import de.rwth.idsg.steve.service.DummyReleaseCheckService; import de.rwth.idsg.steve.service.GithubReleaseCheckService; import de.rwth.idsg.steve.service.ReleaseCheckService; import de.rwth.idsg.steve.utils.InternetChecker; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.conf.Settings; import org.jooq.impl.DSL; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import javax.annotation.PreDestroy; import javax.validation.Validator; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; /** * Configuration and beans of Spring Framework. * * @author Sevket Goekay <goekay@dbis.rwth-aachen.de> * @since 15.08.2014 */ @Slf4j @Configuration @EnableWebMvc @EnableScheduling @ComponentScan("de.rwth.idsg.steve") public class BeanConfiguration extends WebMvcConfigurerAdapter { private HikariDataSource dataSource; private ScheduledThreadPoolExecutor executor; /** * https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration */ private void initDataSource() { MysqlDataSource ds = new MysqlDataSource(); // set standard params ds.setServerName(CONFIG.getDb().getIp()); ds.setPort(CONFIG.getDb().getPort()); ds.setDatabaseName(CONFIG.getDb().getSchema()); ds.setUser(CONFIG.getDb().getUserName()); ds.setPassword(CONFIG.getDb().getPassword()); // set non-standard params ds.getModifiableProperty(PropertyDefinitions.PNAME_cachePrepStmts).setValue(true); ds.getModifiableProperty(PropertyDefinitions.PNAME_prepStmtCacheSize).setValue(250); ds.getModifiableProperty(PropertyDefinitions.PNAME_prepStmtCacheSqlLimit).setValue(2048); ds.getModifiableProperty(PropertyDefinitions.PNAME_characterEncoding).setValue("utf8"); HikariConfig hc = new HikariConfig(); hc.setDataSource(ds); dataSource = new HikariDataSource(hc); } /** * Can we re-use DSLContext as a Spring bean (singleton)? Yes, the Spring tutorial of * Jooq also does it that way, but only if we do not change anything about the * config after the init (which we don't do anyways) and if the ConnectionProvider * does not store any shared state (we use DataSourceConnectionProvider of Jooq, so no problem). * * Some sources and discussion: * - http://www.jooq.org/doc/3.6/manual/getting-started/tutorials/jooq-with-spring/ * - http://jooq-user.narkive.com/2fvuLodn/dslcontext-and-threads * - https://groups.google.com/forum/#!topic/jooq-user/VK7KQcjj3Co * - http://stackoverflow.com/questions/32848865/jooq-dslcontext-correct-autowiring-with-spring */ @Bean public DSLContext dslContext() { initDataSource(); Settings settings = new Settings() // Normally, the records are "attached" to the Configuration that created (i.e. fetch/insert) them. // This means that they hold an internal reference to the same database connection that was used. // The idea behind this is to make CRUD easier for potential subsequent store/refresh/delete // operations. We do not use or need that. .withAttachRecords(false) // To log or not to log the sql queries, that is the question .withExecuteLogging(CONFIG.getDb().isSqlLogging()); // Configuration for JOOQ org.jooq.Configuration conf = new DefaultConfiguration() .set(SQLDialect.MYSQL) .set(new DataSourceConnectionProvider(dataSource)) .set(settings); return DSL.using(conf); } @Bean public ScheduledExecutorService scheduledExecutorService() { ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("SteVe-Executor-%d") .build(); executor = new ScheduledThreadPoolExecutor(5, threadFactory); return executor; } @Bean public Validator validator() { return new LocalValidatorFactoryBean(); } /** * There might be instances deployed in a local/closed network with no internet connection. In such situations, * it is unnecessary to try to access Github every time, even though the request will time out and result * report will be correct (that there is no new version). With DummyReleaseCheckService we bypass the intermediate * steps and return a "no new version" report immediately. */ @Bean public ReleaseCheckService releaseCheckService() { if (InternetChecker.isInternetAvailable()) { return new GithubReleaseCheckService(); } else { return new DummyReleaseCheckService(); } } @PreDestroy public void shutDown() { if (dataSource != null) { dataSource.close(); } if (executor != null) { gracefulShutDown(executor); } } private void gracefulShutDown(ExecutorService executor) { try { executor.shutdown(); executor.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { log.error("Termination interrupted", e); } finally { if (!executor.isTerminated()) { log.warn("Killing non-finished tasks"); } executor.shutdownNow(); } } // ------------------------------------------------------------------------- // Web config // ------------------------------------------------------------------------- /** * Resolver for JSP views/templates. Controller classes process the requests * and forward to JSP files for rendering. */ @Bean public InternalResourceViewResolver urlBasedViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); return resolver; } /** * Resource path for static content of the Web interface. */ @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("static/"); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/manager/signin").setViewName("signin"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); } }