├── .editorconfig ├── .github └── workflows │ ├── boot.yml │ ├── ci.yml │ └── release-to-maven-central.yml ├── .gitignore ├── .java-version ├── .license └── license-header.txt ├── LICENSE ├── README.md ├── db-scheduler-log-boot-starter ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── rocketbase │ │ │ └── extension │ │ │ └── boot │ │ │ ├── autoconfigure │ │ │ ├── DbSchedulerLogAutoConfiguration.java │ │ │ └── DbSchedulerLogMetricAutoConfiguration.java │ │ │ ├── config │ │ │ └── DbSchedulerLogProperties.java │ │ │ └── package-info.java │ └── resources │ │ └── META-INF │ │ ├── spring.factories │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── test │ ├── java │ └── io │ │ └── rocketbase │ │ └── extension │ │ └── boot │ │ └── DbSchedulerLogAutoConfigurationTest.java │ └── resources │ └── schema.sql ├── db-scheduler-log ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── rocketbase │ │ └── extension │ │ ├── ExecutionLog.java │ │ ├── LogRepository.java │ │ ├── jdbc │ │ ├── IdProvider.java │ │ ├── JdbcLogRepository.java │ │ └── Snowflake.java │ │ └── stats │ │ ├── LogStatsMicrometerRegistry.java │ │ └── LogStatsPlainRegistry.java │ └── test │ ├── java │ └── io │ │ └── rocketbase │ │ └── extension │ │ ├── CustomTableNameTest.java │ │ ├── DbUtils.java │ │ ├── EmbeddedPostgresqlExtension.java │ │ └── compatibility │ │ ├── CompatibilityTest.java │ │ ├── MssqlCompatibilityTest.java │ │ ├── MysqlCompatibilityTest.java │ │ ├── NoAutoCommitPostgresqlCompatibilityTest.java │ │ └── Oracle11gCompatibilityTest.java │ └── resources │ ├── container-license-acceptance.txt │ ├── hsql_tables.sql │ ├── io │ └── rocketbase │ │ └── extension │ │ └── postgresql_custom_tablename.sql │ ├── mssql_tables.sql │ ├── mysql_tables.sql │ ├── oracle_tables.sql │ └── postgresql_tables.sql └── pom.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.java] 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.github/workflows/boot.yml: -------------------------------------------------------------------------------- 1 | name: spring-boot-compatibility 2 | on: [ push, pull_request ] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | spring-boot: [ '2.5.14', '2.6.12', '2.7.4', '3.1.0' ] 9 | name: Spring Boot ${{ matrix.spring-boot }} 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Set up Java 14 | uses: actions/setup-java@v3 15 | with: 16 | java-version: '17' 17 | distribution: 'temurin' 18 | cache: 'maven' 19 | 20 | - name: Run Spring Boot tests 21 | run: mvn -B -Dspring-boot.version=${{ matrix.spring-boot }} -PspringBootDevelopment clean test --file pom.xml 22 | env: 23 | TZ: UTC 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | java: [ '8', '11', '17' ] 9 | name: Temurin ${{ matrix.java }} 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Set up Java ${{ matrix.java }} 14 | uses: actions/setup-java@v3 15 | with: 16 | java-version: ${{ matrix.java }} 17 | distribution: 'temurin' 18 | cache: 'maven' 19 | 20 | - name: Run all tests 21 | run: mvn -B -Pcompatibility clean test --file pom.xml 22 | env: 23 | TZ: UTC 24 | -------------------------------------------------------------------------------- /.github/workflows/release-to-maven-central.yml: -------------------------------------------------------------------------------- 1 | name: release-to-maven-central 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | releaseversion: 6 | description: 'Release version' 7 | required: true 8 | default: '1.0.0' 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - run: | 15 | echo "Release version ${{ github.event.inputs.releaseversion }}!" 16 | 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Maven Central Repository 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | server-id: ossrh 24 | server-username: MAVEN_USERNAME 25 | server-password: MAVEN_PASSWORD 26 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 27 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 28 | 29 | - name: Set projects Maven version to GitHub Action GUI set version 30 | run: mvn versions:set "-DnewVersion=${{ github.event.inputs.releaseversion }}" 31 | 32 | - name: Publish package 33 | run: mvn --batch-mode clean deploy -P release -DskipTests=true 34 | env: 35 | MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }} 36 | MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }} 37 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 38 | 39 | - name: Create GitHub Release 40 | id: create_release 41 | uses: actions/create-release@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | tag_name: ${{ github.event.inputs.releaseversion }} 46 | release_name: ${{ github.event.inputs.releaseversion }} 47 | body: | 48 | New version ${{ github.event.inputs.releaseversion }} published 49 | draft: false 50 | prerelease: false 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | target/ 4 | .classpath 5 | .project 6 | .settings/ 7 | dependency-reduced-pom.xml 8 | .terraform 9 | terraform.tfstate* 10 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /.license/license-header.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) Marten Prieß 2 |

3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 |

7 | http://www.apache.org/licenses/LICENSE-2.0 8 |

