package de.otto.edison.jobs.configuration; import de.otto.edison.jobs.definition.JobDefinition; import de.otto.edison.jobs.repository.JobRepository; import de.otto.edison.jobs.repository.JobMetaRepository; import de.otto.edison.jobs.repository.cleanup.DeleteSkippedJobs; import de.otto.edison.jobs.repository.cleanup.KeepLastJobs; import de.otto.edison.jobs.repository.cleanup.StopDeadJobs; import de.otto.edison.jobs.repository.inmem.InMemJobRepository; import de.otto.edison.jobs.repository.inmem.InMemJobMetaRepository; import de.otto.edison.jobs.service.JobDefinitionService; import de.otto.edison.jobs.service.JobMutexGroups; import de.otto.edison.jobs.service.JobService; import de.otto.edison.jobs.status.JobStatusCalculator; import de.otto.edison.jobs.status.JobStatusDetailIndicator; import de.otto.edison.status.domain.Status; import de.otto.edison.status.indicator.CompositeStatusDetailIndicator; import de.otto.edison.status.indicator.StatusDetailIndicator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import static de.otto.edison.status.domain.StatusDetail.statusDetail; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.stream.Collectors.toList; import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; import static org.springframework.core.Ordered.LOWEST_PRECEDENCE; @Configuration @EnableAsync @EnableScheduling @EnableConfigurationProperties(JobsProperties.class) public class JobsConfiguration { public static final Logger LOG = LoggerFactory.getLogger(JobsConfiguration.class); private final JobsProperties jobsProperties; @Autowired public JobsConfiguration(final JobsProperties jobsProperties) { this.jobsProperties = jobsProperties; final Map<String, String> calculator = this.jobsProperties.getStatus().getCalculator(); if (!calculator.containsKey("default")) { this.jobsProperties.getStatus().setCalculator( new HashMap<String,String>() {{ putAll(calculator); put("default", "warningOnLastJobFailed"); }} ); } } @Bean @ConditionalOnMissingBean(ScheduledExecutorService.class) public ScheduledExecutorService scheduledExecutorService() { return newScheduledThreadPool(jobsProperties.getThreadCount()); } @Bean @ConditionalOnMissingBean(JobMetaRepository.class) public JobMetaRepository jobMetaRepository() { return new InMemJobMetaRepository(); } @Bean @ConditionalOnMissingBean(JobRepository.class) public JobRepository jobRepository() { LOG.warn("==============================="); LOG.warn("Using in-memory JobRepository"); LOG.warn("==============================="); return new InMemJobRepository(); } @Bean @ConditionalOnMissingBean(KeepLastJobs.class) public KeepLastJobs keepLastJobsStrategy(final JobRepository jobRepository) { return new KeepLastJobs(jobRepository, jobsProperties.getCleanup().getNumberOfJobsToKeep()); } @Bean @ConditionalOnMissingBean(StopDeadJobs.class) public StopDeadJobs deadJobStrategy(final JobService jobService) { return new StopDeadJobs(jobService, jobsProperties.getCleanup().getMarkDeadAfter()); } @Bean @ConditionalOnMissingBean(DeleteSkippedJobs.class) public DeleteSkippedJobs deleteSkippedJobsStrategy(final JobRepository jobRepository) { return new DeleteSkippedJobs(jobRepository, jobsProperties.getCleanup().getNumberOfSkippedJobsToKeep()); } @Bean public JobStatusCalculator warningOnLastJobFailed(final JobRepository jobRepository) { return JobStatusCalculator.warningOnLastJobFailed("warningOnLastJobFailed", jobRepository); } @Bean public JobStatusCalculator errorOnLastJobFailed(final JobRepository jobRepository) { return JobStatusCalculator.errorOnLastJobFailed("errorOnLastJobFailed", jobRepository); } @Bean public JobStatusCalculator errorOnLastThreeJobsFailed(final JobRepository jobRepository) { return JobStatusCalculator.errorOnLastNumJobsFailed("errorOnLastThreeJobsFailed", 3, jobRepository); } @Bean public JobStatusCalculator errorOnLastTenJobsFailed(final JobRepository jobRepository) { return JobStatusCalculator.errorOnLastNumJobsFailed("errorOnLastTenJobsFailed", 10, jobRepository); } @Bean @ConditionalOnProperty(name = "edison.jobs.status.enabled", havingValue = "true", matchIfMissing = true) public StatusDetailIndicator jobStatusDetailIndicator(final JobDefinitionService service, final List<JobStatusCalculator> calculators) { final List<JobDefinition> jobDefinitions = service.getJobDefinitions(); if (jobDefinitions.isEmpty()) { return () -> statusDetail("Jobs", Status.OK, "No job definitions configured in application."); } else { return new CompositeStatusDetailIndicator("Jobs", jobDefinitions .stream() .map(d -> new JobStatusDetailIndicator(d, findJobStatusCalculator(d.jobType(), calculators))) .collect(toList()) ); } } private JobStatusCalculator findJobStatusCalculator(final String jobType, final List<JobStatusCalculator> calculators) { final Map<String, String> statusCalculators = jobsProperties.getStatus().getCalculator(); final String calculator; final String normalizedJobType = jobType.toLowerCase().replace(" ", "-"); if (statusCalculators.containsKey(normalizedJobType)) { calculator = statusCalculators.get(normalizedJobType); } else { calculator = statusCalculators.get("default"); } return calculators .stream() .filter(c -> calculator.equalsIgnoreCase(c.getKey())) .findAny() .orElseThrow(() -> new IllegalStateException("Unable to find JobStatusCalculator " + calculator)); } }