From 8b33af4fb168dcaf59458ec06cc82bcc5bff1cb6 Mon Sep 17 00:00:00 2001 From: Geir Sagberg Date: Wed, 26 Jun 2024 14:58:55 +0200 Subject: [PATCH] Remove spring-boot-starter-web dependency, add webflux example By removing the dependency on spring-boot-starter-web, we make it easier to utilize in a webflux application. The added webflux example shows how to add a router function to serve the static assets. --- db-scheduler-ui-starter/pom.xml | 9 +- .../autoconfigure/UiApiAutoConfiguration.java | 4 + db-scheduler-ui/pom.xml | 8 +- example-app-webflux/Dockerfile | 12 ++ example-app-webflux/pom.xml | 115 ++++++++++++++++++ .../bekk/exampleapp/ExampleAppWebFlux.java | 37 ++++++ .../exampleapp/config/DatabaseConfig.java | 47 +++++++ .../exampleapp/config/SchedulerConfig.java | 38 ++++++ .../bekk/exampleapp/config/WebConfig.java | 37 ++++++ .../bekk/exampleapp/model/TaskData.java | 42 +++++++ .../bekk/exampleapp/model/TestObject.java | 52 ++++++++ .../bekk/exampleapp/service/TaskService.java | 45 +++++++ .../bekk/exampleapp/tasks/ChainTask.java | 73 +++++++++++ .../bekk/exampleapp/tasks/FailingTask.java | 65 ++++++++++ .../exampleapp/tasks/LongRunningTask.java | 53 ++++++++ .../exampleapp/tasks/OneTimeTaskExample.java | 39 ++++++ .../tasks/RecurringTaskExample.java | 45 +++++++ .../bekk/exampleapp/tasks/SpawnerTask.java | 69 +++++++++++ .../src/main/java/utils/Utils.java | 24 ++++ .../src/main/resources/application.properties | 7 ++ .../resources/db/migration/V1__create_db.sql | 14 +++ .../db/migration/V2__add_logging.sql | 19 +++ .../com/github/bekk/exampleapp/SmokeTest.java | 66 ++++++++++ .../src/test/resources/application.properties | 1 + pom.xml | 1 + 25 files changed, 912 insertions(+), 10 deletions(-) create mode 100644 example-app-webflux/Dockerfile create mode 100644 example-app-webflux/pom.xml create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/ExampleAppWebFlux.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/DatabaseConfig.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/SchedulerConfig.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/WebConfig.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TaskData.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TestObject.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/service/TaskService.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/ChainTask.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/FailingTask.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/LongRunningTask.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/OneTimeTaskExample.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/RecurringTaskExample.java create mode 100644 example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/SpawnerTask.java create mode 100644 example-app-webflux/src/main/java/utils/Utils.java create mode 100644 example-app-webflux/src/main/resources/application.properties create mode 100644 example-app-webflux/src/main/resources/db/migration/V1__create_db.sql create mode 100644 example-app-webflux/src/main/resources/db/migration/V2__add_logging.sql create mode 100644 example-app-webflux/src/test/java/com/github/bekk/exampleapp/SmokeTest.java create mode 100644 example-app-webflux/src/test/resources/application.properties diff --git a/db-scheduler-ui-starter/pom.xml b/db-scheduler-ui-starter/pom.xml index 8c5a594e..bc1c8555 100644 --- a/db-scheduler-ui-starter/pom.xml +++ b/db-scheduler-ui-starter/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 @@ -15,15 +15,12 @@ https://github.com/bekk/db-scheduler-ui - - org.springframework.boot - spring-boot-starter-web - org.springframework.boot spring-boot-starter-test test + com.github.kagkarlsson db-scheduler-spring-boot-starter diff --git a/db-scheduler-ui-starter/src/main/java/no/bekk/dbscheduler/uistarter/autoconfigure/UiApiAutoConfiguration.java b/db-scheduler-ui-starter/src/main/java/no/bekk/dbscheduler/uistarter/autoconfigure/UiApiAutoConfiguration.java index 653b27f0..6e348a11 100644 --- a/db-scheduler-ui-starter/src/main/java/no/bekk/dbscheduler/uistarter/autoconfigure/UiApiAutoConfiguration.java +++ b/db-scheduler-ui-starter/src/main/java/no/bekk/dbscheduler/uistarter/autoconfigure/UiApiAutoConfiguration.java @@ -13,6 +13,7 @@ */ package no.bekk.dbscheduler.uistarter.autoconfigure; + import com.github.kagkarlsson.scheduler.Scheduler; import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerCustomizer; import com.github.kagkarlsson.scheduler.serializer.Serializer; @@ -31,6 +32,8 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.context.annotation.Bean; @AutoConfiguration @@ -101,6 +104,7 @@ DbSchedulerUiConfig dbSchedulerUiConfig() { @Bean @ConditionalOnMissingBean + @ConditionalOnWebApplication(type = Type.SERVLET) UIController uiController() { return new UIController(); } diff --git a/db-scheduler-ui/pom.xml b/db-scheduler-ui/pom.xml index 766e1f67..c0172385 100644 --- a/db-scheduler-ui/pom.xml +++ b/db-scheduler-ui/pom.xml @@ -21,12 +21,12 @@ provided - com.github.kagkarlsson - db-scheduler + org.springframework + spring-web - org.springframework.boot - spring-boot-starter-web + com.github.kagkarlsson + db-scheduler com.fasterxml.jackson.core diff --git a/example-app-webflux/Dockerfile b/example-app-webflux/Dockerfile new file mode 100644 index 00000000..a9a568a7 --- /dev/null +++ b/example-app-webflux/Dockerfile @@ -0,0 +1,12 @@ +# Use the official OpenJDK base image +FROM openjdk:11-jdk-slim + +# Set the working directory inside the container +WORKDIR /app + +COPY ./*.jar /app/app.jar + +EXPOSE 8081 + +# Command to run the application +CMD ["java", "-jar", "/app/app.jar"] \ No newline at end of file diff --git a/example-app-webflux/pom.xml b/example-app-webflux/pom.xml new file mode 100644 index 00000000..ef70f7ee --- /dev/null +++ b/example-app-webflux/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + + no.bekk.db-scheduler-ui + db-scheduler-ui-parent + main-SNAPSHOT + + + example-app-webflux + example-app-webflux + Example app using db-scheduler-ui and WebFlux + + + true + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + org.springframework.boot + spring-boot-devtools + + + + com.h2database + h2 + 2.2.220 + + + org.flywaydb + flyway-core + + + org.springframework.boot + spring-boot-starter-actuator + + + io.rocketbase.extension + db-scheduler-log-spring-boot-starter + 0.7.0 + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.boot + spring-boot-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.assertj + assertj-core + test + + + + org.hsqldb + hsqldb + test + + + no.bekk.db-scheduler-ui + db-scheduler-ui-starter + main-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.github.bekk.exampleapp.ExampleAppWebFlux + JAR + + + + + repackage + + + + + + + + diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/ExampleAppWebFlux.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/ExampleAppWebFlux.java new file mode 100644 index 00000000..96c967df --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/ExampleAppWebFlux.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp; + +import com.github.bekk.exampleapp.service.TaskService; +import com.github.kagkarlsson.scheduler.Scheduler; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class ExampleAppWebFlux { + public static void main(String[] args) { + SpringApplication.run(ExampleAppWebFlux.class, args); + } + + @Bean + public CommandLineRunner runAllManuallyTriggeredTasks(Scheduler scheduler) { + return args -> { + System.out.println("Running all manually triggered tasks"); + TaskService taskService = new TaskService(scheduler); + taskService.runManuallyTriggeredTasks(); + }; + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/DatabaseConfig.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/DatabaseConfig.java new file mode 100644 index 00000000..90ba7ac3 --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/DatabaseConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.config; + +import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; + +@Configuration +public class DatabaseConfig { + private final DataSource dataSource; + + public DatabaseConfig(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate() { + return new JdbcTemplate(dataSource); + } + + @Bean + public SimpleJdbcInsert simpleJdbcInsert() { + return new SimpleJdbcInsert(dataSource) + .withTableName("scheduled_tasks") + .usingGeneratedKeyColumns("id"); + } + + @Bean + public NamedParameterJdbcTemplate namedParameterJdbcTemplate() { + return new NamedParameterJdbcTemplate(dataSource); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/SchedulerConfig.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/SchedulerConfig.java new file mode 100644 index 00000000..399d9d70 --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/SchedulerConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.config; + +import com.github.kagkarlsson.scheduler.serializer.JavaSerializer; +import io.rocketbase.extension.jdbc.JdbcLogRepository; +import io.rocketbase.extension.jdbc.Snowflake; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SchedulerConfig { + private final DataSource dataSource; + + @Autowired + public SchedulerConfig(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Bean + public JdbcLogRepository jdbcLogRepository() { + return new JdbcLogRepository( + dataSource, new JavaSerializer(), JdbcLogRepository.DEFAULT_TABLE_NAME, new Snowflake()); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/WebConfig.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/WebConfig.java new file mode 100644 index 00000000..021fbc12 --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/config/WebConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.config; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +@Configuration +public class WebConfig { + @Bean + public RouterFunction dbSchedulerRouter( + @Value("classpath:/static/db-scheduler-ui/index.html") final Resource indexHtml) { + return route( + GET("/db-scheduler/**"), + request -> ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml)); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TaskData.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TaskData.java new file mode 100644 index 00000000..d13942ec --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TaskData.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.model; + +import java.io.Serializable; + +public class TaskData implements Serializable { + private long id; + private String data; + + public TaskData(long id, String data) { + this.id = id; + this.data = data; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TestObject.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TestObject.java new file mode 100644 index 00000000..20434e0f --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/model/TestObject.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.model; + +import java.io.Serializable; + +public class TestObject implements Serializable { + private String name; + private int id; + private String email; + + public String getName() { + return name; + } + + public TestObject(String initialName, int id, String email) { + this.id = id; + this.name = initialName; + this.email = email; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/service/TaskService.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/service/TaskService.java new file mode 100644 index 00000000..d9d2470a --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/service/TaskService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.service; + +import static com.github.bekk.exampleapp.tasks.ChainTask.CHAINED_STEP_1_TASK; +import static com.github.bekk.exampleapp.tasks.FailingTask.FAILING_ONETIME_TASK; +import static com.github.bekk.exampleapp.tasks.LongRunningTask.LONG_RUNNING_ONETIME_TASK; +import static com.github.bekk.exampleapp.tasks.OneTimeTaskExample.ONE_TIME_TASK; + +import com.github.bekk.exampleapp.model.TaskData; +import com.github.bekk.exampleapp.model.TestObject; +import com.github.kagkarlsson.scheduler.Scheduler; +import java.time.Instant; +import org.springframework.stereotype.Service; + +@Service +public class TaskService { + private final Scheduler scheduler; + + public TaskService(Scheduler scheduler) { + this.scheduler = scheduler; + } + + public void runManuallyTriggeredTasks() { + scheduler.schedule(ONE_TIME_TASK.instance("1", new TaskData(1, "test data")), Instant.now()); + + scheduler.schedule( + CHAINED_STEP_1_TASK.instance("3", new TestObject("Ole Nordman", 1, "ole.nordman@mail.com")), + Instant.now()); + + scheduler.schedule(LONG_RUNNING_ONETIME_TASK.instance("5"), Instant.now().plusSeconds(2)); + scheduler.schedule(FAILING_ONETIME_TASK.instance("6"), Instant.now().plusSeconds(2)); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/ChainTask.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/ChainTask.java new file mode 100644 index 00000000..6d7e9a0f --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/ChainTask.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import static utils.Utils.sleep; + +import com.github.bekk.exampleapp.model.TestObject; +import com.github.kagkarlsson.scheduler.SchedulerClient; +import com.github.kagkarlsson.scheduler.task.Task; +import com.github.kagkarlsson.scheduler.task.TaskWithDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import java.time.Instant; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ChainTask { + + public static final TaskWithDataDescriptor CHAINED_STEP_1_TASK = + new TaskWithDataDescriptor<>("chained-step-1", TestObject.class); + + public static final TaskWithDataDescriptor CHAINED_STEP_2_TASK = + new TaskWithDataDescriptor<>("chained-step-2", TestObject.class); + + @Bean + public Task chainTaskStepOne() { + return Tasks.oneTime(CHAINED_STEP_1_TASK) + .execute( + (inst, ctx) -> { + sleep(5000); + final SchedulerClient client = ctx.getSchedulerClient(); + System.out.println( + "Executed chained onetime task step 1: " + + inst.getTaskName() + + " " + + inst.getId()); + TestObject data = inst.getData(); + data.setId(data.getId() + 1); + client.schedule( + CHAINED_STEP_2_TASK.instance(inst.getId(), data), Instant.now().plusSeconds(10)); + }); + } + + @Bean + public Task chainTaskStepTwo() { + return Tasks.oneTime(CHAINED_STEP_2_TASK) + .execute( + (inst, ctx) -> { + sleep(5000); + final SchedulerClient client = ctx.getSchedulerClient(); + System.out.println( + "Executed chained onetime task step 2: " + + inst.getTaskName() + + " " + + inst.getId()); + TestObject data = inst.getData(); + data.setId(data.getId() + 1); + client.schedule( + CHAINED_STEP_1_TASK.instance(inst.getId(), data), Instant.now().plusSeconds(20)); + }); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/FailingTask.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/FailingTask.java new file mode 100644 index 00000000..cca03aeb --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/FailingTask.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import static java.time.Duration.ofSeconds; + +import com.github.kagkarlsson.scheduler.task.FailureHandler; +import com.github.kagkarlsson.scheduler.task.Task; +import com.github.kagkarlsson.scheduler.task.TaskWithoutDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import com.github.kagkarlsson.scheduler.task.schedule.FixedDelay; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FailingTask { + + public static final TaskWithoutDataDescriptor FAILING_ONETIME_TASK = + new TaskWithoutDataDescriptor("failing-one-time-task"); + + public static final TaskWithoutDataDescriptor FAILING_RECURRING_TASK = + new TaskWithoutDataDescriptor("failing-recurring-task"); + + public static final TaskWithoutDataDescriptor FAILING_ONETIME_TASK_BACKOFF = + new TaskWithoutDataDescriptor("failing-one-time-with-backoff-task"); + + @Bean + public Task runOneTimeFailing() { + return Tasks.oneTime(FAILING_ONETIME_TASK) + .execute( + (inst, ctx) -> { + throw new RuntimeException("Simulated task failure"); + }); + } + + @Bean + public Task runRecurringFailing() { + return Tasks.recurring(FAILING_RECURRING_TASK, FixedDelay.ofSeconds(6)) + .execute( + (inst, ctx) -> { + throw new RuntimeException("Simulated task failure"); + }); + } + + @Bean + public Task runOneTimeFailingWithBackoff() { + return Tasks.oneTime(FAILING_ONETIME_TASK_BACKOFF) + .onFailure(new FailureHandler.ExponentialBackoffFailureHandler<>(ofSeconds(1))) + .execute( + (inst, ctx) -> { + throw new RuntimeException("Simulated task failure"); + }); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/LongRunningTask.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/LongRunningTask.java new file mode 100644 index 00000000..cb9be75a --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/LongRunningTask.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import static utils.Utils.sleep; + +import com.github.kagkarlsson.scheduler.task.Task; +import com.github.kagkarlsson.scheduler.task.TaskWithoutDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import com.github.kagkarlsson.scheduler.task.schedule.FixedDelay; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class LongRunningTask { + + public static final TaskWithoutDataDescriptor LONG_RUNNING_ONETIME_TASK = + new TaskWithoutDataDescriptor("long-running-task"); + + public static final TaskWithoutDataDescriptor LONG_RUNNING_RECURRING_TASK = + new TaskWithoutDataDescriptor("long-running-recurring-task"); + + @Bean + public Task runLongRunningTask() { + return Tasks.oneTime(LONG_RUNNING_ONETIME_TASK) + .execute( + (inst, ctx) -> { + System.out.println("Executing long running task: " + inst.getTaskName()); + sleep(10000); + }); + } + + @Bean + public Task runLongRunningRecurringTask() { + return Tasks.recurring(LONG_RUNNING_RECURRING_TASK, FixedDelay.ofSeconds(20)) + .execute( + (inst, ctx) -> { + System.out.println("Executing long running recurring task: " + inst.getTaskName()); + sleep(10000); + }); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/OneTimeTaskExample.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/OneTimeTaskExample.java new file mode 100644 index 00000000..190381c5 --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/OneTimeTaskExample.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import com.github.bekk.exampleapp.model.TaskData; +import com.github.kagkarlsson.scheduler.task.Task; +import com.github.kagkarlsson.scheduler.task.TaskWithDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OneTimeTaskExample { + + public static final TaskWithDataDescriptor ONE_TIME_TASK = + new TaskWithDataDescriptor<>("onetime-task", TaskData.class); + + @Bean + public Task exampleOneTimeTask() { + return Tasks.oneTime(ONE_TIME_TASK) + .execute( + (inst, ctx) -> { + System.out.println("Executed onetime task: " + inst.getTaskName()); + System.out.println( + "With data id: " + inst.getData().getId() + " data: " + inst.getData().getData()); + }); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/RecurringTaskExample.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/RecurringTaskExample.java new file mode 100644 index 00000000..6344844b --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/RecurringTaskExample.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import static utils.Utils.sleep; + +import com.github.kagkarlsson.scheduler.task.TaskWithoutDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.RecurringTask; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import com.github.kagkarlsson.scheduler.task.schedule.FixedDelay; +import java.util.Random; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RecurringTaskExample { + + public static final TaskWithoutDataDescriptor RECURRING_TASK = + new TaskWithoutDataDescriptor("recurring-task"); + + @Bean + public RecurringTask getExample() { + return Tasks.recurring(RECURRING_TASK, FixedDelay.ofSeconds(6)) + .execute( + (inst, ctx) -> { + sleep(5000); + if (new Random().nextInt(100) < 30) { + throw new RuntimeException("Simulated failure in example recurring task"); + } + + System.out.println("Executed recurring task: " + inst.getTaskName()); + }); + } +} diff --git a/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/SpawnerTask.java b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/SpawnerTask.java new file mode 100644 index 00000000..8f02d4ca --- /dev/null +++ b/example-app-webflux/src/main/java/com/github/bekk/exampleapp/tasks/SpawnerTask.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) Bekk + * + *

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.bekk.exampleapp.tasks; + +import static utils.Utils.sleep; + +import com.github.bekk.exampleapp.model.TaskData; +import com.github.kagkarlsson.scheduler.SchedulerClient; +import com.github.kagkarlsson.scheduler.task.Task; +import com.github.kagkarlsson.scheduler.task.TaskWithDataDescriptor; +import com.github.kagkarlsson.scheduler.task.TaskWithoutDataDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import com.github.kagkarlsson.scheduler.task.schedule.FixedDelay; +import java.time.Instant; +import java.util.Random; +import java.util.UUID; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpawnerTask { + + public static final TaskWithoutDataDescriptor RECURRING_SPAWNER_TASK = + new TaskWithoutDataDescriptor("recurring-spawner-task"); + + public static final TaskWithDataDescriptor ONE_TIME_SPAWNER_TASK = + new TaskWithDataDescriptor<>("onetime-spawned-task", TaskData.class); + + @Bean + public Task runSpawner() { + return Tasks.recurring(RECURRING_SPAWNER_TASK, FixedDelay.ofSeconds(180)) + .execute( + (inst, ctx) -> { + final SchedulerClient client = ctx.getSchedulerClient(); + final long randomUUID = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; + + for (int i = 0; i < 100; i++) { + client.schedule( + ONE_TIME_SPAWNER_TASK.instance( + "spawned " + randomUUID + " loopnr: " + i, + new TaskData(123, "{data: MASSIVEDATA}")), + Instant.now().plusSeconds(60)); + } + }); + } + + @Bean + public Task runOneTimeSpawned() { + return Tasks.oneTime(ONE_TIME_SPAWNER_TASK) + .execute( + (inst, ctx) -> { + sleep(10000); + if (new Random().nextInt(100) < 20) { + throw new RuntimeException("Simulated failure"); + } + }); + } +} diff --git a/example-app-webflux/src/main/java/utils/Utils.java b/example-app-webflux/src/main/java/utils/Utils.java new file mode 100644 index 00000000..607449c6 --- /dev/null +++ b/example-app-webflux/src/main/java/utils/Utils.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) Bekk + * + *

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 utils; + +public class Utils { + public static void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/example-app-webflux/src/main/resources/application.properties b/example-app-webflux/src/main/resources/application.properties new file mode 100644 index 00000000..9eb49d48 --- /dev/null +++ b/example-app-webflux/src/main/resources/application.properties @@ -0,0 +1,7 @@ +server.port=${PORT:8081} +spring.datasource.url=jdbc:h2:file:./db-scheduler-application/test-db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH +spring.datasource.driver-class-name=org.h2.Driver +db-scheduler-log.enabled=true +db-scheduler-log.table-name=scheduled_execution_logs +db-scheduler-ui.history=true +db-scheduler-ui.task-data=true diff --git a/example-app-webflux/src/main/resources/db/migration/V1__create_db.sql b/example-app-webflux/src/main/resources/db/migration/V1__create_db.sql new file mode 100644 index 00000000..ae0652fc --- /dev/null +++ b/example-app-webflux/src/main/resources/db/migration/V1__create_db.sql @@ -0,0 +1,14 @@ +create table if not exists scheduled_tasks ( + task_name varchar(255), + task_instance varchar(255), + task_data BYTEA, + execution_time TIMESTAMP WITH TIME ZONE, + picked BIT, + picked_by varchar(50), + last_success TIMESTAMP WITH TIME ZONE, + last_failure TIMESTAMP WITH TIME ZONE, + consecutive_failures INT, + last_heartbeat TIMESTAMP WITH TIME ZONE, + version BIGINT, + PRIMARY KEY (task_name, task_instance) + ); diff --git a/example-app-webflux/src/main/resources/db/migration/V2__add_logging.sql b/example-app-webflux/src/main/resources/db/migration/V2__add_logging.sql new file mode 100644 index 00000000..9ed7d50c --- /dev/null +++ b/example-app-webflux/src/main/resources/db/migration/V2__add_logging.sql @@ -0,0 +1,19 @@ +create table scheduled_execution_logs +( + id BIGINT not null primary key, + task_name text not null, + task_instance text not null, + task_data bytea, + picked_by text, + time_started timestamp with time zone not null, + time_finished timestamp with time zone not null, + succeeded BOOLEAN not null, + duration_ms BIGINT not null, + exception_class text, + exception_message text, + exception_stacktrace text +); + +CREATE INDEX stl_started_idx ON scheduled_execution_logs (time_started); +CREATE INDEX stl_task_name_idx ON scheduled_execution_logs (task_name); +CREATE INDEX stl_exception_class_idx ON scheduled_execution_logs (exception_class); \ No newline at end of file diff --git a/example-app-webflux/src/test/java/com/github/bekk/exampleapp/SmokeTest.java b/example-app-webflux/src/test/java/com/github/bekk/exampleapp/SmokeTest.java new file mode 100644 index 00000000..c3db513d --- /dev/null +++ b/example-app-webflux/src/test/java/com/github/bekk/exampleapp/SmokeTest.java @@ -0,0 +1,66 @@ +package com.github.bekk.exampleapp; + +import static com.github.bekk.exampleapp.tasks.OneTimeTaskExample.ONE_TIME_TASK; +import static org.assertj.core.api.Assertions.assertThat; + +import no.bekk.dbscheduler.ui.controller.TaskController; +import no.bekk.dbscheduler.ui.model.GetTasksResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest( + classes = {ExampleAppWebFlux.class}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SmokeTest { + + @Autowired TaskController controller; + @LocalServerPort private Integer serverPort; + private String baseUrl; + @Autowired private TestRestTemplate restTemplate; + + @Test + public void testContextLoads() throws Exception { + assertThat(controller).isNotNull(); + } + + @Test + public void testGetTasksReturnsStatusOK() throws Exception { + ResponseEntity result = + this.restTemplate.getForEntity( + baseUrl + + "/db-scheduler-api/tasks/all?filter=ALL&pageNumber=0&size=10&sorting=DEFAULT&asc=true&searchTerm=" + + ONE_TIME_TASK.getTaskName(), + GetTasksResponse.class); + Assertions.assertEquals(result.getStatusCode(), HttpStatus.OK); + assertThat(result.getBody().getItems()).hasSizeGreaterThan(0); + } + + @Test + public void testGetTasksReturnsExampleOneTimeTask() throws Exception { + ResponseEntity result = + this.restTemplate.getForEntity( + baseUrl + + "/db-scheduler-api/tasks/all?filter=ALL&pageNumber=0&size=10&sorting=DEFAULT&asc=true&searchTerm=" + + ONE_TIME_TASK.getTaskName(), + GetTasksResponse.class); + Assertions.assertEquals(result.getStatusCode(), HttpStatus.OK); + result.getBody().getItems().forEach(t -> System.out.println(t.getTaskName())); + assertThat(result.getBody().getItems()) + .anyMatch(taskModel -> taskModel.getTaskName().equals(ONE_TIME_TASK.getTaskName())); + } + + @BeforeEach + public void setUp() { + baseUrl = "http://localhost:" + serverPort; + } +} diff --git a/example-app-webflux/src/test/resources/application.properties b/example-app-webflux/src/test/resources/application.properties new file mode 100644 index 00000000..71c3bde6 --- /dev/null +++ b/example-app-webflux/src/test/resources/application.properties @@ -0,0 +1 @@ +spring.datasource.url=jdbc:h2:mem:testdb diff --git a/pom.xml b/pom.xml index 2a957e3c..1baacc3d 100644 --- a/pom.xml +++ b/pom.xml @@ -341,6 +341,7 @@ db-scheduler-ui db-scheduler-ui-starter example-app + example-app-webflux