9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # db-scheduler-log 2 | 3 | ![build status](https://github.com/rocketbase-io/db-scheduler-log/workflows/build/badge.svg) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.rocketbase.extension/db-scheduler-log/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.rocketbase.extension/db-scheduler-log) 5 | [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 6 | 7 | ## Getting started 8 | 9 | 1. Add maven dependency 10 | 11 | ```xml 12 | 13 | 14 | io.rocketbase.extension 15 | db-scheduler-log 16 | ${version} 17 | 18 | ``` 19 | 20 | 2. Create the `scheduled_execution_logs` table in your database-schema. See table definition 21 | for [postgresql](db-scheduler-log/src/test/resources/postgresql_tables.sql), [oracle](db-scheduler-log/src/test/resources/oracle_tables.sql), [mssql](db-scheduler-log/src/test/resources/mssql_tables.sql) 22 | or [mysql](db-scheduler-log/src/test/resources/mysql_tables.sql). 23 | 24 | > :mega: It's highly recommended to create the log-table with daily partitions based on time_started with a proper 25 | > retention when you have a huge amount of running tasks... otherwise you could run out of disk-space quite soon. 26 | 27 | 3. Customize the scheduler to use extended StatsRegistry. 28 | 29 | ```java 30 | final JdbcLogRepository jdbcLogRepository=new JdbcLogRepository(dataSource,new JavaSerializer(),JdbcLogRepository.DEFAULT_TABLE_NAME,new Snowflake()); 31 | 32 | final Scheduler scheduler=Scheduler 33 | .create(dataSource) 34 | .startTasks(hourlyTask) 35 | .threads(5) 36 | .statsRegistry(new LogStatsPlainRegistry(jdbcLogRepository)) 37 | .build(); 38 | ``` 39 | 40 | ## Spring Boot usage 41 | 42 | For Spring Boot applications, there is a starter `db-scheduler-log-spring-boot-starter` making the scheduler-log-wiring 43 | very simple. 44 | 45 | ### Prerequisites 46 | 47 | - An existing Spring Boot application 48 | - A working `DataSource` with schema initialized. (In the example HSQLDB is used and schema is automatically applied.) 49 | 50 | ### Getting started 51 | 52 | 1. Add the following Maven dependency 53 | ```xml 54 | 55 | io.rocketbase.extension 56 | db-scheduler-log-spring-boot-starter 57 | ${version} 58 | 59 | ``` 60 | **NOTE**: This includes the db-scheduler-spring-boot-starter dependency itself. 61 | 2. Do configuration explained on db-scheduler... 62 | 3Run the app. 63 | 64 | ### Configuration options 65 | 66 | Configuration is mainly done via `application.properties`. Configuration of table-name is done by properties. 67 | 68 | ``` 69 | # application.properties example showing default values 70 | 71 | db-scheduler-log.enabled=true 72 | db-scheduler-log.table-name=scheduled_execution_logs 73 | ``` 74 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | io.rocketbase.extension 6 | db-scheduler-log-parent 7 | master-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | db-scheduler-log-spring-boot-starter 12 | db-scheduler-log: Spring Boot Starter 13 | A starter for Spring Boot that will configure db-scheduler-log 14 | 15 | 16 | ${project.parent.basedir}/.license 17 | 18 | 19 | 20 | 21 | 22 | io.rocketbase.extension 23 | db-scheduler-log 24 | ${project.version} 25 | 26 | 27 | com.github.kagkarlsson 28 | db-scheduler-spring-boot-starter 29 | ${db-scheduler.version} 30 | 31 | 32 | org.slf4j 33 | slf4j-api 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-actuator-autoconfigure 40 | true 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-autoconfigure-processor 45 | true 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-configuration-processor 50 | true 51 | 52 | 53 | 54 | io.micrometer 55 | micrometer-core 56 | true 57 | 58 | 59 | 60 | 61 | javax.annotation 62 | javax.annotation-api 63 | 1.3.2 64 | provided 65 | 66 | 67 | 68 | 69 | ch.qos.logback 70 | logback-classic 71 | test 72 | 73 | 74 | org.junit.jupiter 75 | junit-jupiter 76 | test 77 | 78 | 79 | org.junit.jupiter 80 | junit-jupiter-api 81 | test 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-test 86 | test 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-test 91 | test 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-jdbc 96 | test 97 | 98 | 99 | org.assertj 100 | assertj-core 101 | test 102 | 103 | 104 | org.hsqldb 105 | hsqldb 106 | test 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/java/io/rocketbase/extension/boot/autoconfigure/DbSchedulerLogAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.boot.autoconfigure; 17 | 18 | import com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerMetricsAutoConfiguration; 19 | import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerCustomizer; 20 | import com.github.kagkarlsson.scheduler.exceptions.SerializationException; 21 | import com.github.kagkarlsson.scheduler.serializer.Serializer; 22 | import com.github.kagkarlsson.scheduler.stats.StatsRegistry; 23 | import io.rocketbase.extension.LogRepository; 24 | import io.rocketbase.extension.boot.config.DbSchedulerLogProperties; 25 | import io.rocketbase.extension.jdbc.IdProvider; 26 | import io.rocketbase.extension.jdbc.JdbcLogRepository; 27 | import io.rocketbase.extension.jdbc.Snowflake; 28 | import io.rocketbase.extension.stats.LogStatsPlainRegistry; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | import org.springframework.boot.autoconfigure.AutoConfigurationPackage; 32 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 33 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 34 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 35 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 36 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 37 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 38 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 39 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 40 | import org.springframework.context.annotation.Bean; 41 | import org.springframework.context.annotation.Configuration; 42 | import org.springframework.core.ConfigurableObjectInputStream; 43 | 44 | import javax.sql.DataSource; 45 | import java.io.*; 46 | import java.util.Objects; 47 | 48 | @Configuration 49 | @EnableConfigurationProperties(DbSchedulerLogProperties.class) 50 | @AutoConfigurationPackage 51 | @AutoConfigureAfter({ 52 | DataSourceAutoConfiguration.class, 53 | }) 54 | @AutoConfigureBefore({ 55 | DbSchedulerMetricsAutoConfiguration.class, 56 | }) 57 | @ConditionalOnBean(DataSource.class) 58 | @ConditionalOnProperty(value = "db-scheduler-log.enabled", matchIfMissing = true) 59 | public class DbSchedulerLogAutoConfiguration { 60 | private static final Logger log = LoggerFactory.getLogger(DbSchedulerLogAutoConfiguration.class); 61 | private final DbSchedulerLogProperties config; 62 | private final DataSource existingDataSource; 63 | 64 | public DbSchedulerLogAutoConfiguration(DbSchedulerLogProperties dbSchedulerLogProperties, 65 | DataSource dataSource) { 66 | this.config = Objects.requireNonNull(dbSchedulerLogProperties, "Can't configure db-scheduler-log without required configuration"); 67 | this.existingDataSource = Objects.requireNonNull(dataSource, "An existing javax.sql.DataSource is required"); 68 | } 69 | 70 | @ConditionalOnMissingBean(LogRepository.class) 71 | @Bean 72 | LogRepository logRepository(DbSchedulerCustomizer customizer, IdProvider idProvider) { 73 | log.debug("Missing LogRepository bean in context, creating a JdbcLogRepository"); 74 | return new JdbcLogRepository(existingDataSource, customizer.serializer().orElse(SPRING_JAVA_SERIALIZER), config.getTableName(), idProvider); 75 | } 76 | 77 | @ConditionalOnMissingBean(IdProvider.class) 78 | @Bean 79 | IdProvider idProvider() { 80 | log.debug("Missing IdProvider bean in context, creating a Snowflake"); 81 | return new Snowflake(); 82 | } 83 | 84 | 85 | @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry") 86 | @ConditionalOnMissingBean(StatsRegistry.class) 87 | @Bean 88 | StatsRegistry plainLogStatsRegistry(LogRepository logRepository) { 89 | log.debug("No Spring Boot Actuator / Micrometer has been detected. Will use: {} for StatsRegistry", logRepository.getClass().getName()); 90 | return new LogStatsPlainRegistry(logRepository); 91 | } 92 | 93 | /** 94 | * {@link Serializer} compatible with Spring Boot Devtools. 95 | * 96 | * @see 98 | * Devtools known limitations 99 | */ 100 | private static final Serializer SPRING_JAVA_SERIALIZER = new Serializer() { 101 | 102 | public byte[] serialize(Object data) { 103 | if (data == null) 104 | return null; 105 | try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); 106 | ObjectOutput out = new ObjectOutputStream(bos)) { 107 | out.writeObject(data); 108 | return bos.toByteArray(); 109 | } catch (Exception e) { 110 | throw new SerializationException("Failed to serialize object", e); 111 | } 112 | } 113 | 114 | public T deserialize(Class clazz, byte[] serializedData) { 115 | if (serializedData == null) 116 | return null; 117 | try (ByteArrayInputStream bis = new ByteArrayInputStream(serializedData); 118 | ObjectInput in = new ConfigurableObjectInputStream(bis, Thread.currentThread().getContextClassLoader())) { 119 | return clazz.cast(in.readObject()); 120 | } catch (Exception e) { 121 | throw new SerializationException("Failed to deserialize object", e); 122 | } 123 | } 124 | }; 125 | 126 | } 127 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/java/io/rocketbase/extension/boot/autoconfigure/DbSchedulerLogMetricAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.boot.autoconfigure; 17 | 18 | import com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerMetricsAutoConfiguration; 19 | import com.github.kagkarlsson.scheduler.stats.StatsRegistry; 20 | import com.github.kagkarlsson.scheduler.task.Task; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.rocketbase.extension.LogRepository; 23 | import io.rocketbase.extension.stats.LogStatsMicrometerRegistry; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; 27 | import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; 28 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 29 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 30 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 32 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 33 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 34 | import org.springframework.context.annotation.Bean; 35 | import org.springframework.context.annotation.Configuration; 36 | 37 | import java.util.List; 38 | 39 | @Configuration 40 | @ConditionalOnClass({ 41 | MetricsAutoConfiguration.class, 42 | CompositeMeterRegistryAutoConfiguration.class, 43 | }) 44 | @AutoConfigureAfter({ 45 | MetricsAutoConfiguration.class, 46 | CompositeMeterRegistryAutoConfiguration.class, 47 | DbSchedulerLogAutoConfiguration.class 48 | }) 49 | @AutoConfigureBefore(DbSchedulerMetricsAutoConfiguration.class) 50 | @ConditionalOnProperty(value = "db-scheduler-log.enabled", matchIfMissing = true) 51 | public class DbSchedulerLogMetricAutoConfiguration { 52 | private static final Logger log = LoggerFactory.getLogger(DbSchedulerLogMetricAutoConfiguration.class); 53 | private final List> configuredTasks; 54 | 55 | public DbSchedulerLogMetricAutoConfiguration(List> configuredTasks) { 56 | this.configuredTasks = configuredTasks; 57 | } 58 | 59 | @ConditionalOnClass(MeterRegistry.class) 60 | @ConditionalOnBean(MeterRegistry.class) 61 | @ConditionalOnMissingBean(StatsRegistry.class) 62 | @Bean 63 | StatsRegistry micrometerLogStatsRegistry(MeterRegistry registry, LogRepository logRepository) { 64 | log.debug("Spring Boot Actuator and Micrometer detected. Will use: {} for StatsRegistry", registry.getClass().getName()); 65 | return new LogStatsMicrometerRegistry(registry, configuredTasks, logRepository); 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/java/io/rocketbase/extension/boot/config/DbSchedulerLogProperties.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.boot.config; 17 | 18 | import io.rocketbase.extension.jdbc.JdbcLogRepository; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | @ConfigurationProperties("db-scheduler-log") 22 | public class DbSchedulerLogProperties { 23 | /** 24 | * Whether to enable auto configuration of the db-scheduler-log. 25 | */ 26 | private boolean enabled = true; 27 | 28 | /** 29 | *

