All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerAutoConfiguration Maven / Gradle / Ivy

/*
 * Copyright (C) Gustav Karlsson
 *
 * 

Licensed 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 com.github.kagkarlsson.scheduler.boot.autoconfigure; import com.github.kagkarlsson.scheduler.PollingStrategyConfig; import com.github.kagkarlsson.scheduler.Scheduler; import com.github.kagkarlsson.scheduler.SchedulerBuilder; import com.github.kagkarlsson.scheduler.SchedulerName; import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerCustomizer; import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerProperties; import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerStarter; import com.github.kagkarlsson.scheduler.boot.config.startup.ContextReadyStart; import com.github.kagkarlsson.scheduler.boot.config.startup.ImmediateStart; import com.github.kagkarlsson.scheduler.exceptions.SerializationException; import com.github.kagkarlsson.scheduler.serializer.Serializer; import com.github.kagkarlsson.scheduler.stats.StatsRegistry; import com.github.kagkarlsson.scheduler.task.OnStartup; import com.github.kagkarlsson.scheduler.task.Task; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.util.List; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ConfigurableObjectInputStream; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; @Configuration @EnableConfigurationProperties(DbSchedulerProperties.class) @AutoConfigurationPackage @AutoConfigureAfter({ DataSourceAutoConfiguration.class, }) @ConditionalOnBean(DataSource.class) @ConditionalOnProperty(value = "db-scheduler.enabled", matchIfMissing = true) public class DbSchedulerAutoConfiguration { private static final Logger log = LoggerFactory.getLogger(DbSchedulerAutoConfiguration.class); private static final Predicate> shouldBeStarted = task -> task instanceof OnStartup; private final DbSchedulerProperties config; private final DataSource existingDataSource; private final List> configuredTasks; public DbSchedulerAutoConfiguration( DbSchedulerProperties dbSchedulerProperties, DataSource dataSource, List> configuredTasks) { this.config = Objects.requireNonNull( dbSchedulerProperties, "Can't configure db-scheduler without required configuration"); this.existingDataSource = Objects.requireNonNull(dataSource, "An existing javax.sql.DataSource is required"); this.configuredTasks = Objects.requireNonNull(configuredTasks, "At least one Task must be configured"); } /** Provide an empty customizer if not present in the context. */ @ConditionalOnMissingBean @Bean public DbSchedulerCustomizer noopCustomizer() { return new DbSchedulerCustomizer() {}; } /** Will typically be created if Spring Boot Actuator is not on the classpath. */ @ConditionalOnMissingBean(StatsRegistry.class) @Bean StatsRegistry noopStatsRegistry() { log.debug("Missing StatsRegistry bean in context, creating a no-op StatsRegistry"); return StatsRegistry.NOOP; } @ConditionalOnBean(DataSource.class) @ConditionalOnMissingBean @DependsOnDatabaseInitialization @Bean(destroyMethod = "stop") public Scheduler scheduler(DbSchedulerCustomizer customizer, StatsRegistry registry) { log.info("Creating db-scheduler using tasks from Spring context: {}", configuredTasks); // Ensure that we are using a transactional aware data source DataSource transactionalDataSource = configureDataSource(existingDataSource); // Instantiate a new builder final SchedulerBuilder builder = Scheduler.create(transactionalDataSource, nonStartupTasks(configuredTasks)); builder.threads(config.getThreads()); // Polling builder.pollingInterval(config.getPollingInterval()); // Polling strategy if (config.getPollingStrategy() == PollingStrategyConfig.Type.FETCH) { builder.pollUsingFetchAndLockOnExecute( config.getPollingStrategyLowerLimitFractionOfThreads(), config.getPollingStrategyUpperLimitFractionOfThreads()); } else if (config.getPollingStrategy() == PollingStrategyConfig.Type.LOCK_AND_FETCH) { builder.pollUsingLockAndFetch( config.getPollingStrategyLowerLimitFractionOfThreads(), config.getPollingStrategyUpperLimitFractionOfThreads()); } else { throw new IllegalArgumentException( "Unknown polling-strategy: " + config.getPollingStrategy()); } builder.heartbeatInterval(config.getHeartbeatInterval()); // Use scheduler name implementation from customizer if available, otherwise use // configured scheduler name (String). If both is absent, use the library default if (customizer.schedulerName().isPresent()) { builder.schedulerName(customizer.schedulerName().get()); } else if (config.getSchedulerName() != null) { builder.schedulerName(new SchedulerName.Fixed(config.getSchedulerName())); } builder.tableName(config.getTableName()); // Use custom serializer if provided. Otherwise use devtools friendly serializer. builder.serializer(customizer.serializer().orElse(SPRING_JAVA_SERIALIZER)); // Use custom JdbcCustomizer if provided. customizer.jdbcCustomization().ifPresent(builder::jdbcCustomization); if (config.isAlwaysPersistTimestampInUtc()) { builder.alwaysPersistTimestampInUTC(); } if (config.isImmediateExecutionEnabled()) { builder.enableImmediateExecution(); } // Use custom executor service if provided customizer.executorService().ifPresent(builder::executorService); // Use custom due executor if provided customizer.dueExecutor().ifPresent(builder::dueExecutor); // Use housekeeper executor service if provided customizer.housekeeperExecutor().ifPresent(builder::housekeeperExecutor); builder.deleteUnresolvedAfter(config.getDeleteUnresolvedAfter()); // Add recurring jobs and jobs that implements OnStartup builder.startTasks(startupTasks(configuredTasks)); // Expose metrics builder.statsRegistry(registry); // Failure logging builder.failureLogging(config.getFailureLoggerLevel(), config.isFailureLoggerLogStackTrace()); // Shutdown max wait builder.shutdownMaxWait(config.getShutdownMaxWait()); return builder.build(); } @ConditionalOnBean(Scheduler.class) @ConditionalOnMissingBean @Bean public DbSchedulerStarter dbSchedulerStarter(Scheduler scheduler) { if (config.isDelayStartupUntilContextReady()) { return new ContextReadyStart(scheduler); } return new ImmediateStart(scheduler); } @Bean static LazyInitializationExcludeFilter eagerDbSchedulerStarter() { return LazyInitializationExcludeFilter.forBeanTypes(DbSchedulerStarter.class); } private static DataSource configureDataSource(DataSource existingDataSource) { if (existingDataSource instanceof TransactionAwareDataSourceProxy) { log.debug("Using an already transaction aware DataSource"); return existingDataSource; } log.debug( "The configured DataSource is not transaction aware: '{}'. Wrapping in TransactionAwareDataSourceProxy.", existingDataSource); return new TransactionAwareDataSourceProxy(existingDataSource); } @SuppressWarnings("unchecked") private static & OnStartup> List startupTasks(List> tasks) { return tasks.stream() .filter(shouldBeStarted) .map(task -> (T) task) .collect(Collectors.toList()); } private static List> nonStartupTasks(List> tasks) { return tasks.stream().filter(shouldBeStarted.negate()).collect(Collectors.toList()); } /** * {@link Serializer} compatible with Spring Boot Devtools. * * @see * Devtools known limitations */ private static final Serializer SPRING_JAVA_SERIALIZER = new Serializer() { public byte[] serialize(Object data) { if (data == null) return null; try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) { out.writeObject(data); return bos.toByteArray(); } catch (Exception e) { throw new SerializationException("Failed to serialize object", e); } } public T deserialize(Class clazz, byte[] serializedData) { if (serializedData == null) return null; try (ByteArrayInputStream bis = new ByteArrayInputStream(serializedData); ObjectInput in = new ConfigurableObjectInputStream( bis, Thread.currentThread().getContextClassLoader())) { return clazz.cast(in.readObject()); } catch (Exception e) { throw new SerializationException("Failed to deserialize object", e); } } }; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy