Enable creating an API using Spring Web #5

Merged
niat8586 merged 39 commits from spring into develop 2024-11-06 11:23:29 +01:00
16 changed files with 260 additions and 60 deletions
Showing only changes of commit 6f31c7698f - Show all commits

View File

@ -6,7 +6,7 @@ import se.su.dsv.scipro.file.FileDescription;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
class PrintingMailer implements Mailer { public class PrintingMailer implements Mailer {
@Override @Override
public MailResult mail(final String fromName, final String fromEmail, final String[] recipients, final String subject, final String message, final FileDescription attachment) { public MailResult mail(final String fromName, final String fromEmail, final String[] recipients, final String subject, final String message, final FileDescription attachment) {
return new MailResult() { return new MailResult() {

View File

@ -1,6 +1,6 @@
package se.su.dsv.scipro.match; package se.su.dsv.scipro.match;
class AllowAllIdeaCreationJudge implements IdeaCreationJudge { public class AllowAllIdeaCreationJudge implements IdeaCreationJudge {
@Override @Override
public Decision ruling(Idea idea) { public Decision ruling(Idea idea) {
return Decision.allowed(); return Decision.allowed();

View File

@ -22,8 +22,7 @@ public abstract class AbstractWorker implements Worker {
private WorkerData wd = null; private WorkerData wd = null;
private Date lastSuccessfulRun = new Date(0); private Date lastSuccessfulRun = new Date(0);
private boolean successfulWorker = true; private boolean successfulWorker = true;
private Provider<EntityManager> emProvider; private WorkerTransactionManager txManager;
private EntityTransaction transaction;
/** /**
* Subclasses must be annotated with @Component or similar annotation in order for autowiring of dependencies to work * Subclasses must be annotated with @Component or similar annotation in order for autowiring of dependencies to work
@ -37,16 +36,13 @@ public abstract class AbstractWorker implements Worker {
} }
@Inject @Inject
public void setTxManager(Provider<EntityManager> em) { public void setTxManager(WorkerTransactionManager txManager) {
this.emProvider = em; this.txManager = txManager;
} }
@Override @Override
public void run() { public void run() {
EntityManager em = emProvider.get();
try { try {
transaction = em.getTransaction();
wd = workerDataService.getWorkerDataByName(getName()); wd = workerDataService.getWorkerDataByName(getName());
if (wd != null) { if (wd != null) {
lastSuccessfulRun = wd.getLastSuccessfulRun(); lastSuccessfulRun = wd.getLastSuccessfulRun();
@ -72,9 +68,7 @@ public abstract class AbstractWorker implements Worker {
setSuccessfulWorker(false); setSuccessfulWorker(false);
} }
finally { finally {
if (transaction.isActive() && em.isOpen()) { txManager.rollbackIfActive();
transaction.rollback();
}
} }
// in case a job crashes or clears the cache (so the entity is detached) // in case a job crashes or clears the cache (so the entity is detached)
@ -87,9 +81,7 @@ public abstract class AbstractWorker implements Worker {
saveWorkerData(); saveWorkerData();
} finally { } finally {
if (transaction.isActive() && em.isOpen()) { txManager.rollbackIfActive();
transaction.rollback();
}
} }
} }
@ -139,15 +131,15 @@ public abstract class AbstractWorker implements Worker {
protected abstract void doWork(); protected abstract void doWork();
protected void beginTransaction() { protected void beginTransaction() {
transaction.begin(); txManager.begin();
} }
protected void commitTransaction() { protected void commitTransaction() {
transaction.commit(); txManager.commit();
} }
protected void rollbackTransaction() { protected void rollbackTransaction() {
transaction.rollback(); txManager.rollback();
} }
} }

View File

@ -15,9 +15,9 @@ public class WorkerModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
Multibinder.newSetBinder(binder(), Lifecycle.class).addBinding().to(SchedulerImpl.class); //Multibinder.newSetBinder(binder(), Lifecycle.class).addBinding().to(SchedulerImpl.class);
bind(ScheduledExecutorService.class).toInstance(Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS)); bind(ScheduledExecutorService.class).toInstance(Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS));
bind(Scheduler.class).to(SchedulerImpl.class).in(Scopes.SINGLETON); //bind(Scheduler.class).to(SchedulerImpl.class).in(Scopes.SINGLETON);
bind(TemporaryWorkerScheduler.class).asEagerSingleton(); bind(TemporaryWorkerScheduler.class).asEagerSingleton();
bind(WorkerDataService.class).to(WorkerDataServiceImpl.class); bind(WorkerDataService.class).to(WorkerDataServiceImpl.class);
bind(ThesisUploadDeadlineWorker.class); bind(ThesisUploadDeadlineWorker.class);

View File

@ -0,0 +1,20 @@
package se.su.dsv.scipro.workerthreads;
public interface WorkerTransactionManager {
void rollbackIfActive();
/**
* @throws IllegalStateException if a transaction is already active
*/
void begin();
/**
* @throws IllegalStateException if a transaction is not active
*/
void commit();
/**
* @throws IllegalStateException if a transaction is not active
*/
void rollback();
}

View File

@ -1,10 +1,6 @@
package se.su.dsv.scipro.integration.daisy.workers; package se.su.dsv.scipro.integration.daisy.workers;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.inject.persist.UnitOfWork;
import com.google.inject.util.Providers;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -23,6 +19,7 @@ import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.system.UserService; import se.su.dsv.scipro.system.UserService;
import se.su.dsv.scipro.workerthreads.WorkerData; import se.su.dsv.scipro.workerthreads.WorkerData;
import se.su.dsv.scipro.workerthreads.WorkerDataService; import se.su.dsv.scipro.workerthreads.WorkerDataService;
import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
import java.util.*; import java.util.*;
@ -47,21 +44,16 @@ public class GradingCompletedMilestoneActivatorTest {
@Mock @Mock
private EventBus eventBus; private EventBus eventBus;
@Mock @Mock
private UnitOfWork unitOfWork; private WorkerTransactionManager workerTransactionManager;
@Mock
private EntityManager entityManager;
@Mock
private EntityTransaction entityTransaction;
@Mock @Mock
private WorkerDataService workerDataService; private WorkerDataService workerDataService;
@BeforeEach @BeforeEach
public void setup() { public void setup() {
gradingCompletedMilestoneActivator = new GradingCompletedMilestoneActivator(projectService, daisyAPI, eventBus, userService); gradingCompletedMilestoneActivator = new GradingCompletedMilestoneActivator(projectService, daisyAPI, eventBus, userService);
gradingCompletedMilestoneActivator.setTxManager(Providers.of(entityManager)); gradingCompletedMilestoneActivator.setTxManager(workerTransactionManager);
gradingCompletedMilestoneActivator.setWorkerDataService(workerDataService); gradingCompletedMilestoneActivator.setWorkerDataService(workerDataService);
when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData()); when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
when(entityManager.getTransaction()).thenReturn(entityTransaction);
project.setId(3493L); project.setId(3493L);
project.setIdentifier(234); project.setIdentifier(234);

View File

@ -1,10 +1,6 @@
package se.su.dsv.scipro.integration.daisy.workers; package se.su.dsv.scipro.integration.daisy.workers;
import com.google.inject.persist.UnitOfWork;
import com.google.inject.util.Providers;
import com.querydsl.core.types.Predicate; import com.querydsl.core.types.Predicate;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -26,6 +22,7 @@ import se.su.dsv.scipro.system.Unit;
import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.workerthreads.WorkerData; import se.su.dsv.scipro.workerthreads.WorkerData;
import se.su.dsv.scipro.workerthreads.WorkerDataService; import se.su.dsv.scipro.workerthreads.WorkerDataService;
import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.*; import java.util.*;
@ -45,10 +42,8 @@ public class ProjectExporterTest {
private @Mock ExporterFacade exporterFacade; private @Mock ExporterFacade exporterFacade;
private @Mock DaisyAPI daisyAPI; private @Mock DaisyAPI daisyAPI;
private @Mock ExternalExporter externalExporter; private @Mock ExternalExporter externalExporter;
private @Mock EntityManager entityManager; private @Mock WorkerTransactionManager workerTransactionManager;
private @Mock EntityTransaction entityTransaction;
private @Mock WorkerDataService workerDataService; private @Mock WorkerDataService workerDataService;
private @Mock UnitOfWork unitOfWork;
private @Mock FinalSeminarService finalSeminarService; private @Mock FinalSeminarService finalSeminarService;
private @Mock FinalThesisService finalThesisService; private @Mock FinalThesisService finalThesisService;
@ -61,10 +56,9 @@ public class ProjectExporterTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
projectExporter = new ProjectExporter(projectRepo, exporterFacade, daisyAPI, externalExporter, finalSeminarService, finalThesisService); projectExporter = new ProjectExporter(projectRepo, exporterFacade, daisyAPI, externalExporter, finalSeminarService, finalThesisService);
projectExporter.setTxManager(Providers.of(entityManager)); projectExporter.setTxManager(workerTransactionManager);
projectExporter.setWorkerDataService(workerDataService); projectExporter.setWorkerDataService(workerDataService);
when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData()); when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
when(entityManager.getTransaction()).thenReturn(entityTransaction);
Unit unit = new Unit(); Unit unit = new Unit();
unit.setIdentifier(239478); unit.setIdentifier(239478);

View File

@ -1,7 +1,5 @@
package se.su.dsv.scipro.integration.daisy.workers; package se.su.dsv.scipro.integration.daisy.workers;
import com.google.inject.persist.UnitOfWork;
import com.google.inject.util.Providers;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -18,8 +16,8 @@ import se.su.dsv.scipro.project.QProject;
import se.su.dsv.scipro.workerthreads.WorkerData; import se.su.dsv.scipro.workerthreads.WorkerData;
import se.su.dsv.scipro.workerthreads.WorkerDataService; import se.su.dsv.scipro.workerthreads.WorkerDataService;
import jakarta.persistence.EntityManager; import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
import jakarta.persistence.EntityTransaction;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -38,9 +36,7 @@ public class ProjectFinalizerTest {
private @Mock DaisyAPI daisyAPI; private @Mock DaisyAPI daisyAPI;
private @Mock ProjectService projectService; private @Mock ProjectService projectService;
private @Mock UnitOfWork unitOfWork; private @Mock WorkerTransactionManager workerTransactionManager;
private @Mock EntityManager entityManager;
private @Mock EntityTransaction entityTransaction;
private @Mock WorkerDataService workerDataService; private @Mock WorkerDataService workerDataService;
private @Mock ThesisApprovedHistoryService thesisApprovedHistoryService; private @Mock ThesisApprovedHistoryService thesisApprovedHistoryService;
@ -48,10 +44,9 @@ public class ProjectFinalizerTest {
@BeforeEach @BeforeEach
public void setup() { public void setup() {
projectFinalizer = new ProjectFinalizer(projectService, daisyAPI, thesisApprovedHistoryService); projectFinalizer = new ProjectFinalizer(projectService, daisyAPI, thesisApprovedHistoryService);
projectFinalizer.setTxManager(Providers.of(entityManager)); projectFinalizer.setTxManager(workerTransactionManager);
projectFinalizer.setWorkerDataService(workerDataService); projectFinalizer.setWorkerDataService(workerDataService);
when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData()); when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
when(entityManager.getTransaction()).thenReturn(entityTransaction);
project.setId(3493L); project.setId(3493L);
project.setIdentifier(234); project.setIdentifier(234);

View File

@ -21,8 +21,6 @@ import se.su.dsv.scipro.sukat.LDAP;
import se.su.dsv.scipro.sukat.Sukat; import se.su.dsv.scipro.sukat.Sukat;
import java.time.Clock; import java.time.Clock;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Configuration @Configuration
@ComponentScan( @ComponentScan(
@ -34,13 +32,6 @@ import java.util.concurrent.ScheduledExecutorService;
".*GradingServiceImpl" ".*GradingServiceImpl"
})) }))
public class CoreConfig { public class CoreConfig {
private static final int NUMBER_OF_WORKER_THREADS = 4;
@Bean
public ScheduledExecutorService scheduledExecutorService() {
return Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS);
}
@Bean @Bean
public EventBus eventBus() { public EventBus eventBus() {
return new EventBus(); return new EventBus();

View File

@ -0,0 +1,45 @@
package se.su.dsv.scipro.war;
import jakarta.mail.Session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import se.su.dsv.scipro.data.facade.MailFacade;
import se.su.dsv.scipro.file.FileService;
import se.su.dsv.scipro.generalsystemsettings.GeneralSystemSettingsService;
import se.su.dsv.scipro.mail.Mail;
import se.su.dsv.scipro.mail.Mailer;
import se.su.dsv.scipro.mail.PrintingMailer;
import se.su.dsv.scipro.mail.RedirectingMailer;
import se.su.dsv.scipro.profiles.CurrentProfile;
import java.util.Properties;
@Configuration
public class MailConfig {
@Bean
public MailFacade mailFacade() {
return new MailFacade();
}
@Bean
public Mailer mailer(
CurrentProfile currentProfile,
Session session,
FileService fileDescriptionService)
{
return switch (currentProfile.getCurrentProfile()) {
case DEV -> new PrintingMailer();
case PROD -> new Mail(session, fileDescriptionService);
case TEST -> new RedirectingMailer(session, "scipro-mailtest@dsv.su.se", fileDescriptionService);
};
}
@Bean
public Session session(GeneralSystemSettingsService generalSystemSettings) {
String smtpHost = generalSystemSettings.getGeneralSystemSettingsInstance().getSmtpServer();
Properties properties = new Properties();
properties.setProperty("mail.smtp.host", smtpHost);
properties.setProperty("mail.smtp.sendpartial", Boolean.toString(true));
return Session.getDefaultInstance(properties);
}
}

View File

@ -37,7 +37,7 @@ import java.util.Set;
@SpringBootApplication @SpringBootApplication
@EntityScan("se.su.dsv.scipro") @EntityScan("se.su.dsv.scipro")
@Import({CoreConfig.class, ApiConfig.class}) @Import({CoreConfig.class, ApiConfig.class, WorkerConfig.class, MailConfig.class})
public class Main extends SpringBootServletInitializer implements ServletContainerInitializer { public class Main extends SpringBootServletInitializer implements ServletContainerInitializer {
@Override @Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {

View File

@ -0,0 +1,50 @@
package se.su.dsv.scipro.war;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
public class SpringManagedWorkerTransactions implements WorkerTransactionManager {
private final PlatformTransactionManager platformTransactionManager;
private TransactionStatus activeTransaction;
public SpringManagedWorkerTransactions(PlatformTransactionManager platformTransactionManager) {
this.platformTransactionManager = platformTransactionManager;
}
@Override
public void rollbackIfActive() {
if (this.activeTransaction != null) {
platformTransactionManager.rollback(this.activeTransaction);
this.activeTransaction = null;
}
}
@Override
public void begin() {
if (this.activeTransaction != null) {
throw new IllegalStateException("A transaction is already active");
}
this.activeTransaction = platformTransactionManager.getTransaction(TransactionDefinition.withDefaults());
}
@Override
public void commit() {
if (this.activeTransaction == null) {
throw new IllegalStateException("A transaction is not active");
}
platformTransactionManager.commit(this.activeTransaction);
this.activeTransaction = null;
}
@Override
public void rollback() {
if (this.activeTransaction == null) {
throw new IllegalStateException("A transaction is not active");
}
platformTransactionManager.rollback(this.activeTransaction);
this.activeTransaction = null;
}
}

View File

@ -0,0 +1,94 @@
package se.su.dsv.scipro.war;
import jakarta.inject.Provider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Scope;
import org.springframework.transaction.PlatformTransactionManager;
import se.su.dsv.scipro.file.FileService;
import se.su.dsv.scipro.mail.MailEventWorker;
import se.su.dsv.scipro.match.AllowAllIdeaCreationJudge;
import se.su.dsv.scipro.match.IdeaCreationJudge;
import se.su.dsv.scipro.plagiarism.PlagiarismRequestRepository;
import se.su.dsv.scipro.plagiarism.PlagiarismSubmitter;
import se.su.dsv.scipro.plagiarism.urkund.StatusPollingWorker;
import se.su.dsv.scipro.plagiarism.urkund.UrkundService;
import se.su.dsv.scipro.plagiarism.urkund.UrkundSettings;
import se.su.dsv.scipro.plagiarism.urkund.UrkundSettingsRepository;
import se.su.dsv.scipro.projectpartner.RemoveFulfilledPartnerAdsWorker;
import se.su.dsv.scipro.reviewing.ReviewerDecisionReminderWorker;
import se.su.dsv.scipro.workerthreads.GradeFinalSeminarParticipantReminderWorker;
import se.su.dsv.scipro.workerthreads.IdeaExportWorker;
import se.su.dsv.scipro.workerthreads.ManualMatchRemindWorker;
import se.su.dsv.scipro.workerthreads.NotificationCompilationWorker;
import se.su.dsv.scipro.workerthreads.Scheduler;
import se.su.dsv.scipro.workerthreads.TemporaryWorkerScheduler;
import se.su.dsv.scipro.workerthreads.ThesisUploadDeadlineWorker;
import se.su.dsv.scipro.workerthreads.ThesisUploadReminderWorker;
import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Configuration
@ComponentScan(
basePackages = "se.su.dsv.scipro",
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Worker$"),
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*[Dd]aisy.*")
)
public class WorkerConfig {
private static final int NUMBER_OF_WORKER_THREADS = 4;
@Bean
public ScheduledExecutorService scheduledExecutorService() {
return Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS);
}
@Bean
public TemporaryWorkerScheduler temporaryWorkerScheduler(
Scheduler scheduler,
Provider<MailEventWorker> mailEventWorker,
Provider<NotificationCompilationWorker> notificationCompilationWorker,
Provider<IdeaExportWorker> ideaExportWorker,
Provider<ThesisUploadReminderWorker> thesisUploadReminderWorker,
Provider<ThesisUploadDeadlineWorker> thesisUploadDeadlineWorker,
Provider<ManualMatchRemindWorker> manualMatchRemindWorkerProvider,
Provider<ReviewerDecisionReminderWorker> reviewerDecisionReminderWorker,
Provider<PlagiarismSubmitter> plagiarismSubmitter,
Provider<StatusPollingWorker> urkundPoller,
Provider<RemoveFulfilledPartnerAdsWorker> removeFulfilledPartnerAds,
Provider<GradeFinalSeminarParticipantReminderWorker> gradeFinalSeminarParticipantReminderWorkerProvider)
{
return new TemporaryWorkerScheduler(scheduler, mailEventWorker, notificationCompilationWorker, ideaExportWorker, thesisUploadReminderWorker, thesisUploadDeadlineWorker, manualMatchRemindWorkerProvider, reviewerDecisionReminderWorker, plagiarismSubmitter, urkundPoller, removeFulfilledPartnerAds, gradeFinalSeminarParticipantReminderWorkerProvider);
}
@Bean
@ConditionalOnMissingBean(IdeaCreationJudge.class)
public IdeaCreationJudge ideaCreationJudge() {
return new AllowAllIdeaCreationJudge();
}
@Bean
public PlagiarismSubmitter plagiarismSubmitter(
Provider<UrkundSettings> urkundSettings,
PlagiarismRequestRepository plagiarismRequestRepository,
UrkundService urkundService,
FileService fileService)
{
return new PlagiarismSubmitter(urkundSettings, plagiarismRequestRepository, urkundService, fileService);
}
@Bean
public UrkundSettings urkundSettings(UrkundSettingsRepository urkundSettingsRepository) {
return urkundSettingsRepository.getSettings();
}
@Bean
@Scope("prototype")
public WorkerTransactionManager workerTransactionManager(PlatformTransactionManager platformTransactionManager) {
return new SpringManagedWorkerTransactions(platformTransactionManager);
}
}

View File

@ -1,12 +1,24 @@
package se.su.dsv.scipro.workerthreads; package se.su.dsv.scipro.workerthreads;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.servlet.FilterChain;
ansv7779 marked this conversation as resolved Outdated

Clean up unused imports

Clean up unused imports
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import se.su.dsv.scipro.system.Lifecycle; import se.su.dsv.scipro.system.Lifecycle;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Provider; import jakarta.inject.Provider;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -19,13 +31,15 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerImpl.class); private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerImpl.class);
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
private final EntityManagerFactory emf;
private final Set<Task> tasks = new TreeSet<>(new Task.ByDescriptionComparator()); private final Set<Task> tasks = new TreeSet<>(new Task.ByDescriptionComparator());
private final Set<Task> runningWorkers = Collections.synchronizedSet(new HashSet<>()); private final Set<Task> runningWorkers = Collections.synchronizedSet(new HashSet<>());
@Inject @Inject
public SchedulerImpl(ScheduledExecutorService scheduledExecutorService) { public SchedulerImpl(ScheduledExecutorService scheduledExecutorService, EntityManagerFactory emf) {
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
this.emf = emf;
} }
@Override @Override
@ -43,6 +57,14 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
private Runnable tracked(final Task task) { private Runnable tracked(final Task task) {
return () -> { return () -> {
try { try {
// Since we're not in a web request when running a scheduled job, Spring's OpenEntityManagerInView
// filter is not active. Therefore, we need to manually bind a new EntityManager to the current thread
// to be shared by all DAOs. If we do not, we will get problems with detached entity passed to persist
// since every read and write from the database will be in a new EntityManager.
EntityManager em = emf.createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
runningWorkers.add(task); runningWorkers.add(task);
task.execute(); task.execute();
} }
@ -51,6 +73,11 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
} }
finally { finally {
runningWorkers.remove(task); runningWorkers.remove(task);
// Clean up the shared EntityManager
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(emf);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
} }
}; };
} }