Name of the table used to log executions. Must match the database. Change name in the 30 | * table definitions accordingly when creating or modifying the table. 31 | */ 32 | private String tableName = JdbcLogRepository.DEFAULT_TABLE_NAME; 33 | 34 | public boolean isEnabled() { 35 | return enabled; 36 | } 37 | 38 | public void setEnabled(final boolean enabled) { 39 | this.enabled = enabled; 40 | } 41 | 42 | public String getTableName() { 43 | return tableName; 44 | } 45 | 46 | public void setTableName(final String tableName) { 47 | this.tableName = tableName; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/java/io/rocketbase/extension/boot/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Spring Boot related autoconfiguration and glue code. 18 | */ 19 | package io.rocketbase.extension.boot; 20 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogAutoConfiguration,\ 3 | io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogMetricAutoConfiguration 4 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogAutoConfiguration 2 | io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogMetricAutoConfiguration 3 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/test/java/io/rocketbase/extension/boot/DbSchedulerLogAutoConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.boot; 2 | 3 | import com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerActuatorAutoConfiguration; 4 | import com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerAutoConfiguration; 5 | import com.github.kagkarlsson.scheduler.boot.autoconfigure.DbSchedulerMetricsAutoConfiguration; 6 | import com.github.kagkarlsson.scheduler.stats.StatsRegistry; 7 | import io.rocketbase.extension.LogRepository; 8 | import io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogAutoConfiguration; 9 | import io.rocketbase.extension.boot.autoconfigure.DbSchedulerLogMetricAutoConfiguration; 10 | import io.rocketbase.extension.jdbc.IdProvider; 11 | import io.rocketbase.extension.stats.LogStatsMicrometerRegistry; 12 | import org.junit.jupiter.api.Test; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; 16 | import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; 17 | import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; 18 | import org.springframework.boot.autoconfigure.AutoConfigurations; 19 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 20 | import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; 21 | import org.springframework.boot.test.context.assertj.AssertableApplicationContext; 22 | import org.springframework.boot.test.context.runner.ApplicationContextRunner; 23 | 24 | import javax.sql.DataSource; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | 29 | public class DbSchedulerLogAutoConfigurationTest { 30 | private static final Logger log = LoggerFactory.getLogger(DbSchedulerLogAutoConfigurationTest.class); 31 | private final ApplicationContextRunner ctxRunner; 32 | 33 | public DbSchedulerLogAutoConfigurationTest() { 34 | ctxRunner = new ApplicationContextRunner() 35 | .withPropertyValues( 36 | "spring.application.name=db-scheduler-boot-starter-test", 37 | "spring.profiles.active=integration-test" 38 | ).withConfiguration(AutoConfigurations.of( 39 | DataSourceAutoConfiguration.class, 40 | SqlInitializationAutoConfiguration.class, 41 | MetricsAutoConfiguration.class, 42 | CompositeMeterRegistryAutoConfiguration.class, 43 | HealthContributorAutoConfiguration.class, 44 | DbSchedulerMetricsAutoConfiguration.class, 45 | DbSchedulerActuatorAutoConfiguration.class, 46 | DbSchedulerAutoConfiguration.class, 47 | DbSchedulerLogMetricAutoConfiguration.class, 48 | DbSchedulerLogAutoConfiguration.class 49 | )); 50 | } 51 | 52 | @Test 53 | public void it_should_initialize() { 54 | ctxRunner.run((AssertableApplicationContext ctx) -> { 55 | assertThat(ctx).hasSingleBean(DataSource.class); 56 | assertThat(ctx).hasSingleBean(LogRepository.class); 57 | assertThat(ctx).hasSingleBean(IdProvider.class); 58 | assertThat(ctx).hasSingleBean(StatsRegistry.class); 59 | 60 | assertThat(ctx.getBean(StatsRegistry.class)).isInstanceOf(LogStatsMicrometerRegistry.class); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /db-scheduler-log-boot-starter/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists scheduled_tasks ( 2 | task_name varchar(100), 3 | task_instance varchar(100), 4 | task_data blob, 5 | execution_time TIMESTAMP WITH TIME ZONE, 6 | picked BIT, 7 | picked_by varchar(50), 8 | last_success TIMESTAMP WITH TIME ZONE, 9 | last_failure TIMESTAMP WITH TIME ZONE, 10 | consecutive_failures INT, 11 | last_heartbeat TIMESTAMP WITH TIME ZONE, 12 | version BIGINT, 13 | PRIMARY KEY (task_name, task_instance) 14 | ); 15 | -------------------------------------------------------------------------------- /db-scheduler-log/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | io.rocketbase.extension 6 | db-scheduler-log-parent 7 | master-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | db-scheduler-log 12 | db-scheduler-log: Core 13 | 14 | 15 | ${project.parent.basedir}/.license 16 | 17 | 9.2.0 18 | 1.3 19 | 4.0.3 20 | 2.7.1 21 | 1.6 22 | 3.10.1 23 | 1.12.16 24 | 0.3 25 | 1.0.1 26 | 42.5.1 27 | 1.7.36 28 | compatibility 29 | 30 | 31 | 32 | 33 | com.github.kagkarlsson 34 | micro-jdbc 35 | ${micro-jdbc.version} 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | ${slf4j.version} 41 | 42 | 43 | org.slf4j 44 | slf4j-api 45 | ${slf4j.version} 46 | 47 | 48 | com.github.kagkarlsson 49 | db-scheduler 50 | ${db-scheduler.version} 51 | 52 | 53 | 54 | io.micrometer 55 | micrometer-core 56 | true 57 | 58 | 59 | 60 | 61 | 62 | ch.qos.logback 63 | logback-classic 64 | test 65 | 66 | 67 | ch.qos.logback 68 | logback-core 69 | test 70 | 71 | 72 | org.junit.jupiter 73 | junit-jupiter 74 | test 75 | 76 | 77 | org.junit.jupiter 78 | junit-jupiter-api 79 | test 80 | 81 | 82 | org.junit.jupiter 83 | junit-jupiter-params 84 | test 85 | 86 | 87 | org.hamcrest 88 | hamcrest-core 89 | ${hamcrest.version} 90 | test 91 | 92 | 93 | org.hamcrest 94 | hamcrest-library 95 | ${hamcrest.version} 96 | test 97 | 98 | 99 | co.unruly 100 | java-8-matchers 101 | ${java8-matchers.version} 102 | test 103 | 104 | 105 | nl.jqno.equalsverifier 106 | equalsverifier 107 | ${equals-verifier.version} 108 | test 109 | 110 | 111 | byte-buddy 112 | net.bytebuddy 113 | 114 | 115 | 116 | 117 | 118 | org.apache.commons 119 | commons-lang3 120 | test 121 | 122 | 123 | org.hsqldb 124 | hsqldb 125 | ${hsqldb.version} 126 | test 127 | 128 | 129 | org.postgresql 130 | postgresql 131 | ${postgresql.version} 132 | test 133 | 134 | 135 | 136 | com.opentable.components 137 | otj-pg-embedded 138 | ${otj-pg-embedded.version} 139 | test 140 | 141 | 142 | com.zaxxer 143 | HikariCP 144 | ${hikaricp.version} 145 | test 146 | 147 | 148 | 149 | org.testcontainers 150 | postgresql 151 | test 152 | 153 | 154 | org.testcontainers 155 | mysql 156 | test 157 | 158 | 159 | org.testcontainers 160 | mssqlserver 161 | test 162 | 163 | 164 | org.testcontainers 165 | oracle-xe 166 | test 167 | 168 | 169 | org.testcontainers 170 | junit-jupiter 171 | test 172 | 173 | 174 | mysql 175 | mysql-connector-java 176 | 8.0.28 177 | test 178 | 179 | 180 | com.microsoft.sqlserver 181 | mssql-jdbc 182 | 8.2.1.jre8 183 | test 184 | 185 | 186 | com.oracle.database.jdbc 187 | ojdbc8 188 | 21.5.0.0 189 | test 190 | 191 | 192 | 193 | org.mockito 194 | mockito-core 195 | 4.8.0 196 | test 197 | 198 | 199 | byte-buddy 200 | net.bytebuddy 201 | 202 | 203 | byte-buddy-agent 204 | net.bytebuddy 205 | 206 | 207 | 208 | 209 | org.mockito 210 | mockito-junit-jupiter 211 | test 212 | 213 | 214 | net.bytebuddy 215 | byte-buddy 216 | ${bytebuddy.version} 217 | test 218 | 219 | 220 | net.bytebuddy 221 | byte-buddy-agent 222 | ${bytebuddy.version} 223 | test 224 | 225 | 226 | com.google.guava 227 | guava 228 | 31.1-jre 229 | test 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | maven-surefire-plugin 238 | 239 | ${test.excludedTags} 240 | 241 | 242 | 243 | maven-dependency-plugin 244 | 245 | 246 | com.mycila 247 | license-maven-plugin 248 | 249 | 250 | maven-shade-plugin 251 | 252 | 253 | 254 | 255 | 256 | 257 | compatibility 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/ExecutionLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension; 17 | 18 | import com.github.kagkarlsson.scheduler.task.ExecutionComplete; 19 | import com.github.kagkarlsson.scheduler.task.TaskInstance; 20 | 21 | import java.time.Instant; 22 | import java.util.Objects; 23 | 24 | public final class ExecutionLog { 25 | 26 | public final TaskInstance taskInstance; 27 | public final String pickedBy; 28 | public final Instant timeStarted; 29 | public final Instant timeFinished; 30 | public final boolean succeeded; 31 | public final Throwable cause; 32 | 33 | public ExecutionLog(ExecutionComplete exec) { 34 | taskInstance = exec.getExecution().taskInstance; 35 | pickedBy = exec.getExecution().pickedBy; 36 | timeStarted = exec.getTimeDone().minus(exec.getDuration()); 37 | timeFinished = exec.getTimeDone(); 38 | succeeded = ExecutionComplete.Result.OK.equals(exec.getResult()); 39 | cause = exec.getCause().orElse(null); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | ExecutionLog execLog = (ExecutionLog) o; 47 | return Objects.equals(timeStarted, execLog.timeStarted) && 48 | Objects.equals(timeFinished, execLog.timeFinished) && 49 | Objects.equals(taskInstance, execLog.taskInstance); 50 | } 51 | 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(timeStarted, timeFinished, taskInstance); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ExecutionLog: " + 61 | "task=" + taskInstance.getTaskName() + 62 | ", id=" + taskInstance.getId() + 63 | ", pickedBy=" + pickedBy + 64 | ", timeStarted=" + timeStarted + 65 | ", timeFinished=" + timeFinished + 66 | ", succeeded=" + succeeded; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/LogRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension; 17 | 18 | public interface LogRepository { 19 | boolean createIfNotExists(ExecutionLog log); 20 | } 21 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/jdbc/IdProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.jdbc; 17 | 18 | public interface IdProvider { 19 | 20 | long nextId(); 21 | } 22 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/jdbc/JdbcLogRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.jdbc; 17 | 18 | import com.github.kagkarlsson.jdbc.JdbcRunner; 19 | import com.github.kagkarlsson.jdbc.SQLRuntimeException; 20 | import com.github.kagkarlsson.scheduler.jdbc.AutodetectJdbcCustomization; 21 | import com.github.kagkarlsson.scheduler.jdbc.JdbcCustomization; 22 | import com.github.kagkarlsson.scheduler.serializer.Serializer; 23 | import io.rocketbase.extension.ExecutionLog; 24 | import io.rocketbase.extension.LogRepository; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import javax.sql.DataSource; 29 | import java.io.NotSerializableException; 30 | import java.io.PrintWriter; 31 | import java.io.StringWriter; 32 | import java.sql.PreparedStatement; 33 | import java.time.Duration; 34 | 35 | public class JdbcLogRepository implements LogRepository { 36 | 37 | public static final String DEFAULT_TABLE_NAME = "scheduled_execution_logs"; 38 | 39 | private static final Logger LOG = LoggerFactory.getLogger(JdbcLogRepository.class); 40 | private final JdbcRunner jdbcRunner; 41 | private final Serializer serializer; 42 | private final String tableName; 43 | private final JdbcCustomization jdbcCustomization; 44 | private final IdProvider idProvider; 45 | 46 | 47 | public JdbcLogRepository(DataSource dataSource, Serializer serializer, String tableName, IdProvider idProvider) { 48 | this(tableName, new JdbcRunner(dataSource, true), serializer, new AutodetectJdbcCustomization(dataSource), idProvider); 49 | } 50 | 51 | public JdbcLogRepository(String tableName, JdbcRunner jdbcRunner, Serializer serializer, JdbcCustomization jdbcCustomization, IdProvider idProvider) { 52 | this.tableName = tableName; 53 | this.jdbcRunner = jdbcRunner; 54 | this.serializer = serializer; 55 | this.jdbcCustomization = jdbcCustomization; 56 | this.idProvider = idProvider; 57 | } 58 | 59 | @Override 60 | @SuppressWarnings({"unchecked"}) 61 | public boolean createIfNotExists(ExecutionLog log) { 62 | try { 63 | jdbcRunner.execute( 64 | "insert into " + tableName + "(id, task_name, task_instance, task_data, picked_by, time_started, time_finished, succeeded, duration_ms, exception_class, exception_message, exception_stacktrace) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 65 | (PreparedStatement p) -> { 66 | p.setLong(1, idProvider.nextId()); 67 | p.setString(2, log.taskInstance.getTaskName()); 68 | p.setString(3, log.taskInstance.getId()); 69 | p.setObject(4, serialize(log.taskInstance.getData())); 70 | p.setString(5, log.pickedBy); 71 | jdbcCustomization.setInstant(p, 6, log.timeStarted); 72 | jdbcCustomization.setInstant(p, 7, log.timeFinished); 73 | p.setBoolean(8, log.succeeded); 74 | p.setLong(9, Duration.between(log.timeStarted, log.timeFinished).toMillis()); 75 | p.setString(10, log.cause != null ? log.cause.getClass().getName() : null); 76 | p.setString(11, log.cause != null ? log.cause.getMessage() : null); 77 | p.setString(12, getStacktrace(log.cause)); 78 | }); 79 | return true; 80 | } catch (SQLRuntimeException e) { 81 | LOG.error("Exception when inserting execution-log. Assuming it to be a constraint violation: {}", e.getMessage()); 82 | return false; 83 | } 84 | } 85 | 86 | protected String getStacktrace(Throwable cause) { 87 | if (cause == null) { 88 | return null; 89 | } 90 | StringWriter writer = new StringWriter(); 91 | PrintWriter out = new PrintWriter(writer); 92 | cause.printStackTrace(out); 93 | return writer.toString(); 94 | } 95 | 96 | protected byte[] serialize(Object value) { 97 | if (serializer == null || value == null) { 98 | return null; 99 | } 100 | try { 101 | return serializer.serialize(value); 102 | } catch (Exception e) { 103 | if (e instanceof NotSerializableException) { 104 | LOG.warn("object is not serializable - you need to add Serializable"); 105 | } else { 106 | LOG.error("serialization failed for {} -> {}", value.getClass(), e.getMessage()); 107 | } 108 | return null; 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/jdbc/Snowflake.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.jdbc; 17 | 18 | import com.github.kagkarlsson.scheduler.SchedulerName; 19 | 20 | import java.security.SecureRandom; 21 | import java.time.Instant; 22 | 23 | /** 24 | * @author callicoder: https://github.com/callicoder/java-snowflake 25 | *

26 | * Distributed Sequence Generator. 27 | * Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010 28 | *

29 | * This class should be used as a Singleton. 30 | * Make sure that you create and reuse a Single instance of Snowflake per node in your distributed system cluster. 31 | */ 32 | public final class Snowflake implements IdProvider { 33 | 34 | private static Snowflake INSTANCE; 35 | 36 | public static final int UNUSED_BITS = 1; // Sign bit, Unused (always set to 0) 37 | public static final int EPOCH_BITS = 43; 38 | public static final int NODE_ID_BITS = 10; 39 | public static final int SEQUENCE_BITS = 10; 40 | 41 | public static final long maxNodeId = (1L << NODE_ID_BITS) - 1; 42 | public static final long maxSequence = (1L << SEQUENCE_BITS) - 1; 43 | 44 | // Custom Epoch (January 1, 2020 Midnight UTC = 2020-01-01T00:00:00Z) 45 | public static final long DEFAULT_CUSTOM_EPOCH = 1577836800000L; 46 | 47 | private final long nodeId; 48 | private final long customEpoch; 49 | 50 | private volatile long lastTimestamp = -1L; 51 | private volatile long sequence = 0L; 52 | 53 | // Create Snowflake with a nodeId and custom epoch 54 | public Snowflake(long nodeId, long customEpoch) { 55 | if (nodeId < 0 || nodeId > maxNodeId) { 56 | throw new IllegalArgumentException(String.format("NodeId must be between %d and %d", 0, maxNodeId)); 57 | } 58 | this.nodeId = nodeId; 59 | this.customEpoch = customEpoch; 60 | } 61 | 62 | // Create Snowflake with a nodeId 63 | public Snowflake(long nodeId) { 64 | this(nodeId, DEFAULT_CUSTOM_EPOCH); 65 | } 66 | 67 | // Let Snowflake generate a nodeId 68 | public Snowflake() { 69 | this.nodeId = createNodeId(); 70 | this.customEpoch = DEFAULT_CUSTOM_EPOCH; 71 | } 72 | 73 | public static Snowflake getInstance() { 74 | if (INSTANCE == null) { 75 | INSTANCE = new Snowflake(); 76 | } 77 | return INSTANCE; 78 | } 79 | 80 | public synchronized long nextId() { 81 | long currentTimestamp = timestamp(); 82 | 83 | if (currentTimestamp < lastTimestamp) { 84 | throw new IllegalStateException("Invalid System Clock!"); 85 | } 86 | 87 | if (currentTimestamp == lastTimestamp) { 88 | sequence = (sequence + 1) & maxSequence; 89 | if (sequence == 0) { 90 | // Sequence Exhausted, wait till next millisecond. 91 | currentTimestamp = waitNextMillis(currentTimestamp); 92 | } 93 | } else { 94 | // reset sequence to start with zero for the next millisecond 95 | sequence = 0; 96 | } 97 | 98 | lastTimestamp = currentTimestamp; 99 | 100 | long id = currentTimestamp << (NODE_ID_BITS + SEQUENCE_BITS) 101 | | (nodeId << SEQUENCE_BITS) 102 | | sequence; 103 | 104 | return id; 105 | } 106 | 107 | 108 | // Get current timestamp in milliseconds, adjust for the custom epoch. 109 | private long timestamp() { 110 | return Instant.now().toEpochMilli() - customEpoch; 111 | } 112 | 113 | // Block and wait till next millisecond 114 | private long waitNextMillis(long currentTimestamp) { 115 | while (currentTimestamp == lastTimestamp) { 116 | currentTimestamp = timestamp(); 117 | } 118 | return currentTimestamp; 119 | } 120 | 121 | private long createNodeId() { 122 | long nodeId; 123 | try { 124 | nodeId = new SchedulerName.Hostname().hashCode(); 125 | } catch (Exception ex) { 126 | nodeId = (new SecureRandom().nextInt()); 127 | } 128 | nodeId = nodeId & maxNodeId; 129 | return nodeId; 130 | } 131 | 132 | public long[] parse(long id) { 133 | long maskNodeId = ((1L << NODE_ID_BITS) - 1) << SEQUENCE_BITS; 134 | long maskSequence = (1L << SEQUENCE_BITS) - 1; 135 | 136 | long timestamp = (id >> (NODE_ID_BITS + SEQUENCE_BITS)) + customEpoch; 137 | long nodeId = (id & maskNodeId) >> SEQUENCE_BITS; 138 | long sequence = id & maskSequence; 139 | 140 | return new long[]{timestamp, nodeId, sequence}; 141 | } 142 | 143 | @Override 144 | public String toString() { 145 | return "Snowflake Settings [EPOCH_BITS=" + EPOCH_BITS + ", NODE_ID_BITS=" + NODE_ID_BITS 146 | + ", SEQUENCE_BITS=" + SEQUENCE_BITS + ", CUSTOM_EPOCH=" + customEpoch 147 | + ", NodeId=" + nodeId + "]"; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/stats/LogStatsMicrometerRegistry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.stats; 17 | 18 | import com.github.kagkarlsson.scheduler.stats.MicrometerStatsRegistry; 19 | import com.github.kagkarlsson.scheduler.task.ExecutionComplete; 20 | import com.github.kagkarlsson.scheduler.task.Task; 21 | import io.micrometer.core.instrument.MeterRegistry; 22 | import io.rocketbase.extension.ExecutionLog; 23 | import io.rocketbase.extension.LogRepository; 24 | 25 | import java.util.List; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Executors; 28 | 29 | public class LogStatsMicrometerRegistry extends MicrometerStatsRegistry { 30 | 31 | private final LogRepository logRepository; 32 | private ExecutorService executorService; 33 | 34 | public LogStatsMicrometerRegistry(MeterRegistry meterRegistry, List> expectedTasks, LogRepository logRepository) { 35 | super(meterRegistry, expectedTasks); 36 | this.logRepository = logRepository; 37 | this.executorService = Executors.newFixedThreadPool(5); 38 | } 39 | 40 | @Override 41 | public void registerSingleCompletedExecution(ExecutionComplete completeEvent) { 42 | super.registerSingleCompletedExecution(completeEvent); 43 | 44 | executorService.submit(() -> { 45 | logRepository.createIfNotExists(new ExecutionLog(completeEvent)); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /db-scheduler-log/src/main/java/io/rocketbase/extension/stats/LogStatsPlainRegistry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Marten Prieß 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.rocketbase.extension.stats; 17 | 18 | import com.github.kagkarlsson.scheduler.stats.StatsRegistry; 19 | import com.github.kagkarlsson.scheduler.task.ExecutionComplete; 20 | import io.rocketbase.extension.ExecutionLog; 21 | import io.rocketbase.extension.LogRepository; 22 | 23 | import java.util.concurrent.ExecutorService; 24 | import java.util.concurrent.Executors; 25 | 26 | public class LogStatsPlainRegistry implements StatsRegistry { 27 | 28 | private final LogRepository logRepository; 29 | private ExecutorService executorService; 30 | 31 | public LogStatsPlainRegistry(LogRepository logRepository) { 32 | this.logRepository = logRepository; 33 | this.executorService = Executors.newFixedThreadPool(5); 34 | } 35 | 36 | @Override 37 | public void register(SchedulerStatsEvent e) { 38 | } 39 | 40 | @Override 41 | public void register(CandidateStatsEvent e) { 42 | } 43 | 44 | @Override 45 | public void register(ExecutionStatsEvent e) { 46 | } 47 | 48 | @Override 49 | public void registerSingleCompletedExecution(ExecutionComplete completeEvent) { 50 | executorService.submit(() -> { 51 | logRepository.createIfNotExists(new ExecutionLog(completeEvent)); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/CustomTableNameTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension; 2 | 3 | import com.github.kagkarlsson.jdbc.JdbcRunner; 4 | import com.github.kagkarlsson.jdbc.RowMapper; 5 | import com.github.kagkarlsson.scheduler.serializer.JavaSerializer; 6 | import com.github.kagkarlsson.scheduler.task.Execution; 7 | import com.github.kagkarlsson.scheduler.task.ExecutionComplete; 8 | import com.github.kagkarlsson.scheduler.task.TaskInstance; 9 | import io.rocketbase.extension.compatibility.CompatibilityTest; 10 | import io.rocketbase.extension.jdbc.JdbcLogRepository; 11 | import io.rocketbase.extension.jdbc.Snowflake; 12 | import org.junit.jupiter.api.AfterEach; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.extension.RegisterExtension; 16 | 17 | import java.time.Instant; 18 | 19 | import static com.github.kagkarlsson.jdbc.PreparedStatementSetter.NOOP; 20 | import static java.time.temporal.ChronoUnit.MILLIS; 21 | 22 | public class CustomTableNameTest { 23 | 24 | private static final String CUSTOM_TABLENAME = "custom_tablename_logs"; 25 | 26 | @RegisterExtension 27 | public EmbeddedPostgresqlExtension DB = new EmbeddedPostgresqlExtension(); 28 | 29 | private JdbcLogRepository logRepository; 30 | 31 | @BeforeEach 32 | public void setUp() { 33 | logRepository = new JdbcLogRepository(DB.getDataSource(), new JavaSerializer(), CUSTOM_TABLENAME, new Snowflake()); 34 | 35 | DbUtils.runSqlResource("postgresql_custom_tablename.sql").accept(DB.getDataSource()); 36 | } 37 | 38 | @Test 39 | public void can_customize_table_name() { 40 | Instant now = Instant.now().truncatedTo(MILLIS); 41 | final Execution execution = new Execution(now, new TaskInstance("taskName", "213456", new CompatibilityTest.SampleData("sample", 1234L)), true, "pickedBy", now, now, 0, now, 1); 42 | final ExecutionComplete complete = ExecutionComplete.success(execution, now.minusMillis(10_000), now); 43 | 44 | 45 | logRepository.createIfNotExists(new ExecutionLog(complete)); 46 | 47 | JdbcRunner jdbcRunner = new JdbcRunner(DB.getDataSource()); 48 | jdbcRunner.query("SELECT count(1) AS number_of_tasks FROM " + CUSTOM_TABLENAME, NOOP, (RowMapper) rs -> rs.getInt("number_of_tasks")); 49 | 50 | } 51 | 52 | @AfterEach 53 | public void tearDown() { 54 | new JdbcRunner(DB.getDataSource()).execute("DROP TABLE " + CUSTOM_TABLENAME, NOOP); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/DbUtils.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension; 2 | 3 | import com.github.kagkarlsson.jdbc.JdbcRunner; 4 | import com.github.kagkarlsson.jdbc.Mappers; 5 | import com.github.kagkarlsson.jdbc.PreparedStatementSetter; 6 | import com.google.common.io.CharStreams; 7 | 8 | import javax.sql.DataSource; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.function.Consumer; 12 | 13 | import static com.github.kagkarlsson.jdbc.PreparedStatementSetter.NOOP; 14 | import static io.rocketbase.extension.jdbc.JdbcLogRepository.DEFAULT_TABLE_NAME; 15 | 16 | public class DbUtils { 17 | 18 | public static void clearTables(DataSource dataSource) { 19 | new JdbcRunner(dataSource, true).execute("delete from " + DEFAULT_TABLE_NAME, NOOP); 20 | } 21 | 22 | public static Consumer runSqlResource(String resource) { 23 | return dataSource -> { 24 | 25 | final JdbcRunner jdbcRunner = new JdbcRunner(dataSource); 26 | try { 27 | final String statements = CharStreams.toString(new InputStreamReader(DbUtils.class.getResourceAsStream(resource))); 28 | jdbcRunner.execute(statements, NOOP); 29 | } catch (IOException e) { 30 | throw new RuntimeException(e); 31 | } 32 | }; 33 | } 34 | 35 | public static int countExecutions(DataSource dataSource) { 36 | return new JdbcRunner(dataSource).query("select count(*) from " + DEFAULT_TABLE_NAME, 37 | PreparedStatementSetter.NOOP, Mappers.SINGLE_INT); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/EmbeddedPostgresqlExtension.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension; 2 | 3 | import com.github.kagkarlsson.jdbc.JdbcRunner; 4 | import com.github.kagkarlsson.jdbc.Mappers; 5 | import com.opentable.db.postgres.embedded.EmbeddedPostgres; 6 | import com.zaxxer.hikari.HikariConfig; 7 | import com.zaxxer.hikari.HikariDataSource; 8 | import org.junit.jupiter.api.extension.AfterEachCallback; 9 | import org.junit.jupiter.api.extension.ExtensionContext; 10 | 11 | import javax.sql.DataSource; 12 | import java.io.IOException; 13 | import java.util.function.Consumer; 14 | 15 | import static com.github.kagkarlsson.jdbc.PreparedStatementSetter.NOOP; 16 | 17 | public class EmbeddedPostgresqlExtension implements AfterEachCallback { 18 | 19 | private static EmbeddedPostgres embeddedPostgresql; 20 | private static DataSource dataSource; 21 | private final Consumer initializeSchema; 22 | private final Consumer cleanupAfter; 23 | private DataSource nonPooledDatasource; 24 | 25 | public EmbeddedPostgresqlExtension() { 26 | this(DbUtils.runSqlResource("/postgresql_tables.sql"), DbUtils::clearTables); 27 | } 28 | 29 | public EmbeddedPostgresqlExtension(Consumer initializeSchema, Consumer cleanupAfter) { 30 | this.initializeSchema = initializeSchema; 31 | this.cleanupAfter = cleanupAfter; 32 | try { 33 | synchronized (this) { 34 | 35 | if (embeddedPostgresql == null) { 36 | embeddedPostgresql = initPostgres(); 37 | 38 | HikariConfig config = new HikariConfig(); 39 | nonPooledDatasource = embeddedPostgresql.getDatabase("test", "test"); 40 | config.setDataSource(nonPooledDatasource); 41 | 42 | dataSource = new HikariDataSource(config); 43 | 44 | initializeSchema.accept(dataSource); 45 | } 46 | } 47 | } catch (IOException e) { 48 | throw new RuntimeException(e); 49 | } 50 | } 51 | 52 | public DataSource getDataSource() { 53 | return dataSource; 54 | } 55 | 56 | public DataSource getNonPooledDatasource() { 57 | return nonPooledDatasource; 58 | } 59 | 60 | private EmbeddedPostgres initPostgres() throws IOException { 61 | final EmbeddedPostgres newEmbeddedPostgresql = EmbeddedPostgres.builder().start(); 62 | 63 | final JdbcRunner postgresJdbc = new JdbcRunner(newEmbeddedPostgresql.getPostgresDatabase()); 64 | 65 | final Boolean databaseExists = postgresJdbc.query("SELECT 1 FROM pg_database WHERE datname = 'test'", NOOP, Mappers.NON_EMPTY_RESULTSET); 66 | if (!databaseExists) { 67 | postgresJdbc.execute("CREATE DATABASE test", NOOP); 68 | } 69 | 70 | final Boolean userExists = postgresJdbc.query("SELECT 1 FROM pg_catalog.pg_user WHERE usename = 'test'", NOOP, Mappers.NON_EMPTY_RESULTSET); 71 | if (!userExists) { 72 | postgresJdbc.execute("CREATE ROLE test LOGIN PASSWORD ''", NOOP); 73 | } 74 | 75 | postgresJdbc.execute("CREATE SCHEMA IF NOT EXISTS AUTHORIZATION test ", NOOP); 76 | 77 | return newEmbeddedPostgresql; 78 | } 79 | 80 | @Override 81 | public void afterEach(ExtensionContext extensionContext) throws Exception { 82 | cleanupAfter.accept(getDataSource()); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/compatibility/CompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.compatibility; 2 | 3 | import com.github.kagkarlsson.scheduler.serializer.JavaSerializer; 4 | import com.github.kagkarlsson.scheduler.task.Execution; 5 | import com.github.kagkarlsson.scheduler.task.ExecutionComplete; 6 | import com.github.kagkarlsson.scheduler.task.TaskInstance; 7 | import io.rocketbase.extension.DbUtils; 8 | import io.rocketbase.extension.ExecutionLog; 9 | import io.rocketbase.extension.jdbc.JdbcLogRepository; 10 | import io.rocketbase.extension.jdbc.Snowflake; 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import javax.sql.DataSource; 15 | import java.io.Serializable; 16 | import java.time.Duration; 17 | import java.time.Instant; 18 | 19 | import static java.time.temporal.ChronoUnit.MILLIS; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; 22 | 23 | 24 | @SuppressWarnings("ConstantConditions") 25 | public abstract class CompatibilityTest { 26 | public abstract DataSource getDataSource(); 27 | 28 | @AfterEach 29 | public void clearTables() { 30 | assertTimeoutPreemptively(Duration.ofSeconds(20), () -> 31 | DbUtils.clearTables(getDataSource()) 32 | ); 33 | } 34 | 35 | @Test 36 | public void test_jdbc_repository_compatibility_set_data() { 37 | DataSource dataSource = getDataSource(); 38 | final JdbcLogRepository jdbcLogRepository = new JdbcLogRepository(dataSource, new JavaSerializer(), JdbcLogRepository.DEFAULT_TABLE_NAME, new Snowflake()); 39 | 40 | Instant now = Instant.now().truncatedTo(MILLIS); 41 | final Execution execution = new Execution(now, new TaskInstance("taskName", "213456", new SampleData("sample", 1234L)), true, "pickedBy", now, now, 0, now, 1); 42 | final ExecutionComplete complete = ExecutionComplete.success(execution, now.minusMillis(10_000), now); 43 | 44 | 45 | boolean result = jdbcLogRepository.createIfNotExists(new ExecutionLog(complete)); 46 | 47 | assertEquals(result, true); 48 | } 49 | 50 | public static class SampleData implements Serializable { 51 | public final String name; 52 | public final Long value; 53 | 54 | public SampleData(String name, Long value) { 55 | this.name = name; 56 | this.value = value; 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/compatibility/MssqlCompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.compatibility; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.util.DriverDataSource; 6 | import io.rocketbase.extension.DbUtils; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Disabled; 9 | import org.junit.jupiter.api.Tag; 10 | import org.testcontainers.containers.MSSQLServerContainer; 11 | import org.testcontainers.junit.jupiter.Container; 12 | import org.testcontainers.junit.jupiter.Testcontainers; 13 | 14 | import javax.sql.DataSource; 15 | import java.util.Properties; 16 | 17 | @SuppressWarnings("rawtypes") 18 | @Tag("compatibility") 19 | @Testcontainers 20 | @Disabled 21 | public class MssqlCompatibilityTest extends CompatibilityTest { 22 | 23 | @Container 24 | private static final MSSQLServerContainer MSSQL = new MSSQLServerContainer(); 25 | private static HikariDataSource pooledDatasource; 26 | 27 | 28 | @BeforeAll 29 | static void initSchema() { 30 | final DriverDataSource datasource = new DriverDataSource(MSSQL.getJdbcUrl(), "com.microsoft.sqlserver.jdbc.SQLServerDriver", 31 | new Properties(), MSSQL.getUsername(), MSSQL.getPassword()); 32 | 33 | final HikariConfig hikariConfig = new HikariConfig(); 34 | hikariConfig.setDataSource(datasource); 35 | pooledDatasource = new HikariDataSource(hikariConfig); 36 | 37 | // init schema 38 | DbUtils.runSqlResource("/mssql_tables.sql").accept(pooledDatasource); 39 | } 40 | 41 | @Override 42 | public DataSource getDataSource() { 43 | return pooledDatasource; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/compatibility/MysqlCompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.compatibility; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.util.DriverDataSource; 6 | import io.rocketbase.extension.DbUtils; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Disabled; 9 | import org.junit.jupiter.api.Tag; 10 | import org.testcontainers.containers.MySQLContainer; 11 | import org.testcontainers.junit.jupiter.Container; 12 | import org.testcontainers.junit.jupiter.Testcontainers; 13 | 14 | import javax.sql.DataSource; 15 | import java.util.Properties; 16 | 17 | @Tag("compatibility") 18 | @Testcontainers 19 | @Disabled 20 | public class MysqlCompatibilityTest extends CompatibilityTest { 21 | 22 | @Container 23 | private static final MySQLContainer MY_SQL = new MySQLContainer(); 24 | private static HikariDataSource pooledDatasource; 25 | 26 | @BeforeAll 27 | static void initSchema() { 28 | final DriverDataSource datasource = new DriverDataSource(MY_SQL.getJdbcUrl(), "com.mysql.cj.jdbc.Driver", new Properties(), MY_SQL.getUsername(), MY_SQL.getPassword()); 29 | 30 | final HikariConfig hikariConfig = new HikariConfig(); 31 | hikariConfig.setDataSource(datasource); 32 | pooledDatasource = new HikariDataSource(hikariConfig); 33 | 34 | // init schema 35 | DbUtils.runSqlResource("/mysql_tables.sql").accept(pooledDatasource); 36 | } 37 | 38 | @Override 39 | public DataSource getDataSource() { 40 | return pooledDatasource; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/compatibility/NoAutoCommitPostgresqlCompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.compatibility; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.util.DriverDataSource; 6 | import io.rocketbase.extension.DbUtils; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Tag; 9 | import org.testcontainers.containers.PostgreSQLContainer; 10 | import org.testcontainers.junit.jupiter.Container; 11 | import org.testcontainers.junit.jupiter.Testcontainers; 12 | 13 | import javax.sql.DataSource; 14 | import java.util.Properties; 15 | 16 | @Tag("compatibility") 17 | @Testcontainers 18 | public class NoAutoCommitPostgresqlCompatibilityTest extends CompatibilityTest { 19 | 20 | @Container 21 | private static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer(); 22 | private static HikariDataSource pooledDatasource; 23 | 24 | @BeforeAll 25 | static void initSchema() { 26 | final DriverDataSource datasource = new DriverDataSource(POSTGRES.getJdbcUrl(), "org.postgresql.Driver", 27 | new Properties(), POSTGRES.getUsername(), POSTGRES.getPassword()); 28 | 29 | // init schema 30 | DbUtils.runSqlResource("/postgresql_tables.sql").accept(datasource); 31 | 32 | 33 | // Setup non auto-committing datasource 34 | final HikariConfig hikariConfig = new HikariConfig(); 35 | hikariConfig.setDataSource(datasource); 36 | hikariConfig.setAutoCommit(false); 37 | pooledDatasource = new HikariDataSource(hikariConfig); 38 | 39 | } 40 | 41 | @Override 42 | public DataSource getDataSource() { 43 | return pooledDatasource; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/java/io/rocketbase/extension/compatibility/Oracle11gCompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package io.rocketbase.extension.compatibility; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.util.DriverDataSource; 6 | import io.rocketbase.extension.DbUtils; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Disabled; 10 | import org.junit.jupiter.api.Tag; 11 | import org.testcontainers.containers.OracleContainer; 12 | import org.testcontainers.junit.jupiter.Container; 13 | import org.testcontainers.junit.jupiter.Testcontainers; 14 | 15 | import javax.sql.DataSource; 16 | import java.util.Properties; 17 | 18 | @Tag("compatibility") 19 | @Testcontainers 20 | @Disabled 21 | public class Oracle11gCompatibilityTest extends CompatibilityTest { 22 | @Container 23 | private static final OracleContainer ORACLE = new OracleContainer("oracleinanutshell/oracle-xe-11g:1.0.0"); 24 | private static HikariDataSource pooledDatasource; 25 | 26 | @BeforeAll 27 | static void initSchema() { 28 | final DriverDataSource datasource = new DriverDataSource(ORACLE.getJdbcUrl(), "oracle.jdbc.OracleDriver", new Properties(), ORACLE.getUsername(), ORACLE.getPassword()); 29 | 30 | final HikariConfig hikariConfig = new HikariConfig(); 31 | hikariConfig.setDataSource(datasource); 32 | hikariConfig.setMaximumPoolSize(10); 33 | pooledDatasource = new HikariDataSource(hikariConfig); 34 | 35 | // init schema 36 | DbUtils.runSqlResource("/oracle_tables.sql").accept(pooledDatasource); 37 | } 38 | 39 | @BeforeEach 40 | void overrideSchedulerShutdown() throws InterruptedException { 41 | Thread.sleep(100); 42 | } 43 | 44 | @Override 45 | public DataSource getDataSource() { 46 | return pooledDatasource; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/container-license-acceptance.txt: -------------------------------------------------------------------------------- 1 | mcr.microsoft.com/mssql/server:2017-CU12 2 | 3 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/hsql_tables.sql: -------------------------------------------------------------------------------- 1 | create table scheduled_tasks ( 2 | id BIGINT primary key not null, 3 | task_name varchar(100), 4 | task_instance varchar(100), 5 | task_data blob, 6 | picked_by varchar(50), 7 | time_started TIMESTAMP WITH TIME ZONE, 8 | time_finished TIMESTAMP WITH TIME ZONE, 9 | succeeded BIT, 10 | duration_ms BIGINT, 11 | exception_class varchar(1000), 12 | exception_message blob, 13 | exception_stacktrace blob 14 | ) 15 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/io/rocketbase/extension/postgresql_custom_tablename.sql: -------------------------------------------------------------------------------- 1 | create table custom_tablename_logs 2 | ( 3 | id BIGINT not null primary key, 4 | task_name text not null, 5 | task_instance text not null, 6 | task_data bytea, 7 | picked_by text, 8 | time_started timestamp with time zone not null, 9 | time_finished timestamp with time zone not null, 10 | succeeded BOOLEAN not null, 11 | duration_ms BIGINT not null, 12 | exception_class text, 13 | exception_message text, 14 | exception_stacktrace text 15 | ); 16 | 17 | CREATE INDEX stl_custom_started_idx ON custom_tablename_logs (time_started); 18 | CREATE INDEX stl_custom_task_name_idx ON custom_tablename_logs (task_name); 19 | CREATE INDEX stl_custom_exception_class_idx ON custom_tablename_logs (exception_class); 20 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/mssql_tables.sql: -------------------------------------------------------------------------------- 1 | create table scheduled_execution_logs ( 2 | id BIGINT not null primary key, 3 | task_name varchar(250) not null, 4 | task_instance varchar(250) not null, 5 | task_data nvarchar(max), 6 | picked_by text, 7 | time_started datetimeoffset , 8 | time_finished datetimeoffset , 9 | succeeded bit, 10 | duration_ms BIGINT not null, 11 | exception_class varchar(1000), 12 | exception_message nvarchar(max), 13 | exception_stacktrace nvarchar(max) 14 | 15 | INDEX stl_started_idx (time_started), 16 | INDEX stl_task_name_idx (task_name), 17 | INDEX stl_exception_class_idx (exception_class) 18 | ) 19 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/mysql_tables.sql: -------------------------------------------------------------------------------- 1 | create table test.scheduled_execution_logs ( 2 | id BIGINT not null primary key , 3 | task_name varchar(40) not null, 4 | task_instance varchar(40) not null, 5 | task_data blob, 6 | picked_by varchar(50), 7 | time_started timestamp(6) not null, 8 | time_finished timestamp(6) not null, 9 | succeeded BOOLEAN not null, 10 | duration_ms BIGINT not null, 11 | exception_class varchar(1000), 12 | exception_message blob, 13 | exception_stacktrace blob 14 | 15 | INDEX stl_started_idx (time_started); 16 | INDEX stl_task_name_idx (task_name); 17 | INDEX stl_exception_class_idx (exception_class); 18 | ) 19 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/oracle_tables.sql: -------------------------------------------------------------------------------- 1 | create table scheduled_tasks_log 2 | ( 3 | id NUMBER not null primary key, 4 | task_name varchar(100), 5 | task_instance varchar(100), 6 | task_data blob, 7 | picked_by varchar(50), 8 | time_started TIMESTAMP(6) not null, 9 | time_finished TIMESTAMP(6) not null, 10 | succeeded NUMBER(1, 0), 11 | duration_ms NUMBER not null, 12 | exception_class varchar(1000), 13 | exception_message blob, 14 | exception_stacktrace blob 15 | ) 16 | 17 | -------------------------------------------------------------------------------- /db-scheduler-log/src/test/resources/postgresql_tables.sql: -------------------------------------------------------------------------------- 1 | create table scheduled_execution_logs 2 | ( 3 | id BIGINT not null primary key, 4 | task_name text not null, 5 | task_instance text not null, 6 | task_data bytea, 7 | picked_by text, 8 | time_started timestamp with time zone not null, 9 | time_finished timestamp with time zone not null, 10 | succeeded BOOLEAN not null, 11 | duration_ms BIGINT not null, 12 | exception_class text, 13 | exception_message text, 14 | exception_stacktrace text 15 | ); 16 | 17 | CREATE INDEX stl_started_idx ON scheduled_execution_logs (time_started); 18 | CREATE INDEX stl_task_name_idx ON scheduled_execution_logs (task_name); 19 | CREATE INDEX stl_exception_class_idx ON scheduled_execution_logs (exception_class); 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.rocketbase.extension 7 | db-scheduler-log-parent 8 | master-SNAPSHOT 9 | pom 10 | 11 | db-scheduler-log: Parent 12 | Extention for db-scheduler to have logging of exections. 13 | https://github.com/rocketbase-io/db-scheduler-log 14 | 15 | 16 | Marten Prieß 17 | melistik 18 | marten@rocketbase.io 19 | rocketbase.io software productions GmbH 20 | 21 | 22 | 23 | 24 | 25 | The Apache Software License, Version 2.0 26 | http://www.apache.org/licenses/LICENSE-2.0.txt 27 | repo 28 | 29 | 30 | 31 | 32 | UTF-8 33 | 1.8 34 | ${jdk.version} 35 | ${jdk.version} 36 | 37 | ${project.basedir}/.license 38 | 39 | 40 | false 41 | true 42 | true 43 | 44 | 45 | 2.7.3 46 | 12.4.0 47 | 48 | 49 | 50 | db-scheduler-log 51 | db-scheduler-log-boot-starter 52 | 53 | 54 | 55 | 56 | 57 | com.google.code.gson 58 | gson 59 | 2.9.1 60 | 61 | 62 | com.fasterxml.jackson 63 | jackson-bom 64 | 2.13.2 65 | import 66 | pom 67 | 68 | 69 | ch.qos.logback 70 | logback-classic 71 | 1.2.10 72 | test 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-dependencies 78 | ${spring-boot.version} 79 | pom 80 | import 81 | 82 | 83 | org.junit 84 | junit-bom 85 | 5.9.0 86 | pom 87 | import 88 | 89 | 90 | org.testcontainers 91 | testcontainers-bom 92 | 1.17.3 93 | pom 94 | import 95 | 96 | 97 | 98 | net.java.dev.jna 99 | jna 100 | 5.11.0 101 | 102 | 103 | org.apache.commons 104 | commons-lang3 105 | 3.12.0 106 | test 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | maven-compiler-plugin 117 | 118 | ${compilerArgument} 119 | 120 | 121 | 122 | maven-surefire-plugin 123 | 3.1.2 124 | 125 | 126 | com.mycila 127 | license-maven-plugin 128 | 2.11 129 | 130 |

${license.dir}/license-header.txt
131 | true 132 | 133 | pom.xml 134 | LICENSE* 135 | todo.txt 136 | .java-version 137 | .license/** 138 | src/test/** 139 | **/*.properties 140 | **/*.sql 141 | .github/** 142 | test/** 143 | 144 | ${failOnMissingHeader} 145 | 146 | 147 | 148 | check-license-header 149 | 150 | check 151 | 152 | test 153 | 154 | 155 | 156 | 157 | 158 | maven-javadoc-plugin 159 | 3.5.0 160 | 161 | 162 | attach-javadocs 163 | 164 | jar 165 | 166 | 167 | 168 | 169 | 170 | maven-source-plugin 171 | 3.3.0 172 | 173 | 174 | attach-sources 175 | 176 | jar-no-fork 177 | 178 | 179 | true 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | maven-dependency-plugin 190 | 191 | 192 | maven-surefire-plugin 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | skipChecks 201 | 202 | 203 | skipChecks 204 | true 205 | 206 | 207 | 208 | true 209 | false 210 | false 211 | 212 | 213 | 214 | sources 215 | 216 | 217 | 218 | maven-javadoc-plugin 219 | 220 | 221 | maven-source-plugin 222 | 223 | 224 | 225 | 226 | 227 | 228 | release 229 | 230 | 231 | 232 | org.sonatype.plugins 233 | nexus-staging-maven-plugin 234 | 1.6.13 235 | true 236 | 237 | ossrh 238 | https://s01.oss.sonatype.org/ 239 | true 240 | 241 | 242 | 243 | org.apache.maven.plugins 244 | maven-source-plugin 245 | 3.2.1 246 | 247 | 248 | attach-sources 249 | 250 | jar-no-fork 251 | 252 | 253 | 254 | 255 | 256 | org.apache.maven.plugins 257 | maven-javadoc-plugin 258 | 3.4.1 259 | 260 | 261 | attach-javadocs 262 | 263 | jar 264 | 265 | 266 | none 267 | 268 | 269 | 270 | 271 | 272 | org.apache.maven.plugins 273 | maven-gpg-plugin 274 | 3.0.1 275 | 276 | 277 | sign-artifacts 278 | verify 279 | 280 | sign 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | springBootDevelopment 290 | 291 | 292 | spring-milestones 293 | Spring Milestones 294 | https://repo.spring.io/milestone 295 | 296 | false 297 | 298 | 299 | 300 | spring-snapshots 301 | Spring Snapshots 302 | https://repo.spring.io/snapshot 303 | 304 | false 305 | 306 | 307 | 308 | 309 | 310 | spring-milestones 311 | Spring Milestones 312 | https://repo.spring.io/milestone 313 | 314 | false 315 | 316 | 317 | 318 | spring-snapshots 319 | Spring Snapshots 320 | https://repo.spring.io/snapshot 321 | 322 | false 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | scm:git:git@github.com:rocketbase-io/db-scheduler-log.git 331 | scm:git:git@github.com:rocketbase-io/db-scheduler-log.git 332 | https://github.com/rocketbase-io/db-scheduler-log/ 333 | HEAD 334 | 335 | 336 | 337 | 338 | ossrh 339 | https://oss.sonatype.org/content/repositories/snapshots 340 | 341 | 342 | ossrh 343 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 344 | 345 | 346 | 347 | 348 | --------------------------------------------------------------------------------