diff --git a/src/main/java/sirius/biz/jobs/scheduler/JobSchedulerLoop.java b/src/main/java/sirius/biz/jobs/scheduler/JobSchedulerLoop.java index e882f07a4..41bf4dd2d 100644 --- a/src/main/java/sirius/biz/jobs/scheduler/JobSchedulerLoop.java +++ b/src/main/java/sirius/biz/jobs/scheduler/JobSchedulerLoop.java @@ -9,20 +9,13 @@ package sirius.biz.jobs.scheduler; import sirius.biz.jobs.Jobs; -import sirius.biz.process.ProcessContext; -import sirius.biz.process.ProcessLink; -import sirius.biz.process.Processes; -import sirius.biz.process.logs.ProcessLog; import sirius.kernel.async.BackgroundLoop; import sirius.kernel.di.PartCollection; import sirius.kernel.di.std.Part; import sirius.kernel.di.std.Parts; import sirius.kernel.di.std.Register; import sirius.kernel.health.Exceptions; -import sirius.kernel.health.HandledException; import sirius.kernel.health.Log; -import sirius.web.security.UserContext; -import sirius.web.security.UserInfo; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -38,7 +31,7 @@ public class JobSchedulerLoop extends BackgroundLoop { private Jobs jobs; @Part - private Processes processes; + private ScheduledEntryExecution entryExecution; @Parts(SchedulerEntryProvider.class) private PartCollection> providers; @@ -77,7 +70,7 @@ private int executeEntriesOfProvider(LocalDateTime no try { if (entry.getSchedulerData().shouldRun(now)) { entry = provider.fetchFullInformation(entry); - executeJob(provider, entry, now); + entryExecution.executeJob(provider, entry, now); startedJobs++; } } catch (Exception exception) { @@ -94,66 +87,4 @@ private int executeEntriesOfProvider(LocalDateTime no return startedJobs; } - - private void executeJob(SchedulerEntryProvider provider, J entry, LocalDateTime now) { - try { - UserInfo user = UserContext.get().getUserManager().findUserByUserId(entry.getSchedulerData().getUserId()); - UserContext.get().runAs(user, () -> executeJobAsUser(provider, entry, now)); - } catch (Exception exception) { - Exceptions.handle() - .to(Log.BACKGROUND) - .error(exception) - .withSystemErrorMessage("An error occurred while starting a scheduled task of %s: %s - %s (%s)", - provider.getClass().getSimpleName(), - entry) - .handle(); - } - } - - private void executeJobAsUser(SchedulerEntryProvider provider, - J entry, - LocalDateTime now) { - processes.executeInStandbyProcessForCurrentTenant("biz-scheduler", - () -> "Job Scheduler", - ctx -> executeJobInProcess(provider, entry, now, ctx)); - } - - private void executeJobInProcess(SchedulerEntryProvider provider, - J entry, - LocalDateTime now, - ProcessContext ctx) { - if (ctx.isDebugging()) { - ctx.debug(ProcessLog.info() - .withFormattedMessage("Starting scheduled job %s (%s) for user %s.", - entry, - entry.getJobConfigData().getJobName(), - UserContext.getCurrentUser().getUserName())); - } - - try { - String processId = entry.getJobConfigData() - .getJobFactory() - .startInBackground(entry.getJobConfigData()::fetchParameter); - - if (processId != null) { - processes.log(processId, - ProcessLog.info() - .withNLSKey("JobSchedulerLoop.scheduledExecutionInfo") - .withContext("entry", entry.toString())); - processes.addLink(processId, - new ProcessLink().withLabel("$JobSchedulerLoop.jobLink") - .withUri("/jobs/scheduler/entry/" + entry.getIdAsString())); - processes.addReference(processId, entry.getUniqueName()); - } - - provider.markExecuted(entry, now); - } catch (HandledException exception) { - ctx.log(ProcessLog.error() - .withFormattedMessage("Failed to start scheduled job %s (%s) for user %s: %s", - entry, - entry.getJobConfigData().getJobName(), - UserContext.getCurrentUser().getUserName(), - exception.getMessage())); - } - } } diff --git a/src/main/java/sirius/biz/jobs/scheduler/ScheduledEntryExecution.java b/src/main/java/sirius/biz/jobs/scheduler/ScheduledEntryExecution.java new file mode 100644 index 000000000..d95520f50 --- /dev/null +++ b/src/main/java/sirius/biz/jobs/scheduler/ScheduledEntryExecution.java @@ -0,0 +1,104 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.biz.jobs.scheduler; + +import sirius.biz.jobs.Jobs; +import sirius.biz.process.ProcessContext; +import sirius.biz.process.ProcessLink; +import sirius.biz.process.Processes; +import sirius.biz.process.logs.ProcessLog; +import sirius.kernel.di.std.Part; +import sirius.kernel.di.std.Register; +import sirius.kernel.health.Exceptions; +import sirius.kernel.health.HandledException; +import sirius.kernel.health.Log; +import sirius.web.security.UserContext; +import sirius.web.security.UserInfo; + +import java.time.LocalDateTime; + +/** + * Provides the logic to execute a scheduled entry with the appropriate settings. + */ +@Register(framework = Jobs.FRAMEWORK_JOBS, classes = ScheduledEntryExecution.class) +public class ScheduledEntryExecution { + + @Part + private Processes processes; + + /** + * Executes the provided scheduled entry immediately using the given provider. + * + * @param provider The provider which manages data persistence for the given entry + * @param entry the entry to execute + * @param now the current time to be used for marking the job execution + * @param the type of the entry to execute + */ + public void executeJob(SchedulerEntryProvider provider, J entry, LocalDateTime now) { + try { + UserInfo user = UserContext.get().getUserManager().findUserByUserId(entry.getSchedulerData().getUserId()); + UserContext.get().runAs(user, () -> executeJobAsUser(provider, entry, now)); + } catch (Exception exception) { + Exceptions.handle() + .to(Log.BACKGROUND) + .error(exception) + .withSystemErrorMessage("An error occurred while starting a scheduled task of %s: %s - %s (%s)", + provider.getClass().getSimpleName(), + entry) + .handle(); + } + } + + private void executeJobAsUser(SchedulerEntryProvider provider, + J entry, + LocalDateTime now) { + processes.executeInStandbyProcessForCurrentTenant("biz-scheduler", + () -> "Job Scheduler", + ctx -> executeJobInProcess(provider, entry, now, ctx)); + } + + private void executeJobInProcess(SchedulerEntryProvider provider, + J entry, + LocalDateTime now, + ProcessContext ctx) { + if (ctx.isDebugging()) { + ctx.debug(ProcessLog.info() + .withFormattedMessage("Starting scheduled job %s (%s) for user %s.", + entry, + entry.getJobConfigData().getJobName(), + UserContext.getCurrentUser().getUserName())); + } + + try { + String processId = entry.getJobConfigData() + .getJobFactory() + .startInBackground(entry.getJobConfigData()::fetchParameter); + + if (processId != null) { + processes.log(processId, + ProcessLog.info() + .withNLSKey("JobSchedulerLoop.scheduledExecutionInfo") + .withContext("entry", entry.toString())); + processes.addLink(processId, + new ProcessLink().withLabel("$JobSchedulerLoop.jobLink") + .withUri("/jobs/scheduler/entry/" + entry.getIdAsString())); + processes.addReference(processId, entry.getUniqueName()); + } + + provider.markExecuted(entry, now); + } catch (HandledException exception) { + ctx.log(ProcessLog.error() + .withFormattedMessage("Failed to start scheduled job %s (%s) for user %s: %s", + entry, + entry.getJobConfigData().getJobName(), + UserContext.getCurrentUser().getUserName(), + exception.getMessage())); + } + } +} diff --git a/src/main/java/sirius/biz/jobs/scheduler/SchedulerController.java b/src/main/java/sirius/biz/jobs/scheduler/SchedulerController.java index 2250a9418..a15377f02 100644 --- a/src/main/java/sirius/biz/jobs/scheduler/SchedulerController.java +++ b/src/main/java/sirius/biz/jobs/scheduler/SchedulerController.java @@ -11,13 +11,16 @@ import sirius.biz.jobs.JobConfigData; import sirius.biz.jobs.JobFactory; import sirius.biz.jobs.Jobs; +import sirius.biz.process.Processes; import sirius.biz.web.BasePageHelper; import sirius.biz.web.BizController; import sirius.biz.web.TenantAware; import sirius.db.mixing.BaseEntity; import sirius.db.mixing.query.QueryField; import sirius.kernel.commons.Strings; +import sirius.kernel.di.PartCollection; import sirius.kernel.di.std.Part; +import sirius.kernel.di.std.Parts; import sirius.kernel.nls.NLS; import sirius.web.controller.AutocompleteHelper; import sirius.web.controller.Routed; @@ -26,6 +29,8 @@ import sirius.web.security.UserContext; import sirius.web.security.UserInfo; +import java.time.LocalDateTime; + /** * Provides a base class to create the management UI for the job scheduler. *

@@ -41,6 +46,15 @@ public abstract class SchedulerController & SchedulerEnt @Part private Jobs jobs; + @Part + private Processes processes; + + @Parts(SchedulerEntryProvider.class) + private PartCollection> providers; + + @Part + private ScheduledEntryExecution entryExecution; + /** * Returns the entity class being used by this controller. * @@ -76,8 +90,8 @@ public void schedulerEntries(WebContext webContext) { /** * Renders a details page for the given scheduler entry. * - * @param webContext the current request - * @param entryId the id of the entry to display or new to create a new one + * @param webContext the current request + * @param entryId the id of the entry to display or new to create a new one */ @Permission(PERMISSION_MANAGE_SCHEDULER) @Routed("/jobs/scheduler/entry/:1") @@ -92,10 +106,11 @@ public void schedulerEntry(WebContext webContext, String entryId) { return; } - boolean requestHandled = prepareSave(webContext).withAfterSaveURI("/jobs/scheduler").withPreSaveHandler(isNew -> { - loadUser(webContext, entry); - entry.getJobConfigData().loadFromContext(webContext); - }).saveEntity(entry); + boolean requestHandled = + prepareSave(webContext).withAfterSaveURI("/jobs/scheduler").withPreSaveHandler(isNew -> { + loadUser(webContext, entry); + entry.getJobConfigData().loadFromContext(webContext); + }).saveEntity(entry); if (!requestHandled) { validate(entry); @@ -103,11 +118,51 @@ public void schedulerEntry(WebContext webContext, String entryId) { } } + /** + * Executes the given scheduler entry immediately and redirects to the process detail view. + * + * @param webContext the current request + * @param entryId the id of the entry to display or new to create a new one + */ + @Permission(Jobs.PERMISSION_EXECUTE_JOBS) + @Routed("/jobs/scheduler/:1/execute") + public void executeSchedulerEntry(WebContext webContext, String entryId) { + J entry = findForTenant(getEntryType(), entryId); + + if (entry.isNew()) { + schedulerEntries(webContext); + return; + } + + executeInBelongingProvider(entry); + String entryProcessesUrl = Strings.apply("/ps?reference=%s&reference-label=%s", + entry.getUniqueName(), + Strings.urlEncode(entry.toString())); + webContext.respondWith().redirectToGet(entryProcessesUrl); + } + + private void executeInBelongingProvider(J entry) { + for (SchedulerEntryProvider provider : providers) { + J entryFromProvider = provider.fetchFullInformation(entry); + if (entryBelongsToProvider(entry, entryFromProvider)) { + entryExecution.executeJob(provider, entryFromProvider, LocalDateTime.now()); + return; + } + } + } + + private static & SchedulerEntry & TenantAware> boolean entryBelongsToProvider(J entry, + J providersEntry) { + // The belonging provided must have refreshed data, so the object identity is different. + return System.identityHashCode(providersEntry) != System.identityHashCode(entry); + } + protected void loadUser(WebContext webContext, J entry) { UserInfo user = UserContext.get() .getUserManager() .findUserByUserId(webContext.get(SchedulerEntry.SCHEDULER_DATA.inner(SchedulerData.USER_ID) - .toString()).asString()); + .toString()) + .asString()); // Ensure that an active and accessible user was selected... if (user == null || !Strings.areEqual(UserContext.getCurrentUser().getTenantId(), user.getTenantId())) { @@ -150,8 +205,8 @@ private boolean handleNewEntryWithoutJob(J entry, WebContext webContext) { /** * Deletes the given scheduler entry. * - * @param webContext the curren request - * @param entryId the id of the entry to delete + * @param webContext the curren request + * @param entryId the id of the entry to delete */ @Permission(PERMISSION_MANAGE_SCHEDULER) @Routed("/jobs/scheduler/entry/:1/delete") diff --git a/src/main/resources/biz_de.properties b/src/main/resources/biz_de.properties index 61f909ba7..8eb0c1284 100644 --- a/src/main/resources/biz_de.properties +++ b/src/main/resources/biz_de.properties @@ -929,6 +929,7 @@ SchedulerEntry.invalidPatternInField = Ungültiger Wert im Feld ${field}: ${msg} SchedulerEntry.lastExecution = Letzte Ausführung SchedulerEntry.minute = Minute SchedulerEntry.month = Monat +SchedulerEntry.executeNow = Jetzt ausführen SchedulerEntry.numberOfExecutions = Anzahl Ausführungen SchedulerEntry.performedExecutions = Ausführungen SchedulerEntry.plural = Geplante Ausführungen diff --git a/src/main/resources/default/templates/biz/jobs/scheduler/entries.html.pasta b/src/main/resources/default/templates/biz/jobs/scheduler/entries.html.pasta index 4c50163bc..9ba8c788e 100644 --- a/src/main/resources/default/templates/biz/jobs/scheduler/entries.html.pasta +++ b/src/main/resources/default/templates/biz/jobs/scheduler/entries.html.pasta @@ -34,6 +34,10 @@ url="@apply('/ps?reference=%s&reference-label=%s', entry.getUniqueName(), urlEncode(entry.toString()))" icon="fa-solid fa-list" labelKey="SchedulerEntry.performedExecutions"/> +