diff --git a/.editorconfig b/.editorconfig index cae16714a..d6eead7ac 100644 --- a/.editorconfig +++ b/.editorconfig @@ -113,7 +113,7 @@ ij_java_for_statement_wrap = off ij_java_generate_final_locals = false ij_java_generate_final_parameters = false ij_java_if_brace_force = never -ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_imports_layout = $*,|,* ij_java_indent_case_from_switch = true ij_java_insert_inner_class_imports = false ij_java_insert_override_annotation = true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..cbc37a3f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Community Support + url: https://stackoverflow.com/tags/shedlock + about: Please ask and answer questions on StackOverflow with the tag `shedlock`. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5b87dc93..cb77e774f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,34 +6,37 @@ on: pull_request: types: [ opened, reopened, synchronize ] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest strategy: matrix: - java: [ '17', '19'] - # Kotlin needs access to java.util - include: - - java: '17' - maven-opts: --add-opens java.base/java.util=ALL-UNNAMED - maven-params: '' - - java: '19' - maven-opts: --add-opens java.base/java.util=ALL-UNNAMED - maven-params: '' + java: [ '17', '21'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} - distribution: 'temurin' - cache: 'maven' + distribution: 'zulu' + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: maven-full-${{ hashFiles('**/pom.xml') }} + restore-keys: | + maven- - name: Build with Maven env: - MAVEN_OPTS: ${{ matrix.maven-opts }} - run: mvn ${{ matrix.maven-params }} test javadoc:javadoc + # Kotlin needs access to java.util + MAVEN_OPTS: --add-opens java.base/java.util=ALL-UNNAMED + run: ./mvnw validate test package - name: Publish Test Report - uses: mikepenz/action-junit-report@v3 + uses: mikepenz/action-junit-report@v5 if: always() # always run even if the previous step fails with: report_paths: '**/target/surefire-reports/TEST-*.xml' diff --git a/.gitignore b/.gitignore index f8603a0d8..8dab6ac73 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ .attach_pid* micronaut/micronaut-test/dependency-reduced-pom.xml dependency-reduced-pom.xml +.vscode/settings.json diff --git a/.mvn/maven-build-cache-config.xml b/.mvn/maven-build-cache-config.xml new file mode 100644 index 000000000..2ea921178 --- /dev/null +++ b/.mvn/maven-build-cache-config.xml @@ -0,0 +1,140 @@ + + + + + + + + true + SHA-256 + true + + 3 + + + + + + + + + + install + + + + + deploy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..12fbe1e90 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/README.md b/README.md index e00742e91..43ad0cede 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ShedLock ======== -[![Apache License 2](https://img.shields.io/badge/license-ASF2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![Build Status](https://github.com/lukas-krecan/ShedLock/workflows/CI/badge.svg)](https://github.com/lukas-krecan/ShedLock/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.javacrumbs.shedlock/shedlock-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.javacrumbs.shedlock/shedlock-parent) +[![Apache License 2](https://img.shields.io/badge/license-ASF2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![Build Status](https://github.com/lukas-krecan/ShedLock/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/lukas-krecan/ShedLock/actions) ShedLock makes sure that your scheduled tasks are executed at most once at the same time. If a task is being executed on one node, it acquires a lock which prevents execution of the same task from another node (or thread). @@ -21,9 +21,9 @@ executed repeatedly. Moreover, the locks are time-based and ShedLock assumes tha + [Lock Providers](#configure-lockprovider) - [JdbcTemplate](#jdbctemplate) - [R2DBC](#r2dbc) + - [jOOQ](#jooq-lock-provider) - [Micronaut Data Jdbc](#micronaut-data-jdbc) - [Mongo](#mongo) - - [DynamoDB](#dynamodb) - [DynamoDB 2](#dynamodb-2) - [ZooKeeper (using Curator)](#zookeeper-using-curator) - [Redis (using Spring RedisConnectionFactory)](#redis-using-spring-redisconnectionfactory) @@ -40,19 +40,26 @@ executed repeatedly. Moreover, the locks are time-based and ShedLock assumes tha - [Neo4j](#neo4j) - [Etcd](#etcd) - [Apache Ignite](#apache-ignite) - - [Multi-tenancy](#Multi-tenancy) - - [In-Memory](#In-Memory) - - [Memcached](#Memcached) + - [In-Memory](#in-memory) + - [Memcached](#memcached-using-spymemcached) + - [Datastore](#datastore) + - [Firestore](#firestore) + - [S3](#s3) ++ [Multi-tenancy](#multi-tenancy) ++ [Customization](#customization) + [Duration specification](#duration-specification) + [Extending the lock](#extending-the-lock) + [Micronaut integration](#micronaut-integration) ++ [CDI integration](#cdi-integration) + [Locking without a framework](#locking-without-a-framework) + [Troubleshooting](#troubleshooting) + [Modes of Spring integration](#modes-of-spring-integration) - [Scheduled method proxy](#scheduled-method-proxy) - [TaskScheduler proxy](#taskscheduler-proxy) ++ [Compatibility matrix](#compatibility-matrix) + [Release notes](#release-notes) + ## Components Shedlock consists of three parts * Core - The locking mechanism @@ -73,7 +80,7 @@ First of all, we have to import the project net.javacrumbs.shedlock shedlock-spring - 4.42.0 + 6.10.0 ``` @@ -103,22 +110,19 @@ public void scheduledTask() { // do something } ``` - -The `@SchedulerLock` annotation has several purposes. First of all, only annotated methods are locked, the library ignores -all other scheduled tasks. You also have to specify the name for the lock. Only one task with the same name can be executed -at the same time. - -You can also set `lockAtMostFor` attribute which specifies how long the lock should be kept in case the -executing node dies. This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes. -**You have to set `lockAtMostFor` to a value which is much longer than normal execution time.** If the task takes longer than +### Behavior +1. Only methods annotated by `@SchedulerLock` are locked, the library ignores all other scheduled tasks. By default, the lock will be applied +even if the method is called directly, not only thorough the scheduler. +2. Only one task with the same name can be executed at the same time. +3. If the lock is being held by a task, **other tasks protected by the same lock are not blocked, but are simply skipped.** +4. The lock is released as soon as the task is finished (unless `lockAtLeastFor` is specified, see below) +5. If the JVM crashes before the task finishes, `lockAtMostFor` attribute comes to play. The lock is always released after +`lockAtMostFor`. **You have to set `lockAtMostFor` to a value which is much longer than normal execution time.** If the task takes longer than `lockAtMostFor` the resulting behavior may be unpredictable (more than one process will effectively hold the lock). - -If you do not specify `lockAtMostFor` in `@SchedulerLock` default value from `@EnableSchedulerLock` will be used. - -Lastly, you can set `lockAtLeastFor` attribute which specifies minimum amount of time for which the lock should be kept. +6. If you don't specify `lockAtMostFor` in `@SchedulerLock`, the default value from `@EnableSchedulerLock` will be used. +7. You can set `lockAtLeastFor` attribute which specifies minimum amount of time for which the lock should be kept. Its main purpose is to prevent execution from multiple nodes in case of really short tasks and clock difference between the nodes. - -All the annotations support Spring Expression Language (SpEL). +8. All the annotations support Spring Expression Language (SpEL). #### Example Let's say you have a task which you execute every 15 minutes and which usually takes few minutes to run. @@ -135,7 +139,7 @@ public void scheduledTask() { } ``` -By setting `lockAtMostFor` we make sure that the lock is released even if the node dies and by setting `lockAtLeastFor` +By setting `lockAtMostFor` we make sure that the lock is released even if the node dies. By setting `lockAtLeastFor` we make sure it's not executed more than once in fifteen minutes. Please note that **`lockAtMostFor` is just a safety net in case that the node executing the task dies, so set it to a time that is significantly larger than maximum estimated execution time.** If the task takes longer than `lockAtMostFor`, @@ -169,7 +173,7 @@ CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAM locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL); ``` -Or use [this](micronaut/test/micronaut-jdbc/src/main/resources/db/liquibase-changelog.xml) liquibase change-set. +Or use [this](micronaut/test/micronaut4-jdbc/src/main/resources/db/liquibase-changelog.xml) liquibase change-set. Add dependency @@ -177,7 +181,7 @@ Add dependency net.javacrumbs.shedlock shedlock-provider-jdbc-template - 4.42.0 + 6.10.0 ``` @@ -189,18 +193,21 @@ import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; ... @Bean public LockProvider lockProvider(DataSource dataSource) { - return new JdbcTemplateLockProvider( - JdbcTemplateLockProvider.Configuration.builder() - .withJdbcTemplate(new JdbcTemplate(dataSource)) - .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2 - .build() - ); + return new JdbcTemplateLockProvider( + JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(new JdbcTemplate(dataSource)) + .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2 + .build() + ); } ``` By specifying `usingDbTime()` the lock provider will use UTC time based on the DB server clock. If you do not specify this option, clock from the app server will be used (the clocks on app servers may not be synchronized thus leading to various locking issues). +It's strongly recommended to use `usingDbTime()` option as it uses DB engine specific SQL that prevents INSERT conflicts. +See more details [here](https://stackoverflow.com/a/76774461/277042). + For more fine-grained configuration use other options of the `Configuration` object ```java @@ -209,12 +216,17 @@ new JdbcTemplateLockProvider(builder() .withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by")) .withJdbcTemplate(new JdbcTemplate(getDatasource())) .withLockedByValue("my-value") + .withDbUpperCase(true) .build()) ``` If you need to specify a schema, you can set it in the table name using the usual dot notation `new JdbcTemplateLockProvider(datasource, "my_schema.shedlock")` +To use a database with case-sensitive table and column names, the `.withDbUpperCase(true)` flag can be used. +Default is `false` (lowercase). + + #### Warning **Do not manually delete lock row from the DB table.** ShedLock has an in-memory cache of existing lock rows so the row will NOT be automatically recreated until application restart. If you need to, you can edit the row/document, risking only @@ -229,7 +241,7 @@ is in flux and may easily break. net.javacrumbs.shedlock shedlock-provider-r2dbc - 4.42.0 + 6.10.0 ``` @@ -241,11 +253,67 @@ protected LockProvider getLockProvider() { return new R2dbcLockProvider(connectionFactory); } ``` -I recommend using [R2DBC connection pool](https://github.com/r2dbc/r2dbc-pool), unless you are connecting to Oracle that does not work with the pool. +I recommend using [R2DBC connection pool](https://github.com/r2dbc/r2dbc-pool). + +#### jOOQ lock provider +First, create lock table as described in the [JdbcTemplate](#jdbctemplate) section above. + +Add dependency + +```xml + + net.javacrumbs.shedlock + shedlock-provider-jooq + 6.10.0 + +``` + +Configure: + +```java +import net.javacrumbs.shedlock.provider.jooq; + +... +@Bean +public LockProvider getLockProvider(DSLContext dslContext) { + return new JooqLockProvider(dslContext); +} +``` + +jOOQ provider has a bit different transactional behavior. While the other JDBC lock providers +create new transaction (with REQUIRES_NEW), jOOQ [does not support setting it](https://github.com/jOOQ/jOOQ/issues/4836). +ShedLock tries to create a new transaction, but depending on your set-up, ShedLock DB operations may +end-up being part of the enclosing transaction. + +If you need to configure the table name, schema or column names, you can use jOOQ render mapping as +described [here](https://github.com/lukas-krecan/ShedLock/issues/1830#issuecomment-2015820509). + +#### Exposed lock provider +First, create lock table as described in the [JdbcTemplate](#jdbctemplate) section above. + +Add dependency + +```xml + + net.javacrumbs.shedlock + shedlock-provider-exposed + 6.10.0 + +``` + +Configure: + +```kotlin +import net.javacrumbs.shedlock.provider.exposed; + +... +@Bean +fun getLockProvider(database: Database) = ExposedLockProvider(database) +``` #### Micronaut Data Jdbc -If you are using Micronaut data and you do not want to add dependency on Spring JDBC, you can use +If you are using Micronaut data, and you do not want to add dependency on Spring JDBC, you can use Micronaut JDBC support. Just be aware that it has just a basic functionality when compared to the JdbcTemplate provider. @@ -257,7 +325,7 @@ Add dependency net.javacrumbs.shedlock shedlock-provider-jdbc-micronaut - 4.42.0 + 6.10.0 ``` @@ -280,7 +348,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-mongo - 4.42.0 + 6.10.0 ``` @@ -307,7 +375,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-mongo-reactivestreams - 4.42.0 + 6.10.0 ``` @@ -327,38 +395,8 @@ public LockProvider lockProvider(MongoClient mongo) { Please note that MongoDB integration requires Mongo >= 4.x and mongodb-driver-reactivestreams 1.x -#### DynamoDB -This depends on AWS SDK v1. - -Import the project - -```xml - - net.javacrumbs.shedlock - shedlock-provider-dynamodb - 4.42.0 - -``` - -Configure: - -```java -import net.javacrumbs.shedlock.provider.dynamodb.DynamoDBLockProvider; - -... - -@Bean -public LockProvider lockProvider(com.amazonaws.services.dynamodbv2.document.DynamoDB dynamoDB) { - return new DynamoDBLockProvider(dynamoDB.getTable("Shedlock")); -} -``` - -> Please note that the lock table must be created externally with `_id` as a partition key. -> `DynamoDBUtils#createLockTable` may be used for creating it programmatically. -> A table definition is available from `DynamoDBLockProvider`'s Javadoc. - #### DynamoDB 2 -This depends on AWS SDK v2. +Depends on AWS SDK v2. Import the project @@ -366,7 +404,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-dynamodb2 - 4.42.0 + 6.10.0 ``` @@ -393,7 +431,7 @@ Import net.javacrumbs.shedlock shedlock-provider-zookeeper-curator - 4.42.0 + 6.10.0 ``` @@ -417,7 +455,7 @@ Import net.javacrumbs.shedlock shedlock-provider-redis-spring - 4.42.0 + 6.10.0 ``` @@ -441,7 +479,7 @@ Import net.javacrumbs.shedlock shedlock-provider-redis-spring - 4.42.0 + 6.10.0 ``` @@ -464,20 +502,13 @@ public LockProvider lockProvider(ReactiveRedisConnectionFactory connectionFactor Redis lock provider uses classical lock mechanism as described [here](https://redis.io/commands/setnx#design-pattern-locking-with-codesetnxcode) which may not be reliable in case of Redis master failure. -If you are still using Spring Data Redis 1, import special lock provider `shedlock-provider-redis-spring-1` which works around -issue #105 or upgrade to Spring Data Redis 2 or higher. - - #### Redis (using Jedis) Import ```xml net.javacrumbs.shedlock - - - shedlock-provider-redis-jedis4 - 4.42.0 + 6.10.0 ``` @@ -500,10 +531,8 @@ Import the project ```xml net.javacrumbs.shedlock - - shedlock-provider-hazelcast4 - 4.42.0 + 6.10.0 ``` @@ -520,17 +549,38 @@ public HazelcastLockProvider lockProvider(HazelcastInstance hazelcastInstance) { } ``` +#### Redis (using Lettuce) + +Import + +```xml + + + net.javacrumbs.shedlock + shedlock-provider-redis-lettuce + 6.10.0 + +``` + +and configure + +```java +import net.javacrumbs.shedlock.provider.redis.lettuce.LettuceLockProvider; +... +@Bean +public LockProvider lockProvider(StatefulRedisConnection connection) { + return new LettuceLockProvider(connection, ENV); +} +``` + #### Couchbase Import the project ```xml net.javacrumbs.shedlock - - shedlock-provider-couchbase-javaclient - - - 4.42.0 + shedlock-provider-couchbase-javaclient3 + 6.10.0 ``` @@ -550,13 +600,14 @@ public CouchbaseLockProvider lockProvider(Bucket bucket) { For Couchbase 3 use `shedlock-provider-couchbase-javaclient3` module and `net.javacrumbs.shedlock.provider.couchbase3` package. #### Elasticsearch -I am really not sure it's a good idea to use Elasticsearch as a lock provider. But if you have no other choice, you can. Import the project +I am really not sure if it's a good idea to use Elasticsearch as a lock provider. But if you have no other choice, you can. Import the project ```xml net.javacrumbs.shedlock shedlock-provider-elasticsearch8 - 4.42.0 + + 6.10.0 ``` @@ -579,21 +630,22 @@ Import the project ```xml net.javacrumbs.shedlock - shedlock-provider-opensearch - 4.36.1 + + shedlock-provider-opensearch-java + 6.10.0 ``` Configure: ```java -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider; ... @Bean -public OpenSearchLockProvider lockProvider(RestHighLevelClient highLevelClient) { - return new OpenSearchLockProvider(highLevelClient); +public OpenSearchLockProvider lockProvider(OpenSearchClient openSearchClient) { + return new OpenSearchLockProvider(openSearchClient); } ``` @@ -608,7 +660,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-cassandra - 4.42.0 + 6.10.0 ``` @@ -643,7 +695,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-consul - 4.42.0 + 6.10.0 ``` @@ -668,7 +720,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-arangodb - 4.42.0 + 6.10.0 ``` @@ -694,7 +746,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-neo4j - 4.42.0 + 6.10.0 ``` @@ -719,7 +771,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-etcd-jetcd - 4.42.0 + 6.10.0 ``` @@ -743,7 +795,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-ignite - 4.42.0 + 6.10.0 ``` @@ -760,25 +812,6 @@ public LockProvider lockProvider(Ignite ignite) { } ``` -#### Multi-tenancy -If you have multi-tenancy use-case you can use a lock provider similar to this one -(see the full [example](https://github.com/lukas-krecan/ShedLock/blob/master/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java#L87)) -```java -private static abstract class MultiTenancyLockProvider implements LockProvider { - private final ConcurrentHashMap providers = new ConcurrentHashMap<>(); - - @Override - public @NonNull Optional lock(@NonNull LockConfiguration lockConfiguration) { - String tenantName = getTenantName(lockConfiguration); - return providers.computeIfAbsent(tenantName, this::createLockProvider).lock(lockConfiguration); - } - - protected abstract LockProvider createLockProvider(String tenantName) ; - - protected abstract String getTenantName(LockConfiguration lockConfiguration); -} -``` - #### In-Memory If you want to use a lock provider in tests there is an in-Memory implementation. @@ -787,7 +820,7 @@ Import the project net.javacrumbs.shedlock shedlock-provider-inmemory - 4.42.0 + 6.10.0 test ``` @@ -813,7 +846,7 @@ Import net.javacrumbs.shedlock shedlock-provider-memcached-spy - 4.42.0 + 6.10.0 ``` @@ -837,6 +870,174 @@ Memcached Standard Protocol: - An expiration time, in `seconds`. '0' means never expire. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date. (support `seconds`、`minutes`、`days`, and less than `30` days) +#### Datastore + +Import the project +```xml + + net.javacrumbs.shedlock + shedlock-provider-datastore + 6.10.0 + +``` + +and configure +```java +import net.javacrumbs.shedlock.provider.datastore.DatastoreLockProvider; + +... + +@Bean +public LockProvider lockProvider(com.google.cloud.datastore.Datastore datastore) { + return new DatastoreLockProvider(datastore); +} + +``` + +#### Firestore + +Import the project +```xml + + net.javacrumbs.shedlock + shedlock-provider-firestore + 6.10.0 + +``` + +and configure +```java +import net.javacrumbs.shedlock.provider.firestore.FirestoreLockProvider; + +... + +@Bean +public LockProvider lockProvider(com.google.cloud.firestore.Firestore firestore) { + return new FirestoreLockProvider(firestore); +} +``` + +For more fine-grained configuration, you can use the builder: + +```java +@Bean +public LockProvider lockProvider(com.google.cloud.firestore.Firestore firestore) { + return new FirestoreLockProvider( + FirestoreLockProvider.Configuration.builder() + .withFirestore(firestore) + .withCollectionName("custom_lock_collection") + .withFieldNames(new FirestoreLockProvider.FieldNames("custom_lock_until", "custom_locked_at", "custom_locked_by")) + .build() + ); +} +``` + +#### Spanner +Import the project +```xml + + net.javacrumbs.shedlock + shedlock-provider-spanner + 6.10.0 + +``` +Configure +```java +import net.javacrumbs.shedlock.provider.spanner.SpannerLockProvider; + +... + +// Basic +@Bean +public LockProvider lockProvider(DatabaseClient databaseClient) { + return new SpannerLockProvider(databaseClientSupplier); +} + +// Custom host, table and column names +@Bean +public LockProvider lockProvider(DatabaseClient databaseClient) { + var config = SpannerLockProvider.Configuration.builder() + .withDatabaseClient(databaseClientSupplier) + .withTableConfiguration(SpannerLockProvider.TableConfiguration.builder() + ... + // Custom table and column names + .build()) + .withHostName("customHostName") + .build(); + + return new SpannerLockProvider(config); +} +``` + +#### S3 + +Import the project +```xml + + net.javacrumbs.shedlock + shedlock-provider-s3v2 + + + 6.10.0 + +``` + +and configure +```java +import net.javacrumbs.shedlock.provider.s3v2.S3LockProvider; + +... + +@Bean +public LockProvider lockProvider(S3Client s3Client) { + return new S3LockProvider(s3Client, "BUCKET_NAME"); +} +``` + +## Multi-tenancy +If you have multi-tenancy use-case you can use a lock provider similar to this one +(see the full [example](https://github.com/lukas-krecan/ShedLock/blob/master/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java#L87)) +```java +private static abstract class MultiTenancyLockProvider implements LockProvider { + private final ConcurrentHashMap providers = new ConcurrentHashMap<>(); + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + String tenantName = getTenantName(lockConfiguration); + return providers.computeIfAbsent(tenantName, this::createLockProvider).lock(lockConfiguration); + } + + protected abstract LockProvider createLockProvider(String tenantName); + + protected abstract String getTenantName(LockConfiguration lockConfiguration); +} +``` + +## Customization +You can customize the behavior of the library by implementing `LockProvider` interface. Let's say you want to implement +a special behavior after a lock is obtained. You can do it like this: + +```java +public class MyLockProvider implements LockProvider { + private final LockProvider delegate; + + public MyLockProvider(LockProvider delegate) { + this.delegate = delegate; + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + Optional lock = delegate.lock(lockConfiguration); + if (lock.isPresent()) { + // do something + } + return lock; + } +} +``` + +You can see a full example in [TrackingLockProviderWrapper](https://github.com/lukas-krecan/ShedLock/blob/master/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapper.java) + ## Duration specification All the annotations where you need to specify a duration support the following formats @@ -862,12 +1063,13 @@ it adds more complexity to the library and the flow is harder to reason about so ```java @Bean public LockProvider lockProvider(...) { - return new KeepAliveLockProvider(new XyzProvider(...), scheduler); + return new KeepAliveLockProvider(new XyzProvider(...), scheduler); } ``` KeepAliveLockProvider extends the lock in the middle of the lockAtMostFor interval. For example, if the lockAtMostFor is 10 minutes the lock is extended every 5 minutes for 10 minutes until the lock is released. Please note that the minimal -lockAtMostFor time supported by this provider is 30s. +lockAtMostFor time supported by this provider is 30s. The scheduler is used only for the lock extension, single thread +should be enough. ## Micronaut integration Since version 4.0.0, it's possible to use Micronaut framework for integration @@ -876,8 +1078,8 @@ Import the project: ```xml net.javacrumbs.shedlock - shedlock-micronaut - 4.42.0 + shedlock-micronaut4 + 6.10.0 ``` @@ -906,6 +1108,49 @@ public void myTask() { } ``` +## CDI integration +Since version 5.0.0, it's possible to use CDI for integration (tested only with Quarkus) + +Import the project: +```xml + + net.javacrumbs.shedlock + shedlock-cdi + 6.10.0 + +``` + +Configure default lockAtMostFor value (application.properties): +```properties +shedlock.defaults.lock-at-most-for=PT30S +``` + +Configure lock provider: +```java +@Produces +@Singleton +public LockProvider lockProvider() { + ... +} +``` + +Configure the scheduled task: +```java +@Scheduled(every = "1s") +@SchedulerLock(name = "myTask") +public void myTask() { + assertLocked(); + ... +} +``` + +The implementation only depends on `jakarta.enterprise.cdi-api` and `microprofile-config-api` so it should be +usable in other CDI compatible frameworks, but it has not been tested with anything else than Quarkus. It's +built on top of javax annotation as Quarkus has not moved to Jakarta EE namespace yet. + +The support is minimalistic, for example there is no support for expressions in the annotation parameters yet, +if you need it, feel free to send a PR. + ## Locking without a framework It is possible to use ShedLock without a framework @@ -913,9 +1158,10 @@ It is possible to use ShedLock without a framework LockingTaskExecutor executor = new DefaultLockingTaskExecutor(lockProvider); ... - -Instant lockAtMostUntil = Instant.now().plusSeconds(600); -executor.executeWithLock(runnable, new LockConfiguration("lockName", lockAtMostUntil)); +Instant createdAt = Instant.now(); +Duration lockAtMostFor = Duration.ofSeconds(60); +Duration lockAtLeastFor = Duration.ZERO; +executor.executeWithLock(runnable, new LockConfiguration(createdAt, "lockName", lockAtMostFor, lockAtLeastFor)); ``` @@ -923,6 +1169,12 @@ executor.executeWithLock(runnable, new LockConfiguration("lockName", lockAtMostU Some lock providers support extension of the lock. For the time being, it requires manual lock manipulation, directly using `LockProvider` and calling `extend` method on the `SimpleLock`. +## Multiple LockProvider support in Spring +Since version 6.0.0 you can use multiple lock provider implementations. Just define them in your application context +and disambiguate them using `@LockProviderToUse("lockProviderBeanName")` annotation on method, class or package. +If the annotation is not found, the execution fails in the runtime, not in startup-time. If you need more dynamic resolution +of LockProviders, use a LockProvider wrapper as described in [Multi-tenancy](#multi-tenancy). + ## Modes of Spring integration ShedLock supports two modes of Spring integration. One that uses an AOP proxy around scheduled method (PROXY_METHOD) and one that proxies TaskScheduler (PROXY_SCHEDULER) @@ -931,14 +1183,14 @@ and one that proxies TaskScheduler (PROXY_SCHEDULER) Since version 4.0.0, the default mode of Spring integration is an AOP proxy around the annotated method. The main advantage of this mode is that it plays well with other frameworks that want to somehow alter the default Spring scheduling mechanism. -The disadvantage is that the lock is applied even if you call the method directly. If the method returns a value and the lock is held +It also means that *the lock is applied even if you call the method directly*. If the method returns a value and the lock is held by another process, null or an empty Optional will be returned (primitive return types are not supported). Final and non-public methods are not proxied so either you have to make your scheduled methods public and non-final or use TaskScheduler proxy. ![Method proxy sequenceDiagram](https://github.com/lukas-krecan/ShedLock/raw/master/documentation/method_proxy.png) -#### TaskScheduler proxy +#### TaskScheduler proxy (deprecated) This mode wraps Spring `TaskScheduler` in an AOP proxy. **This mode does not play well with instrumentation libraries** like opentelementry that also wrap TaskScheduler. Please only use it if you know what you are doing. It can be switched-on like this (PROXY_SCHEDULER was the default method before 4.0.0): @@ -948,7 +1200,7 @@ It can be switched-on like this (PROXY_SCHEDULER was the default method before 4 ``` If you do not specify your task scheduler, a default one is created for you. If you have special needs, just create a bean implementing `TaskScheduler` -interface and it will get wrapped into the AOP proxy automatically. +interface, and it will get wrapped into the AOP proxy automatically. ```java @Bean @@ -982,9 +1234,9 @@ public void scheduledTask() { In unit tests you can switch-off the assertion by calling `LockAssert.TestHelper.makeAllAssertsPass(true)` on given thread (as in this [example](https://github.com/lukas-krecan/ShedLock/commit/e8d63b7c56644c4189e0a8b420d8581d6eae1443)). ## Kotlin gotchas -The library is tested with Kotlin and works fine. The only issue is Spring AOP which does not work on final method. If you use `@SchedulerLock` with `@Scheduled` -annotation, everything should work since Kotlin Spring compiler plugin will automatically 'open' the method for you. If `@Scheduled` annotation is not present, you -have to open the method by yourself. +The library is tested with Kotlin and works fine. The only issue is Spring AOP which does not work on final method. If you use `@SchedulerLock` with `@Component` +annotation, everything should work since Kotlin Spring compiler plugin will automatically 'open' the method for you. If `@Component` annotation is not present, you +have to open the method by yourself. (see [this issue](https://github.com/lukas-krecan/ShedLock/issues/1268) for more details) ## Caveats Locks in ShedLock have an expiration time which leads to the following possible issues. @@ -997,6 +1249,7 @@ Help, ShedLock does not do what it's supposed to do! 1. Upgrade to the newest version 2. Use [LockAssert](https://github.com/lukas-krecan/ShedLock#lock-assert) to ensure that AOP is correctly configured. + - If it does not work, please read about Spring AOP internals (for example [here](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying)) 3. Check the storage. If you are using JDBC, check the ShedLock table. If it's empty, ShedLock is not properly configured. If there is more than one record with the same name, you are missing a primary key. 4. Use ShedLock debug log. ShedLock logs interesting information on DEBUG level with logger name `net.javacrumbs.shedlock`. @@ -1004,361 +1257,19 @@ It should help you to see what's going on. 5. For short-running tasks consider using `lockAtLeastFor`. If the tasks are short-running, they could be executed one after another, `lockAtLeastFor` can prevent it. +# Compatibility matrix -## Requirements and dependencies -* Java 8 -* slf4j-api - -# Release notes -## 4.42.0 (2022-09-16) -* Deprecate old Couchbase lock provider -* Dependency updates - -## 4.41.0 (2022-08-17) -* Couchbase collection support (thanks @mesuutt) -* Dependency updates - -## 4.40.0 (2022-08-11) -* Fixed caching issues when the app is started by the DB does not exist yet (#1129) -* Dependency updates - -## 4.39.0 (2022-07-26) -* Introduced elasticsearch8 LockProvider and deperecated the orignal one (thanks @MarAra) -* Dependency updates - -## 4.38.0 (2022-07-02) -* ReactiveRedisLockProvider added (thanks @ericwcc) -* Dependency updates - -## 4.37.0 (2022-06-14) -* OpenSearch provider (thanks @Pinny3) -* Fix wrong reference to reactive Mongo in BOM #1048 -* Dependency updates - -## 4.36.0 (2022-05-28) -* shedlock-bom module added -* Dependency updates - -## 4.35.0 (2022-05-16) -* Neo4j allows to specify database thanks @SergeyPlatonov -* Dependency updates +| ShedLock Version | Minimal JVM version | Tested with | +|------------------|---------------------|--------------------------------------------------------------------| +| 6.x.x | 17 | Spring 6.2, 6.1
Spring Boot 3.4, 3.3
Micronaut 4 | +| 5.x.x | 17 | Spring 6.1, 6.0
Spring Boot 3.4, 3.3, 3.2
Micronaut 3, 4 | +| 4.x.x | 8 | Spring 6.0, 5.3
Spring Boot 3.0, 2.7, 2.6 | +| 3.x.x | 8 | Spring 5.2, 5.1
Spring Boot 2.2, 2.1 | +| 2.x.x | 8 | Spring 5.1, 5.0
Spring Boot 2.1 | +| 1.x.x | 8 | Spring 5.0
Spring Boot 2.0 | -## 4.34.0 (2022-04-09) -* Dropped support for Hazelcast <= 3 as it has unfixed vulnerability -* Dropped support for Spring Data Redis 1 as it is not supported -* Dependency updates +ShedLock may work with additional versions of the Spring, this table just depicts what it was tested with. -## 4.33.0 -* memcached provider added (thanks @pinkhello) -* Dependency updates -## 4.32.0 -* JDBC provider does not change autocommit attribute -* Dependency updates - -## 4.31.0 -* Jedis 4 lock provider -* Dependency updates - -## 4.30.0 -* In-memory lock provider added (thanks @kkocel) -* Dependency updates - -## 4.29.0 -* R2DBC support added (thanks @sokomishalov) -* Library upgrades - -## 4.28.0 -* Neo4j lock provider added (thanks @thimmwork) -* Library upgrades - -## 4.27.0 -* Ability to set transaction isolation in JdbcTemplateLockProvider -* Library upgrades - -## 4.26.0 -* KeepAliveLockProvider introduced -* Library upgrades - -## 4.25.0 -* LockExtender added - -## 4.24.0 -* Support for Apache Ignite (thanks @wirtsleg) -* Library upgrades - -## 4.23.0 -* Ability to set serialConsistencyLevel in Cassandra (thanks @DebajitKumarPhukan) -* Introduced shedlock-provider-jdbc-micronaut module (thanks @drmaas) - -## 4.22.1 -* Catching and logging Cassandra exception - -## 4.22.0 -* Support for custom keyspace in Cassandra provider - -## 4.21.0 -* Elastic unlock using IMMEDIATE refresh policy #422 -* DB2 JDBC lock provider uses microseconds in DB time -* Various library upgrades - -## 4.20.1 -* Fixed DB JDBC server time #378 - -## 4.20.0 -* Support for etcd (thanks grofoli) - -## 4.19.1 -* Fixed devtools compatibility #368 - -## 4.19.0 -* Support for enhanced configuration in Cassandra provider (thanks DebajitKumarPhukan) -* LockConfigurationExtractor exposed as a Spring bean #359 -* Handle CannotSerializeTransactionException #364 - -## 4.18.0 -* Fixed Consul support for tokens and added enhanced Consul configuration (thanks DrWifey) - -## 4.17.0 -* Consul support for tokens - -## 4.16.0 -* Spring - EnableSchedulerLock.order param added to specify AOP proxy order -* JDBC - Log unexpected exceptions at ERROR level -* Hazelcast upgraded to 4.1 - -## 4.15.1 -* Fix session leak in Consul provider #340 (thanks @haraldpusch) - -## 4.15.0 -* ArangoDB lock provider added (thanks @patrick-birkle) - -## 4.14.0 -* Support for Couchbase 3 driver (thanks @blitzenzzz) -* Removed forgotten configuration files form micronaut package (thanks @drmaas) -* Shutdown hook for Consul (thanks @kaliy) - -## 4.13.0 -* Support for Consul (thanks @kaliy) -* Various dependencies updated -* Deprecated default LockConfiguration constructor - -## 4.12.0 -* Lazy initialization of SqlStatementsSource #258 - -## 4.11.1 -* MongoLockProvider uses mongodb-driver-sync -* Removed deprecated constructors from MongoLockProvider - -## 4.10.1 -* New Mongo reactive streams driver (thanks @codependent) - -## 4.9.3 -* Fixed JdbcTemplateLockProvider useDbTime() locking #244 thanks @gjorgievskivlatko - -## 4.9.2 -* Do not fail on DB type determining code if DB connection is not available - -## 4.9.1 -* Support for server time in DB2 -* removed shedlock-provider-jdbc-internal module - -## 4.9.0 -* Support for server time in JdbcTemplateLockProvider -* Using custom non-null annotations -* Trimming time precision to milliseconds -* Micronaut upgraded to 1.3.4 -* Add automatic DB tests for Oracle, MariaDB and MS SQL. - -## 4.8.0 -* DynamoDB 2 module introduced (thanks Mark Egan) -* JDBC template code refactored to not log error on failed insert in Postgres - * INSERT .. ON CONFLICT UPDATE is used for Postgres - -## 4.7.1 -* Make LockAssert.TestHelper public - -## 4.7.0 -* New module for Hazelcasts 4 -* Ability to switch-off LockAssert in unit tests - -## 4.6.0 -* Support for Meta annotations and annotation inheritance in Spring - -## 4.5.2 -* Made compatible with PostgreSQL JDBC Driver 42.2.11 - -## 4.5.1 -* Inject redis template - -## 4.5.0 -* ClockProvider introduced -* MongoLockProvider(MongoDatabase) introduced - -## 4.4.0 -* Support for non-void returning methods when PROXY_METHOD interception is used - -## 4.3.1 -* Introduced shedlock-provider-redis-spring-1 to make it work around Spring Data Redis 1 issue #105 (thanks @rygh4775) - -## 4.3.0 -* Jedis dependency upgraded to 3.2.0 -* Support for JedisCluster -* Tests upgraded to JUnit 5 - -## 4.2.0 -* Cassandra provider (thanks @mitjag) - -## 4.1.0 -* More configuration option for JdbcTemplateProvider - -## 4.0.4 -* Allow configuration of key prefix in RedisLockProvider #181 (thanks @krm1312) - -## 4.0.3 -* Fixed junit dependency scope #179 - -## 4.0.2 -* Fix NPE caused by Redisson #178 -## 4.0.1 -* DefaultLockingTaskExecutor made reentrant #175 -## 4.0.0 -Version 4.0.0 is a major release changing quite a lot of stuff -* `net.javacrumbs.shedlock.core.SchedulerLock` has been replaced by `net.javacrumbs.shedlock.spring.annotation.SchedulerLock`. The original annotation has been in wrong module and -was too complex. Please use the new annotation, the old one still works, but in few years it will be removed. -* Default intercept mode changed from `PROXY_SCHEDULER` to `PROXY_METHOD`. The reason is that there were a lot of issues with `PROXY_SCHEDULER` (for example #168). You can still -use `PROXY_SCHEDULER` mode if you specify it manually. -* Support for more readable [duration strings](#duration-specification) -* Support for lock assertion `LockAssert.assertLocked()` -* [Support for Micronaut](#micronaut-integration) added - -## 3.0.1 -* Fixed bean definition configuration #171 - -## 3.0.0 -* `EnableSchedulerLock.mode` renamed to `interceptMode` -* Use standard Spring AOP configuration to honor Spring Boot config (supports `proxyTargetClass` flag) -* Removed deprecated SpringLockableTaskSchedulerFactoryBean and related classes -* Removed support for XML configuration - -## 2.6.0 -* Updated dependency to Spring 2.1.9 -* Support for lock extensions (beta) - -## 2.5.0 -* Zookeeper supports *lockAtMostFor* and *lockAtLeastFor* params -* Better debug logging - -## 2.4.0 -* Fixed potential deadlock in Hazelcast (thanks @HubertTatar) -* Finding class level annotation in proxy method mode (thanks @volkovs) -* ScheduledLockConfigurationBuilder deprecated - -## 2.3.0 -* LockProvides is initialized lazilly so it does not change DataSource initialization order - -## 2.2.1 -* MongoLockProvider accepts MongoCollection as a constructor param - -## 2.2.0 -* DynamoDBLockProvider added - -## 2.1.0 -* MongoLockProvider rewritten to use upsert -* ElasticsearchLockProvider added - -## 2.0.1 -* AOP proxy and annotation configuration support - -## 1.3.0 -* Can set Timezone to JdbcTemplateLock provider - -## 1.2.0 -* Support for Couchbase (thanks to @MoranVaisberg) - -## 1.1.1 -* Spring RedisLockProvider refactored to use RedisTemplate - -## 1.1.0 -* Support for transaction manager in JdbcTemplateLockProvider (thanks to @grmblfrz) - -## 1.0.0 -* Upgraded dependencies to Spring 5 and Spring Data 2 -* Removed deprecated net.javacrumbs.shedlock.provider.jedis.JedisLockProvider (use net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvide instead) -* Removed deprecated SpringLockableTaskSchedulerFactory (use ScheduledLockConfigurationBuilder instead) - -## 0.18.2 -* ablility to clean lock cache - -## 0.18.1 -* shedlock-provider-redis-spring made compatible with spring-data-redis 1.x.x - -## 0.18.0 -* Added shedlock-provider-redis-spring (thanks to @siposr) -* shedlock-provider-jedis moved to shedlock-provider-redis-jedis - -## 0.17.0 -* Support for SPEL in lock name annotation - -## 0.16.1 -* Automatically closing TaskExecutor on Spring shutdown - -## 0.16.0 -* Removed spring-test from shedlock-spring compile time dependencies -* Added Automatic-Module-Names - -## 0.15.1 -* Hazelcast works with remote cluster - -## 0.15.0 -* Fixed ScheduledLockConfigurationBuilder interfaces #32 -* Hazelcast code refactoring - -## 0.14.0 -* Support for Hazelcast (thanks to @peyo) - -## 0.13.0 -* Jedis constructor made more generic (thanks to @mgrzeszczak) - -## 0.12.0 -* Support for property placeholders in annotation lockAtMostForString/lockAtLeastForString -* Support for composed annotations -* ScheduledLockConfigurationBuilder introduced (deprecating SpringLockableTaskSchedulerFactory) - -## 0.11.0 -* Support for Redis (thanks to @clamey) -* Checking that lockAtMostFor is in the future -* Checking that lockAtMostFor is larger than lockAtLeastFor - - -## 0.10.0 -* jdbc-template-provider does not participate in task transaction - -## 0.9.0 -* Support for @SchedulerLock annotations on proxied classes - -## 0.8.0 -* LockableTaskScheduler made AutoClosable so it's closed upon Spring shutdown - -## 0.7.0 -* Support for lockAtLeastFor - -## 0.6.0 -* Possible to configure defaultLockFor time so it does not have to be repeated in every annotation - -## 0.5.0 -* ZooKeeper nodes created under /shedlock by default - -## 0.4.1 -* JdbcLockProvider insert does not fail on DataIntegrityViolationException - -## 0.4.0 -* Extracted LockingTaskExecutor -* LockManager.executeIfNotLocked renamed to executeWithLock -* Default table name in JDBC lock providers - -## 0.3.0 -* `@ShedlulerLock.name` made obligatory -* `@ShedlulerLock.lockForMillis` renamed to lockAtMostFor -* Adding plain JDBC LockProvider -* Adding ZooKeepr LockProvider +# Release notes +See [here](RELEASES.md) diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 000000000..fb02e336c --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,533 @@ +# Release notes + +## 6.10.0 (2025-08-20) +* Firestore provider added (thanks @rgopaluni) +* Nullability annotations refactored +* Dependency updates + +## 6.9.2 (2025-07-07) +* OpenSearchLockProvider constructor made public + +## 6.9.1 (2025-07-07) +* Ability to specify index in OpenSearchLockProvider +* OpenSearchLockProvider constructor made public +* Dependency updates + +## 6.9.0 (2025-06-07) +* Added Exposed provider (thanks @hungrytech) +* Dependency updates + +## 6.8.0 (2025-05-31) +* Added `riskyUnlock` to HazelcastLockProvider +* Dependency updates + +## 6.7.0 (2025-05-22) +* safeUpdate mode for Redis Providers (thanks @shotmk) + +## 6.6.0 (2025-05-05) +* Redis lettuce provider (thanks @s4got10dev) +* Dependency updates + +## 6.5.0 (2025-04-29) +* Support for Sort Keys for DynamoDBLockProvider (thanks @avanish-p1) +* Dependency updates + +## 6.4.0 (2025-04-19) +* Support ElasticSearch 9 +* Dependency updates + +## 6.3.1 (2025-03-28) +* Add shedlock-provider-jdbc to bom (thanks @svenallers) +* Dependency updates + +## 6.3.0 (2025-02-06) +* Support for S3 V2 lock provider (thanks @JordiMartinezVicent) +* Dependency updates + +## 6.2.0 (2025-01-08) +* Support OpenSearch Java client (thanks @harisonde) +* Dependency updates + +## 6.1.0 (2025-01-06) +* Support for S3 lock provider (thanks @caiooliveiraeti) +* Dependency updates + +## 6.0.2 +* #2272 Don't fail on startup if LockProvider not found +* Dependency updates + +## 6.0.1 +* Support for multiple LockProviders +* cdi-vintage module removed +* Micronaut 3 support removed +* micronaut-jdbc built on top of Micronaut 4 +* PROXY_SCHEDULER mode deprecated +* Cassandra lock provider stated to use the org.apache.cassandra driver +* Dependency updates + +## 5.16.0 (2024-09-06) +* Support for custome partition key for Dynamo #2128 (thanks @kumar-himans) +* Upgrade OpenSearch rest-high-level-client #2115 - Breaking change due to rest-high-level-client backward incompatibility +* Dependency updates + +## 5.15.1 (2024-08-27) +* Fix for Neo4j Enterprise #2099 (thanks @tle130475c) +* Dependency updates + +## 5.15.0 (2024-08-15) +* Dependency updates +* ElasticSearch updated to 8.15.0 containing backward incompatible change (thanks @mputz86) + +## 5.14.0 + 4.48.0 (2024-07-24) +* RedisLockProvider made extensible (thanks @shubhajyoti-bagchi-groww) +* Dependency updates + +## 5.13.0 (2024-04-05) +* #1779 Ability to rethrow unexpected exception in JdbcTemplateStorageAccessor +* Dependency updates + +## 5.12.0 (2024-02-29) +* #1800 Enable lower case for database type when using usingDbTime() +* #1804 Startup error with Neo4j 5.17.0 +* Dependency updates + +## 4.47.0 (2024-03-01) +* #1800 Enable lower case for database type when using usingDbTime() (thanks @yuagu1) + +## 5.11.0 (2024-02-13) +* #1753 Fix SpEL for methods with parameters +* Dependency updates + +## 5.10.2 (2023-12-07) +* #1635 fix makeAllAssertsPass locks only once +* Dependency updates + +## 5.10.1 (2023-12-06) +* #1635 fix makeAllAssertsPass(false) throws NoSuchElementException +* Dependency updates + +## 5.10.0 (2023-11-07) +* SpannerLockProvider added (thanks @pXius) +* Dependency updates + +## 5.9.1 (2023-10-19) +* QuarkusRedisLockProvider supports Redis 6.2 (thanks @ricardojlrufino) + +## 5.9.0 (2023-10-15) +* Support Quarkus 2 Redis client (thanks @ricardojlrufino) +* Better handling of timeouts in ReactiveStreamsMongoLockProvider +* Dependency updates + +## 5.8.0 (2023-09-15) +* Support for Micronaut 4 +* Use Merge instead of Insert for Oracle #1528 (thanks @xmojsic) +* Dependency updates + +## 5.7.0 (2023-08-25) +* JedisLockProvider supports extending (thanks @shotmk) +* Better behavior when locks are nested #1493 + +## 4.46.0 (2023-09-05) +* JedisLockProvider (version 3) supports extending (thanks @shotmk) + +## 4.45.0 (2023-09-04) +* JedisLockProvider supports extending (thanks @shotmk) + +## 5.6.0 +* Ability to explicitly set database product in JdbTemplateLockProvider (thanks @metron2) +* Removed forgotten versions from BOM +* Dependency updates + +## 5.5.0 (2023-06-19) +* Datastore support (thanks @mmastika) +* Dependency updates + +## 5.4.0 (2023-06-06) +* Handle [uncategorized SQL exceptions](https://github.com/lukas-krecan/ShedLock/pull/1442) (thanks @jaam) +* Dependency updates + +## 5.3.0 (2023-05-13) +* Added shedlock-cdi module (supports newest CDI version) +* Dependency updates + +## 5.2.0 (2023-03-06) +* Uppercase in JdbcTemplateProvider (thanks @Ragin-LundF) +* Dependency updates + +## 5.1.0 (2023-01-07) +* Added SpEL support to @SchedulerLock name attribute (thanks @ipalbeniz) +* Dependency updates + +## 5.0.1 (2022-12-10) +* Work around broken Spring 6 exception translation https://github.com/lukas-krecan/ShedLock/issues/1272 + +## 4.44.0 (2022-12-29) +* Insert ignore for MySQL https://github.com/lukas-krecan/ShedLock/commit/8a4ae7ad8103bb47f55d43bccf043ca261c24d7a + +## 5.0.0 (2022-12-10) +* Requires JDK 17 +* Tested with Spring 6 (Spring Boot 3) +* Micronaut updated to 3.x.x +* R2DBC 1.x.x (still sucks) +* Spring Data 3.x.x +* Rudimentary support for CDI (tested with quarkus) +* New jOOQ lock provider +* SLF4j 2 +* Deleted all deprecated code and support for old versions of libraries + +## 4.43.0 (2022-12-04) +* Better logging in JdbcTemplateProvider +* Dependency updates + +## 4.42.0 (2022-09-16) +* Deprecate old Couchbase lock provider +* Dependency updates + +## 4.41.0 (2022-08-17) +* Couchbase collection support (thanks @mesuutt) +* Dependency updates + +## 4.40.0 (2022-08-11) +* Fixed caching issues when the app is started by the DB does not exist yet (#1129) +* Dependency updates + +## 4.39.0 (2022-07-26) +* Introduced elasticsearch8 LockProvider and deperecated the orignal one (thanks @MarAra) +* Dependency updates + +## 4.38.0 (2022-07-02) +* ReactiveRedisLockProvider added (thanks @ericwcc) +* Dependency updates + +## 4.37.0 (2022-06-14) +* OpenSearch provider (thanks @Pinny3) +* Fix wrong reference to reactive Mongo in BOM #1048 +* Dependency updates + +## 4.36.0 (2022-05-28) +* shedlock-bom module added +* Dependency updates + +## 4.35.0 (2022-05-16) +* Neo4j allows to specify database thanks @SergeyPlatonov +* Dependency updates + +## 4.34.0 (2022-04-09) +* Dropped support for Hazelcast <= 3 as it has unfixed vulnerability +* Dropped support for Spring Data Redis 1 as it is not supported +* Dependency updates + +## 4.33.0 +* memcached provider added (thanks @pinkhello) +* Dependency updates + +## 4.32.0 +* JDBC provider does not change autocommit attribute +* Dependency updates + +## 4.31.0 +* Jedis 4 lock provider +* Dependency updates + +## 4.30.0 +* In-memory lock provider added (thanks @kkocel) +* Dependency updates + +## 4.29.0 +* R2DBC support added (thanks @sokomishalov) +* Library upgrades + +## 4.28.0 +* Neo4j lock provider added (thanks @thimmwork) +* Library upgrades + +## 4.27.0 +* Ability to set transaction isolation in JdbcTemplateLockProvider +* Library upgrades + +## 4.26.0 +* KeepAliveLockProvider introduced +* Library upgrades + +## 4.25.0 +* LockExtender added + +## 4.24.0 +* Support for Apache Ignite (thanks @wirtsleg) +* Library upgrades + +## 4.23.0 +* Ability to set serialConsistencyLevel in Cassandra (thanks @DebajitKumarPhukan) +* Introduced shedlock-provider-jdbc-micronaut module (thanks @drmaas) + +## 4.22.1 +* Catching and logging Cassandra exception + +## 4.22.0 +* Support for custom keyspace in Cassandra provider + +## 4.21.0 +* Elastic unlock using IMMEDIATE refresh policy #422 +* DB2 JDBC lock provider uses microseconds in DB time +* Various library upgrades + +## 4.20.1 +* Fixed DB JDBC server time #378 + +## 4.20.0 +* Support for etcd (thanks grofoli) + +## 4.19.1 +* Fixed devtools compatibility #368 + +## 4.19.0 +* Support for enhanced configuration in Cassandra provider (thanks DebajitKumarPhukan) +* LockConfigurationExtractor exposed as a Spring bean #359 +* Handle CannotSerializeTransactionException #364 + +## 4.18.0 +* Fixed Consul support for tokens and added enhanced Consul configuration (thanks DrWifey) + +## 4.17.0 +* Consul support for tokens + +## 4.16.0 +* Spring - EnableSchedulerLock.order param added to specify AOP proxy order +* JDBC - Log unexpected exceptions at ERROR level +* Hazelcast upgraded to 4.1 + +## 4.15.1 +* Fix session leak in Consul provider #340 (thanks @haraldpusch) + +## 4.15.0 +* ArangoDB lock provider added (thanks @patrick-birkle) + +## 4.14.0 +* Support for Couchbase 3 driver (thanks @blitzenzzz) +* Removed forgotten configuration files form micronaut package (thanks @drmaas) +* Shutdown hook for Consul (thanks @kaliy) + +## 4.13.0 +* Support for Consul (thanks @kaliy) +* Various dependencies updated +* Deprecated default LockConfiguration constructor + +## 4.12.0 +* Lazy initialization of SqlStatementsSource #258 + +## 4.11.1 +* MongoLockProvider uses mongodb-driver-sync +* Removed deprecated constructors from MongoLockProvider + +## 4.10.1 +* New Mongo reactive streams driver (thanks @codependent) + +## 4.9.3 +* Fixed JdbcTemplateLockProvider useDbTime() locking #244 thanks @gjorgievskivlatko + +## 4.9.2 +* Do not fail on DB type determining code if DB connection is not available + +## 4.9.1 +* Support for server time in DB2 +* removed shedlock-provider-jdbc-internal module + +## 4.9.0 +* Support for server time in JdbcTemplateLockProvider +* Using custom non-null annotations +* Trimming time precision to milliseconds +* Micronaut upgraded to 1.3.4 +* Add automatic DB tests for Oracle, MariaDB and MS SQL. + +## 4.8.0 +* DynamoDB 2 module introduced (thanks Mark Egan) +* JDBC template code refactored to not log error on failed insert in Postgres + * INSERT .. ON CONFLICT UPDATE is used for Postgres + +## 4.7.1 +* Make LockAssert.TestHelper public + +## 4.7.0 +* New module for Hazelcasts 4 +* Ability to switch-off LockAssert in unit tests + +## 4.6.0 +* Support for Meta annotations and annotation inheritance in Spring + +## 4.5.2 +* Made compatible with PostgreSQL JDBC Driver 42.2.11 + +## 4.5.1 +* Inject redis template + +## 4.5.0 +* ClockProvider introduced +* MongoLockProvider(MongoDatabase) introduced + +## 4.4.0 +* Support for non-void returning methods when PROXY_METHOD interception is used + +## 4.3.1 +* Introduced shedlock-provider-redis-spring-1 to make it work around Spring Data Redis 1 issue #105 (thanks @rygh4775) + +## 4.3.0 +* Jedis dependency upgraded to 3.2.0 +* Support for JedisCluster +* Tests upgraded to JUnit 5 + +## 4.2.0 +* Cassandra provider (thanks @mitjag) + +## 4.1.0 +* More configuration option for JdbcTemplateProvider + +## 4.0.4 +* Allow configuration of key prefix in RedisLockProvider #181 (thanks @krm1312) + +## 4.0.3 +* Fixed junit dependency scope #179 + +## 4.0.2 +* Fix NPE caused by Redisson #178 +## 4.0.1 +* DefaultLockingTaskExecutor made reentrant #175 +## 4.0.0 +Version 4.0.0 is a major release changing quite a lot of stuff +* `net.javacrumbs.shedlock.core.SchedulerLock` has been replaced by `net.javacrumbs.shedlock.spring.annotation.SchedulerLock`. The original annotation has been in wrong module and + was too complex. Please use the new annotation, the old one still works, but in few years it will be removed. +* Default intercept mode changed from `PROXY_SCHEDULER` to `PROXY_METHOD`. The reason is that there were a lot of issues with `PROXY_SCHEDULER` (for example #168). You can still + use `PROXY_SCHEDULER` mode if you specify it manually. +* Support for more readable [duration strings](#duration-specification) +* Support for lock assertion `LockAssert.assertLocked()` +* [Support for Micronaut](#micronaut-integration) added + +## 3.0.1 +* Fixed bean definition configuration #171 + +## 3.0.0 +* `EnableSchedulerLock.mode` renamed to `interceptMode` +* Use standard Spring AOP configuration to honor Spring Boot config (supports `proxyTargetClass` flag) +* Removed deprecated SpringLockableTaskSchedulerFactoryBean and related classes +* Removed support for XML configuration + +## 2.6.0 +* Updated dependency to Spring 2.1.9 +* Support for lock extensions (beta) + +## 2.5.0 +* Zookeeper supports *lockAtMostFor* and *lockAtLeastFor* params +* Better debug logging + +## 2.4.0 +* Fixed potential deadlock in Hazelcast (thanks @HubertTatar) +* Finding class level annotation in proxy method mode (thanks @volkovs) +* ScheduledLockConfigurationBuilder deprecated + +## 2.3.0 +* LockProvides is initialized lazilly so it does not change DataSource initialization order + +## 2.2.1 +* MongoLockProvider accepts MongoCollection as a constructor param + +## 2.2.0 +* DynamoDBLockProvider added + +## 2.1.0 +* MongoLockProvider rewritten to use upsert +* ElasticsearchLockProvider added + +## 2.0.1 +* AOP proxy and annotation configuration support + +## 1.3.0 +* Can set Timezone to JdbcTemplateLock provider + +## 1.2.0 +* Support for Couchbase (thanks to @MoranVaisberg) + +## 1.1.1 +* Spring RedisLockProvider refactored to use RedisTemplate + +## 1.1.0 +* Support for transaction manager in JdbcTemplateLockProvider (thanks to @grmblfrz) + +## 1.0.0 +* Upgraded dependencies to Spring 5 and Spring Data 2 +* Removed deprecated net.javacrumbs.shedlock.provider.jedis.JedisLockProvider (use net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvide instead) +* Removed deprecated SpringLockableTaskSchedulerFactory (use ScheduledLockConfigurationBuilder instead) + +## 0.18.2 +* ablility to clean lock cache + +## 0.18.1 +* shedlock-provider-redis-spring made compatible with spring-data-redis 1.x.x + +## 0.18.0 +* Added shedlock-provider-redis-spring (thanks to @siposr) +* shedlock-provider-jedis moved to shedlock-provider-redis-jedis + +## 0.17.0 +* Support for SPEL in lock name annotation + +## 0.16.1 +* Automatically closing TaskExecutor on Spring shutdown + +## 0.16.0 +* Removed spring-test from shedlock-spring compile time dependencies +* Added Automatic-Module-Names + +## 0.15.1 +* Hazelcast works with remote cluster + +## 0.15.0 +* Fixed ScheduledLockConfigurationBuilder interfaces #32 +* Hazelcast code refactoring + +## 0.14.0 +* Support for Hazelcast (thanks to @peyo) + +## 0.13.0 +* Jedis constructor made more generic (thanks to @mgrzeszczak) + +## 0.12.0 +* Support for property placeholders in annotation lockAtMostForString/lockAtLeastForString +* Support for composed annotations +* ScheduledLockConfigurationBuilder introduced (deprecating SpringLockableTaskSchedulerFactory) + +## 0.11.0 +* Support for Redis (thanks to @clamey) +* Checking that lockAtMostFor is in the future +* Checking that lockAtMostFor is larger than lockAtLeastFor + + +## 0.10.0 +* jdbc-template-provider does not participate in task transaction + +## 0.9.0 +* Support for @SchedulerLock annotations on proxied classes + +## 0.8.0 +* LockableTaskScheduler made AutoClosable so it's closed upon Spring shutdown + +## 0.7.0 +* Support for lockAtLeastFor + +## 0.6.0 +* Possible to configure defaultLockFor time so it does not have to be repeated in every annotation + +## 0.5.0 +* ZooKeeper nodes created under /shedlock by default + +## 0.4.1 +* JdbcLockProvider insert does not fail on DataIntegrityViolationException + +## 0.4.0 +* Extracted LockingTaskExecutor +* LockManager.executeIfNotLocked renamed to executeWithLock +* Default table name in JDBC lock providers + +## 0.3.0 +* `@ShedlulerLock.name` made obligatory +* `@ShedlulerLock.lockForMillis` renamed to lockAtMostFor +* Adding plain JDBC LockProvider +* Adding ZooKeepr LockProvider diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 75eb34257..000000000 --- a/TODO.txt +++ /dev/null @@ -1,10 +0,0 @@ -[x] New Micronaut -[x] default nullable annotation -[x] javax package?? -[] io.micrometer.Observation -[x] test AOT -[x] elasticsearch - drop type -[] Quarkus - -- CDI rudimentary - no expressions in annotations -- jOOQ - transactions - no requires_new diff --git a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java b/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java deleted file mode 100644 index 1de570832..000000000 --- a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.javacrumbs.shedlock.cdi; - -import javax.enterprise.util.Nonbinding; -import javax.interceptor.InterceptorBinding; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@InterceptorBinding -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited -public @interface SchedulerLock { - /** - * Lock name. - */ - @Nonbinding String name(); - - /** - * How long the lock should be kept in case the machine which obtained the lock died before releasing it. - * This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes. Can be any format - * supported by Duration Conversion - *

- */ - @Nonbinding String lockAtMostFor() default ""; - - /** - * The lock will be held at least for this period of time. Can be used if you really need to execute the task - * at most once in given period of time. If the duration of the task is shorter than clock difference between nodes, the task can - * be theoretically executed more than once (one node after another). By setting this parameter, you can make sure that the - * lock will be kept at least for given period of time. Can be any format - * supported by Duration Conversion - */ - @Nonbinding String lockAtLeastFor() default ""; -} - diff --git a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java b/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java deleted file mode 100644 index 7532ee44e..000000000 --- a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.cdi.internal; - -import net.javacrumbs.shedlock.support.LockException; - -class LockingNotSupportedException extends LockException { - LockingNotSupportedException() { - super("Can not lock method returning value (do not know what to return if it's locked)"); - } -} diff --git a/cdi/shedlock-cdi-vintage/src/main/resources/META-INF/beans.xml b/cdi/shedlock-cdi-vintage/src/main/resources/META-INF/beans.xml deleted file mode 100644 index 75b9e9cce..000000000 --- a/cdi/shedlock-cdi-vintage/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/cdi/shedlock-cdi-vintage/pom.xml b/cdi/shedlock-cdi/pom.xml similarity index 87% rename from cdi/shedlock-cdi-vintage/pom.xml rename to cdi/shedlock-cdi/pom.xml index 906d8a7a7..dd5ef7dd0 100644 --- a/cdi/shedlock-cdi-vintage/pom.xml +++ b/cdi/shedlock-cdi/pom.xml @@ -5,13 +5,12 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../pom.xml - shedlock-cdi-vintage - 5.0.0-SNAPSHOT - + shedlock-cdi + ${project.groupId}:${project.artifactId} @@ -24,19 +23,19 @@ jakarta.enterprise jakarta.enterprise.cdi-api - 2.0.2 + 4.1.0 jakarta.annotation jakarta.annotation-api - 1.3.5 + 3.0.0 org.eclipse.microprofile.config microprofile-config-api - 2.0.1 + 3.1 @@ -48,13 +47,11 @@ org.junit.jupiter junit-jupiter-api - ${junit.ver} test org.junit.jupiter junit-jupiter-engine - ${junit.ver} test @@ -80,7 +77,7 @@ - net.javacrumbs.shedlock.cdivintage + net.javacrumbs.shedlock.cdi diff --git a/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java new file mode 100644 index 000000000..34845c168 --- /dev/null +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/SchedulerLock.java @@ -0,0 +1,45 @@ +package net.javacrumbs.shedlock.cdi; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.interceptor.InterceptorBinding; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@InterceptorBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Inherited +public @interface SchedulerLock { + /** Lock name. */ + @Nonbinding + String name(); + + /** + * How long the lock should be kept in case the machine which obtained the lock + * died before releasing it. This is just a fallback, under normal circumstances + * the lock is released as soon the tasks finishes. Can be any format supported + * by Duration + * Conversion + * + *

+ */ + @Nonbinding + String lockAtMostFor() default ""; + + /** + * The lock will be held at least for this period of time. Can be used if you + * really need to execute the task at most once in given period of time. If the + * duration of the task is shorter than clock difference between nodes, the task + * can be theoretically executed more than once (one node after another). By + * setting this parameter, you can make sure that the lock will be kept at least + * for given period of time. Can be any format supported by Duration + * Conversion + */ + @Nonbinding + String lockAtLeastFor() default ""; +} diff --git a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java similarity index 66% rename from cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java rename to cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java index 3a5e54ab3..39d64de22 100644 --- a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/CdiLockConfigurationExtractor.java @@ -1,31 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.cdi.internal; - -import net.javacrumbs.shedlock.cdi.SchedulerLock; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.cdi.internal.Utils.parseDuration; import java.lang.reflect.Method; import java.time.Duration; import java.util.Optional; - -import static java.util.Objects.requireNonNull; -import static net.javacrumbs.shedlock.cdi.internal.Utils.parseDuration; +import net.javacrumbs.shedlock.cdi.SchedulerLock; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; class CdiLockConfigurationExtractor { private final Duration defaultLockAtMostFor; @@ -36,7 +32,6 @@ class CdiLockConfigurationExtractor { this.defaultLockAtLeastFor = requireNonNull(defaultLockAtLeastFor); } - Optional getLockConfiguration(Method method) { Optional annotation = findAnnotation(method); return annotation.map(this::getLockConfiguration); @@ -44,11 +39,7 @@ Optional getLockConfiguration(Method method) { private LockConfiguration getLockConfiguration(SchedulerLock annotation) { return new LockConfiguration( - ClockProvider.now(), - getName(annotation), - getLockAtMostFor(annotation), - getLockAtLeastFor(annotation) - ); + ClockProvider.now(), getName(annotation), getLockAtMostFor(annotation), getLockAtLeastFor(annotation)); } private String getName(SchedulerLock annotation) { @@ -56,19 +47,11 @@ private String getName(SchedulerLock annotation) { } Duration getLockAtMostFor(SchedulerLock annotation) { - return getValue( - annotation.lockAtMostFor(), - this.defaultLockAtMostFor, - "lockAtMostFor" - ); + return getValue(annotation.lockAtMostFor(), this.defaultLockAtMostFor, "lockAtMostFor"); } Duration getLockAtLeastFor(SchedulerLock annotation) { - return getValue( - annotation.lockAtLeastFor(), - this.defaultLockAtLeastFor, - "lockAtLeastFor" - ); + return getValue(annotation.lockAtLeastFor(), this.defaultLockAtLeastFor, "lockAtLeastFor"); } private Duration getValue(String stringValueFromAnnotation, Duration defaultValue, String paramName) { @@ -83,5 +66,3 @@ Optional findAnnotation(Method method) { return Optional.ofNullable(method.getAnnotation(SchedulerLock.class)); } } - - diff --git a/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java new file mode 100644 index 000000000..7dea76e24 --- /dev/null +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/LockingNotSupportedException.java @@ -0,0 +1,22 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.cdi.internal; + +import net.javacrumbs.shedlock.support.LockException; + +class LockingNotSupportedException extends LockException { + LockingNotSupportedException() { + super("Can not lock method returning value (do not know what to return if it's locked)"); + } +} diff --git a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java similarity index 78% rename from cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java rename to cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java index 129fad619..78388a74d 100644 --- a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/SchedulerLockInterceptor.java @@ -1,24 +1,23 @@ package net.javacrumbs.shedlock.cdi.internal; +import static net.javacrumbs.shedlock.cdi.internal.Utils.parseDuration; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; import net.javacrumbs.shedlock.cdi.SchedulerLock; import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.LockingTaskExecutor; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.eclipse.microprofile.config.ConfigProvider; -import javax.annotation.Priority; -import javax.inject.Inject; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import java.time.Duration; -import java.util.Objects; -import java.util.Optional; - -import static net.javacrumbs.shedlock.cdi.internal.Utils.parseDuration; - - @SchedulerLock(name = "?") @Priority(3001) @Interceptor @@ -33,23 +32,24 @@ public SchedulerLockInterceptor(LockProvider lockProvider) { String lockAtLeastFor = getConfigValue("shedlock.defaults.lock-at-least-for"); Objects.requireNonNull(lockAtMostFor, "shedlock.defaults.lock-at-most-for parameter is mandatory"); this.lockConfigurationExtractor = new CdiLockConfigurationExtractor( - parseDuration(lockAtMostFor), - lockAtLeastFor != null ? parseDuration(lockAtLeastFor) : Duration.ZERO - ); + parseDuration(lockAtMostFor), lockAtLeastFor != null ? parseDuration(lockAtLeastFor) : Duration.ZERO); } + @Nullable private static String getConfigValue(String propertyName) { return ConfigProvider.getConfig().getConfigValue(propertyName).getValue(); } @AroundInvoke + @Nullable Object lock(InvocationContext context) throws Throwable { Class returnType = context.getMethod().getReturnType(); if (!void.class.equals(returnType) && !Void.class.equals(returnType)) { throw new LockingNotSupportedException(); } - Optional lockConfiguration = lockConfigurationExtractor.getLockConfiguration(context.getMethod()); + Optional lockConfiguration = + lockConfigurationExtractor.getLockConfiguration(context.getMethod()); if (lockConfiguration.isPresent()) { lockingTaskExecutor.executeWithLock((LockingTaskExecutor.Task) context::proceed, lockConfiguration.get()); return null; diff --git a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java similarity index 85% rename from cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java rename to cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java index 3ae189000..837baff1f 100644 --- a/cdi/shedlock-cdi-vintage/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/Utils.java @@ -2,8 +2,11 @@ import java.time.Duration; import java.time.format.DateTimeParseException; +import net.javacrumbs.shedlock.support.annotation.Nullable; class Utils { + + @Nullable static Duration parseDuration(String value) { value = value.trim(); if (value.isEmpty()) { diff --git a/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/package-info.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/package-info.java new file mode 100644 index 000000000..6865654a6 --- /dev/null +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/internal/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.cdi.internal; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/package-info.java b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/package-info.java similarity index 80% rename from shedlock-core/src/main/java/net/javacrumbs/shedlock/package-info.java rename to cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/package-info.java index 5d0f38461..377f69785 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/package-info.java +++ b/cdi/shedlock-cdi/src/main/java/net/javacrumbs/shedlock/cdi/package-info.java @@ -1,6 +1,6 @@ @NonNullApi @NonNullFields -package net.javacrumbs.shedlock; +package net.javacrumbs.shedlock.cdi; import net.javacrumbs.shedlock.support.annotation.NonNullApi; import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/cdi/shedlock-cdi/src/main/resources/META-INF/beans.xml b/cdi/shedlock-cdi/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..e312fca4d --- /dev/null +++ b/cdi/shedlock-cdi/src/main/resources/META-INF/beans.xml @@ -0,0 +1,3 @@ + + diff --git a/cdi/test/quarkus-test/pom.xml b/cdi/test/quarkus-test/pom.xml index 820ab72d3..af9fa16e7 100644 --- a/cdi/test/quarkus-test/pom.xml +++ b/cdi/test/quarkus-test/pom.xml @@ -1,21 +1,20 @@ - + shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 quarkus-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} quarkus-bom io.quarkus.platform - 2.14.1.Final + 3.25.3 true @@ -33,7 +32,7 @@ net.javacrumbs.shedlock - shedlock-cdi-vintage + shedlock-cdi ${project.version} @@ -44,15 +43,10 @@ io.quarkus quarkus-scheduler - - net.javacrumbs.shedlock - shedlock-provider-jdbc - 4.42.0 - org.testcontainers postgresql - 1.17.6 + 1.21.3 io.quarkus diff --git a/cdi/test/quarkus-test/src/main/java/QuarkusProdConfig.java b/cdi/test/quarkus-test/src/main/java/QuarkusProdConfig.java new file mode 100644 index 000000000..89bb78d25 --- /dev/null +++ b/cdi/test/quarkus-test/src/main/java/QuarkusProdConfig.java @@ -0,0 +1,17 @@ +import io.quarkus.arc.DefaultBean; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockProvider; + +/* + * Just to make the build to pass + */ +public class QuarkusProdConfig { + @Produces + @Singleton + @DefaultBean + public LockProvider lockProvider() { + return lockConfiguration -> Optional.empty(); + } +} diff --git a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusConfig.java b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusConfig.java index a1ba035a6..27fe2a8ea 100644 --- a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusConfig.java +++ b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusConfig.java @@ -1,41 +1,38 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.quarkus.test; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; +import static org.mockito.Mockito.mock; +import io.quarkus.arc.profile.IfBuildProfile; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; +import java.io.IOException; import net.javacrumbs.shedlock.cdi.SchedulerLock; import net.javacrumbs.shedlock.core.LockProvider; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; -import javax.inject.Singleton; -import java.io.IOException; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; -import static org.mockito.Mockito.mock; - public class QuarkusConfig { @Produces @Singleton + @IfBuildProfile("test") public LockProvider lockProvider() { return mock(LockProvider.class); } - @ApplicationScoped static class TestBean { @@ -64,12 +61,9 @@ public int returnsValue() { } @SchedulerLock(name = "${property.value}", lockAtLeastFor = "${property.lock-at-least-for}") - public void property() { - - } + public void property() {} } - interface AnotherTestBean { void runManually(); } @@ -79,8 +73,6 @@ static class AnotherTestBeanImpl implements AnotherTestBean { @Override @SchedulerLock(name = "classAnnotation") - public void runManually() { - - } + public void runManually() {} } } diff --git a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusShedlockTest.java b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusShedlockTest.java index 6f556634e..bec2a4fa4 100644 --- a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusShedlockTest.java +++ b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/QuarkusShedlockTest.java @@ -1,21 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.quarkus.test; +import static net.javacrumbs.shedlock.quarkus.test.TestUtils.hasParams; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import java.io.IOException; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.quarkus.test.QuarkusConfig.AnotherTestBean; @@ -26,19 +35,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import javax.inject.Inject; -import java.io.IOException; -import java.util.Optional; - -import static net.javacrumbs.shedlock.quarkus.test.TestUtils.hasParams; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - - @QuarkusTest class QuarkusShedlockTest { @Inject @@ -92,7 +88,9 @@ void shouldFailOnReturnType() { } @Test - @Disabled // Not implemented, waiting if anyone is going to use it. When needed, get the code from Quarkus SchedulerUtils + @Disabled // Not implemented, waiting if anyone is going to use it. When needed, get the + // code from + // Quarkus SchedulerUtils void shouldReadConfigurationProperty() { testBean.property(); verify(lockProvider).lock(hasParams("property", 30_000, 1_000)); diff --git a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/TestUtils.java b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/TestUtils.java index 5a76b6c19..ff5c0dae3 100644 --- a/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/TestUtils.java +++ b/cdi/test/quarkus-test/src/test/java/net/javacrumbs/shedlock/quarkus/test/TestUtils.java @@ -1,30 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.quarkus.test; +import static org.mockito.ArgumentMatchers.argThat; + +import java.time.Instant; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import org.mockito.ArgumentMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; - -import static org.mockito.ArgumentMatchers.argThat; - public class TestUtils { private static final int GAP = 1000; @@ -36,14 +33,15 @@ public static LockConfiguration hasParams(String name, long lockAtMostFor, long @Override public boolean matches(LockConfiguration c) { return name.equals(c.getName()) - && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) - && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); + && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) + && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); } @Override public String toString() { Instant now = ClockProvider.now(); - return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + now.plusMillis(lockAtLeastFor) + ")"; + return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + + now.plusMillis(lockAtLeastFor) + ")"; } }); } diff --git a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java b/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java deleted file mode 100644 index 5967da724..000000000 --- a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.micronaut; - - -import io.micronaut.aop.Around; -import io.micronaut.context.annotation.Executable; -import io.micronaut.context.annotation.Type; -import net.javacrumbs.shedlock.micronaut.internal.SchedulerLockInterceptor; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Documented -@Retention(RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -@Around -@Executable -@Type(SchedulerLockInterceptor.class) -public @interface SchedulerLock { - /** - * Lock name. - */ - String name(); - - /** - * How long the lock should be kept in case the machine which obtained the lock died before releasing it. - * This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes. Can be any format - * supported by Duration Conversion - *

- */ - String lockAtMostFor() default ""; - - /** - * The lock will be held at least for this period of time. Can be used if you really need to execute the task - * at most once in given period of time. If the duration of the task is shorter than clock difference between nodes, the task can - * be theoretically executed more than once (one node after another). By setting this parameter, you can make sure that the - * lock will be kept at least for given period of time. Can be any format - * supported by Duration Conversion - */ - String lockAtLeastFor() default ""; -} diff --git a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java b/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java deleted file mode 100644 index a9f7f7df7..000000000 --- a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.micronaut.internal; - -import net.javacrumbs.shedlock.support.LockException; - -class LockingNotSupportedException extends LockException { - LockingNotSupportedException() { - super("Can not lock method returning value (do not know what to return if it's locked)"); - } -} diff --git a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/SchedulerLockInterceptor.java b/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/SchedulerLockInterceptor.java deleted file mode 100644 index 0c03592ee..000000000 --- a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/SchedulerLockInterceptor.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.micronaut.internal; - -import io.micronaut.aop.MethodInterceptor; -import io.micronaut.aop.MethodInvocationContext; -import io.micronaut.context.annotation.Value; -import io.micronaut.core.convert.ConversionService; -import jakarta.inject.Singleton; -import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.core.LockingTaskExecutor; - -import java.time.Duration; -import java.util.Optional; - -@Singleton -public class SchedulerLockInterceptor implements MethodInterceptor { - private final LockingTaskExecutor lockingTaskExecutor; - private final MicronautLockConfigurationExtractor micronautLockConfigurationExtractor; - - public SchedulerLockInterceptor( - LockProvider lockProvider, - Optional> conversionService, - @Value("${shedlock.defaults.lock-at-most-for}") String defaultLockAtMostFor, - @Value("${shedlock.defaults.lock-at-least-for:PT0S}") String defaultLockAtLeastFor - ) { - ConversionService resolvedConversionService = conversionService.orElse(ConversionService.SHARED); - - lockingTaskExecutor = new DefaultLockingTaskExecutor(lockProvider); - micronautLockConfigurationExtractor = new MicronautLockConfigurationExtractor( - resolvedConversionService.convert(defaultLockAtMostFor, Duration.class).orElseThrow(() -> new IllegalArgumentException("Invalid 'defaultLockAtMostFor' value")), - resolvedConversionService.convert(defaultLockAtLeastFor, Duration.class).orElseThrow(() -> new IllegalArgumentException("Invalid 'defaultLockAtLeastFor' value")), - resolvedConversionService); - } - - @Override - public Object intercept(MethodInvocationContext context) { - Class returnType = context.getReturnType().getType(); - if (!void.class.equals(returnType) && !Void.class.equals(returnType)) { - throw new LockingNotSupportedException(); - } - - Optional lockConfiguration = micronautLockConfigurationExtractor.getLockConfiguration(context.getExecutableMethod()); - if (lockConfiguration.isPresent()) { - lockingTaskExecutor.executeWithLock((Runnable) context::proceed, lockConfiguration.get()); - return null; - } else { - return context.proceed(); - } - } -} diff --git a/micronaut/shedlock-micronaut/micronaut-cli.yml b/micronaut/shedlock-micronaut4/micronaut-cli.yml similarity index 100% rename from micronaut/shedlock-micronaut/micronaut-cli.yml rename to micronaut/shedlock-micronaut4/micronaut-cli.yml diff --git a/micronaut/shedlock-micronaut/pom.xml b/micronaut/shedlock-micronaut4/pom.xml similarity index 81% rename from micronaut/shedlock-micronaut/pom.xml rename to micronaut/shedlock-micronaut4/pom.xml index 331eac5b6..4631c6ddb 100644 --- a/micronaut/shedlock-micronaut/pom.xml +++ b/micronaut/shedlock-micronaut4/pom.xml @@ -5,19 +5,19 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../pom.xml - shedlock-micronaut - 5.0.0-SNAPSHOT + shedlock-micronaut4 + ${project.groupId}:${project.artifactId} - io.micronaut - micronaut-bom - ${micronaut.ver} + io.micronaut.platform + micronaut-platform + ${micronaut4.version} pom import @@ -40,6 +40,17 @@ micronaut-runtime compile + + org.yaml + snakeyaml + runtime + + + ch.qos.logback + logback-core + ${logback.ver} + test + ch.qos.logback logback-classic @@ -84,7 +95,7 @@ - net.javacrumbs.shedlock.micronaut + net.javacrumbs.shedlock.micronaut4 @@ -104,12 +115,12 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} @@ -127,12 +138,12 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} diff --git a/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java new file mode 100644 index 000000000..5384bacde --- /dev/null +++ b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/SchedulerLock.java @@ -0,0 +1,60 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.micronaut; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import io.micronaut.aop.Around; +import io.micronaut.context.annotation.Executable; +import io.micronaut.context.annotation.Type; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import net.javacrumbs.shedlock.micronaut.internal.SchedulerLockInterceptor; + +@Documented +@Retention(RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Around +@Executable +@Type(SchedulerLockInterceptor.class) +public @interface SchedulerLock { + /** Lock name. */ + String name(); + + /** + * How long the lock should be kept in case the machine which obtained the lock + * died before releasing it. This is just a fallback, under normal circumstances + * the lock is released as soon the tasks finishes. Can be any format supported + * by Duration + * Conversion + * + *

+ */ + String lockAtMostFor() default ""; + + /** + * The lock will be held at least for this period of time. Can be used if you + * really need to execute the task at most once in given period of time. If the + * duration of the task is shorter than clock difference between nodes, the task + * can be theoretically executed more than once (one node after another). By + * setting this parameter, you can make sure that the lock will be kept at least + * for given period of time. Can be any format supported by Duration + * Conversion + */ + String lockAtLeastFor() default ""; +} diff --git a/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java new file mode 100644 index 000000000..fdd16259c --- /dev/null +++ b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/LockingNotSupportedException.java @@ -0,0 +1,22 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.micronaut.internal; + +import net.javacrumbs.shedlock.support.LockException; + +class LockingNotSupportedException extends LockException { + LockingNotSupportedException() { + super("Can not lock method returning value (do not know what to return if it's locked)"); + } +} diff --git a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java similarity index 58% rename from micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java rename to micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java index 47322919e..b8317c79a 100644 --- a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java +++ b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/MicronautLockConfigurationExtractor.java @@ -1,45 +1,42 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.micronaut.internal; +import static java.util.Objects.requireNonNull; + import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.convert.ConversionService; import io.micronaut.core.util.StringUtils; import io.micronaut.inject.ExecutableMethod; +import java.time.Duration; +import java.util.Optional; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.micronaut.SchedulerLock; -import java.time.Duration; -import java.util.Optional; - -import static java.util.Objects.requireNonNull; - class MicronautLockConfigurationExtractor { private final Duration defaultLockAtMostFor; private final Duration defaultLockAtLeastFor; - private final ConversionService conversionService; + private final ConversionService conversionService; - MicronautLockConfigurationExtractor(Duration defaultLockAtMostFor, Duration defaultLockAtLeastFor, ConversionService conversionService) { + MicronautLockConfigurationExtractor( + Duration defaultLockAtMostFor, Duration defaultLockAtLeastFor, ConversionService conversionService) { this.defaultLockAtMostFor = requireNonNull(defaultLockAtMostFor); this.defaultLockAtLeastFor = requireNonNull(defaultLockAtLeastFor); this.conversionService = conversionService; } - Optional getLockConfiguration(ExecutableMethod method) { Optional> annotation = findAnnotation(method); return annotation.map(this::getLockConfiguration); @@ -47,11 +44,7 @@ Optional getLockConfiguration(ExecutableMethod annotation) { return new LockConfiguration( - ClockProvider.now(), - getName(annotation), - getLockAtMostFor(annotation), - getLockAtLeastFor(annotation) - ); + ClockProvider.now(), getName(annotation), getLockAtMostFor(annotation), getLockAtLeastFor(annotation)); } private String getName(AnnotationValue annotation) { @@ -59,26 +52,21 @@ private String getName(AnnotationValue annotation) { } Duration getLockAtMostFor(AnnotationValue annotation) { - return getValue( - annotation, - this.defaultLockAtMostFor, - "lockAtMostFor" - ); + return getValue(annotation, this.defaultLockAtMostFor, "lockAtMostFor"); } Duration getLockAtLeastFor(AnnotationValue annotation) { - return getValue( - annotation, - this.defaultLockAtLeastFor, - "lockAtLeastFor" - ); + return getValue(annotation, this.defaultLockAtLeastFor, "lockAtLeastFor"); } private Duration getValue(AnnotationValue annotation, Duration defaultValue, String paramName) { - String stringValueFromAnnotation = annotation.get(paramName, String.class).orElse(""); + String stringValueFromAnnotation = + annotation.get(paramName, String.class).orElse(""); if (StringUtils.hasText(stringValueFromAnnotation)) { - return conversionService.convert(stringValueFromAnnotation, Duration.class) - .orElseThrow(() -> new IllegalArgumentException("Invalid " + paramName + " value \"" + stringValueFromAnnotation + "\" - cannot parse into duration")); + return conversionService + .convert(stringValueFromAnnotation, Duration.class) + .orElseThrow(() -> new IllegalArgumentException("Invalid " + paramName + " value \"" + + stringValueFromAnnotation + "\" - cannot parse into duration")); } else { return defaultValue; } @@ -88,5 +76,3 @@ Optional> findAnnotation(ExecutableMethodLicensed 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 net.javacrumbs.shedlock.micronaut.internal; + +import io.micronaut.aop.MethodInterceptor; +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.context.annotation.Value; +import io.micronaut.core.convert.ConversionService; +import jakarta.inject.Singleton; +import java.time.Duration; +import java.util.Optional; +import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.LockingTaskExecutor; +import net.javacrumbs.shedlock.support.annotation.Nullable; + +@Singleton +public class SchedulerLockInterceptor implements MethodInterceptor { + private final LockingTaskExecutor lockingTaskExecutor; + private final MicronautLockConfigurationExtractor micronautLockConfigurationExtractor; + + public SchedulerLockInterceptor( + LockProvider lockProvider, + Optional conversionService, + @Value("${shedlock.defaults.lock-at-most-for}") String defaultLockAtMostFor, + @Value("${shedlock.defaults.lock-at-least-for:PT0S}") String defaultLockAtLeastFor) { + /* + * From Micronaut 3 to 4, ConversionService changes from a parameterized type to + * a non-parameterized one, so some raw type usage and unchecked casts are done + * to support both Micronaut versions. + */ + ConversionService resolvedConversionService = conversionService.orElse(ConversionService.SHARED); + + lockingTaskExecutor = new DefaultLockingTaskExecutor(lockProvider); + + micronautLockConfigurationExtractor = new MicronautLockConfigurationExtractor( + convert(resolvedConversionService, defaultLockAtMostFor, "defaultLockAtMostFor"), + convert(resolvedConversionService, defaultLockAtLeastFor, "defaultLockAtLeastFor"), + resolvedConversionService); + } + + private static Duration convert( + ConversionService resolvedConversionService, String defaultLockAtMostFor, String label) { + return resolvedConversionService + .convert(defaultLockAtMostFor, Duration.class) + .orElseThrow(() -> new IllegalArgumentException("Invalid '" + label + "' value")); + } + + @Override + @Nullable + public Object intercept(MethodInvocationContext context) { + Class returnType = context.getReturnType().getType(); + if (!void.class.equals(returnType) && !Void.class.equals(returnType)) { + throw new LockingNotSupportedException(); + } + + Optional lockConfiguration = + micronautLockConfigurationExtractor.getLockConfiguration(context.getExecutableMethod()); + if (lockConfiguration.isPresent()) { + lockingTaskExecutor.executeWithLock((Runnable) context::proceed, lockConfiguration.get()); + return null; + } else { + return context.proceed(); + } + } +} diff --git a/micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/package-info.java b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/package-info.java similarity index 100% rename from micronaut/shedlock-micronaut/src/main/java/net/javacrumbs/shedlock/micronaut/internal/package-info.java rename to micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/internal/package-info.java diff --git a/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/package-info.java b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/package-info.java new file mode 100644 index 000000000..c874bcce6 --- /dev/null +++ b/micronaut/shedlock-micronaut4/src/main/java/net/javacrumbs/shedlock/micronaut/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.micronaut; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java similarity index 72% rename from micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java rename to micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java index d75966688..0a66a7d3e 100644 --- a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java +++ b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopConfig.java @@ -1,31 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.micronaut.internal; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; +import static org.mockito.Mockito.mock; import io.micronaut.context.annotation.Factory; import jakarta.inject.Singleton; +import java.io.IOException; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.micronaut.SchedulerLock; -import java.io.IOException; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; -import static org.mockito.Mockito.mock; - @Factory public class MethodProxyAopConfig { @@ -34,7 +30,6 @@ public LockProvider lockProvider() { return mock(LockProvider.class); } - @Singleton static class TestBean { @@ -63,12 +58,9 @@ public int returnsValue() { } @SchedulerLock(name = "${property.value}", lockAtLeastFor = "${property.lock-at-least-for}") - public void property() { - - } + public void property() {} } - interface AnotherTestBean { void runManually(); } @@ -78,8 +70,6 @@ static class AnotherTestBeanImpl implements AnotherTestBean { @Override @SchedulerLock(name = "classAnnotation") - public void runManually() { - - } + public void runManually() {} } } diff --git a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java similarity index 85% rename from micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java rename to micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java index c8eddf133..9e68726a7 100644 --- a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java +++ b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/MethodProxyAopTest.java @@ -1,22 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.micronaut.internal; +import static net.javacrumbs.shedlock.micronaut.internal.TestUtils.hasParams; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; +import java.io.IOException; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.micronaut.internal.MethodProxyAopConfig.AnotherTestBean; @@ -25,18 +33,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.io.IOException; -import java.util.Optional; - -import static net.javacrumbs.shedlock.micronaut.internal.TestUtils.hasParams; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - - @MicronautTest class MethodProxyAopTest { @Inject diff --git a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java similarity index 66% rename from micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java rename to micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java index 66b9f743e..d5306f077 100644 --- a/micronaut/shedlock-micronaut/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java +++ b/micronaut/shedlock-micronaut4/src/test/java/net/javacrumbs/shedlock/micronaut/internal/TestUtils.java @@ -1,30 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.micronaut.internal; +import static org.mockito.ArgumentMatchers.argThat; + +import java.time.Instant; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import org.mockito.ArgumentMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; - -import static org.mockito.ArgumentMatchers.argThat; - public class TestUtils { private static final int GAP = 1000; @@ -36,14 +33,15 @@ public static LockConfiguration hasParams(String name, long lockAtMostFor, long @Override public boolean matches(LockConfiguration c) { return name.equals(c.getName()) - && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) - && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); + && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) + && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); } @Override public String toString() { Instant now = ClockProvider.now(); - return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + now.plusMillis(lockAtLeastFor) + ")"; + return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + + now.plusMillis(lockAtLeastFor) + ")"; } }); } diff --git a/micronaut/shedlock-micronaut/src/test/resources/application.yml b/micronaut/shedlock-micronaut4/src/test/resources/application.yml similarity index 100% rename from micronaut/shedlock-micronaut/src/test/resources/application.yml rename to micronaut/shedlock-micronaut4/src/test/resources/application.yml diff --git a/micronaut/test/micronaut-jdbc-new/pom.xml b/micronaut/test/micronaut-jdbc-new/pom.xml deleted file mode 100644 index fa6a2da80..000000000 --- a/micronaut/test/micronaut-jdbc-new/pom.xml +++ /dev/null @@ -1,229 +0,0 @@ - - - 4.0.0 - - - shedlock-parent - net.javacrumbs.shedlock - 4.42.1-SNAPSHOT - ../../../pom.xml - - - micronaut-jdbc-new - 4.42.1-SNAPSHOT - - - UTF-8 - net.javacrumbs.micronaut.test2.Application - 3.0.0 - - - - jcenter.bintray.com - https://jcenter.bintray.com - - - - - - io.micronaut - micronaut-bom - ${micronaut.version} - pom - import - - - - - - io.micronaut - micronaut-inject - compile - - - io.micronaut - micronaut-validation - compile - - - io.micronaut - micronaut-runtime - compile - - - io.micronaut.liquibase - micronaut-liquibase - - - io.micronaut.data - micronaut-data-jdbc - ${micronaut-data.ver} - - - ch.qos.logback - logback-classic - ${logback.ver} - runtime - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - io.micronaut.test - micronaut-test-junit5 - test - - - net.javacrumbs.shedlock - shedlock-micronaut - ${project.version} - compile - - - - net.javacrumbs.shedlock - shedlock-provider-jdbc-micronaut - ${project.version} - compile - - - - io.micronaut.sql - micronaut-jdbc-hikari - runtime - - - - com.h2database - h2 - 2.1.214 - - - org.assertj - assertj-core - ${assertj.ver} - test - - - org.awaitility - awaitility - ${awaitility.ver} - test - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.1.0 - - java - - -noverify - -XX:TieredStopAtLevel=1 - -Dcom.sun.management.jmxremote - -classpath - - ${exec.mainClass} - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - ${jdk.version} - ${jdk.version} - UTF-8 - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - io.micronaut.data - micronaut-data-processor - ${micronaut-data.ver} - - - - - - test-compile - - testCompile - - - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - - diff --git a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java b/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java deleted file mode 100644 index b14c34d97..000000000 --- a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.micronaut.test; - -import io.micronaut.runtime.Micronaut; - -public class Application { - - public static void main(String[] args) { - Micronaut.run(Application.class); - } -} \ No newline at end of file diff --git a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java b/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java deleted file mode 100644 index 045212472..000000000 --- a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.micronaut.test; - -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; - -import javax.sql.DataSource; - -@Factory -public class Configuration { - - @Singleton - public LockProvider lockProvider(DataSource dataSource) { - return new JdbcTemplateLockProvider(dataSource); - } -} diff --git a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java b/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java deleted file mode 100644 index b14c34d97..000000000 --- a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.micronaut.test; - -import io.micronaut.runtime.Micronaut; - -public class Application { - - public static void main(String[] args) { - Micronaut.run(Application.class); - } -} \ No newline at end of file diff --git a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java b/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java deleted file mode 100644 index fdc92e0fc..000000000 --- a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.micronaut.test; - -import io.micronaut.context.annotation.Factory; -import io.micronaut.transaction.TransactionOperations; -import jakarta.inject.Singleton; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.provider.jdbc.micronaut.MicronautJdbcLockProvider; - -import java.sql.Connection; - -@Factory -public class Configuration { - - @Singleton - public LockProvider lockProvider(TransactionOperations transactionManager) { - return new MicronautJdbcLockProvider(transactionManager); - } -} diff --git a/micronaut/test/micronaut-jdbc-template/micronaut-cli.yml b/micronaut/test/micronaut4-jdbc-template/micronaut-cli.yml similarity index 100% rename from micronaut/test/micronaut-jdbc-template/micronaut-cli.yml rename to micronaut/test/micronaut4-jdbc-template/micronaut-cli.yml diff --git a/micronaut/test/micronaut-jdbc-template/pom.xml b/micronaut/test/micronaut4-jdbc-template/pom.xml similarity index 76% rename from micronaut/test/micronaut-jdbc-template/pom.xml rename to micronaut/test/micronaut4-jdbc-template/pom.xml index e9a86793d..e83306bc9 100644 --- a/micronaut/test/micronaut-jdbc-template/pom.xml +++ b/micronaut/test/micronaut4-jdbc-template/pom.xml @@ -5,12 +5,12 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml - micronaut-jdbc-template - 5.0.0-SNAPSHOT + micronaut4-jdbc-template + ${project.groupId}:${project.artifactId} UTF-8 @@ -25,9 +25,9 @@ - io.micronaut - micronaut-bom - ${micronaut.ver} + io.micronaut.platform + micronaut-platform + ${micronaut4.version} pom import @@ -40,7 +40,7 @@ compile - io.micronaut + io.micronaut.validation micronaut-validation compile @@ -55,14 +55,11 @@ io.micronaut.data - micronaut-data-jdbc - ${micronaut-data.ver} + micronaut-data-spring-jdbc ch.qos.logback logback-classic - ${logback.ver} - runtime org.junit.jupiter @@ -81,7 +78,7 @@ net.javacrumbs.shedlock - shedlock-micronaut + shedlock-micronaut4 ${project.version} compile @@ -102,7 +99,7 @@ com.h2database h2 - 2.1.214 + 2.3.232 org.assertj @@ -119,31 +116,10 @@ - - org.apache.maven.plugins - maven-shade-plugin - 3.4.1 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.5.1 java @@ -162,7 +138,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.14.0 ${jdk.version} ${jdk.version} @@ -174,17 +150,17 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} io.micronaut.data micronaut-data-processor - ${micronaut-data.ver} + ${micronaut4-data.version} @@ -202,12 +178,12 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} diff --git a/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java new file mode 100644 index 000000000..f058451a3 --- /dev/null +++ b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Application.java @@ -0,0 +1,23 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.micronaut.test; + +import io.micronaut.runtime.Micronaut; + +public class Application { + + public static void main(String[] args) { + Micronaut.run(Application.class); + } +} diff --git a/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java new file mode 100644 index 000000000..1eba19b9f --- /dev/null +++ b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/Configuration.java @@ -0,0 +1,31 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.micronaut.test; + +import io.micronaut.context.annotation.Factory; +import jakarta.inject.Singleton; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +@Factory +public class Configuration { + + @Singleton + public LockProvider lockProvider(DataSource dataSource, PlatformTransactionManager transactionManager) { + return new JdbcTemplateLockProvider(new JdbcTemplate(dataSource), transactionManager); + } +} diff --git a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java similarity index 61% rename from micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java rename to micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java index 42410580e..701cd6623 100644 --- a/micronaut/test/micronaut-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java +++ b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.micronaut.test; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; + import io.micronaut.scheduling.annotation.Scheduled; import jakarta.inject.Singleton; -import net.javacrumbs.shedlock.micronaut.SchedulerLock; - import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; +import net.javacrumbs.shedlock.micronaut.SchedulerLock; @Singleton public class ScheduledTasks { diff --git a/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/package-info.java b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/package-info.java new file mode 100644 index 000000000..bc49d14f8 --- /dev/null +++ b/micronaut/test/micronaut4-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.micronaut.test; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/micronaut/test/micronaut-jdbc-template/src/main/resources/application.yml b/micronaut/test/micronaut4-jdbc-template/src/main/resources/application.yml similarity index 100% rename from micronaut/test/micronaut-jdbc-template/src/main/resources/application.yml rename to micronaut/test/micronaut4-jdbc-template/src/main/resources/application.yml diff --git a/micronaut/test/micronaut-jdbc-template/src/main/resources/db/liquibase-changelog.xml b/micronaut/test/micronaut4-jdbc-template/src/main/resources/db/liquibase-changelog.xml similarity index 100% rename from micronaut/test/micronaut-jdbc-template/src/main/resources/db/liquibase-changelog.xml rename to micronaut/test/micronaut4-jdbc-template/src/main/resources/db/liquibase-changelog.xml diff --git a/micronaut/test/micronaut-jdbc-template/src/main/resources/logback.xml b/micronaut/test/micronaut4-jdbc-template/src/main/resources/logback.xml similarity index 93% rename from micronaut/test/micronaut-jdbc-template/src/main/resources/logback.xml rename to micronaut/test/micronaut4-jdbc-template/src/main/resources/logback.xml index b8844d63e..050c0dc7c 100644 --- a/micronaut/test/micronaut-jdbc-template/src/main/resources/logback.xml +++ b/micronaut/test/micronaut4-jdbc-template/src/main/resources/logback.xml @@ -1,7 +1,6 @@ - true diff --git a/micronaut/test/micronaut-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java b/micronaut/test/micronaut4-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java similarity index 54% rename from micronaut/test/micronaut-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java rename to micronaut/test/micronaut4-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java index bb303376c..fafae5461 100644 --- a/micronaut/test/micronaut-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java +++ b/micronaut/test/micronaut4-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java @@ -1,27 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.micronaut.test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - @MicronautTest public class ApplicationTest { @Inject diff --git a/micronaut/test/micronaut-jdbc/micronaut-cli.yml b/micronaut/test/micronaut4-jdbc/micronaut-cli.yml similarity index 100% rename from micronaut/test/micronaut-jdbc/micronaut-cli.yml rename to micronaut/test/micronaut4-jdbc/micronaut-cli.yml diff --git a/micronaut/test/micronaut-jdbc/pom.xml b/micronaut/test/micronaut4-jdbc/pom.xml similarity index 77% rename from micronaut/test/micronaut-jdbc/pom.xml rename to micronaut/test/micronaut4-jdbc/pom.xml index 97a8c5dfe..ddeb68dc7 100644 --- a/micronaut/test/micronaut-jdbc/pom.xml +++ b/micronaut/test/micronaut4-jdbc/pom.xml @@ -5,12 +5,12 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml - micronaut-jdbc - 5.0.0-SNAPSHOT + micronaut4-jdbc + ${project.groupId}:${project.artifactId} UTF-8 @@ -25,9 +25,9 @@ - io.micronaut - micronaut-bom - ${micronaut.ver} + io.micronaut.platform + micronaut-platform + ${micronaut4.version} pom import @@ -40,7 +40,7 @@ compile - io.micronaut + io.micronaut.validation micronaut-validation compile @@ -56,13 +56,10 @@ io.micronaut.data micronaut-data-jdbc - ${micronaut-data.ver} ch.qos.logback logback-classic - ${logback.ver} - runtime org.junit.jupiter @@ -81,7 +78,7 @@ net.javacrumbs.shedlock - shedlock-micronaut + shedlock-micronaut4 ${project.version} compile @@ -102,7 +99,7 @@ com.h2database h2 - 2.1.214 + 2.3.232 org.assertj @@ -119,31 +116,10 @@ - - org.apache.maven.plugins - maven-shade-plugin - 3.4.1 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.5.1 java @@ -162,7 +138,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.14.0 ${jdk.version} ${jdk.version} @@ -174,17 +150,17 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} io.micronaut.data micronaut-data-processor - ${micronaut-data.ver} + ${micronaut4-data.version} @@ -202,12 +178,12 @@ io.micronaut micronaut-inject-java - ${micronaut.ver} + ${micronaut4.version} - io.micronaut + io.micronaut.validation micronaut-validation - ${micronaut.ver} + ${micronaut4.validation.version} diff --git a/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java new file mode 100644 index 000000000..f058451a3 --- /dev/null +++ b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Application.java @@ -0,0 +1,23 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.micronaut.test; + +import io.micronaut.runtime.Micronaut; + +public class Application { + + public static void main(String[] args) { + Micronaut.run(Application.class); + } +} diff --git a/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java new file mode 100644 index 000000000..e88ef74bd --- /dev/null +++ b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/Configuration.java @@ -0,0 +1,30 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.micronaut.test; + +import io.micronaut.context.annotation.Factory; +import io.micronaut.transaction.TransactionOperations; +import jakarta.inject.Singleton; +import java.sql.Connection; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbc.micronaut.MicronautJdbcLockProvider; + +@Factory +public class Configuration { + + @Singleton + public LockProvider lockProvider(TransactionOperations transactionOperations) { + return new MicronautJdbcLockProvider(transactionOperations); + } +} diff --git a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java similarity index 61% rename from micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java rename to micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java index 42410580e..701cd6623 100644 --- a/micronaut/test/micronaut-jdbc-template/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java +++ b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/ScheduledTasks.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.micronaut.test; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; + import io.micronaut.scheduling.annotation.Scheduled; import jakarta.inject.Singleton; -import net.javacrumbs.shedlock.micronaut.SchedulerLock; - import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; +import net.javacrumbs.shedlock.micronaut.SchedulerLock; @Singleton public class ScheduledTasks { diff --git a/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/package-info.java b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/package-info.java new file mode 100644 index 000000000..bc49d14f8 --- /dev/null +++ b/micronaut/test/micronaut4-jdbc/src/main/java/net/javacrumbs/micronaut/test/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.micronaut.test; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/micronaut/test/micronaut-jdbc/src/main/resources/application.yml b/micronaut/test/micronaut4-jdbc/src/main/resources/application.yml similarity index 100% rename from micronaut/test/micronaut-jdbc/src/main/resources/application.yml rename to micronaut/test/micronaut4-jdbc/src/main/resources/application.yml diff --git a/micronaut/test/micronaut-jdbc/src/main/resources/db/liquibase-changelog.xml b/micronaut/test/micronaut4-jdbc/src/main/resources/db/liquibase-changelog.xml similarity index 100% rename from micronaut/test/micronaut-jdbc/src/main/resources/db/liquibase-changelog.xml rename to micronaut/test/micronaut4-jdbc/src/main/resources/db/liquibase-changelog.xml diff --git a/micronaut/test/micronaut-jdbc/src/main/resources/logback.xml b/micronaut/test/micronaut4-jdbc/src/main/resources/logback.xml similarity index 93% rename from micronaut/test/micronaut-jdbc/src/main/resources/logback.xml rename to micronaut/test/micronaut4-jdbc/src/main/resources/logback.xml index b8844d63e..050c0dc7c 100644 --- a/micronaut/test/micronaut-jdbc/src/main/resources/logback.xml +++ b/micronaut/test/micronaut4-jdbc/src/main/resources/logback.xml @@ -1,7 +1,6 @@ - true diff --git a/micronaut/test/micronaut-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java b/micronaut/test/micronaut4-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java similarity index 54% rename from micronaut/test/micronaut-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java rename to micronaut/test/micronaut4-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java index bb303376c..fafae5461 100644 --- a/micronaut/test/micronaut-jdbc-template/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java +++ b/micronaut/test/micronaut4-jdbc/src/test/java/net/javacrumbs/micronaut/test/ApplicationTest.java @@ -1,27 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.micronaut.test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - @MicronautTest public class ApplicationTest { @Inject diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..19529ddf8 --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..249bdf382 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index e18711721..c3ea47c37 100644 --- a/pom.xml +++ b/pom.xml @@ -18,16 +18,17 @@ net.javacrumbs.shedlock shedlock-parent + ShedLock pom - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT shedlock-bom shedlock-core - cdi/shedlock-cdi-vintage + cdi/shedlock-cdi cdi/test/quarkus-test - micronaut/shedlock-micronaut - micronaut/test/micronaut-jdbc - micronaut/test/micronaut-jdbc-template + micronaut/shedlock-micronaut4 + micronaut/test/micronaut4-jdbc + micronaut/test/micronaut4-jdbc-template spring/shedlock-spring spring/test/shedlock-springboot-old-test spring/test/shedlock-springboot-test @@ -42,17 +43,22 @@ providers/jdbc/shedlock-provider-jdbc-internal providers/jdbc/shedlock-provider-jdbc providers/jdbc/shedlock-provider-jooq + providers/jdbc/shedlock-provider-exposed providers/jdbc/shedlock-provider-jdbc-template providers/jdbc/shedlock-provider-jdbc-micronaut providers/r2dbc/shedlock-provider-r2dbc providers/mongo/shedlock-provider-mongo providers/mongo/shedlock-provider-mongo-reactivestreams - providers/neo4j/shedlock-provider-neo4j providers/elasticsearch/shedlock-provider-elasticsearch8 + providers/elasticsearch/shedlock-provider-elasticsearch9 providers/opensearch/shedlock-provider-opensearch + providers/opensearch/shedlock-provider-opensearch-java providers/couchbase/shedlock-provider-couchbase-javaclient3 providers/zookeeper/shedlock-provider-zookeeper-curator + providers/redis/shedlock-support-redis + providers/redis/shedlock-test-support-redis providers/redis/shedlock-provider-redis-jedis4 + providers/redis/shedlock-provider-redis-lettuce providers/redis/shedlock-provider-redis-spring providers/dynamodb/shedlock-provider-dynamodb2 providers/cassandra/shedlock-provider-cassandra @@ -61,40 +67,71 @@ providers/ignite/shedlock-provider-ignite providers/inmemory/shedlock-provider-inmemory providers/memcached/shedlock-provider-memcached-spy + providers/datastore/shedlock-provider-datastore + providers/spanner/shedlock-provider-spanner + providers/neo4j/shedlock-provider-neo4j + providers/s3/shedlock-provider-s3 + providers/s3/shedlock-provider-s3v2 + providers/firestore/shedlock-provider-firestore - 6.0.2 + 6.2.10 UTF-8 - 11 - 3.0.0 - 5.9.1 - 1.17.6 - 1.7.20 - 3.5.1 - 3.4.2 - 1.4.5 - 4.2.0 - 3.23.1 - 4.9.0 - 2.0.4 + 17 + 3.5.5 + 1.21.3 + 2.2.10 + 4.9.3 + 4.10.0 + 4.8.1 + 1.5.18 + 4.3.0 + 3.27.4 + 5.19.0 + 2.0.17 + 1 + + com.diffplug.spotless + spotless-maven-plugin + 2.46.1 + + + 2.57.0 + + + + + 120 + + + + + + + spotless-check + validate + + check + + + + maven-compiler-plugin - 3.10.1 + 3.14.0 - ${jdk.version} - ${jdk.version} ${jdk.version} com.mycila license-maven-plugin - 4.1 + 5.0.0

header.txt
@@ -107,32 +144,71 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + 3.5.3 - 4 + ${fork.count} -Xmx512m org.apache.maven.plugins maven-jar-plugin - 3.3.0 + 3.4.2 org.apache.maven.plugins maven-javadoc-plugin - 3.4.1 + 3.11.3 - - 8 + false + + + attach-javadocs + package + + jar + + + org.apache.maven.plugins maven-deploy-plugin - 3.0.0 + 3.1.4 + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + central + true + + shedlock-springboot-future-test + shedlock-springboot-kotlin-test + shedlock-springboot-old-test + shedlock-springboot-sleuth-test + shedlock-springboot-test + shedlock-testng-test + quarkus-test + micronaut4-jdbc + micronaut4-jdbc-template + shedlock-test-support + shedlock-test-support-jdbc + shedlock-test-support-redis + + + + + org.apache.maven.extensions + maven-build-cache-extension + 1.2.0 + + @@ -142,6 +218,18 @@ + + + + org.junit + junit-bom + 5.13.4 + pom + import + + + + release-sign-artifacts @@ -156,10 +244,7 @@ org.apache.maven.plugins maven-gpg-plugin - - C51E10BD96D06071 - - 3.0.1 + 3.2.8 sign-artifacts @@ -173,7 +258,7 @@ org.codehaus.mojo versions-maven-plugin - 2.12.0 + 2.18.0 @@ -190,7 +275,7 @@ maven-surefire-plugin --illegal-access=permit - 4 + ${fork.count} -Xmx512m diff --git a/providers/arangodb/shedlock-provider-arangodb/pom.xml b/providers/arangodb/shedlock-provider-arangodb/pom.xml index 53ec0c22d..79b20fc18 100644 --- a/providers/arangodb/shedlock-provider-arangodb/pom.xml +++ b/providers/arangodb/shedlock-provider-arangodb/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-arangodb - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -20,7 +20,7 @@ com.arangodb arangodb-java-driver - 6.19.0 + 7.22.0 diff --git a/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProvider.java b/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProvider.java index 1c8737157..9c037946e 100644 --- a/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProvider.java +++ b/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProvider.java @@ -1,20 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.arangodb; +import static net.javacrumbs.shedlock.support.Utils.getHostname; + import com.arangodb.ArangoCollection; import com.arangodb.ArangoDBException; import com.arangodb.ArangoDatabase; @@ -25,6 +25,8 @@ import com.arangodb.model.DocumentReadOptions; import com.arangodb.model.DocumentUpdateOptions; import com.arangodb.model.StreamTransactionOptions; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; @@ -32,17 +34,11 @@ import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; import net.javacrumbs.shedlock.support.Utils; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.time.Instant; -import java.util.Optional; - -import static net.javacrumbs.shedlock.support.Utils.getHostname; /** - * Arango Lock Provider needs existing collection - *
+ * Arango Lock Provider needs existing collection
* Example creating a collection through init scripts (javascript) + * *

  * db._useDatabase("DB_NAME");
  * db._create("COLLECTION_NAME");
@@ -60,53 +56,59 @@ public class ArangoLockProvider implements LockProvider {
     /**
      * Instantiates a new Arango lock provider.
      *
-     * @param arangoDatabase the arango database
+     * @param arangoDatabase
+     *            the arango database
      */
-    public ArangoLockProvider(@NonNull ArangoDatabase arangoDatabase) {
+    public ArangoLockProvider(ArangoDatabase arangoDatabase) {
         this(arangoDatabase.collection(COLLECTION_NAME));
     }
 
     /**
      * Instantiates a new Arango lock provider.
      *
-     * @param arangoCollection the arango collection
+     * @param arangoCollection
+     *            the arango collection
      */
-    public ArangoLockProvider(@NonNull ArangoCollection arangoCollection) {
+    public ArangoLockProvider(ArangoCollection arangoCollection) {
         this.arangoCollection = arangoCollection;
     }
 
     @Override
-    @NonNull
-    public Optional lock(@NonNull LockConfiguration lockConfiguration) {
+    public Optional lock(LockConfiguration lockConfiguration) {
         String transactionId = null;
 
         try {
             /*
-                Transaction is necessary because repsert (insert with overwrite=true in arangodb)
-                is not possible with condition check (see case 2 description below)
+             * Transaction is necessary because repsert (insert with overwrite=true in
+             * arangodb) is not possible with condition check (see case 2 description below)
              */
-            StreamTransactionEntity streamTransactionEntity = arangoCollection.db().beginStreamTransaction(
-                new StreamTransactionOptions().exclusiveCollections(arangoCollection.name())
-            );
+            StreamTransactionEntity streamTransactionEntity = arangoCollection
+                    .db()
+                    .beginStreamTransaction(
+                            new StreamTransactionOptions().exclusiveCollections(arangoCollection.name()));
 
             transactionId = streamTransactionEntity.getId();
 
-            /*  There are three possible situations:
-                1. The lock document does not exist yet - insert document
-                2. The lock document exists and lockUtil <= now - update document
-                3. The lock document exists and lockUtil > now - nothing to do
+            /*
+             * There are three possible situations: 1. The lock document does not exist yet
+             * - insert document 2. The lock document exists and lockUtil <= now - update
+             * document 3. The lock document exists and lockUtil > now - nothing to do
              */
-            BaseDocument existingDocument = arangoCollection.getDocument(lockConfiguration.getName(),
-                BaseDocument.class, new DocumentReadOptions().streamTransactionId(transactionId));
+            BaseDocument existingDocument = arangoCollection.getDocument(
+                    lockConfiguration.getName(),
+                    BaseDocument.class,
+                    new DocumentReadOptions().streamTransactionId(transactionId));
 
             // 1. case
             if (existingDocument == null) {
-                BaseDocument newDocument = insertNewLock(transactionId, lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil());
+                BaseDocument newDocument = insertNewLock(
+                        transactionId, lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil());
                 return Optional.of(new ArangoLock(arangoCollection, newDocument, lockConfiguration));
             }
 
             // 2. case
-            Instant lockUntil = Instant.parse(existingDocument.getAttribute(LOCK_UNTIL).toString());
+            Instant lockUntil =
+                    Instant.parse(existingDocument.getAttribute(LOCK_UNTIL).toString());
             if (lockUntil.compareTo(ClockProvider.now()) <= 0) {
                 updateLockAtMostUntil(transactionId, existingDocument, lockConfiguration.getLockAtMostUntil());
                 return Optional.of(new ArangoLock(arangoCollection, existingDocument, lockConfiguration));
@@ -127,22 +129,23 @@ is not possible with condition check (see case 2 description below)
         }
     }
 
-    private BaseDocument insertNewLock(String transactionId, String documentKey, Instant lockAtMostUntil) throws ArangoDBException {
+    private BaseDocument insertNewLock(String transactionId, String documentKey, Instant lockAtMostUntil)
+            throws ArangoDBException {
         BaseDocument newDocument = new BaseDocument();
         newDocument.setKey(documentKey);
         setDocumentAttributes(newDocument, lockAtMostUntil);
-        DocumentCreateEntity document = arangoCollection.insertDocument(newDocument,
-            new DocumentCreateOptions().streamTransactionId(transactionId).returnNew(true)
-        );
+        DocumentCreateEntity document = arangoCollection.insertDocument(
+                newDocument,
+                new DocumentCreateOptions().streamTransactionId(transactionId).returnNew(true));
         return document.getNew();
     }
 
-    private void updateLockAtMostUntil(String transactionId,
-                                       BaseDocument existingDocument,
-                                       Instant lockAtMostUntil) {
+    private void updateLockAtMostUntil(String transactionId, BaseDocument existingDocument, Instant lockAtMostUntil) {
         setDocumentAttributes(existingDocument, lockAtMostUntil);
-        arangoCollection.updateDocument(existingDocument.getKey(), existingDocument,
-            new DocumentUpdateOptions().streamTransactionId(transactionId));
+        arangoCollection.updateDocument(
+                existingDocument.getKey(),
+                existingDocument,
+                new DocumentUpdateOptions().streamTransactionId(transactionId));
     }
 
     private void setDocumentAttributes(BaseDocument baseDocument, Instant lockAtMostUntil) {
@@ -156,9 +159,10 @@ private static final class ArangoLock extends AbstractSimpleLock {
         private final ArangoCollection arangoCollection;
         private final BaseDocument document;
 
-        public ArangoLock(final ArangoCollection arangoCollection,
-                          final BaseDocument document,
-                          final LockConfiguration lockConfiguration) {
+        public ArangoLock(
+                final ArangoCollection arangoCollection,
+                final BaseDocument document,
+                final LockConfiguration lockConfiguration) {
 
             super(lockConfiguration);
             this.arangoCollection = arangoCollection;
@@ -173,9 +177,6 @@ protected void doUnlock() {
             } catch (ArangoDBException e) {
                 throw new LockException("Unexpected error occured", e);
             }
-
         }
-
     }
-
 }
diff --git a/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/package-info.java b/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/package-info.java
new file mode 100644
index 000000000..64586f967
--- /dev/null
+++ b/providers/arangodb/shedlock-provider-arangodb/src/main/java/net/javacrumbs/shedlock/provider/arangodb/package-info.java
@@ -0,0 +1,6 @@
+@NonNullApi
+@NonNullFields
+package net.javacrumbs.shedlock.provider.arangodb;
+
+import net.javacrumbs.shedlock.support.annotation.NonNullApi;
+import net.javacrumbs.shedlock.support.annotation.NonNullFields;
diff --git a/providers/arangodb/shedlock-provider-arangodb/src/test/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProviderIntegrationTest.java b/providers/arangodb/shedlock-provider-arangodb/src/test/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProviderIntegrationTest.java
index 7c647a6db..9cc50ed32 100644
--- a/providers/arangodb/shedlock-provider-arangodb/src/test/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProviderIntegrationTest.java
+++ b/providers/arangodb/shedlock-provider-arangodb/src/test/java/net/javacrumbs/shedlock/provider/arangodb/ArangoLockProviderIntegrationTest.java
@@ -1,24 +1,30 @@
 /**
  * Copyright 2009 the original author or authors.
  *
- * 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
+ * 

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.arangodb; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.COLLECTION_NAME; +import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCK_UNTIL; +import static org.assertj.core.api.Assertions.assertThat; + import com.arangodb.ArangoCollection; import com.arangodb.ArangoDB; import com.arangodb.ArangoDatabase; import com.arangodb.entity.BaseDocument; +import java.time.Instant; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; import org.junit.jupiter.api.AfterAll; @@ -30,16 +36,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.time.Instant; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.COLLECTION_NAME; -import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.arangodb.ArangoLockProvider.LOCK_UNTIL; -import static org.assertj.core.api.Assertions.assertThat; - - @Testcontainers public class ArangoLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { @@ -62,11 +58,11 @@ static void beforeAll() { arangoContainer.start(); arango = new ArangoDB.Builder() - .host(DB_HOSTNAME, arangoContainer.getMappedPort(DB_PORT)) - .user(DB_USER) - .password(DB_PASSWORD) - .useSsl(false) - .build(); + .host(DB_HOSTNAME, arangoContainer.getMappedPort(DB_PORT)) + .user(DB_USER) + .password(DB_PASSWORD) + .useSsl(false) + .build(); if (!arango.getDatabases().contains(DB_NAME)) { arango.createDatabase(DB_NAME); @@ -75,7 +71,7 @@ static void beforeAll() { arangoDatabase = arango.db(DB_NAME); if (arangoDatabase.getCollections().stream() - .anyMatch(collectionEntity -> collectionEntity.getName().equals(COLLECTION_NAME))) { + .anyMatch(collectionEntity -> collectionEntity.getName().equals(COLLECTION_NAME))) { arangoCollection = arangoDatabase.collection(COLLECTION_NAME); arangoCollection.drop(); } diff --git a/providers/cassandra/shedlock-provider-cassandra/pom.xml b/providers/cassandra/shedlock-provider-cassandra/pom.xml index 1cf0b0b64..3738e282f 100644 --- a/providers/cassandra/shedlock-provider-cassandra/pom.xml +++ b/providers/cassandra/shedlock-provider-cassandra/pom.xml @@ -3,16 +3,16 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-cassandra - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - 4.15.0 + 4.19.0 @@ -23,13 +23,13 @@ - com.datastax.oss + org.apache.cassandra java-driver-core ${java-driver.version} - com.datastax.oss + org.apache.cassandra java-driver-query-builder ${java-driver.version} diff --git a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProvider.java b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProvider.java index 7f12829ce..e85c74b2b 100644 --- a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProvider.java +++ b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProvider.java @@ -1,27 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.cassandra; +import static java.util.Objects.requireNonNull; + import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import static java.util.Objects.requireNonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; /** * Cassandra Lock Provider needs a keyspace and uses a lock table
@@ -35,42 +33,50 @@ public class CassandraLockProvider extends StorageBasedLockProvider { static final String DEFAULT_TABLE = "lock"; - public CassandraLockProvider(@NonNull CqlSession cqlSession) { + public CassandraLockProvider(CqlSession cqlSession) { this(cqlSession, DEFAULT_TABLE, ConsistencyLevel.QUORUM); } - public CassandraLockProvider(@NonNull CqlSession cqlSession, @NonNull String table, @NonNull ConsistencyLevel consistencyLevel) { - this(Configuration.builder().withCqlSession(cqlSession).withTableName(table).withConsistencyLevel(consistencyLevel).build()); + public CassandraLockProvider(CqlSession cqlSession, String table, ConsistencyLevel consistencyLevel) { + this(Configuration.builder() + .withCqlSession(cqlSession) + .withTableName(table) + .withConsistencyLevel(consistencyLevel) + .build()); } - public CassandraLockProvider(@NonNull Configuration configuration) { + public CassandraLockProvider(Configuration configuration) { super(new CassandraStorageAccessor(configuration)); } - /** - * Convenience class to specify configuration - */ + /** Convenience class to specify configuration */ public static final class Configuration { private final CqlIdentifier table; private final ColumnNames columnNames; private final CqlSession cqlSession; + + @Nullable private final ConsistencyLevel consistencyLevel; + + @Nullable private final ConsistencyLevel serialConsistencyLevel; + + @Nullable private final CqlIdentifier keyspace; Configuration( - @NonNull CqlSession cqlSession, - @NonNull CqlIdentifier table, - @NonNull ColumnNames columnNames, - @NonNull ConsistencyLevel consistencyLevel, - @NonNull ConsistencyLevel serialConsistencyLevel, - CqlIdentifier keyspace - ) { + CqlSession cqlSession, + CqlIdentifier table, + ColumnNames columnNames, + @Nullable ConsistencyLevel consistencyLevel, + @Nullable ConsistencyLevel serialConsistencyLevel, + @Nullable CqlIdentifier keyspace) { this.table = requireNonNull(table, "table can not be null"); this.columnNames = requireNonNull(columnNames, "columnNames can not be null"); this.cqlSession = requireNonNull(cqlSession, "cqlSession can not be null"); this.consistencyLevel = requireNonNull(consistencyLevel, "consistencyLevel can not be null"); - this.serialConsistencyLevel = requireNonNull(serialConsistencyLevel, "serialConsistencyLevel can not be null"); + this.serialConsistencyLevel = + requireNonNull(serialConsistencyLevel, "serialConsistencyLevel can not be null"); this.keyspace = keyspace; } @@ -86,14 +92,17 @@ public CqlSession getCqlSession() { return cqlSession; } + @Nullable public ConsistencyLevel getConsistencyLevel() { return consistencyLevel; } + @Nullable public ConsistencyLevel getSerialConsistencyLevel() { return serialConsistencyLevel; } + @Nullable public CqlIdentifier getKeyspace() { return keyspace; } @@ -102,22 +111,26 @@ public static Configuration.Builder builder() { return new Configuration.Builder(); } - /** - * Convenience builder class to build Configuration - */ + /** Convenience builder class to build Configuration */ public static final class Builder { private CqlIdentifier table = CqlIdentifier.fromCql(DEFAULT_TABLE); private ColumnNames columnNames = new ColumnNames("name", "lockUntil", "lockedAt", "lockedBy"); private CqlSession cqlSession; + + @Nullable private ConsistencyLevel consistencyLevel = ConsistencyLevel.QUORUM; + + @Nullable private ConsistencyLevel serialConsistencyLevel = ConsistencyLevel.SERIAL; + + @Nullable private CqlIdentifier keyspace; - public Builder withTableName(@NonNull String table) { + public Builder withTableName(String table) { return withTableName(CqlIdentifier.fromCql(table)); } - public Builder withTableName(@NonNull CqlIdentifier table) { + public Builder withTableName(CqlIdentifier table) { this.table = table; return this; } @@ -127,41 +140,40 @@ public Builder withColumnNames(ColumnNames columnNames) { return this; } - public Builder withCqlSession(@NonNull CqlSession cqlSession) { + public Builder withCqlSession(CqlSession cqlSession) { this.cqlSession = cqlSession; return this; } - public Builder withConsistencyLevel(@NonNull ConsistencyLevel consistencyLevel) { + public Builder withConsistencyLevel(ConsistencyLevel consistencyLevel) { this.consistencyLevel = consistencyLevel; return this; } /** - * Since Shedlock internally uses CAS (Compare And Set) operations - * This configuration helps to have a granular control on the CAS consistency. + * Since Shedlock internally uses CAS (Compare And Set) operations This + * configuration helps to have a granular control on the CAS consistency. + * * @return Builder */ - - public Builder withSerialConsistencyLevel(@NonNull ConsistencyLevel serialConsistencyLevel) { + public Builder withSerialConsistencyLevel(ConsistencyLevel serialConsistencyLevel) { this.serialConsistencyLevel = serialConsistencyLevel; return this; } - public Builder withKeyspace(@NonNull CqlIdentifier keyspace) { + public Builder withKeyspace(CqlIdentifier keyspace) { this.keyspace = keyspace; return this; } public CassandraLockProvider.Configuration build() { - return new CassandraLockProvider.Configuration(cqlSession, table, columnNames, consistencyLevel, serialConsistencyLevel, keyspace); + return new CassandraLockProvider.Configuration( + cqlSession, table, columnNames, consistencyLevel, serialConsistencyLevel, keyspace); } } } - /** - * Convenience class to specify column names - */ + /** Convenience class to specify column names */ public static final class ColumnNames { private final String lockName; private final String lockUntil; @@ -169,7 +181,8 @@ public static final class ColumnNames { private final String lockedBy; /** - * Each column names are optional and if not specified the default column name would be considered. + * Each column names are optional and if not specified the default column name + * would be considered. */ public ColumnNames(String lockName, String lockUntil, String lockedAt, String lockedBy) { this.lockName = requireNonNull(lockName, "'lockName' column name can not be null"); diff --git a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraStorageAccessor.java b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraStorageAccessor.java index 006af888f..ff95f316d 100644 --- a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraStorageAccessor.java +++ b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/CassandraStorageAccessor.java @@ -1,20 +1,21 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.cassandra; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import static java.util.Objects.requireNonNull; + import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; @@ -23,39 +24,41 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.servererrors.QueryExecutionException; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider.Configuration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; import net.javacrumbs.shedlock.support.Utils; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; -import java.time.Instant; -import java.util.Optional; - -import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; -import static java.util.Objects.requireNonNull; - -/** - * StorageAccessor for cassandra. - **/ +/** StorageAccessor for cassandra. */ /* - * In theory, all the reads (find() method calls) in update methods are not necessary, - * but it's a performance optimization. Moreover, the fuzzTest sometimes fails without them. + * In theory, all the reads (find() method calls) in update methods are not + * necessary, but it's a performance optimization. Moreover, the fuzzTest + * sometimes fails without them. */ class CassandraStorageAccessor extends AbstractStorageAccessor { private final String hostname; private final CqlIdentifier table; + + @Nullable private final CqlIdentifier keyspace; + private final String lockName; private final String lockUntil; private final String lockedAt; private final String lockedBy; private final CqlSession cqlSession; + + @Nullable private final ConsistencyLevel consistencyLevel; + + @Nullable private final ConsistencyLevel serialConsistencyLevel; - CassandraStorageAccessor(@NonNull Configuration configuration) { + CassandraStorageAccessor(Configuration configuration) { requireNonNull(configuration, "configuration can not be null"); this.hostname = Utils.getHostname(); this.table = configuration.getTable(); @@ -70,7 +73,7 @@ class CassandraStorageAccessor extends AbstractStorageAccessor { } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean insertRecord(LockConfiguration lockConfiguration) { if (find(lockConfiguration.getName()).isPresent()) { return false; } @@ -84,9 +87,9 @@ public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean updateRecord(LockConfiguration lockConfiguration) { Optional lock = find(lockConfiguration.getName()); - if (lock.isEmpty() || lock.get().getLockUntil().isAfter(ClockProvider.now())) { + if (lock.isEmpty() || lock.get().lockUntil().isAfter(ClockProvider.now())) { return false; } @@ -99,14 +102,16 @@ public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { + public void unlock(LockConfiguration lockConfiguration) { updateUntil(lockConfiguration.getName(), lockConfiguration.getUnlockTime()); } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { + public boolean extend(LockConfiguration lockConfiguration) { Optional lock = find(lockConfiguration.getName()); - if (lock.isEmpty() || lock.get().getLockUntil().isBefore(ClockProvider.now()) || !lock.get().getLockedBy().equals(hostname)) { + if (lock.isEmpty() + || lock.get().lockUntil().isBefore(ClockProvider.now()) + || !lock.get().lockedBy().equals(hostname)) { logger.trace("extend false"); return false; } @@ -117,18 +122,20 @@ public boolean extend(@NonNull LockConfiguration lockConfiguration) { /** * Find existing row by primary key lock.name * - * @param name lock name + * @param name + * lock name * @return optional lock row or empty */ Optional find(String name) { SimpleStatement selectStatement = QueryBuilder.selectFrom(keyspace, table) - .column(lockUntil) - .column(lockedAt) - .column(lockedBy) - .whereColumn(lockName).isEqualTo(literal(name)) - .build() - .setConsistencyLevel(consistencyLevel) - .setSerialConsistencyLevel(serialConsistencyLevel); + .column(lockUntil) + .column(lockedAt) + .column(lockedBy) + .whereColumn(lockName) + .isEqualTo(literal(name)) + .build() + .setConsistencyLevel(consistencyLevel) + .setSerialConsistencyLevel(serialConsistencyLevel); ResultSet resultSet = cqlSession.execute(selectStatement); Row row = resultSet.one(); @@ -142,52 +149,66 @@ Optional find(String name) { /** * Insert new lock row * - * @param name lock name - * @param until new until instant value + * @param name + * lock name + * @param until + * new until instant value */ private boolean insert(String name, Instant until) { return execute(QueryBuilder.insertInto(keyspace, table) - .value(lockName, literal(name)) - .value(lockUntil, literal(until)) - .value(lockedAt, literal(ClockProvider.now())) - .value(lockedBy, literal(hostname)) - .ifNotExists() - .build()); + .value(lockName, literal(name)) + .value(lockUntil, literal(until)) + .value(lockedAt, literal(ClockProvider.now())) + .value(lockedBy, literal(hostname)) + .ifNotExists() + .build()); } /** * Update existing lock row * - * @param name lock name - * @param until new until instant value + * @param name + * lock name + * @param until + * new until instant value */ private boolean update(String name, Instant until) { return execute(QueryBuilder.update(keyspace, table) - .setColumn(lockUntil, literal(until)) - .setColumn(lockedAt, literal(ClockProvider.now())) - .setColumn(lockedBy, literal(hostname)) - .whereColumn(lockName).isEqualTo(literal(name)) - .ifColumn(lockUntil).isLessThan(literal(ClockProvider.now())) - .build()); + .setColumn(lockUntil, literal(until)) + .setColumn(lockedAt, literal(ClockProvider.now())) + .setColumn(lockedBy, literal(hostname)) + .whereColumn(lockName) + .isEqualTo(literal(name)) + .ifColumn(lockUntil) + .isLessThan(literal(ClockProvider.now())) + .build()); } /** * Updates lock.until field where lockConfiguration.name * - * @param name lock name - * @param until new until instant value + * @param name + * lock name + * @param until + * new until instant value */ private boolean updateUntil(String name, Instant until) { return execute(QueryBuilder.update(keyspace, table) - .setColumn(lockUntil, literal(until)) - .whereColumn(lockName).isEqualTo(literal(name)) - .ifColumn(lockUntil).isGreaterThanOrEqualTo(literal(ClockProvider.now())) - .ifColumn(lockedBy).isEqualTo(literal(hostname)) - .build()); + .setColumn(lockUntil, literal(until)) + .whereColumn(lockName) + .isEqualTo(literal(name)) + .ifColumn(lockUntil) + .isGreaterThanOrEqualTo(literal(ClockProvider.now())) + .ifColumn(lockedBy) + .isEqualTo(literal(hostname)) + .build()); } - private boolean execute(SimpleStatement statement) { - return cqlSession.execute(statement.setConsistencyLevel(consistencyLevel).setSerialConsistencyLevel(serialConsistencyLevel)).wasApplied(); + return cqlSession + .execute(statement + .setConsistencyLevel(consistencyLevel) + .setSerialConsistencyLevel(serialConsistencyLevel)) + .wasApplied(); } } diff --git a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/Lock.java b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/Lock.java index e66e3ed0c..329c220ad 100644 --- a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/Lock.java +++ b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/Lock.java @@ -1,42 +1,18 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.cassandra; import java.time.Instant; -class Lock { - private final Instant lockUntil; - private final Instant lockedAt; - private final String lockedBy; - - Lock(Instant lockUntil, Instant lockedAt, String lockedBy) { - this.lockUntil = lockUntil; - this.lockedAt = lockedAt; - this.lockedBy = lockedBy; - } - - Instant getLockUntil() { - return lockUntil; - } - - Instant getLockedAt() { - return lockedAt; - } - - String getLockedBy() { - return lockedBy; - } -} +record Lock(Instant lockUntil, Instant lockedAt, String lockedBy) {} diff --git a/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/package-info.java b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/package-info.java new file mode 100644 index 000000000..08eae344e --- /dev/null +++ b/providers/cassandra/shedlock-provider-cassandra/src/main/java/net/javacrumbs/shedlock/provider/cassandra/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.cassandra; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/cassandra/shedlock-provider-cassandra/src/test/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProviderIntegrationTest.java b/providers/cassandra/shedlock-provider-cassandra/src/test/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProviderIntegrationTest.java index fa9023ada..789bb3795 100644 --- a/providers/cassandra/shedlock-provider-cassandra/src/test/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProviderIntegrationTest.java +++ b/providers/cassandra/shedlock-provider-cassandra/src/test/java/net/javacrumbs/shedlock/provider/cassandra/CassandraLockProviderIntegrationTest.java @@ -1,23 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.cassandra; +import static com.datastax.oss.driver.api.core.CqlIdentifier.fromCql; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider.DEFAULT_TABLE; +import static org.assertj.core.api.Assertions.assertThat; + import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; +import java.net.InetSocketAddress; import net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider.Configuration; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; @@ -27,15 +31,9 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.net.InetSocketAddress; - -import static com.datastax.oss.driver.api.core.CqlIdentifier.fromCql; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider.DEFAULT_TABLE; -import static org.assertj.core.api.Assertions.assertThat; - /** - * Integration test uses local instance of Cassandra running on localhost at port 9042 using keyspace shedlock and table lock + * Integration test uses local instance of Cassandra running on localhost at + * port 9042 using keyspace shedlock and table lock * * @see net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider */ @@ -46,20 +44,20 @@ public class CassandraLockProviderIntegrationTest extends AbstractStorageBasedLo @Container public static final MyCassandraContainer cassandra = new MyCassandraContainer() - .withInitScript("shedlock.cql") - .withEnv("CASSANDRA_DC", "local") - .withEnv("CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch"); + .withInitScript("shedlock.cql") + .withEnv("CASSANDRA_DC", "local") + .withEnv("CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch"); @BeforeAll public static void startCassandra() { - String containerIpAddress = cassandra.getContainerIpAddress(); + String containerIpAddress = cassandra.getHost(); int containerPort = cassandra.getMappedPort(9042); InetSocketAddress containerEndPoint = new InetSocketAddress(containerIpAddress, containerPort); session = CqlSession.builder() - .addContactPoint(containerEndPoint) - .withLocalDatacenter("local") - .build(); + .addContactPoint(containerEndPoint) + .withLocalDatacenter("local") + .build(); } @AfterEach @@ -69,34 +67,34 @@ public void after() { @Override protected StorageBasedLockProvider getLockProvider() { - return new CassandraLockProvider( - Configuration.builder() + return new CassandraLockProvider(Configuration.builder() .withCqlSession(session) .withKeyspace(KEYSPACE) - .build() - ); + .build()); } @Override protected void assertUnlocked(String lockName) { Lock lock = findLock(lockName); - assertThat(lock.getLockUntil()).isBefore(now()); - assertThat(lock.getLockedAt()).isBefore(now()); - assertThat(lock.getLockedBy()).isNotEmpty(); + assertThat(lock.lockUntil()).isBefore(now()); + assertThat(lock.lockedAt()).isBefore(now()); + assertThat(lock.lockedBy()).isNotEmpty(); } @Override protected void assertLocked(String lockName) { Lock lock = findLock(lockName); - assertThat(lock.getLockUntil()).isAfter(now()); - assertThat(lock.getLockedAt()).isBefore(now()); - assertThat(lock.getLockedBy()).isNotEmpty(); + assertThat(lock.lockUntil()).isAfter(now()); + assertThat(lock.lockedAt()).isBefore(now()); + assertThat(lock.lockedBy()).isNotEmpty(); } private Lock findLock(String lockName) { - CassandraStorageAccessor cassandraStorageAccessor = new CassandraStorageAccessor( - Configuration.builder().withCqlSession(session).withKeyspace(KEYSPACE).withTableName(DEFAULT_TABLE).build() - ); + CassandraStorageAccessor cassandraStorageAccessor = new CassandraStorageAccessor(Configuration.builder() + .withCqlSession(session) + .withKeyspace(KEYSPACE) + .withTableName(DEFAULT_TABLE) + .build()); return cassandraStorageAccessor.find(lockName).get(); } diff --git a/providers/consul/shedlock-provider-consul/pom.xml b/providers/consul/shedlock-provider-consul/pom.xml index 1d56ce40c..ef431cbfb 100644 --- a/providers/consul/shedlock-provider-consul/pom.xml +++ b/providers/consul/shedlock-provider-consul/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-consul - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -28,7 +28,7 @@ org.apache.httpcomponents httpclient - 4.5.13 + 4.5.14 diff --git a/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProvider.java b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProvider.java index e3c1ed0de..6be4189dd 100644 --- a/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProvider.java +++ b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProvider.java @@ -1,54 +1,60 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.consul; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.QueryParams; import com.ecwid.consul.v1.kv.model.PutParams; import com.ecwid.consul.v1.session.model.NewSession; import com.ecwid.consul.v1.session.model.Session; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.annotation.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.time.Duration; import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; - -import static java.util.Objects.requireNonNull; -import static net.javacrumbs.shedlock.core.ClockProvider.now; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - *

This lock provider registers a new session for lock and on unlock this session is removed together with all associated locks.

+ * This lock provider registers a new session for lock and on unlock this + * session is removed together with all associated locks. * - *

The main point you need to be aware about is that consul holds session for up to twice TTL. That means, - * even if the session TTL is set to 10 seconds, consul will hold still this session for 20 seconds. - * This is an expected behaviour and it's impossible to change it. - * This is the reason consul recommends to set the lowest possible TTL - * and constantly extend it. With this lock it means that even if your lockAtMostFor is less that 20 seconds, the timeout will be - * higher than 10 seconds and most likely will be 20.

+ *

+ * The main point you need to be aware about is that consul holds session for up + * to twice TTL. That means, even if the session TTL is set to 10 seconds, + * consul will hold still this session for 20 seconds. This is an + * expected + * behaviour and it's impossible to change it. This is the reason consul + * recommends to set + * the lowest possible TTL and constantly extend it. With this lock it means + * that even if your lockAtMostFor is less that 20 seconds, the timeout will be + * higher than 10 seconds and most likely will be 20. * - *

The lock is acquired for the time specified in {@code @SchedulerLock.lockAtMostFor}. Please note that this lock provider - * doesn't make any correction to the aforementioned TTL behaviour so most likely your locked session will live for - * longer than specified in lockAtMostFor. In this lock provider there is no session renewal done in the background.

+ *

+ * The lock is acquired for the time specified in + * {@code @SchedulerLock.lockAtMostFor}. Please note that this lock provider + * doesn't make any correction to the aforementioned TTL behaviour so most + * likely your locked session will live for longer than specified in + * lockAtMostFor. In this lock provider there is no session renewal done in the + * background. * * @author Artur Kalimullin */ @@ -63,42 +69,36 @@ public class ConsulLockProvider implements LockProvider, AutoCloseable { private final Configuration configuration; - public ConsulLockProvider(@NonNull ConsulClient consulClient) { - this(Configuration.builder() - .withConsulClient(consulClient) - .build() - ); + public ConsulLockProvider(ConsulClient consulClient) { + this(Configuration.builder().withConsulClient(consulClient).build()); } - public ConsulLockProvider(@NonNull ConsulClient consulClient, Duration minSessionTtl) { + public ConsulLockProvider(ConsulClient consulClient, Duration minSessionTtl) { this(Configuration.builder() - .withConsulClient(consulClient) - .withMinSessionTtl(minSessionTtl) - .build() - ); + .withConsulClient(consulClient) + .withMinSessionTtl(minSessionTtl) + .build()); } public ConsulLockProvider( - @NonNull ConsulClient consulClient, - Duration minSessionTtl, - String consulLockPostfix, - Duration gracefulShutdownInterval) { + ConsulClient consulClient, + Duration minSessionTtl, + String consulLockPostfix, + Duration gracefulShutdownInterval) { this(Configuration.builder() - .withConsulClient(consulClient) - .withMinSessionTtl(minSessionTtl) - .withConsulLockPostfix(consulLockPostfix) - .withGracefulShutdownInterval(gracefulShutdownInterval) - .build() - ); + .withConsulClient(consulClient) + .withMinSessionTtl(minSessionTtl) + .withConsulLockPostfix(consulLockPostfix) + .withGracefulShutdownInterval(gracefulShutdownInterval) + .build()); } - public ConsulLockProvider(@NonNull Configuration configuration) { + public ConsulLockProvider(Configuration configuration) { this.configuration = configuration; } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { String sessionId = createSession(lockConfiguration); return tryLock(sessionId, lockConfiguration); } @@ -114,15 +114,17 @@ void unlock(String sessionId, LockConfiguration lockConfiguration) { } private String createSession(LockConfiguration lockConfiguration) { - long ttlInSeconds = Math.max(lockConfiguration.getLockAtMostFor().getSeconds(), - configuration.getMinSessionTtl().getSeconds()); + long ttlInSeconds = Math.max( + lockConfiguration.getLockAtMostFor().getSeconds(), + configuration.getMinSessionTtl().getSeconds()); NewSession newSession = new NewSession(); newSession.setName(lockConfiguration.getName()); newSession.setLockDelay(0); newSession.setBehavior(Session.Behavior.DELETE); newSession.setTtl(ttlInSeconds + "s"); - String sessionId = client().sessionCreate(newSession, QueryParams.DEFAULT, token()).getValue(); + String sessionId = + client().sessionCreate(newSession, QueryParams.DEFAULT, token()).getValue(); logger.debug("Acquired session {} for {} seconds", sessionId, ttlInSeconds); return sessionId; @@ -132,7 +134,8 @@ private Optional tryLock(String sessionId, LockConfiguration lockCon PutParams putParams = new PutParams(); putParams.setAcquireSession(sessionId); String leaderKey = getLeaderKey(lockConfiguration); - boolean isLockSuccessful = client().setKVValue(leaderKey, lockConfiguration.getName(), token(), putParams).getValue(); + boolean isLockSuccessful = client().setKVValue(leaderKey, lockConfiguration.getName(), token(), putParams) + .getValue(); if (isLockSuccessful) { return Optional.of(new ConsulSimpleLock(lockConfiguration, this, sessionId)); @@ -150,14 +153,13 @@ private String getLeaderKey(LockConfiguration lockConfiguration) { if (configuration.getConsulLockPrefix() == null) return lockConfiguration.getName() + configuration.getConsulLockPostfix(); - return configuration.getConsulLockPrefix() + "/" + lockConfiguration.getName() + configuration.getConsulLockPostfix(); + return configuration.getConsulLockPrefix() + "/" + lockConfiguration.getName() + + configuration.getConsulLockPostfix(); } private void scheduleUnlock(String sessionId, Duration unlockTime) { unlockScheduler.schedule( - catchExceptions(() -> destroy(sessionId)), - unlockTime.toMillis(), TimeUnit.MILLISECONDS - ); + catchExceptions(() -> destroy(sessionId)), unlockTime.toMillis(), TimeUnit.MILLISECONDS); } private void destroy(String sessionId) { @@ -179,8 +181,8 @@ private Runnable catchExceptions(Runnable runnable) { public void close() { unlockScheduler.shutdown(); try { - if (!unlockScheduler.awaitTermination(configuration.getGracefulShutdownInterval().toMillis(), - TimeUnit.MILLISECONDS)) { + if (!unlockScheduler.awaitTermination( + configuration.getGracefulShutdownInterval().toMillis(), TimeUnit.MILLISECONDS)) { unlockScheduler.shutdownNow(); } } catch (InterruptedException ignored) { @@ -193,21 +195,23 @@ private ConsulClient client() { public static final class Configuration { private final Duration minSessionTtl; + + @Nullable private final String consulLockPrefix; + private final String consulLockPostfix; private final ConsulClient consulClient; private final Duration gracefulShutdownInterval; private final String token; - /** - * Use Builder to create. - */ + /** Use Builder to create. */ Configuration( - Duration minSessionTtl, - String consulLockPostfix, - ConsulClient consulClient, - Duration gracefulShutdownInterval, - String token, String consulLockPrefix) { + Duration minSessionTtl, + String consulLockPostfix, + ConsulClient consulClient, + Duration gracefulShutdownInterval, + String token, + @Nullable String consulLockPrefix) { this.minSessionTtl = minSessionTtl; this.consulLockPrefix = consulLockPrefix; @@ -237,6 +241,7 @@ public String getToken() { return token; } + @Nullable public String getConsulLockPrefix() { return consulLockPrefix; } @@ -252,6 +257,8 @@ public static final class Builder { private ConsulClient consulClient; private Duration gracefulShutdownInterval = DEFAULT_GRACEFUL_SHUTDOWN_INTERVAL; private String token; + + @Nullable private String consulLockPrefix; public Builder withMinSessionTtl(Duration minSessionTtl) { @@ -286,9 +293,13 @@ public Builder withConsulLockPrefix(String leaderKeyPrefix) { public ConsulLockProvider.Configuration build() { return new ConsulLockProvider.Configuration( - minSessionTtl, consulLockPostfix, consulClient, gracefulShutdownInterval, token, consulLockPrefix); + minSessionTtl, + consulLockPostfix, + consulClient, + gracefulShutdownInterval, + token, + consulLockPrefix); } } } - } diff --git a/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulSimpleLock.java b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulSimpleLock.java index a58da82c5..b4d615d30 100644 --- a/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulSimpleLock.java +++ b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/ConsulSimpleLock.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.consul; @@ -22,9 +20,8 @@ class ConsulSimpleLock extends AbstractSimpleLock { private final ConsulLockProvider consulLockProvider; private final String sessionId; - public ConsulSimpleLock(LockConfiguration lockConfiguration, - ConsulLockProvider consulLockProvider, - String sessionId) { + public ConsulSimpleLock( + LockConfiguration lockConfiguration, ConsulLockProvider consulLockProvider, String sessionId) { super(lockConfiguration); this.consulLockProvider = consulLockProvider; this.sessionId = sessionId; diff --git a/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/package-info.java b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/package-info.java new file mode 100644 index 000000000..71ee0b922 --- /dev/null +++ b/providers/consul/shedlock-provider-consul/src/main/java/net/javacrumbs/shedlock/provider/consul/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.consul; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/consul/shedlock-provider-consul/src/test/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProviderIntegrationTest.java b/providers/consul/shedlock-provider-consul/src/test/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProviderIntegrationTest.java index 45508db86..79d04f5d7 100644 --- a/providers/consul/shedlock-provider-consul/src/test/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProviderIntegrationTest.java +++ b/providers/consul/shedlock-provider-consul/src/test/java/net/javacrumbs/shedlock/provider/consul/ConsulLockProviderIntegrationTest.java @@ -1,25 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.consul; +import static java.lang.Thread.sleep; +import static org.assertj.core.api.Assertions.assertThat; + import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.QueryParams; import com.ecwid.consul.v1.Response; import com.ecwid.consul.v1.kv.model.GetValue; import com.ecwid.consul.v1.session.model.Session; +import java.time.Duration; +import java.util.List; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; @@ -32,13 +36,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.time.Duration; -import java.util.List; -import java.util.Optional; - -import static java.lang.Thread.sleep; -import static org.assertj.core.api.Assertions.assertThat; - @Testcontainers class ConsulLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { @@ -56,16 +53,15 @@ public static void startConsul() { public void checkSessions() { Response> sessionListResponse = consulClient.getSessionList(QueryParams.DEFAULT); assertThat(sessionListResponse.getValue()) - .as("There should no sessions remain in consul after all locks have been released.") - .isEmpty(); + .as("There should no sessions remain in consul after all locks have been released.") + .isEmpty(); } @Override protected LockProvider getLockProvider() { - return new ConsulLockProvider( - ConsulLockProvider.Configuration.builder() - .withConsulClient(consulClient).build() - ); + return new ConsulLockProvider(ConsulLockProvider.Configuration.builder() + .withConsulClient(consulClient) + .build()); } @Override @@ -87,7 +83,9 @@ private GetValue getLockValue(String lockName) { @Test @Override public void shouldTimeout() throws InterruptedException { - // as consul has 10 seconds ttl minimum and has double ttl unlocking time, you have to wait for 20 seconds for the unlock time. + // as consul has 10 seconds ttl minimum and has double ttl unlocking time, you + // have to wait for + // 20 seconds for the unlock time. Duration lockAtMostFor = Duration.ofSeconds(11); LockConfiguration configWithShortTimeout = lockConfig(LOCK_NAME1, lockAtMostFor, Duration.ZERO); Optional lock1 = getLockProvider().lock(configWithShortTimeout); @@ -96,7 +94,8 @@ public void shouldTimeout() throws InterruptedException { sleep(lockAtMostFor.multipliedBy(2).toMillis() + 100); assertUnlocked(LOCK_NAME1); - Optional lock2 = getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(50), Duration.ZERO)); + Optional lock2 = + getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(50), Duration.ZERO)); assertThat(lock2).isNotEmpty(); lock2.get().unlock(); } @@ -119,18 +118,9 @@ private static class MyConsulContainer extends GenericContainerLicensed 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 + *

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 + *

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 net.javacrumbs.shedlock.provider.consul; -import com.ecwid.consul.v1.ConsulClient; -import com.ecwid.consul.v1.QueryParams; -import com.ecwid.consul.v1.Response; -import com.ecwid.consul.v1.kv.model.PutParams; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.SimpleLock; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Optional; -import java.util.UUID; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyString; @@ -39,15 +23,31 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.ecwid.consul.v1.ConsulClient; +import com.ecwid.consul.v1.QueryParams; +import com.ecwid.consul.v1.Response; +import com.ecwid.consul.v1.kv.model.PutParams; +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ConsulLockProviderTest { - // lower values may produce false negatives because scheduler may not complete necessary tasks in time + // lower values may produce false negatives because scheduler may not complete + // necessary tasks in + // time private static final Duration SMALL_MIN_TTL = Duration.ofMillis(200); private final ConsulClient mockConsulClient = mock(ConsulClient.class); private final ConsulLockProvider lockProvider = new ConsulLockProvider(mockConsulClient, SMALL_MIN_TTL); @BeforeEach void setUp() { - when(mockConsulClient.sessionCreate(any(), any(), any())).thenReturn(new Response<>(UUID.randomUUID().toString(), null, null, null)); + when(mockConsulClient.sessionCreate(any(), any(), any())) + .thenReturn(new Response<>(UUID.randomUUID().toString(), null, null, null)); mockLock(any(), true); } @@ -90,15 +90,18 @@ void destroysSessionIfLockIsAlreadyObtained() { @Test void doesNotBlockSchedulerInCaseOfFailure() { when(mockConsulClient.sessionDestroy(any(), any(), any())) - .thenThrow(new RuntimeException("Sasuke is not in Konoha, Naruto alone is unable to destroy session :(")) - .thenReturn(new Response<>(null, null, null, null)); + .thenThrow( + new RuntimeException("Sasuke is not in Konoha, Naruto alone is unable to destroy session :(")) + .thenReturn(new Response<>(null, null, null, null)); - Optional lock = lockProvider.lock(lockConfig("sasuke", SMALL_MIN_TTL.multipliedBy(10), SMALL_MIN_TTL)); + Optional lock = + lockProvider.lock(lockConfig("sasuke", SMALL_MIN_TTL.multipliedBy(10), SMALL_MIN_TTL)); assertThat(lock).isNotEmpty(); lock.get().unlock(); sleep(SMALL_MIN_TTL.toMillis() + 10); - Optional lock2 = lockProvider.lock(lockConfig("sakura", SMALL_MIN_TTL.multipliedBy(10), SMALL_MIN_TTL)); + Optional lock2 = + lockProvider.lock(lockConfig("sakura", SMALL_MIN_TTL.multipliedBy(10), SMALL_MIN_TTL)); assertThat(lock2).isNotEmpty(); lock2.get().unlock(); sleep(SMALL_MIN_TTL.toMillis() + 10); @@ -108,7 +111,7 @@ void doesNotBlockSchedulerInCaseOfFailure() { private void mockLock(String eq, boolean b) { when(mockConsulClient.setKVValue(eq, any(), any(), any(PutParams.class))) - .thenReturn(new Response<>(b, null, null, null)); + .thenReturn(new Response<>(b, null, null, null)); } private LockConfiguration lockConfig(String name, Duration lockAtMostFor, Duration lockAtLeastFor) { diff --git a/providers/couchbase/shedlock-provider-couchbase-javaclient3/pom.xml b/providers/couchbase/shedlock-provider-couchbase-javaclient3/pom.xml index 52c75e017..a9e1e663b 100644 --- a/providers/couchbase/shedlock-provider-couchbase-javaclient3/pom.xml +++ b/providers/couchbase/shedlock-provider-couchbase-javaclient3/pom.xml @@ -3,32 +3,32 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-couchbase-javaclient3 - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - - + + net.javacrumbs.shedlock shedlock-core ${project.version} - + net.javacrumbs.shedlock shedlock-test-support ${project.version} test - + com.couchbase.client java-client - 3.3.3 + 3.7.5 diff --git a/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProvider.java b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProvider.java index 4e04a3f49..347ccd9f4 100644 --- a/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProvider.java +++ b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProvider.java @@ -1,20 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.couchbase.javaclient3; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + import com.couchbase.client.core.error.CasMismatchException; import com.couchbase.client.core.error.DocumentExistsException; import com.couchbase.client.java.Bucket; @@ -22,20 +22,18 @@ import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.kv.GetResult; import com.couchbase.client.java.kv.ReplaceOptions; +import java.time.Instant; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.time.Instant; - -import static net.javacrumbs.shedlock.support.Utils.toIsoString; /** * Distributed lock using CouchbaseDB + * *

* It uses a collection that contains documents like this: + * *

  * {
  *    "_id" : "lock name",
@@ -45,24 +43,21 @@
  * }
  * 
* - * lockedAt and lockedBy are just for troubleshooting and are not read by the code + * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code * *
    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter _id == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - * Obtaining a optimistic lock in Couchbase Server, Uses the check-and-set (CAS) API to retrieve a CAS revision number - * CAS number prevents from 2 users to update the same document at the same time. - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock Obtaining a + * optimistic lock in Couchbase Server, Uses the check-and-set (CAS) API to + * retrieve a CAS revision number CAS number prevents from 2 users to update the + * same document at the same time. + *
  12. When unlocking, lock_until is set to now. *
*/ public class CouchbaseLockProvider extends StorageBasedLockProvider { @@ -92,12 +87,12 @@ private static class CouchbaseAccessor extends AbstractStorageAccessor { } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean insertRecord(LockConfiguration lockConfiguration) { JsonObject content = JsonObject.create() - .put(LOCK_NAME, lockConfiguration.getName()) - .put(LOCK_UNTIL, toIsoString(lockConfiguration.getLockAtMostUntil())) - .put(LOCKED_AT, toIsoString(ClockProvider.now())) - .put(LOCKED_BY, getHostname()); + .put(LOCK_NAME, lockConfiguration.getName()) + .put(LOCK_UNTIL, toIsoString(lockConfiguration.getLockAtMostUntil())) + .put(LOCKED_AT, toIsoString(ClockProvider.now())) + .put(LOCKED_BY, getHostname()); try { collection.insert(lockConfiguration.getName(), content); @@ -112,7 +107,7 @@ private Instant parse(Object instant) { } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean updateRecord(LockConfiguration lockConfiguration) { GetResult result = collection.get(lockConfiguration.getName()); JsonObject document = result.contentAsObject(); @@ -127,8 +122,10 @@ public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { document.put(LOCKED_BY, getHostname()); try { - collection.replace(lockConfiguration.getName(), document, - ReplaceOptions.replaceOptions().cas(result.cas())); + collection.replace( + lockConfiguration.getName(), + document, + ReplaceOptions.replaceOptions().cas(result.cas())); } catch (CasMismatchException e) { return false; } @@ -136,7 +133,7 @@ public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { + public boolean extend(LockConfiguration lockConfiguration) { GetResult result = collection.get(lockConfiguration.getName()); JsonObject document = result.contentAsObject(); @@ -149,8 +146,10 @@ public boolean extend(@NonNull LockConfiguration lockConfiguration) { document.put(LOCK_UNTIL, toIsoString(lockConfiguration.getLockAtMostUntil())); try { - collection.replace(lockConfiguration.getName(), document, - ReplaceOptions.replaceOptions().cas(result.cas())); + collection.replace( + lockConfiguration.getName(), + document, + ReplaceOptions.replaceOptions().cas(result.cas())); } catch (CasMismatchException e) { return false; } @@ -158,7 +157,7 @@ public boolean extend(@NonNull LockConfiguration lockConfiguration) { } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { + public void unlock(LockConfiguration lockConfiguration) { GetResult result = collection.get(lockConfiguration.getName()); JsonObject document = result.contentAsObject(); @@ -166,6 +165,4 @@ public void unlock(@NonNull LockConfiguration lockConfiguration) { collection.replace(lockConfiguration.getName(), document); } } - } - diff --git a/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/package-info.java b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/package-info.java new file mode 100644 index 000000000..a51222f0f --- /dev/null +++ b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/main/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.couchbase.javaclient3; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/test/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProviderIntegrationTest.java b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/test/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProviderIntegrationTest.java index 9a3f7333f..56ea605bc 100644 --- a/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/test/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProviderIntegrationTest.java +++ b/providers/couchbase/shedlock-provider-couchbase-javaclient3/src/test/java/net/javacrumbs/shedlock/provider/couchbase/javaclient3/CouchbaseLockProviderIntegrationTest.java @@ -1,20 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.couchbase.javaclient3; +import static java.time.Instant.parse; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCK_UNTIL; +import static org.assertj.core.api.Assertions.assertThat; + import com.couchbase.client.core.env.SeedNode; import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; @@ -22,6 +27,11 @@ import com.couchbase.client.java.Collection; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.kv.GetResult; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; import org.junit.jupiter.api.AfterAll; @@ -31,20 +41,6 @@ import org.testcontainers.couchbase.BucketDefinition; import org.testcontainers.couchbase.CouchbaseContainer; -import java.time.Duration; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import static java.time.Instant.parse; -import static java.util.Collections.singletonList; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.couchbase.javaclient3.CouchbaseLockProvider.LOCK_UNTIL; -import static org.assertj.core.api.Assertions.assertThat; - public class CouchbaseLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { private static final String BUCKET_NAME = "test"; @@ -56,12 +52,12 @@ public class CouchbaseLockProviderIntegrationTest extends AbstractStorageBasedLo private static CouchbaseContainer container; @BeforeAll - public static void startCouchbase () { + public static void startCouchbase() { container = new CouchbaseContainer().withBucket(new BucketDefinition(BUCKET_NAME)); container.start(); - Set seedNodes = new HashSet<>(List.of( - SeedNode.create(container.getContainerIpAddress(), + Set seedNodes = new HashSet<>(List.of(SeedNode.create( + container.getHost(), Optional.of(container.getBootstrapCarrierDirectPort()), Optional.of(container.getBootstrapHttpDirectPort())))); ClusterOptions options = ClusterOptions.clusterOptions(container.getUsername(), container.getPassword()); @@ -73,13 +69,13 @@ public static void startCouchbase () { } @AfterAll - public static void stopCouchbase () { + public static void stopCouchbase() { cluster.disconnect(); container.stop(); } @BeforeEach - public void createLockProvider() { + public void createLockProvider() { lockProvider = new CouchbaseLockProvider(bucket.defaultCollection()); } diff --git a/providers/datastore/shedlock-provider-datastore/pom.xml b/providers/datastore/shedlock-provider-datastore/pom.xml new file mode 100644 index 000000000..a5ad9b9d6 --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + net.javacrumbs.shedlock + shedlock-parent + 6.10.1-SNAPSHOT + ../../../pom.xml + + + shedlock-provider-datastore + ${project.groupId}:${project.artifactId} + + + 2.31.4 + + + + + com.google.cloud + google-cloud-datastore + ${datastore.ver} + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + gcloud + ${test-containers.ver} + test + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + org.assertj + assertj-core + ${assertj.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.datastore + + + + + + + + diff --git a/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProvider.java b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProvider.java new file mode 100644 index 000000000..3813dea16 --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProvider.java @@ -0,0 +1,79 @@ +package net.javacrumbs.shedlock.provider.datastore; + +import static java.util.Objects.requireNonNull; + +import com.google.cloud.datastore.Datastore; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; + +public class DatastoreLockProvider extends StorageBasedLockProvider { + + public DatastoreLockProvider(Datastore datastore) { + super(new DatastoreStorageAccessor( + Configuration.builder().withDatastore(datastore).build())); + } + + public DatastoreLockProvider(Configuration configuration) { + super(new DatastoreStorageAccessor(configuration)); + } + + public static class Configuration { + private final String entityName; + private final FieldNames fieldNames; + private final Datastore datastore; + + Configuration(String entityName, FieldNames fieldNames, Datastore datastore) { + this.entityName = requireNonNull(entityName); + this.fieldNames = requireNonNull(fieldNames); + this.datastore = requireNonNull(datastore); + } + + public String getEntityName() { + return entityName; + } + + public FieldNames getFieldNames() { + return fieldNames; + } + + public Datastore getDatastore() { + return datastore; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String entityName = "lock"; + private FieldNames fieldNames = new FieldNames("lock_until", "locked_at", "locked_by"); + private Datastore datastore; + + public Builder withEntityName(String entityName) { + this.entityName = entityName; + return this; + } + + public Builder withFieldNames(FieldNames fieldNames) { + this.fieldNames = fieldNames; + return this; + } + + public Builder withDatastore(Datastore datastore) { + this.datastore = datastore; + return this; + } + + public Configuration build() { + return new Configuration(this.entityName, this.fieldNames, this.datastore); + } + } + } + + public record FieldNames(String lockUntil, String lockedAt, String lockedBy) { + public FieldNames(String lockUntil, String lockedAt, String lockedBy) { + this.lockUntil = requireNonNull(lockUntil); + this.lockedAt = requireNonNull(lockedAt); + this.lockedBy = requireNonNull(lockedBy); + } + } +} diff --git a/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreStorageAccessor.java b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreStorageAccessor.java new file mode 100644 index 000000000..3ef6d2035 --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/DatastoreStorageAccessor.java @@ -0,0 +1,167 @@ +package net.javacrumbs.shedlock.provider.datastore; + +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + +import com.google.cloud.Timestamp; +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreException; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.KeyFactory; +import com.google.cloud.datastore.Transaction; +import java.time.Instant; +import java.util.Optional; +import java.util.function.Function; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; +import net.javacrumbs.shedlock.support.Utils; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DatastoreStorageAccessor extends AbstractStorageAccessor { + private static final Logger log = LoggerFactory.getLogger(DatastoreStorageAccessor.class); + + private final Datastore datastore; + private final String hostname; + private final String entityName; + private final DatastoreLockProvider.FieldNames fieldNames; + + public DatastoreStorageAccessor(DatastoreLockProvider.Configuration configuration) { + requireNonNull(configuration); + this.datastore = configuration.getDatastore(); + this.hostname = Utils.getHostname(); + this.entityName = configuration.getEntityName(); + this.fieldNames = configuration.getFieldNames(); + } + + @Override + public boolean insertRecord(LockConfiguration config) { + return insert(config.getName(), config.getLockAtMostUntil()); + } + + @Override + public boolean updateRecord(LockConfiguration config) { + return updateExisting(config.getName(), config.getLockAtMostUntil()); + } + + @Override + public void unlock(LockConfiguration config) { + updateOwn(config.getName(), config.getUnlockTime()); + } + + @Override + public boolean extend(LockConfiguration config) { + return updateOwn(config.getName(), config.getLockAtMostUntil()); + } + + private boolean insert(String name, Instant until) { + return doInTxn(txn -> { + KeyFactory keyFactory = this.datastore.newKeyFactory().setKind(this.entityName); + Key key = keyFactory.newKey(name); + Entity entity = Entity.newBuilder(key) + .set(this.fieldNames.lockUntil(), fromInstant(until)) + .set(this.fieldNames.lockedAt(), fromInstant(ClockProvider.now())) + .set(this.fieldNames.lockedBy(), this.hostname) + .build(); + txn.add(entity); + return Optional.of(true); + }) + .orElse(false); + } + + private boolean updateExisting(String name, Instant until) { + return doInTxn(txn -> get(name, txn) + .filter(entity -> { + var now = ClockProvider.now(); + var lockUntilTs = nullableTimestamp(entity, this.fieldNames.lockUntil()); + return lockUntilTs != null && lockUntilTs.isBefore(now); + }) + .map(entity -> { + txn.put(Entity.newBuilder(entity) + .set(this.fieldNames.lockUntil(), fromInstant(until)) + .set(this.fieldNames.lockedAt(), fromInstant(ClockProvider.now())) + .set(this.fieldNames.lockedBy(), this.hostname) + .build()); + return true; + })) + .orElse(false); + } + + private boolean updateOwn(String name, Instant until) { + return doInTxn(txn -> get(name, txn) + .filter(entity -> this.hostname.equals(nullableString(entity, this.fieldNames.lockedBy()))) + .filter(entity -> { + var now = ClockProvider.now(); + var lockUntilTs = nullableTimestamp(entity, this.fieldNames.lockUntil()); + return lockUntilTs != null && (lockUntilTs.isAfter(now) || lockUntilTs.equals(now)); + }) + .map(entity -> { + txn.put(Entity.newBuilder(entity) + .set(this.fieldNames.lockUntil(), fromInstant(until)) + .build()); + return true; + })) + .orElse(false); + } + + public Optional findLock(String name) { + return get(name) + .map(entity -> new Lock( + entity.getKey().getName(), + nullableTimestamp(entity, this.fieldNames.lockedAt()), + nullableTimestamp(entity, this.fieldNames.lockUntil()), + nullableString(entity, this.fieldNames.lockedBy()))); + } + + private Optional get(String name) { + KeyFactory keyFactory = this.datastore.newKeyFactory().setKind(this.entityName); + Key key = keyFactory.newKey(name); + return ofNullable(this.datastore.get(key)); + } + + private Optional get(String name, Transaction txn) { + KeyFactory keyFactory = this.datastore.newKeyFactory().setKind(this.entityName); + Key key = keyFactory.newKey(name); + return ofNullable(txn.get(key)); + } + + private Optional doInTxn(Function> work) { + var txn = this.datastore.newTransaction(); + try { + var result = work.apply(txn); + txn.commit(); + return result; + } catch (DatastoreException ex) { + log.debug("Unable to perform a transactional unit of work: {}", ex.getMessage()); + return Optional.empty(); + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + + @Nullable + private static String nullableString(Entity entity, String property) { + return entity.contains(property) ? entity.getString(property) : null; + } + + @Nullable + private static Instant nullableTimestamp(Entity entity, String property) { + return entity.contains(property) ? toInstant(entity.getTimestamp(property)) : null; + } + + private static Timestamp fromInstant(Instant instant) { + return Timestamp.of(java.sql.Timestamp.from(requireNonNull(instant))); + } + + private static Instant toInstant(Timestamp timestamp) { + return requireNonNull(timestamp).toSqlTimestamp().toInstant(); + } + + public record Lock( + String name, @Nullable Instant lockedAt, @Nullable Instant lockedUntil, @Nullable String lockedBy) {} +} diff --git a/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/package-info.java b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/package-info.java new file mode 100644 index 000000000..6ab0a30e8 --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/src/main/java/net/javacrumbs/shedlock/provider/datastore/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.datastore; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/datastore/shedlock-provider-datastore/src/test/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProviderIntegrationTest.java b/providers/datastore/shedlock-provider-datastore/src/test/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProviderIntegrationTest.java new file mode 100644 index 000000000..ba8f8e9fa --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/src/test/java/net/javacrumbs/shedlock/provider/datastore/DatastoreLockProviderIntegrationTest.java @@ -0,0 +1,90 @@ +package net.javacrumbs.shedlock.provider.datastore; + +import static com.google.cloud.datastore.Query.ResultType.ENTITY; +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.cloud.NoCredentials; +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.Query; +import java.util.Optional; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.DatastoreEmulatorContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +class DatastoreLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { + @Container + public static final DatastoreEmulatorContainer datastoreEmulator; + + static { + DockerImageName googleCloudCliImage = + DockerImageName.parse("gcr.io/google.com/cloudsdktool/google-cloud-cli:425.0.0-emulators"); + datastoreEmulator = new DatastoreEmulatorContainer(googleCloudCliImage) + .withFlags( + "--project shedlock-provider-datastore-test --host-port 0.0.0.0:8081 --use-firestore-in-datastore-mode") + .withReuse(true); + } + + private Datastore datastore; + private DatastoreLockProvider.Configuration configuration; + private DatastoreStorageAccessor accessor; + private DatastoreLockProvider provider; + + @BeforeEach + void init() { + this.datastore = DatastoreOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setProjectId("shedlock-provider-datastore-test") + .setHost("http://" + datastoreEmulator.getEmulatorEndpoint()) + .build() + .getService(); + this.configuration = DatastoreLockProvider.Configuration.builder() + .withDatastore(datastore) + .withEntityName("shedlock") + .withFieldNames(new DatastoreLockProvider.FieldNames("until", "at", "by")) + .build(); + this.accessor = new DatastoreStorageAccessor(this.configuration); + this.provider = new DatastoreLockProvider(this.configuration); + } + + @AfterEach + void tearDown() { + var locks = String.format("select * from %s", this.configuration.getEntityName()); + var results = this.datastore.run(Query.newGqlQueryBuilder(ENTITY, locks).build()); + results.forEachRemaining(entity -> this.datastore.delete(entity.getKey())); + } + + @Override + protected void assertUnlocked(String lockName) { + var now = ClockProvider.now(); + var lock = findLock(lockName).orElseThrow(); + assertThat(lock.lockedUntil()).isBefore(now); + assertThat(lock.lockedAt()).isBefore(now); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected void assertLocked(String lockName) { + var now = ClockProvider.now(); + var lock = findLock(lockName).orElseThrow(); + assertThat(lock.lockedUntil()).isAfter(now); + assertThat(lock.lockedAt()).isBefore(now); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected StorageBasedLockProvider getLockProvider() { + return this.provider; + } + + Optional findLock(String lockName) { + return this.accessor.findLock(lockName); + } +} diff --git a/providers/datastore/shedlock-provider-datastore/src/test/resources/logback.xml b/providers/datastore/shedlock-provider-datastore/src/test/resources/logback.xml new file mode 100644 index 000000000..8ee4ac478 --- /dev/null +++ b/providers/datastore/shedlock-provider-datastore/src/test/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/pom.xml b/providers/dynamodb/shedlock-provider-dynamodb2/pom.xml index 6eb806b7d..7d6d352cf 100644 --- a/providers/dynamodb/shedlock-provider-dynamodb2/pom.xml +++ b/providers/dynamodb/shedlock-provider-dynamodb2/pom.xml @@ -3,20 +3,20 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-dynamodb2 - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} software.amazon.awssdk bom - 2.17.1 + 2.29.19 pom import diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProvider.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProvider.java index a444062b4..e7014c887 100644 --- a/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProvider.java +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProvider.java @@ -1,47 +1,46 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.dynamodb2; +import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; -import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.Utils; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; import software.amazon.awssdk.services.dynamodb.model.ReturnValue; import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static java.util.Collections.singletonMap; -import static java.util.Objects.requireNonNull; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; +import software.amazon.awssdk.utils.StringUtils; /** - * Distributed lock using DynamoDB. - * Depends on software.amazon.awssdk:dynamodb. + * Distributed lock using DynamoDB. Depends on + * software.amazon.awssdk:dynamodb. + * *

* It uses a table with the following structure: + * *

  * {
  *    "_id" : "lock name",
@@ -55,19 +54,14 @@
  * and are not read by the code.
  *
  * 
    - *
  1. - * Attempts to insert a new lock record. - *
  2. - *
  3. - * We will try to update lock record using filter _id == :name AND lock_until <= :now. - *
  4. - *
  5. - * If the update succeeded, we have the lock. If the update failed (condition check exception) - * somebody else holds the lock. - *
  6. - *
  7. - * When unlocking, lock_until is set to now or lockAtLeastUntil whichever is later. - *
  8. + *
  9. Attempts to insert a new lock record. + *
  10. We will try to update lock record using + * filter _id == :name AND lock_until <= :now + * . + *
  11. If the update succeeded, we have the lock. If the update failed + * (condition check exception) somebody else holds the lock. + *
  12. When unlocking, lock_until is set to now or + * lockAtLeastUntil whichever is later. *
*/ public class DynamoDBLockProvider implements LockProvider { @@ -75,42 +69,71 @@ public class DynamoDBLockProvider implements LockProvider { static final String LOCKED_AT = "lockedAt"; static final String LOCKED_BY = "lockedBy"; static final String ID = "_id"; + static final String SORT = "_SortKey"; private static final String OBTAIN_LOCK_QUERY = "set " + LOCK_UNTIL + " = :lockUntil, " + LOCKED_AT + " = :lockedAt, " + LOCKED_BY + " = :lockedBy"; private static final String OBTAIN_LOCK_CONDITION = LOCK_UNTIL + " <= :lockedAt or attribute_not_exists(" + LOCK_UNTIL + ")"; - private static final String RELEASE_LOCK_QUERY = - "set " + LOCK_UNTIL + " = :lockUntil"; + private static final String RELEASE_LOCK_QUERY = "set " + LOCK_UNTIL + " = :lockUntil"; private final String hostname; private final DynamoDbClient dynamoDbClient; private final String tableName; + private final String partitionKeyName; + + @Nullable + private final String sortKeyName; /** * Uses DynamoDB to coordinate locks * - * @param dynamoDbClient v2 of DynamoDB client - * @param tableName the lock table name + * @param dynamoDbClient + * v2 of DynamoDB client + * @param tableName + * the lock table name */ - public DynamoDBLockProvider(@NonNull DynamoDbClient dynamoDbClient, @NonNull String tableName) { + public DynamoDBLockProvider(DynamoDbClient dynamoDbClient, String tableName) { + this(dynamoDbClient, tableName, ID); + } + + /** + * Uses DynamoDB to coordinate locks + * + * @param dynamoDbClient v2 of DynamoDB client + * @param tableName the lock table name + * @param partitionKeyName the partitionKey name of table + */ + public DynamoDBLockProvider(DynamoDbClient dynamoDbClient, String tableName, String partitionKeyName) { + this(dynamoDbClient, tableName, partitionKeyName, null); + } + + /** + * Uses DynamoDB to coordinate locks + * + * @param dynamoDbClient v2 of DynamoDB client + * @param tableName the lock table name + * @param partitionKeyName the partitionKey name of table + * @param sortKeyName the sortKey name of table + */ + public DynamoDBLockProvider( + DynamoDbClient dynamoDbClient, String tableName, String partitionKeyName, @Nullable String sortKeyName) { this.dynamoDbClient = requireNonNull(dynamoDbClient, "dynamoDbClient can not be null"); this.tableName = requireNonNull(tableName, "tableName can not be null"); + this.partitionKeyName = requireNonNull(partitionKeyName, "partitionKeyName can not be null"); + this.sortKeyName = sortKeyName; this.hostname = Utils.getHostname(); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { String nowIso = toIsoString(now()); String lockUntilIso = toIsoString(lockConfiguration.getLockAtMostUntil()); - Map key = singletonMap(ID, attr(lockConfiguration.getName())); + Map key = getKey(lockConfiguration); - Map attributeUpdates = new HashMap<>(3); - attributeUpdates.put(":lockUntil", attr(lockUntilIso)); - attributeUpdates.put(":lockedAt", attr(nowIso)); - attributeUpdates.put(":lockedBy", attr(hostname)); + Map attributeUpdates = + Map.of(":lockUntil", attr(lockUntilIso), ":lockedAt", attr(nowIso), ":lockedBy", attr(hostname)); UpdateItemRequest request = UpdateItemRequest.builder() .tableName(tableName) @@ -124,38 +147,45 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration) { try { // There are three possible situations: // 1. The lock document does not exist yet - it is inserted - we have the lock - // 2. The lock document exists and lockUtil <= now - it is updated - we have the lock - // 3. The lock document exists and lockUtil > now - ConditionalCheckFailedException is thrown + // 2. The lock document exists and lockUtil <= now - it is updated - we have the + // lock + // 3. The lock document exists and lockUtil > now - + // ConditionalCheckFailedException is thrown dynamoDbClient.updateItem(request); - return Optional.of(new DynamoDBLock(dynamoDbClient, tableName, lockConfiguration)); + return Optional.of(new DynamoDBLock(dynamoDbClient, tableName, lockConfiguration, key)); } catch (ConditionalCheckFailedException e) { // Condition failed. This means there was a lock with lockUntil > now. return Optional.empty(); } } - private static AttributeValue attr(String lockUntilIso) { - return AttributeValue.builder() - .s(lockUntilIso) - .build(); + private Map getKey(LockConfiguration lockConfiguration) { + Map key = new HashMap<>(); + key.put(partitionKeyName, attr(lockConfiguration.getName())); + if (StringUtils.isNotBlank(sortKeyName)) { + key.put(sortKeyName, attr(lockConfiguration.getName().concat(SORT))); + } + return key; } - private Instant now() { - return ClockProvider.now(); + private static AttributeValue attr(String lockUntilIso) { + return AttributeValue.builder().s(lockUntilIso).build(); } private static final class DynamoDBLock extends AbstractSimpleLock { private final DynamoDbClient dynamoDbClient; private final String tableName; + private final Map key; private DynamoDBLock( - DynamoDbClient dynamoDbClient, - String tableName, - LockConfiguration lockConfiguration - ) { + DynamoDbClient dynamoDbClient, + String tableName, + LockConfiguration lockConfiguration, + Map key) { super(lockConfiguration); this.dynamoDbClient = dynamoDbClient; this.tableName = tableName; + this.key = key; } @Override @@ -163,8 +193,6 @@ public void doUnlock() { // Set lockUntil to now or lockAtLeastUntil whichever is later String unlockTimeIso = toIsoString(lockConfiguration.getUnlockTime()); - Map key = singletonMap(ID, attr(lockConfiguration.getName())); - Map attributeUpdates = singletonMap(":lockUntil", attr(unlockTimeIso)); UpdateItemRequest request = UpdateItemRequest.builder() diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBUtils.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBUtils.java index bd56433bc..767a460c2 100644 --- a/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBUtils.java +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBUtils.java @@ -1,20 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.dynamodb2; +import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.ID; + import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; @@ -24,38 +24,58 @@ import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.ID; - public class DynamoDBUtils { /** * Creates a locking table with the given name. + * *

* This method does not check if a table with the given name exists already. * - * @param ddbClient v2 of DynamoDBClient - * @param tableName table to be used - * @param throughput AWS {@link ProvisionedThroughput throughput requirements} for the given lock setup - * @return the table name + * @param ddbClient + * v2 of DynamoDBClient + * @param tableName + * table to be used + * @param throughput + * AWS {@link ProvisionedThroughput throughput requirements} for the + * given lock setup + * @return the table name + * @throws ResourceInUseException + * The operation conflicts with the resource's availability. You + * attempted to recreate an existing table. + */ + public static String createLockTable(DynamoDbClient ddbClient, String tableName, ProvisionedThroughput throughput) { + return createLockTable(ddbClient, tableName, throughput, ID); + } + /** + * Creates a locking table with the given name. + * + *

+ * This method does not check if a table with the given name exists already. * + * @param ddbClient + * v2 of DynamoDBClient + * @param tableName + * table to be used + * @param throughput + * AWS {@link ProvisionedThroughput throughput requirements} for the + * given lock setup + * @return the table name * @throws ResourceInUseException - * The operation conflicts with the resource's availability. You attempted to recreate an - * existing table. + * The operation conflicts with the resource's availability. You + * attempted to recreate an existing table. */ public static String createLockTable( - DynamoDbClient ddbClient, - String tableName, - ProvisionedThroughput throughput - ) { + DynamoDbClient ddbClient, String tableName, ProvisionedThroughput throughput, String partitionKeyName) { CreateTableRequest request = CreateTableRequest.builder() .tableName(tableName) .keySchema(KeySchemaElement.builder() - .attributeName(ID) + .attributeName(partitionKeyName) .keyType(KeyType.HASH) .build()) .attributeDefinitions(AttributeDefinition.builder() - .attributeName(ID) + .attributeName(partitionKeyName) .attributeType(ScalarAttributeType.S) .build()) .provisionedThroughput(throughput) diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/package-info.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/package-info.java new file mode 100644 index 000000000..4acf303e1 --- /dev/null +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/main/java/net/javacrumbs/shedlock/provider/dynamodb2/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.dynamodb2; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/AbstractDynamoDBLockProviderIntegrationTest.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/AbstractDynamoDBLockProviderIntegrationTest.java new file mode 100644 index 000000000..3bef41dad --- /dev/null +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/AbstractDynamoDBLockProviderIntegrationTest.java @@ -0,0 +1,103 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.dynamodb2; + +import static java.time.Instant.now; +import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCK_UNTIL; +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.time.Instant; +import java.util.Map; +import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest; +import software.amazon.awssdk.services.dynamodb.model.TableStatus; + +@Testcontainers +public abstract class AbstractDynamoDBLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { + protected static final String ID = "_id2"; + + @Container + static final DynamoDbContainer dynamoDbContainer = + new DynamoDbContainer("amazon/dynamodb-local:2.6.1").withExposedPorts(8000); + + protected static final String TABLE_NAME = "Shedlock"; + protected static DynamoDbClient dynamodb; + + protected static void waitForTableBeingActive() { + while (getTableStatus() != TableStatus.ACTIVE) + ; + } + + private static TableStatus getTableStatus() { + return dynamodb.describeTable( + DescribeTableRequest.builder().tableName(TABLE_NAME).build()) + .table() + .tableStatus(); + } + + /** + * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance + * + * @return A DynamoDbClient pointing to the local DynamoDb instance + */ + protected static DynamoDbClient createClient() { + String endpoint = "http://" + dynamoDbContainer.getHost() + ":" + dynamoDbContainer.getFirstMappedPort(); + return DynamoDbClient.builder() + .endpointOverride(URI.create(endpoint)) + // The region is meaningless for local DynamoDb but required for client builder + // validation + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("dummy", "dummy"))) + .region(Region.US_EAST_1) + .build(); + } + + @Override + protected void assertUnlocked(String lockName) { + Map lockItem = getLockItem(lockName); + assertThat(getTimestamp(lockItem, LOCK_UNTIL)).isBeforeOrEqualTo(now()); + assertThat(getTimestamp(lockItem, LOCKED_AT)).isBeforeOrEqualTo(now()); + assertThat(lockItem.get(LOCKED_BY).s()).isNotEmpty(); + } + + private Instant getTimestamp(Map lockItem, String name) { + return Instant.parse(lockItem.get(name).s()); + } + + @Override + protected void assertLocked(String lockName) { + Map lockItem = getLockItem(lockName); + assertThat(getTimestamp(lockItem, LOCK_UNTIL)).isAfter(now()); + assertThat(getTimestamp(lockItem, LOCKED_AT)).isBeforeOrEqualTo(now()); + assertThat(lockItem.get(LOCKED_BY).s()).isNotEmpty(); + } + + protected abstract Map getLockItem(String lockName); + + private static class DynamoDbContainer extends GenericContainer { + public DynamoDbContainer(String dockerImageName) { + super(dockerImageName); + } + } +} diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderIntegrationTest.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderIntegrationTest.java index ce91ff389..561c84b84 100644 --- a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderIntegrationTest.java +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderIntegrationTest.java @@ -1,152 +1,72 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.dynamodb2; +import java.util.Collections; +import java.util.List; +import java.util.Map; import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; -import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest; import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; import software.amazon.awssdk.services.dynamodb.model.ScanRequest; -import software.amazon.awssdk.services.dynamodb.model.TableStatus; - -import java.net.URI; -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.ID; -import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.LOCK_UNTIL; -import static org.assertj.core.api.Assertions.assertThat; - -@Testcontainers -public class DynamoDBLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { - @Container - public static final DynamoDbContainer dynamoDbContainer = - new DynamoDbContainer("quay.io/testcontainers/dynalite:v1.2.1-1") - .withExposedPorts(4567); - - - private static final String TABLE_NAME = "Shedlock"; - private static DynamoDbClient dynamodb; +public class DynamoDBLockProviderIntegrationTest extends AbstractDynamoDBLockProviderIntegrationTest { + private static final String ID = "_id2"; @BeforeAll - static void createLockProvider() { + static void createLockTable() { dynamodb = createClient(); - String lockTable = DynamoDBUtils.createLockTable( - dynamodb, - TABLE_NAME, - ProvisionedThroughput.builder() - .readCapacityUnits(1L) - .writeCapacityUnits(1L) - .build() - ); - while (getTableStatus(lockTable) != TableStatus.ACTIVE) ; - } - - private static TableStatus getTableStatus(String lockTable) { - return dynamodb.describeTable(DescribeTableRequest.builder().tableName(lockTable).build()).table().tableStatus(); - } - - /** - * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance - * - * @return A DynamoDbClient pointing to the local DynamoDb instance - */ - static DynamoDbClient createClient() { - String endpoint = "http://" + dynamoDbContainer.getContainerIpAddress() + ":" + dynamoDbContainer.getFirstMappedPort(); - return DynamoDbClient.builder() - .endpointOverride(URI.create(endpoint)) - // The region is meaningless for local DynamoDb but required for client builder validation - .region(Region.US_EAST_1) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("dummy-key", "dummy-secret")) - ) - .build(); + DynamoDBUtils.createLockTable( + dynamodb, + TABLE_NAME, + ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build(), + ID); + waitForTableBeingActive(); } @AfterEach public void truncateLockTable() { - List> items = dynamodb.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).items(); + List> items = dynamodb.scan( + ScanRequest.builder().tableName(TABLE_NAME).build()) + .items(); for (Map item : items) { dynamodb.deleteItem(DeleteItemRequest.builder() - .tableName(TABLE_NAME) - .key(Collections.singletonMap(ID, item.get(ID))) - .build()); + .tableName(TABLE_NAME) + .key(Collections.singletonMap(ID, item.get(ID))) + .build()); } } @Override protected LockProvider getLockProvider() { - return new DynamoDBLockProvider(dynamodb, TABLE_NAME); + return new DynamoDBLockProvider(dynamodb, TABLE_NAME, ID); } @Override - protected void assertUnlocked(String lockName) { - Map lockItem = getLockItem(lockName); - assertThat(fromIsoString(lockItem.get(LOCK_UNTIL).s())).isBeforeOrEqualTo(now()); - assertThat(fromIsoString(lockItem.get(LOCKED_AT).s())).isBeforeOrEqualTo(now()); - assertThat(lockItem.get(LOCKED_BY).s()).isNotEmpty(); - } - - private Instant now() { - return Instant.now(); - } - - @Override - protected void assertLocked(String lockName) { - Map lockItem = getLockItem(lockName); - assertThat(fromIsoString(lockItem.get(LOCK_UNTIL).s())).isAfter(now()); - assertThat(fromIsoString(lockItem.get(LOCKED_AT).s())).isBeforeOrEqualTo(now()); - assertThat(lockItem.get(LOCKED_BY).s()).isNotEmpty(); - } - - private Instant fromIsoString(String isoString) { - return Instant.parse(isoString); - } - - private Map getLockItem(String lockName) { + protected Map getLockItem(String lockName) { GetItemRequest request = GetItemRequest.builder() - .tableName(TABLE_NAME) - .key(Collections.singletonMap(ID, AttributeValue.builder() - .s(lockName) - .build())) - .build(); - GetItemResponse response = dynamodb.getItem(request); - return response.item(); - } - - private static class DynamoDbContainer extends GenericContainer { - public DynamoDbContainer(String dockerImageName) { - super(dockerImageName); - } + .tableName(TABLE_NAME) + .key(Collections.singletonMap( + ID, AttributeValue.builder().s(lockName).build())) + .build(); + return dynamodb.getItem(request).item(); } } diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderSortKeyIntegrationTest.java b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderSortKeyIntegrationTest.java new file mode 100644 index 000000000..498831b38 --- /dev/null +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/java/net/javacrumbs/shedlock/provider/dynamodb2/DynamoDBLockProviderSortKeyIntegrationTest.java @@ -0,0 +1,102 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.dynamodb2; + +import static net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider.SORT; + +import java.util.List; +import java.util.Map; +import net.javacrumbs.shedlock.core.LockProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.junit.jupiter.Testcontainers; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; + +@Testcontainers +public class DynamoDBLockProviderSortKeyIntegrationTest extends AbstractDynamoDBLockProviderIntegrationTest { + private static final String SORT_KEY = "_sk"; + + @BeforeAll + static void createLockTable() { + dynamodb = createClient(); + CreateTableRequest request = CreateTableRequest.builder() + .tableName(TABLE_NAME) + .keySchema( + KeySchemaElement.builder() + .attributeName(ID) + .keyType(KeyType.HASH) + .build(), + KeySchemaElement.builder() + .attributeName(SORT_KEY) + .keyType(KeyType.RANGE) + .build()) + .attributeDefinitions( + AttributeDefinition.builder() + .attributeName(ID) + .attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder() + .attributeName(SORT_KEY) + .attributeType(ScalarAttributeType.S) + .build()) + .provisionedThroughput(ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()) + .build(); + dynamodb.createTable(request); + + waitForTableBeingActive(); + } + + @AfterEach + public void truncateLockTable() { + List> items = dynamodb.scan( + ScanRequest.builder().tableName(TABLE_NAME).build()) + .items(); + for (Map item : items) { + dynamodb.deleteItem(DeleteItemRequest.builder() + .tableName(TABLE_NAME) + .key(Map.of(ID, item.get(ID), SORT_KEY, item.get(SORT_KEY))) + .build()); + } + } + + @Override + protected LockProvider getLockProvider() { + return new DynamoDBLockProvider(dynamodb, TABLE_NAME, ID, SORT_KEY); + } + + @Override + protected Map getLockItem(String lockName) { + GetItemRequest request = GetItemRequest.builder() + .tableName(TABLE_NAME) + .key(Map.of( + ID, + AttributeValue.builder().s(lockName).build(), + SORT_KEY, + AttributeValue.builder().s(lockName.concat(SORT)).build())) + .build(); + return dynamodb.getItem(request).item(); + } +} diff --git a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/resources/logback.xml b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/resources/logback.xml index 348588530..d8ff54991 100644 --- a/providers/dynamodb/shedlock-provider-dynamodb2/src/test/resources/logback.xml +++ b/providers/dynamodb/shedlock-provider-dynamodb2/src/test/resources/logback.xml @@ -8,7 +8,7 @@ - + diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch8/pom.xml b/providers/elasticsearch/shedlock-provider-elasticsearch8/pom.xml index 7bcdd91cf..0436ea0c4 100644 --- a/providers/elasticsearch/shedlock-provider-elasticsearch8/pom.xml +++ b/providers/elasticsearch/shedlock-provider-elasticsearch8/pom.xml @@ -3,18 +3,18 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-elasticsearch8 - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - 8.5.2 - 2.1.1 - 2.13.4 + 8.18.0 + 2.1.3 + 2.19.2 diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProvider.java b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProvider.java index 37225855c..16c792046 100644 --- a/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProvider.java +++ b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProvider.java @@ -1,20 +1,21 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.elasticsearch8; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.support.Utils.getHostname; + import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchException; import co.elastic.clients.elasticsearch._types.Refresh; @@ -22,26 +23,21 @@ import co.elastic.clients.elasticsearch.core.UpdateRequest; import co.elastic.clients.elasticsearch.core.UpdateResponse; import co.elastic.clients.json.JsonData; +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; import org.elasticsearch.client.ResponseException; -import java.io.IOException; -import java.time.Instant; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.support.Utils.getHostname; - /** - *

* It uses a collection that contains documents like this: + * *

  * {
  *    "name" : "lock name",
@@ -56,23 +52,20 @@
  *    "lockedBy" : "hostname"
  * }
  * 
+ * *

- * lockedAt and lockedBy are just for troubleshooting and are not read by the code + * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code * *

    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter _id == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  12. When unlocking, lock_until is set to now. *
*/ public class ElasticsearchLockProvider implements LockProvider { @@ -82,58 +75,55 @@ public class ElasticsearchLockProvider implements LockProvider { static final String LOCKED_BY = "lockedBy"; static final String NAME = "name"; - - private static final String UPDATE_SCRIPT = - "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + ") { " + - "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + - "ctx._source." + LOCKED_AT + " = params." + LOCKED_AT + "; " + - "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + - "} else { " + - "ctx.op = 'none' " + - "}"; + private static final String UPDATE_SCRIPT = "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + + ") { " + "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + "ctx._source." + LOCKED_AT + + " = params." + LOCKED_AT + "; " + "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + + "} else { " + "ctx.op = 'none' " + "}"; private final ElasticsearchClient client; private final String hostname; private final String index; - private ElasticsearchLockProvider(@NonNull ElasticsearchClient client, @NonNull String index) { + private ElasticsearchLockProvider(ElasticsearchClient client, String index) { this.client = client; this.hostname = getHostname(); this.index = index; } - public ElasticsearchLockProvider(@NonNull ElasticsearchClient client) { + public ElasticsearchLockProvider(ElasticsearchClient client) { this(client, SCHEDLOCK_DEFAULT_INDEX); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { try { Instant now = now(); Instant lockAtMostUntil = lockConfiguration.getLockAtMostUntil(); - Map lockObject = - lockObject(lockConfiguration.getName(), lockAtMostUntil, now); + Map lockObject = lockObject(lockConfiguration.getName(), lockAtMostUntil, now); // The object exist only to have some type we can work with Lock pojo = new Lock(lockConfiguration.getName(), hostname, now, lockAtMostUntil); - UpdateRequest updateRequest = UpdateRequest.of(ur -> ur - .index(index) - .id(lockConfiguration.getName()) - .refresh(Refresh.True) - .script(sc -> sc.inline(in -> in.lang("painless").source(UPDATE_SCRIPT).params(lockObject))) - .upsert(pojo)); + UpdateRequest updateRequest = UpdateRequest.of(ur -> ur.index(index) + .id(lockConfiguration.getName()) + .refresh(Refresh.True) + .script(sc -> sc.lang("painless").source(UPDATE_SCRIPT).params(lockObject)) + .upsert(pojo)); UpdateResponse res = client.update(updateRequest, Lock.class); if (res.result() != Result.NoOp) { return Optional.of(new ElasticsearchSimpleLock(lockConfiguration)); - } else { //nothing happened + } else { // nothing happened return Optional.empty(); } } catch (IOException | ElasticsearchException e) { - if ((e instanceof ElasticsearchException && ((ElasticsearchException)e).status() == 409) || - (e instanceof ResponseException && ((ResponseException)e).getResponse().getStatusLine().getStatusCode() == 409)) { + if ((e instanceof ElasticsearchException && ((ElasticsearchException) e).status() == 409) + || (e instanceof ResponseException + && ((ResponseException) e) + .getResponse() + .getStatusLine() + .getStatusCode() + == 409)) { return Optional.empty(); } else { throw new LockException("Unexpected exception occurred", e); @@ -143,11 +133,14 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration) { private Map lockObject(String name, Instant lockUntil, Instant lockedAt) { return Map.of( - NAME, JsonData.of(name), - LOCKED_BY, JsonData.of(hostname), - LOCKED_AT, JsonData.of(lockedAt.toEpochMilli()), - LOCK_UNTIL, JsonData.of(lockUntil.toEpochMilli()) - ); + NAME, + JsonData.of(name), + LOCKED_BY, + JsonData.of(hostname), + LOCKED_AT, + JsonData.of(lockedAt.toEpochMilli()), + LOCK_UNTIL, + JsonData.of(lockUntil.toEpochMilli())); } private final class ElasticsearchSimpleLock extends AbstractSimpleLock { @@ -160,14 +153,16 @@ private ElasticsearchSimpleLock(LockConfiguration lockConfiguration) { public void doUnlock() { // Set lockUtil to now or lockAtLeastUntil whichever is later try { - Map lockObject = Collections.singletonMap("unlockTime", JsonData.of(lockConfiguration.getUnlockTime().toEpochMilli())); + Map lockObject = Collections.singletonMap( + "unlockTime", + JsonData.of(lockConfiguration.getUnlockTime().toEpochMilli())); - UpdateRequest updateRequest = UpdateRequest.of(ur -> ur - .index(index) + UpdateRequest updateRequest = UpdateRequest.of(ur -> ur.index(index) .id(lockConfiguration.getName()) .refresh(Refresh.True) - .script(sc -> sc.inline(in -> in.lang("painless").source("ctx._source.lockUntil = params.unlockTime") - .params(lockObject)))); + .script(sc -> sc.lang("painless") + .source("ctx._source.lockUntil = params.unlockTime") + .params(lockObject))); client.update(updateRequest, Lock.class); } catch (IOException | ElasticsearchException e) { throw new LockException("Unexpected exception occurred", e); diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/package-info.java b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/package-info.java new file mode 100644 index 000000000..869170053 --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch8/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.elasticsearch8; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch8/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProviderTest.java b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProviderTest.java index 4459c9e3e..f22b78148 100644 --- a/providers/elasticsearch/shedlock-provider-elasticsearch8/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProviderTest.java +++ b/providers/elasticsearch/shedlock-provider-elasticsearch8/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch8/ElasticsearchLockProviderTest.java @@ -1,20 +1,26 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.elasticsearch8; +import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCK_UNTIL; +import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.NAME; +import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.GetRequest; import co.elastic.clients.elasticsearch.core.GetResponse; @@ -25,6 +31,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Map; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; import org.apache.http.HttpHost; @@ -39,28 +50,16 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; -import java.util.Map; - -import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.LOCK_UNTIL; -import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.NAME; -import static net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Testcontainers public class ElasticsearchLockProviderTest extends AbstractLockProviderIntegrationTest { - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch"); + private static final DockerImageName DEFAULT_IMAGE_NAME = + DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch"); @Container private static final ElasticsearchContainer container = - new ElasticsearchContainer(DEFAULT_IMAGE_NAME.withTag("7.17.5")).withPassword("elastic1234"); + new ElasticsearchContainer(DEFAULT_IMAGE_NAME.withTag("7.17.28")).withPassword("elastic1234"); + private ElasticsearchClient client; private ElasticsearchLockProvider lockProvider; @@ -68,10 +67,10 @@ public class ElasticsearchLockProviderTest extends AbstractLockProviderIntegrati public void setUp() { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "elastic1234")); - RestClient restClient = RestClient.builder( - HttpHost.create(container.getHttpHostAddress())) - .setHttpClientConfigCallback(clientBuilder -> clientBuilder.setDefaultCredentialsProvider(credentialsProvider)) - .build(); + RestClient restClient = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) + .setHttpClientConfigCallback( + clientBuilder -> clientBuilder.setDefaultCredentialsProvider(credentialsProvider)) + .build(); ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(objectMapper())); client = new ElasticsearchClient(transport); lockProvider = new ElasticsearchLockProvider(client); @@ -79,11 +78,11 @@ public void setUp() { private ObjectMapper objectMapper() { JavaTimeModule module = new JavaTimeModule(); - module.addSerializer(LocalDateTime.class, - new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + module.addSerializer( + LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); return new ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .registerModule(module); + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .registerModule(module); } @Override @@ -93,13 +92,12 @@ protected LockProvider getLockProvider() { @Override protected void assertUnlocked(String lockName) { - GetRequest request = GetRequest.of(gr -> gr - .index(SCHEDLOCK_DEFAULT_INDEX) - .id(lockName)); + GetRequest request = + GetRequest.of(gr -> gr.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); try { GetResponse response = client.get(request, Map.class); Map source = response.source(); - assertThat(new Date((Long)source.get(LOCK_UNTIL))).isBeforeOrEqualsTo(now()); + assertThat(new Date((Long) source.get(LOCK_UNTIL))).isBeforeOrEqualsTo(now()); assertThat(new Date((Long) source.get(LOCKED_AT))).isBeforeOrEqualsTo(now()); assertThat((String) source.get(LOCKED_BY)).isNotBlank(); assertThat((String) source.get(NAME)).isEqualTo(lockName); @@ -110,9 +108,8 @@ protected void assertUnlocked(String lockName) { @Override protected void assertLocked(String lockName) { - GetRequest request = GetRequest.of(gr -> gr - .index(SCHEDLOCK_DEFAULT_INDEX) - .id(lockName)); + GetRequest request = + GetRequest.of(gr -> gr.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); try { GetResponse response = client.get(request, Map.class); Map source = response.source(); @@ -128,5 +125,4 @@ protected void assertLocked(String lockName) { private Date now() { return new Date(); } - } diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch9/pom.xml b/providers/elasticsearch/shedlock-provider-elasticsearch9/pom.xml new file mode 100644 index 000000000..78a8ea87c --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch9/pom.xml @@ -0,0 +1,85 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-elasticsearch9 + ${project.groupId}:${project.artifactId} + + + 9.1.0 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + co.elastic.clients + elasticsearch-java + ${elasticsearch.version} + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + elasticsearch + ${test-containers.ver} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + commons-logging + commons-logging + 1.3.5 + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.elasticsearch9 + + + + + + + + + diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProvider.java b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProvider.java new file mode 100644 index 000000000..d31735238 --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProvider.java @@ -0,0 +1,173 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.elasticsearch9; + +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.support.Utils.getHostname; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.ElasticsearchException; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch._types.Result; +import co.elastic.clients.elasticsearch.core.UpdateRequest; +import co.elastic.clients.elasticsearch.core.UpdateResponse; +import co.elastic.clients.json.JsonData; +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import net.javacrumbs.shedlock.core.AbstractSimpleLock; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.support.LockException; + +/** + * It uses a collection that contains documents like this: + * + *

+ * {
+ *    "name" : "lock name",
+ *    "lockUntil" :  {
+ *      "type":   "date",
+ *      "format": "epoch_millis"
+ *    },
+ *    "lockedAt" : {
+ *      "type":   "date",
+ *      "format": "epoch_millis"
+ *    }:
+ *    "lockedBy" : "hostname"
+ * }
+ * 
+ * + *

+ * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code + * + *

    + *
  1. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  2. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  3. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  4. When unlocking, lock_until is set to now. + *
+ */ +public class ElasticsearchLockProvider implements LockProvider { + static final String SCHEDLOCK_DEFAULT_INDEX = "shedlock"; + static final String LOCK_UNTIL = "lockUntil"; + static final String LOCKED_AT = "lockedAt"; + static final String LOCKED_BY = "lockedBy"; + static final String NAME = "name"; + + private static final String UPDATE_SCRIPT = "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + + ") { " + "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + "ctx._source." + LOCKED_AT + + " = params." + LOCKED_AT + "; " + "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + + "} else { " + "ctx.op = 'none' " + "}"; + + private final ElasticsearchClient client; + private final String hostname; + private final String index; + + private ElasticsearchLockProvider(ElasticsearchClient client, String index) { + this.client = client; + this.hostname = getHostname(); + this.index = index; + } + + public ElasticsearchLockProvider(ElasticsearchClient client) { + this(client, SCHEDLOCK_DEFAULT_INDEX); + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + try { + Instant now = now(); + Instant lockAtMostUntil = lockConfiguration.getLockAtMostUntil(); + Map lockObject = lockObject(lockConfiguration.getName(), lockAtMostUntil, now); + + // The object exist only to have some type we can work with + Lock pojo = new Lock(lockConfiguration.getName(), hostname, now, lockAtMostUntil); + + UpdateRequest updateRequest = UpdateRequest.of(ur -> ur.index(index) + .id(lockConfiguration.getName()) + .refresh(Refresh.True) + .script(sc -> sc.lang("painless") + .source(builder -> builder.scriptString(UPDATE_SCRIPT)) + .params(lockObject)) + .upsert(pojo)); + + UpdateResponse res = client.update(updateRequest, Lock.class); + if (res.result() != Result.NoOp) { + return Optional.of(new ElasticsearchSimpleLock(lockConfiguration)); + } else { // nothing happened + return Optional.empty(); + } + } catch (IOException | ElasticsearchException e) { + if ((e instanceof ElasticsearchException && ((ElasticsearchException) e).status() == 409)) { + return Optional.empty(); + } else { + throw new LockException("Unexpected exception occurred", e); + } + } + } + + private Map lockObject(String name, Instant lockUntil, Instant lockedAt) { + return Map.of( + NAME, + JsonData.of(name), + LOCKED_BY, + JsonData.of(hostname), + LOCKED_AT, + JsonData.of(lockedAt.toEpochMilli()), + LOCK_UNTIL, + JsonData.of(lockUntil.toEpochMilli())); + } + + private final class ElasticsearchSimpleLock extends AbstractSimpleLock { + + private ElasticsearchSimpleLock(LockConfiguration lockConfiguration) { + super(lockConfiguration); + } + + @Override + public void doUnlock() { + // Set lockUtil to now or lockAtLeastUntil whichever is later + try { + Map lockObject = Collections.singletonMap( + "unlockTime", + JsonData.of(lockConfiguration.getUnlockTime().toEpochMilli())); + + UpdateRequest updateRequest = UpdateRequest.of(ur -> ur.index(index) + .id(lockConfiguration.getName()) + .refresh(Refresh.True) + .script(sc -> sc.lang("painless") + .source(builder -> builder.scriptString("ctx._source.lockUntil = params.unlockTime")) + .params(lockObject))); + client.update(updateRequest, Lock.class); + } catch (IOException | ElasticsearchException e) { + throw new LockException("Unexpected exception occurred", e); + } + } + } + + private record Lock(String name, String lockedBy, long lockedAt, long lockUntil) { + Lock(String name, String lockedBy, Instant lockedAt, Instant lockUntil) { + this(name, lockedBy, lockedAt.toEpochMilli(), lockUntil.toEpochMilli()); + } + } +} diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/package-info.java b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/package-info.java new file mode 100644 index 000000000..596a3a8ab --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/main/java/net/javacrumbs/shedlock/provider/elasticsearch9/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.elasticsearch9; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProviderTest.java b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProviderTest.java new file mode 100644 index 000000000..dc644fd69 --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/java/net/javacrumbs/shedlock/provider/elasticsearch9/ElasticsearchLockProviderTest.java @@ -0,0 +1,98 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.elasticsearch9; + +import static net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider.LOCK_UNTIL; +import static net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider.NAME; +import static net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.GetRequest; +import co.elastic.clients.elasticsearch.core.GetResponse; +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +public class ElasticsearchLockProviderTest extends AbstractLockProviderIntegrationTest { + + private static final DockerImageName DEFAULT_IMAGE_NAME = + DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch"); + + @Container + private static final ElasticsearchContainer container = + new ElasticsearchContainer(DEFAULT_IMAGE_NAME.withTag("7.17.28")); + + private ElasticsearchClient client; + private ElasticsearchLockProvider lockProvider; + + @BeforeEach + public void setUp() { + client = ElasticsearchClient.of( + b -> b.host("http://" + container.getHttpHostAddress()).usernameAndPassword("elastic", "changeme")); + lockProvider = new ElasticsearchLockProvider(client); + } + + @Override + protected LockProvider getLockProvider() { + return lockProvider; + } + + @Override + protected void assertUnlocked(String lockName) { + GetRequest request = + GetRequest.of(gr -> gr.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); + try { + GetResponse response = client.get(request, Map.class); + Map source = response.source(); + assertThat(new Date((Long) source.get(LOCK_UNTIL))).isBeforeOrEqualTo(now()); + assertThat(new Date((Long) source.get(LOCKED_AT))).isBeforeOrEqualTo(now()); + assertThat((String) source.get(LOCKED_BY)).isNotBlank(); + assertThat((String) source.get(NAME)).isEqualTo(lockName); + } catch (IOException e) { + fail("Call to embedded ES failed."); + } + } + + @Override + protected void assertLocked(String lockName) { + GetRequest request = + GetRequest.of(gr -> gr.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); + try { + GetResponse response = client.get(request, Map.class); + Map source = response.source(); + assertThat(new Date((Long) source.get(LOCK_UNTIL))).isAfter(now()); + assertThat(new Date((Long) source.get(LOCKED_AT))).isBeforeOrEqualTo(now()); + assertThat((String) source.get(LOCKED_BY)).isNotBlank(); + assertThat((String) source.get(NAME)).isEqualTo(lockName); + } catch (IOException e) { + fail("Call to embedded ES failed."); + } + } + + private Date now() { + return new Date(); + } +} diff --git a/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/resources/logback.xml b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/resources/logback.xml new file mode 100644 index 000000000..a35e40a2f --- /dev/null +++ b/providers/elasticsearch/shedlock-provider-elasticsearch9/src/test/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/etcd/shedlock-provider-etcd-jetcd/pom.xml b/providers/etcd/shedlock-provider-etcd-jetcd/pom.xml index bf094e8f7..eb51ae6a8 100644 --- a/providers/etcd/shedlock-provider-etcd-jetcd/pom.xml +++ b/providers/etcd/shedlock-provider-etcd-jetcd/pom.xml @@ -3,15 +3,16 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-etcd-jetcd + ${project.groupId}:${project.artifactId} - 0.7.3 + 0.8.5 diff --git a/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProvider.java b/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProvider.java index 04b0ac40b..47e5aaf9a 100644 --- a/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProvider.java +++ b/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProvider.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.etcd.jetcd; +import static io.etcd.jetcd.options.GetOption.DEFAULT; +import static java.nio.charset.StandardCharsets.UTF_8; +import static net.javacrumbs.shedlock.support.Utils.getHostname; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + import io.etcd.jetcd.ByteSequence; import io.etcd.jetcd.Client; import io.etcd.jetcd.KV; @@ -25,29 +28,25 @@ import io.etcd.jetcd.op.CmpTarget; import io.etcd.jetcd.op.Op; import io.etcd.jetcd.options.PutOption; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - -import static io.etcd.jetcd.options.GetOption.DEFAULT; -import static java.nio.charset.StandardCharsets.UTF_8; -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; /** * Uses etcd keys and the version of the key value pairs as locking mechanism. + * *

* https://etcd.io/docs/v3.4.0/learning/api/#key-value-pair * - * The timeout is implemented with the lease concept of etcd, which grants a TTL for key value pairs. + *

+ * The timeout is implemented with the lease concept of etcd, which grants a TTL + * for key value pairs. */ public class EtcdLockProvider implements LockProvider { private static final double MILLIS_IN_SECOND = 1000; @@ -60,24 +59,22 @@ public class EtcdLockProvider implements LockProvider { private final String environment; - public EtcdLockProvider(@NonNull Client client) { + public EtcdLockProvider(Client client) { this(client, ENV_DEFAULT); } - public EtcdLockProvider(@NonNull Client client, @NonNull String environment) { + public EtcdLockProvider(Client client, String environment) { this.etcdTemplate = new EtcdTemplate(client); this.environment = environment; } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { String key = buildKey(lockConfiguration.getName()); String value = buildValue(); Optional leaseIdOpt = etcdTemplate.tryToLock(key, value, lockConfiguration.getLockAtMostUntil()); return leaseIdOpt.map(leaseId -> new EtcdLock(key, value, leaseId, etcdTemplate, lockConfiguration)); - } private static long getSecondsUntil(Instant instant) { @@ -102,7 +99,12 @@ private static final class EtcdLock extends AbstractSimpleLock { private final Long successLeaseId; private final EtcdTemplate etcdTemplate; - private EtcdLock(String key, String value, Long successLeaseId, EtcdTemplate etcdTemplate, LockConfiguration lockConfiguration) { + private EtcdLock( + String key, + String value, + Long successLeaseId, + EtcdTemplate etcdTemplate, + LockConfiguration lockConfiguration) { super(lockConfiguration); this.key = key; this.value = value; @@ -157,11 +159,13 @@ public Optional tryToLock(String key, String value, Instant lockAtMostUnti PutOption putOption = putOptionWithLeaseId(leaseId); // Version is the version of the key. - // A deletion resets the version to zero and any modification of the key increases its version. + // A deletion resets the version to zero and any modification of the key + // increases its + // version. Txn txn = kvClient.txn() - .If(new Cmp(lockKey, Cmp.Op.EQUAL, CmpTarget.version(0))) - .Then(Op.put(lockKey, toByteSequence(value), putOption)) - .Else(Op.get(lockKey, DEFAULT)); + .If(new Cmp(lockKey, Cmp.Op.EQUAL, CmpTarget.version(0))) + .Then(Op.put(lockKey, toByteSequence(value), putOption)) + .Else(Op.get(lockKey, DEFAULT)); TxnResponse tr = txn.commit().get(); if (tr.isSucceeded()) { @@ -174,7 +178,6 @@ public Optional tryToLock(String key, String value, Instant lockAtMostUnti revoke(leaseId); throw new LockException("Failed to set lock " + key, e); } - } public void revoke(Long leaseId) { @@ -186,12 +189,15 @@ public void revoke(Long leaseId) { } /** - * Set the provided leaseId lease for the key-value pair similar to the CLI command + * Set the provided leaseId lease for the key-value pair similar to the CLI + * command + * *

* etcdctl put key value --lease + * *

- * If the key has already been put with an other leaseId earlier, the old leaseId - * will be timed out and then removed, eventually. + * If the key has already been put with an other leaseId earlier, the old + * leaseId will be timed out and then removed, eventually. */ public void putWithLeaseId(String key, String value, Long leaseId) { ByteSequence lockKey = toByteSequence(key); @@ -206,8 +212,7 @@ private ByteSequence toByteSequence(String key) { } private PutOption putOptionWithLeaseId(Long leaseId) { - return PutOption.newBuilder().withLeaseId(leaseId).build(); + return PutOption.builder().withLeaseId(leaseId).build(); } } - } diff --git a/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/package-info.java b/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/package-info.java new file mode 100644 index 000000000..0094d1b2b --- /dev/null +++ b/providers/etcd/shedlock-provider-etcd-jetcd/src/main/java/net/javacrumbs/shedlock/provider/etcd/jetcd/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.etcd.jetcd; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/etcd/shedlock-provider-etcd-jetcd/src/test/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProviderIntegrationTest.java b/providers/etcd/shedlock-provider-etcd-jetcd/src/test/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProviderIntegrationTest.java index 8d513ddc9..efcab26e5 100644 --- a/providers/etcd/shedlock-provider-etcd-jetcd/src/test/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProviderIntegrationTest.java +++ b/providers/etcd/shedlock-provider-etcd-jetcd/src/test/java/net/javacrumbs/shedlock/provider/etcd/jetcd/EtcdLockProviderIntegrationTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.etcd.jetcd; +import static java.time.Duration.ofSeconds; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.fail; + import io.etcd.jetcd.ByteSequence; import io.etcd.jetcd.Client; import io.etcd.jetcd.KV; @@ -28,11 +31,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static java.time.Duration.ofSeconds; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.fail; - public class EtcdLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { private static final EtcdCluster cluster = new Etcd.Builder().withNodes(1).build(); @@ -72,18 +70,14 @@ private void warmUpLeaseClient(Client client) { } } - /** - * Modified for etcd, since its lease grants only suppport TTL in seconds - */ + /** Modified for etcd, since its lease grants only suppport TTL in seconds */ @Test @Override public void shouldTimeout() throws InterruptedException { doTestTimeout(ofSeconds(1)); } - /** - * Modified for etcd, since its lease grants only suppport TTL in seconds - */ + /** Modified for etcd, since its lease grants only suppport TTL in seconds */ @Test @Override public void shouldLockAtLeastFor() throws InterruptedException { @@ -121,5 +115,4 @@ protected LockProvider getLockProvider() { private Client buildClient() { return Client.builder().endpoints(cluster.clientEndpoints()).build(); } - } diff --git a/providers/firestore/shedlock-provider-firestore/pom.xml b/providers/firestore/shedlock-provider-firestore/pom.xml new file mode 100644 index 000000000..137dc478a --- /dev/null +++ b/providers/firestore/shedlock-provider-firestore/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + net.javacrumbs.shedlock + shedlock-parent + 6.10.1-SNAPSHOT + ../../../pom.xml + + + shedlock-provider-firestore + ${project.groupId}:${project.artifactId} + + + 3.32.2 + + + + + com.google.cloud + google-cloud-firestore + ${firestore.ver} + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + gcloud + ${test-containers.ver} + test + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + org.assertj + assertj-core + ${assertj.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.firestore + + + + + + + + diff --git a/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProvider.java b/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProvider.java new file mode 100644 index 000000000..c86d5e8d4 --- /dev/null +++ b/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProvider.java @@ -0,0 +1,78 @@ +package net.javacrumbs.shedlock.provider.firestore; + +import static java.util.Objects.requireNonNull; + +import com.google.cloud.firestore.Firestore; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.support.annotation.NonNull; + +public class FirestoreLockProvider extends StorageBasedLockProvider { + + public FirestoreLockProvider(@NonNull Firestore firestore) { + super(new FirestoreStorageAccessor( + Configuration.builder().withFirestore(firestore).build())); + } + + public FirestoreLockProvider(@NonNull Configuration configuration) { + super(new FirestoreStorageAccessor(configuration)); + } + + public static class Configuration { + private final String collectionName; + private final FieldNames fieldNames; + private final Firestore firestore; + + Configuration(@NonNull String collectionName, @NonNull FieldNames fieldNames, @NonNull Firestore firestore) { + this.collectionName = requireNonNull(collectionName); + this.fieldNames = requireNonNull(fieldNames); + this.firestore = requireNonNull(firestore); + } + + @NonNull + public String getCollectionName() { + return collectionName; + } + + @NonNull + public FieldNames getFieldNames() { + return fieldNames; + } + + @NonNull + public Firestore getFirestore() { + return firestore; + } + + @NonNull + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String collectionName = "shedlock"; + private FieldNames fieldNames = new FieldNames("lockUntil", "lockedAt", "lockedBy"); + private Firestore firestore; + + public Builder withCollectionName(String collectionName) { + this.collectionName = collectionName; + return this; + } + + public Builder withFieldNames(FieldNames fieldNames) { + this.fieldNames = fieldNames; + return this; + } + + public Builder withFirestore(Firestore firestore) { + this.firestore = firestore; + return this; + } + + public Configuration build() { + return new Configuration(this.collectionName, this.fieldNames, this.firestore); + } + } + } + + public record FieldNames(String lockUntil, String lockedAt, String lockedBy) {} +} diff --git a/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreStorageAccessor.java b/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreStorageAccessor.java new file mode 100644 index 000000000..b90614035 --- /dev/null +++ b/providers/firestore/shedlock-provider-firestore/src/main/java/net/javacrumbs/shedlock/provider/firestore/FirestoreStorageAccessor.java @@ -0,0 +1,172 @@ +package net.javacrumbs.shedlock.provider.firestore; + +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + +import com.google.cloud.Timestamp; +import com.google.cloud.firestore.DocumentReference; +import com.google.cloud.firestore.DocumentSnapshot; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.Transaction; +import java.time.Instant; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import javax.annotation.Nonnull; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; +import net.javacrumbs.shedlock.support.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class FirestoreStorageAccessor extends AbstractStorageAccessor { + private static final Logger log = LoggerFactory.getLogger(FirestoreStorageAccessor.class); + + private final Firestore firestore; + private final String hostname; + private final String collectionName; + private final FirestoreLockProvider.FieldNames fieldNames; + + FirestoreStorageAccessor(FirestoreLockProvider.Configuration configuration) { + requireNonNull(configuration); + this.firestore = configuration.getFirestore(); + this.hostname = Utils.getHostname(); + this.collectionName = configuration.getCollectionName(); + this.fieldNames = configuration.getFieldNames(); + } + + @Override + public boolean insertRecord(LockConfiguration config) { + return insert(config.getName(), config.getLockAtMostUntil()); + } + + @Override + public boolean updateRecord(LockConfiguration config) { + return updateExisting(config.getName(), config.getLockAtMostUntil()); + } + + @Override + public void unlock(LockConfiguration config) { + updateOwn(config.getName(), config.getUnlockTime()); + } + + @Override + public boolean extend(LockConfiguration config) { + return updateOwn(config.getName(), config.getLockAtMostUntil()); + } + + private boolean insert(String name, Instant until) { + try { + DocumentReference docRef = getDocument(name); + + // Try to create the document if it doesn't exist + Map lockData = getLockData(until); + + return runTransaction(transaction -> { + DocumentSnapshot snapshot = transaction.get(docRef).get(); + if (!snapshot.exists()) { + transaction.set(docRef, lockData); + return true; + } + return false; + }); + } catch (InterruptedException | ExecutionException e) { + log.debug("Error inserting lock record", e); + return false; + } + } + + private boolean updateExisting(String name, Instant until) { + try { + DocumentReference docRef = getDocument(name); + + return runTransaction(transaction -> { + DocumentSnapshot snapshot = transaction.get(docRef).get(); + if (snapshot.exists()) { + Timestamp lockUntilTs = snapshot.getTimestamp(fieldNames.lockUntil()); + if (lockUntilTs != null && toInstant(lockUntilTs).isBefore(now())) { + Map updates = getLockData(until); + transaction.update(docRef, updates); + return true; + } + } + return false; + }); + } catch (InterruptedException | ExecutionException e) { + log.debug("Error updating lock record", e); + return false; + } + } + + private Map getLockData(Instant until) { + return Map.of( + fieldNames.lockUntil(), fromInstant(until), + fieldNames.lockedAt(), fromInstant(now()), + fieldNames.lockedBy(), hostname); + } + + private DocumentReference getDocument(String name) { + return firestore.collection(collectionName).document(name); + } + + private boolean updateOwn(String name, Instant until) { + try { + DocumentReference docRef = getDocument(name); + + return runTransaction(transaction -> { + DocumentSnapshot snapshot = transaction.get(docRef).get(); + if (snapshot.exists() && hostname.equals(snapshot.getString(fieldNames.lockedBy()))) { + + Timestamp lockUntilTs = snapshot.getTimestamp(fieldNames.lockUntil()); + if (lockUntilTs != null) { + Instant lockUntil = toInstant(lockUntilTs); + Instant now = now(); + if (lockUntil.isAfter(now) || lockUntil.equals(now)) { + Map updates = Map.of(fieldNames.lockUntil(), fromInstant(until)); + transaction.update(docRef, updates); + return true; + } + } + } + return false; + }); + } catch (InterruptedException | ExecutionException e) { + log.debug("Error updating own lock record", e); + return false; + } + } + + Optional findLock(String name) { + try { + DocumentReference docRef = getDocument(name); + DocumentSnapshot snapshot = docRef.get().get(); + + if (snapshot.exists()) { + return Optional.of(new Lock( + snapshot.getId(), + toInstant(snapshot.getTimestamp(fieldNames.lockedAt())), + toInstant(snapshot.getTimestamp(fieldNames.lockUntil())), + snapshot.getString(fieldNames.lockedBy()))); + } + return Optional.empty(); + } catch (InterruptedException | ExecutionException e) { + log.debug("Error finding lock", e); + return Optional.empty(); + } + } + + private T runTransaction(@Nonnull final Transaction.Function updateFunction) + throws ExecutionException, InterruptedException { + return firestore.runTransaction(updateFunction).get(); + } + + private static Timestamp fromInstant(@Nonnull Instant instant) { + return Timestamp.ofTimeSecondsAndNanos(instant.getEpochSecond(), instant.getNano()); + } + + private static Instant toInstant(@Nonnull Timestamp timestamp) { + return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); + } + + record Lock(String name, Instant lockedAt, Instant lockedUntil, String lockedBy) {} +} diff --git a/providers/firestore/shedlock-provider-firestore/src/test/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProviderIntegrationTest.java b/providers/firestore/shedlock-provider-firestore/src/test/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProviderIntegrationTest.java new file mode 100644 index 000000000..f3b8c85ce --- /dev/null +++ b/providers/firestore/shedlock-provider-firestore/src/test/java/net/javacrumbs/shedlock/provider/firestore/FirestoreLockProviderIntegrationTest.java @@ -0,0 +1,86 @@ +package net.javacrumbs.shedlock.provider.firestore; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.cloud.NoCredentials; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; +import java.util.concurrent.ExecutionException; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import org.junit.jupiter.api.AfterEach; +import org.testcontainers.containers.FirestoreEmulatorContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +class FirestoreLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { + + @Container + public static final FirestoreEmulatorContainer firestoreEmulator = new FirestoreEmulatorContainer( + DockerImageName.parse("gcr.io/google.com/cloudsdktool/google-cloud-cli:529.0.0-emulators")); + + private final Firestore firestore = FirestoreOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setProjectId("shedlock-provider-firestore-test") + .setHost(firestoreEmulator.getEmulatorEndpoint()) + .build() + .getService(); + private final FirestoreLockProvider.Configuration configuration = FirestoreLockProvider.Configuration.builder() + .withFirestore(firestore) + .withCollectionName("shedlock") + .withFieldNames(new FirestoreLockProvider.FieldNames("lockUntil", "lockedAt", "lockedBy")) + .build(); + private final FirestoreStorageAccessor accessor = new FirestoreStorageAccessor(this.configuration); + private final FirestoreLockProvider provider = new FirestoreLockProvider(this.configuration); + + @AfterEach + void tearDown() { + try { + // Delete all documents in the collection + firestore + .collection(this.configuration.getCollectionName()) + .get() + .get() + .getDocuments() + .forEach(document -> { + try { + document.getReference().delete().get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to delete document", e); + } + }); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to clean up Firestore collection", e); + } + } + + @Override + protected void assertUnlocked(String lockName) { + var now = ClockProvider.now(); + var lock = findLock(lockName); + assertThat(lock.lockedUntil()).isBefore(now); + assertThat(lock.lockedAt()).isBefore(now); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected void assertLocked(String lockName) { + var now = ClockProvider.now(); + var lock = findLock(lockName); + assertThat(lock.lockedUntil()).isAfter(now); + assertThat(lock.lockedAt()).isBefore(now); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected StorageBasedLockProvider getLockProvider() { + return this.provider; + } + + FirestoreStorageAccessor.Lock findLock(String lockName) { + return this.accessor.findLock(lockName).orElseThrow(); + } +} diff --git a/providers/firestore/shedlock-provider-firestore/src/test/resources/logback.xml b/providers/firestore/shedlock-provider-firestore/src/test/resources/logback.xml new file mode 100644 index 000000000..a35e40a2f --- /dev/null +++ b/providers/firestore/shedlock-provider-firestore/src/test/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/pom.xml b/providers/hazelcast/shedlock-provider-hazelcast4/pom.xml index 5380a224b..cf32d601d 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/pom.xml +++ b/providers/hazelcast/shedlock-provider-hazelcast4/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-hazelcast4 - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,7 +21,7 @@ com.hazelcast hazelcast - 5.1.4 + 5.5.0 diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLock.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLock.java index 46736815f..ad68709b6 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLock.java +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLock.java @@ -1,27 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.hazelcast4; -import net.javacrumbs.shedlock.core.LockConfiguration; - import java.io.Serializable; import java.time.Instant; +import net.javacrumbs.shedlock.core.LockConfiguration; /** * Hazelcast lock entity. + * *

* It's used to persist lock information into Hazelcast instances (cluster). */ @@ -34,12 +32,16 @@ class HazelcastLock implements Serializable { private final Instant lockAtLeastUntil; /** - * Moment when the lock is expired, so unlockable. - * The first value of this is {@link #lockAtMostUntil}. + * Moment when the lock is expired, so unlockable. The first value of this is + * {@link #lockAtMostUntil}. */ private final Instant timeToLive; - private HazelcastLock(final String name, final Instant lockAtMostUntil, final Instant lockAtLeastUntil, final Instant timeToLive) { + private HazelcastLock( + final String name, + final Instant lockAtMostUntil, + final Instant lockAtLeastUntil, + final Instant timeToLive) { this.name = name; this.lockAtMostUntil = lockAtMostUntil; this.lockAtLeastUntil = lockAtLeastUntil; @@ -51,13 +53,18 @@ boolean isExpired(Instant now) { } /** - * Instantiate {@link HazelcastLock} with {@link LockConfiguration} and Hazelcast member UUID. + * Instantiate {@link HazelcastLock} with {@link LockConfiguration} and + * Hazelcast member UUID. * * @param configuration * @return the new instance of {@link HazelcastLock}. */ static HazelcastLock fromConfigurationWhereTtlIsUntilTime(final LockConfiguration configuration) { - return new HazelcastLock(configuration.getName(), configuration.getLockAtMostUntil(), configuration.getLockAtLeastUntil(), configuration.getLockAtMostUntil()); + return new HazelcastLock( + configuration.getName(), + configuration.getLockAtMostUntil(), + configuration.getLockAtLeastUntil(), + configuration.getLockAtMostUntil()); } /** @@ -84,12 +91,7 @@ Instant getTimeToLive() { @Override public String toString() { - return "HazelcastLock{" + - "name='" + name + '\'' + - ", lockAtMostUntil=" + lockAtMostUntil + - ", lockAtLeastUntil=" + lockAtLeastUntil + - ", timeToLive=" + timeToLive + - '}'; + return "HazelcastLock{" + "name='" + name + '\'' + ", lockAtMostUntil=" + lockAtMostUntil + + ", lockAtLeastUntil=" + lockAtLeastUntil + ", timeToLive=" + timeToLive + '}'; } - } diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProvider.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProvider.java index f3edab5d2..7bad79f83 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProvider.java +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProvider.java @@ -1,51 +1,54 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.hazelcast4; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; -import net.javacrumbs.shedlock.core.ClockProvider; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.TimeUnit; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - /** * HazelcastLockProvider. + * *

- * Implementation of {@link LockProvider} using Hazelcast for storing and sharing lock information and mechanisms between a cluster's members + * Implementation of {@link LockProvider} using Hazelcast for storing and + * sharing lock information and mechanisms between a cluster's members + * *

- * Below, the mechanisms : - * - The Lock, an instance of {@link HazelcastLock}, is obtained / created when : - * -- the lock is not already locked by other process (lock - referenced by its name - is not present in the Hazelcast locks store OR unlockable) - * -- the lock is expired : {@link Instant#now()} > {@link HazelcastLock#timeToLive} where unlockTime have by default the same value of {@link HazelcastLock#lockAtMostUntil} - * and can have the value of {@link HazelcastLock#lockAtLeastUntil} if unlock action is used - * --- expired object is removed - * -- the lock is owned by not available member of Hazelcast cluster member - * --- no owner object is removed - * - Unlock action : - * -- removes lock object when {@link HazelcastLock#lockAtLeastUntil} is not come - * -- override value of {@link HazelcastLock#timeToLive} with {@link HazelcastLock#lockAtLeastUntil} (its default value is the same of {@link HazelcastLock#lockAtLeastUntil} + * Below, the mechanisms : - The Lock, an instance of {@link HazelcastLock}, is + * obtained / created when : -- the lock is not already locked by other process + * (lock - referenced by its name - is not present in the Hazelcast locks store + * OR unlockable) -- the lock is expired : {@link Instant#now()} > + * {@link HazelcastLock#timeToLive} where unlockTime have by default the same + * value of {@link HazelcastLock#lockAtMostUntil} and can have the value of + * {@link HazelcastLock#lockAtLeastUntil} if unlock action is used --- expired + * object is removed -- the lock is owned by not available member of Hazelcast + * cluster member --- no owner object is removed - Unlock action : -- removes + * lock object when {@link HazelcastLock#lockAtLeastUntil} is not come -- + * override value of {@link HazelcastLock#timeToLive} with + * {@link HazelcastLock#lockAtLeastUntil} (its default value is the same of + * {@link HazelcastLock#lockAtLeastUntil} */ public class HazelcastLockProvider implements LockProvider { @@ -55,14 +58,12 @@ public class HazelcastLockProvider implements LockProvider { private static final Duration DEFAULT_LOCK_LEASE_TIME = Duration.ofSeconds(30); /** - * Key used for get the lock container (an {@link IMap}) inside {@link #hazelcastInstance}. - * By default : {@link #LOCK_STORE_KEY_DEFAULT} + * Key used for get the lock container (an {@link IMap}) inside + * {@link #hazelcastInstance}. By default : {@link #LOCK_STORE_KEY_DEFAULT} */ private final String lockStoreKey; - /** - * Instance of the Hazelcast engine used by the application. - */ + /** Instance of the Hazelcast engine used by the application. */ private final HazelcastInstance hazelcastInstance; private final long lockLeaseTimeMs; @@ -70,50 +71,57 @@ public class HazelcastLockProvider implements LockProvider { /** * Instantiate the provider. * - * @param hazelcastInstance The Hazelcast engine used by the application. + * @param hazelcastInstance + * The Hazelcast engine used by the application. */ - public HazelcastLockProvider(@NonNull HazelcastInstance hazelcastInstance) { + public HazelcastLockProvider(HazelcastInstance hazelcastInstance) { this(hazelcastInstance, LOCK_STORE_KEY_DEFAULT); } /** * Instantiate the provider. * - * @param hazelcastInstance The Hazelcast engine used by the application - * @param lockStoreKey The key where the locks are stored (by default {@link #LOCK_STORE_KEY_DEFAULT}). + * @param hazelcastInstance + * The Hazelcast engine used by the application + * @param lockStoreKey + * The key where the locks are stored (by default + * {@link #LOCK_STORE_KEY_DEFAULT}). */ - public HazelcastLockProvider(@NonNull HazelcastInstance hazelcastInstance, @NonNull String lockStoreKey) { + public HazelcastLockProvider(HazelcastInstance hazelcastInstance, String lockStoreKey) { this(hazelcastInstance, lockStoreKey, DEFAULT_LOCK_LEASE_TIME); } /** * Instantiate the provider. * - * @param hazelcastInstance The com.hazelcast.core.Hazelcast engine used by the application - * @param lockStoreKey The key where the locks are stored (by default {@link #LOCK_STORE_KEY_DEFAULT}). - * @param lockLeaseTime When lock is being obtained there is a Hazelcast lock used to make it thread-safe. - * This lock should be released quite fast but if the process dies while holding the lock, it is held forever. - * lockLeaseTime is used as a safety-net for such situations. + * @param hazelcastInstance + * The com.hazelcast.core.Hazelcast engine used by the application + * @param lockStoreKey + * The key where the locks are stored (by default + * {@link #LOCK_STORE_KEY_DEFAULT}). + * @param lockLeaseTime + * When lock is being obtained there is a Hazelcast lock used to make + * it thread-safe. This lock should be released quite fast but if the + * process dies while holding the lock, it is held forever. + * lockLeaseTime is used as a safety-net for such situations. */ - public HazelcastLockProvider(@NonNull HazelcastInstance hazelcastInstance, @NonNull String lockStoreKey, @NonNull Duration lockLeaseTime) { + public HazelcastLockProvider(HazelcastInstance hazelcastInstance, String lockStoreKey, Duration lockLeaseTime) { this.hazelcastInstance = hazelcastInstance; this.lockStoreKey = lockStoreKey; this.lockLeaseTimeMs = lockLeaseTime.toMillis(); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { log.trace("lock - Attempt : {}", lockConfiguration); - final Instant now = ClockProvider.now(); - final String lockName = lockConfiguration.getName(); - final IMap store = getStore(); + String lockName = lockConfiguration.getName(); + IMap store = getStore(); try { // lock the map key entry store.lock(lockName, keyLockTime(lockConfiguration), TimeUnit.MILLISECONDS); // just one thread at a time, in the cluster, can run this code // each thread waits until the lock to be unlock - if (tryLock(lockConfiguration, now)) { + if (tryLock(lockConfiguration)) { return Optional.of(new HazelcastSimpleLock(this, lockConfiguration)); } } finally { @@ -124,19 +132,20 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration) { } private long keyLockTime(LockConfiguration lockConfiguration) { - Duration between = Duration.between(ClockProvider.now(), lockConfiguration.getLockAtMostUntil()); + Duration between = Duration.between(now(), lockConfiguration.getLockAtMostUntil()); return between.toMillis(); } - private boolean tryLock(final LockConfiguration lockConfiguration, final Instant now) { - final String lockName = lockConfiguration.getName(); - final HazelcastLock lock = getLock(lockName); + private boolean tryLock(LockConfiguration lockConfiguration) { + String lockName = lockConfiguration.getName(); + HazelcastLock lock = getLock(lockName); if (isUnlocked(lock)) { log.debug("lock - lock obtained, it wasn't locked : conf={}", lockConfiguration); addNewLock(lockConfiguration); return true; - } else if (lock.isExpired(now)) { - log.debug("lock - lock obtained, it was locked but expired : oldLock={}; conf={}", lock, lockConfiguration); + } else if (lock.isExpired(now())) { + log.debug( + "lock - lock obtained, it was locked but expired : oldLock={}; conf={}", lock, lockConfiguration); replaceLock(lockName, lockConfiguration); return true; } else { @@ -145,72 +154,72 @@ private boolean tryLock(final LockConfiguration lockConfiguration, final Instant } } - private IMap getStore() { return hazelcastInstance.getMap(lockStoreKey); } - HazelcastLock getLock(final String lockName) { + @Nullable + HazelcastLock getLock(String lockName) { return getStore().get(lockName); } - private void removeLock(final String lockName) { + private void removeLock(String lockName) { getStore().delete(lockName); log.debug("lock store - lock deleted : {}", lockName); } - private void addNewLock(final LockConfiguration lockConfiguration) { - final HazelcastLock lock = HazelcastLock.fromConfigurationWhereTtlIsUntilTime(lockConfiguration); + private void addNewLock(LockConfiguration lockConfiguration) { + HazelcastLock lock = HazelcastLock.fromConfigurationWhereTtlIsUntilTime(lockConfiguration); log.trace("lock store - new lock created from configuration : {}", lockConfiguration); - final String lockName = lockConfiguration.getName(); + String lockName = lockConfiguration.getName(); getStore().put(lockName, lock); log.debug("lock store - new lock added : {}", lock); } - private void replaceLock(final String lockName, final LockConfiguration lockConfiguration) { + private void replaceLock(String lockName, LockConfiguration lockConfiguration) { log.debug("lock store - replace lock : {}", lockName); removeLock(lockName); addNewLock(lockConfiguration); } - private boolean isUnlocked(final HazelcastLock lock) { + private boolean isUnlocked(@Nullable HazelcastLock lock) { return lock == null; } /** - * Unlock the lock with its name. - * - * @param lockConfiguration the name of the lock to unlock. + * Unlock the lock with its name. Don't use unless you know what you are doing, unlocking a lock held by an active + * task may result in multiple concurrent task executions. */ - /* package */ void unlock(LockConfiguration lockConfiguration) { - String lockName = lockConfiguration.getName(); + public void riskyUnlock(String lockName) { + log.info("riskyUnlock : {}", lockName); + unlock(lockName); + } + + void unlock(String lockName) { log.trace("unlock - attempt : {}", lockName); - final Instant now = ClockProvider.now(); - final IMap store = getStore(); + IMap store = getStore(); try { store.lock(lockName, lockLeaseTimeMs, TimeUnit.MILLISECONDS); - final HazelcastLock lock = getLock(lockName); - unlockProperly(lock, now); + unlockProperly(getLock(lockName)); } finally { store.unlock(lockName); } } - private void unlockProperly(final HazelcastLock lock, final Instant now) { + private void unlockProperly(@Nullable HazelcastLock lock) { if (isUnlocked(lock)) { log.debug("unlock - it is already unlocked"); return; } - final String lockName = lock.getName(); - final Instant lockAtLeastInstant = lock.getLockAtLeastUntil(); - if (!now.isBefore(lockAtLeastInstant)) { + String lockName = lock.getName(); + Instant lockAtLeastInstant = lock.getLockAtLeastUntil(); + if (!now().isBefore(lockAtLeastInstant)) { removeLock(lockName); log.debug("unlock - done : {}", lock); } else { log.debug("unlock - it doesn't unlock, least time is not passed : {}", lock); - final HazelcastLock newLock = HazelcastLock.fromLockWhereTtlIsReduceToLeastTime(lock); + HazelcastLock newLock = HazelcastLock.fromLockWhereTtlIsReduceToLeastTime(lock); getStore().put(lockName, newLock); } - } } diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastSimpleLock.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastSimpleLock.java index 69964116c..c38f9276a 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastSimpleLock.java +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastSimpleLock.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.hazelcast4; @@ -19,9 +17,7 @@ import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.SimpleLock; -/** - * Implementation of {@link SimpleLock} for unlock {@link HazelcastLock}. - */ +/** Implementation of {@link SimpleLock} for unlock {@link HazelcastLock}. */ class HazelcastSimpleLock extends AbstractSimpleLock { private final HazelcastLockProvider lockProvider; @@ -30,9 +26,8 @@ class HazelcastSimpleLock extends AbstractSimpleLock { this.lockProvider = lockProvider; } - @Override - public void doUnlock() { - lockProvider.unlock(lockConfiguration); + protected void doUnlock() { + lockProvider.unlock(lockConfiguration.getName()); } } diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/package-info.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/package-info.java new file mode 100644 index 000000000..7c7452b2a --- /dev/null +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/main/java/net/javacrumbs/shedlock/provider/hazelcast4/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.hazelcast4; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderClusterTest.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderClusterTest.java index 5f7afafe2..1ddf76c67 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderClusterTest.java +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderClusterTest.java @@ -1,23 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.hazelcast4; +import static java.time.temporal.ChronoUnit.SECONDS; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import com.hazelcast.client.HazelcastClient; import com.hazelcast.core.Hazelcast; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.SimpleLock; import org.junit.jupiter.api.AfterEach; @@ -25,17 +32,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import static java.time.temporal.ChronoUnit.SECONDS; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - @Disabled public class HazelcastLockProviderClusterTest { @@ -85,7 +81,7 @@ public void testGetLocksByTwoMembersOfCluster() { assertUnlocked(lockProvider1, LOCK_NAME_2); } - private void assertUnlocked(HazelcastLockProvider lockProvider, String lockName) { + private void assertUnlocked(HazelcastLockProvider lockProvider, String lockName) { HazelcastLock lock = lockProvider.getLock(lockName); if (lock != null) { Instant now = now(); @@ -108,7 +104,8 @@ public void testGetLockByLateMemberOfCluster() { @Test public void testGetLockInCluster() throws InterruptedException { - Optional lock1 = lockProvider1.lock(lockConfig(LOCK_NAME_1, Duration.of(5, SECONDS), Duration.of(3, SECONDS))); + Optional lock1 = + lockProvider1.lock(lockConfig(LOCK_NAME_1, Duration.of(5, SECONDS), Duration.of(3, SECONDS))); assertThat(lock1).isNotEmpty(); Optional lock2 = lockProvider2.lock(simpleLockConfig(LOCK_NAME_1)); assertThat(lock2).isEmpty(); diff --git a/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderIntegrationTest.java b/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderIntegrationTest.java index e3580ef2f..2355be690 100644 --- a/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderIntegrationTest.java +++ b/providers/hazelcast/shedlock-provider-hazelcast4/src/test/java/net/javacrumbs/shedlock/provider/hazelcast4/HazelcastLockProviderIntegrationTest.java @@ -1,20 +1,19 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.hazelcast4; +import static org.assertj.core.api.Assertions.assertThat; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; @@ -25,8 +24,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import static org.assertj.core.api.Assertions.assertThat; - public class HazelcastLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { private static HazelcastInstance hazelcastInstance; @@ -54,8 +51,10 @@ protected LockProvider getLockProvider() { return lockProvider; } - @Override - protected void assertUnlocked(final String lockName) { assertThat(isUnlocked(lockName)).isTrue(); } + @Override + protected void assertUnlocked(final String lockName) { + assertThat(isUnlocked(lockName)).isTrue(); + } private boolean isUnlocked(final String lockName) { final HazelcastLock lock = lockProvider.getLock(lockName); diff --git a/providers/ignite/shedlock-provider-ignite/pom.xml b/providers/ignite/shedlock-provider-ignite/pom.xml index 1bd6ed89f..252c704a9 100644 --- a/providers/ignite/shedlock-provider-ignite/pom.xml +++ b/providers/ignite/shedlock-provider-ignite/pom.xml @@ -3,16 +3,16 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-ignite - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - 2.14.0 + 2.17.0 @@ -55,9 +55,9 @@ - surefire-java16 + surefire-java17 - [16,) + [17,) @@ -65,11 +65,32 @@ org.apache.maven.plugins maven-surefire-plugin - --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED + --add-opens java.base/java.nio=ALL-UNNAMED --add-opens + java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens + java.base/java.time=ALL-UNNAMED + + + + + + + + + skip-tests-in-java21 + + 21 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true - + diff --git a/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProvider.java b/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProvider.java index f9b5d78c3..c07a54a29 100644 --- a/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProvider.java +++ b/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProvider.java @@ -1,20 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.ignite; +import static net.javacrumbs.shedlock.support.Utils.getHostname; + +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; @@ -22,50 +24,39 @@ import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; -import java.time.Instant; -import java.util.Optional; - -import static net.javacrumbs.shedlock.support.Utils.getHostname; - /** * Distributed lock using Apache Ignite. + * *

* It uses a {@link String} key (lock name) and {@link LockValue} value. - *

* - *

lockedAt and lockedBy are just for troubleshooting and are not read by the code.

+ *

+ * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code. Creating a lock: * - * Creating a lock: *

    - *
  1. - * If there is no locks with given name, try to use {@link IgniteCache#putIfAbsent}. - *
  2. - *
  3. - * If there is a lock with given name, and its lockUntil is before or equal {@code now}, - * try to use {@link IgniteCache#replace}. - *
  4. - *
  5. - * Otherwise, return {@link Optional#empty}. - *
  6. + *
  7. If there is no locks with given name, try to use + * {@link IgniteCache#putIfAbsent}. + *
  8. If there is a lock with given name, and its lockUntil is before or equal + * {@code now}, try to use {@link IgniteCache#replace}. + *
  9. Otherwise, return {@link Optional#empty}. *
* * Extending a lock: + * *
    - *
  1. - * If there is a lock with given name and hostname, and its lockUntil is after {@code now}, try to use - * {@link IgniteCache#replace} to set lockUntil to {@link LockConfiguration#getLockAtMostUntil}. - *
  2. - *
  3. - * Otherwise, return {@link Optional#empty}. - *
  4. + *
  5. If there is a lock with given name and hostname, and its lockUntil is + * after {@code now}, try to use {@link IgniteCache#replace} to set lockUntil to + * {@link LockConfiguration#getLockAtMostUntil}. + *
  6. Otherwise, return {@link Optional#empty}. *
* * Unlock: + * *
    - *
  1. - * If there is a lock with given name and hostname, try to use {@link IgniteCache#replace} to set lockUntil to + *
  2. If there is a lock with given name and hostname, try to use + * {@link IgniteCache#replace} to set lockUntil to * {@link LockConfiguration#getUnlockTime}. - *
  3. *
*/ public class IgniteLockProvider implements ExtensibleLockProvider { @@ -76,23 +67,24 @@ public class IgniteLockProvider implements ExtensibleLockProvider { private final IgniteCache cache; /** - * @param ignite Ignite instance. + * @param ignite + * Ignite instance. */ public IgniteLockProvider(Ignite ignite) { this(ignite, DEFAULT_SHEDLOCK_CACHE_NAME); } /** - * @param ignite Ignite instance. - * @param shedLockCacheName ShedLock cache name to use instead of default. + * @param ignite + * Ignite instance. + * @param shedLockCacheName + * ShedLock cache name to use instead of default. */ public IgniteLockProvider(Ignite ignite, String shedLockCacheName) { this.cache = ignite.getOrCreateCache(shedLockCacheName); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional lock(LockConfiguration lockCfg) { Instant now = Instant.now(); @@ -102,8 +94,7 @@ public Optional lock(LockConfiguration lockCfg) { LockValue oldVal = cache.get(key); if (oldVal == null) { - if (cache.putIfAbsent(key, newVal)) - return Optional.of(new IgniteLock(lockCfg, this)); + if (cache.putIfAbsent(key, newVal)) return Optional.of(new IgniteLock(lockCfg, this)); return Optional.empty(); } @@ -115,10 +106,12 @@ public Optional lock(LockConfiguration lockCfg) { } /** - * If there is a lock with given name and hostname, try to use {@link IgniteCache#replace} to set lockUntil to + * If there is a lock with given name and hostname, try to use + * {@link IgniteCache#replace} to set lockUntil to * {@link LockConfiguration#getLockAtMostUntil}. * - * @param lockCfg Lock configuration. + * @param lockCfg + * Lock configuration. * @return New lock if succeed extended. Empty, otherwise. */ private Optional extend(LockConfiguration lockCfg) { @@ -127,22 +120,23 @@ private Optional extend(LockConfiguration lockCfg) { String key = lockCfg.getName(); LockValue oldVal = cache.get(key); - if (oldVal == null || !oldVal.getLockedBy().equals(getHostname()) || !oldVal.getLockUntil().isAfter(now)) - return Optional.empty(); + if (oldVal == null + || !oldVal.getLockedBy().equals(getHostname()) + || !oldVal.getLockUntil().isAfter(now)) return Optional.empty(); LockValue newVal = oldVal.withLockUntil(lockCfg.getLockAtMostUntil()); - if (cache.replace(key, oldVal, newVal)) - return Optional.of(new IgniteLock(lockCfg, this)); + if (cache.replace(key, oldVal, newVal)) return Optional.of(new IgniteLock(lockCfg, this)); return Optional.empty(); } /** - * If there is a lock with given name and hostname, try to use {@link IgniteCache#replace} to set lockUntil to - * {@code now}. + * If there is a lock with given name and hostname, try to use + * {@link IgniteCache#replace} to set lockUntil to {@code now}. * - * @param lockCfg Lock configuration. + * @param lockCfg + * Lock configuration. */ private void unlock(LockConfiguration lockCfg) { String key = lockCfg.getName(); @@ -155,16 +149,16 @@ private void unlock(LockConfiguration lockCfg) { } } - /** - * Ignite lock. - */ + /** Ignite lock. */ private static final class IgniteLock extends AbstractSimpleLock { /** Ignite lock provider. */ private final IgniteLockProvider lockProvider; /** - * @param lockCfg Lock configuration. - * @param lockProvider Ignite lock provider. + * @param lockCfg + * Lock configuration. + * @param lockProvider + * Ignite lock provider. */ private IgniteLock(LockConfiguration lockCfg, IgniteLockProvider lockProvider) { super(lockCfg); diff --git a/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/LockValue.java b/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/LockValue.java index 773dc0c40..1c31d09e7 100644 --- a/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/LockValue.java +++ b/providers/ignite/shedlock-provider-ignite/src/main/java/net/javacrumbs/shedlock/provider/ignite/LockValue.java @@ -1,25 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.ignite; import java.time.Instant; -/** - * Value object for ShedLock cache. - */ +/** Value object for ShedLock cache. */ +// Don't convert to record, does not work class LockValue { /** Locked at time. */ private final Instant lockedAt; @@ -31,9 +28,12 @@ class LockValue { private final String lockedBy; /** - * @param lockedAt Locked at time. - * @param lockUntil Locked until time. - * @param lockedBy Locked by hostname. + * @param lockedAt + * Locked at time. + * @param lockUntil + * Locked until time. + * @param lockedBy + * Locked by hostname. */ public LockValue(Instant lockedAt, Instant lockUntil, String lockedBy) { this.lockedAt = lockedAt; diff --git a/providers/ignite/shedlock-provider-ignite/src/test/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProviderTest.java b/providers/ignite/shedlock-provider-ignite/src/test/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProviderTest.java index eca4f2fbf..f7486b667 100644 --- a/providers/ignite/shedlock-provider-ignite/src/test/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProviderTest.java +++ b/providers/ignite/shedlock-provider-ignite/src/test/java/net/javacrumbs/shedlock/provider/ignite/IgniteLockProviderTest.java @@ -1,20 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.ignite; +import static net.javacrumbs.shedlock.provider.ignite.IgniteLockProvider.DEFAULT_SHEDLOCK_CACHE_NAME; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest; import org.apache.ignite.Ignite; @@ -24,14 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import java.time.Instant; - -import static net.javacrumbs.shedlock.provider.ignite.IgniteLockProvider.DEFAULT_SHEDLOCK_CACHE_NAME; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for {@link IgniteLockProvider}. - */ +/** Test for {@link IgniteLockProvider}. */ public class IgniteLockProviderTest extends AbstractExtensibleLockProviderIntegrationTest { private static Ignite ignite; private static IgniteCache cache; diff --git a/providers/inmemory/shedlock-provider-inmemory/pom.xml b/providers/inmemory/shedlock-provider-inmemory/pom.xml index e447df31c..cd0053533 100644 --- a/providers/inmemory/shedlock-provider-inmemory/pom.xml +++ b/providers/inmemory/shedlock-provider-inmemory/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-inmemory - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -43,4 +43,4 @@ - + diff --git a/providers/inmemory/shedlock-provider-inmemory/src/main/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProvider.java b/providers/inmemory/shedlock-provider-inmemory/src/main/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProvider.java index c6ede2bb7..d17605751 100644 --- a/providers/inmemory/shedlock-provider-inmemory/src/main/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProvider.java +++ b/providers/inmemory/shedlock-provider-inmemory/src/main/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProvider.java @@ -1,5 +1,11 @@ package net.javacrumbs.shedlock.provider.inmemory; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; @@ -7,16 +13,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; - /** - * In memory lock that is suitable only for tests and running application locally. -*/ + * In memory lock that is suitable only for tests and running application + * locally. + */ public class InMemoryLockProvider implements ExtensibleLockProvider { private final Map locks = new HashMap<>(); @@ -31,7 +31,7 @@ public Optional lock(LockConfiguration lockConfiguration) { } else { LockRecord lockRecord = new LockRecord(lockConfiguration.getLockAtMostUntil()); locks.put(lockName, lockRecord); - logger.debug("Locked {}", lockConfiguration); + logger.debug("Locked {}", lockConfiguration); return Optional.of(new InMemoryLock(lockConfiguration)); } } @@ -46,7 +46,7 @@ boolean isLocked(String lockName) { private void doUnlock(LockConfiguration lockConfiguration) { synchronized (locks) { - locks.put(lockConfiguration.getName(), new LockRecord(lockConfiguration.getLockAtLeastUntil())); + locks.put(lockConfiguration.getName(), new LockRecord(lockConfiguration.getLockAtLeastUntil())); logger.debug("Unlocked {}", lockConfiguration); } } @@ -64,13 +64,7 @@ private Optional doExtend(LockConfiguration newConfiguration) { } } - private static class LockRecord { - private final Instant lockedUntil; - - private LockRecord(Instant lockedUntil) { - this.lockedUntil = lockedUntil; - } - } + private record LockRecord(Instant lockedUntil) {} private class InMemoryLock extends AbstractSimpleLock { diff --git a/providers/inmemory/shedlock-provider-inmemory/src/test/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProviderIntegrationTest.java b/providers/inmemory/shedlock-provider-inmemory/src/test/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProviderIntegrationTest.java index bdbdfa4f2..7970eceaf 100644 --- a/providers/inmemory/shedlock-provider-inmemory/src/test/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProviderIntegrationTest.java +++ b/providers/inmemory/shedlock-provider-inmemory/src/test/java/net/javacrumbs/shedlock/provider/inmemory/InMemoryLockProviderIntegrationTest.java @@ -1,10 +1,10 @@ package net.javacrumbs.shedlock.provider.inmemory; +import static org.assertj.core.api.Assertions.assertThat; + import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest; -import static org.assertj.core.api.Assertions.assertThat; - public class InMemoryLockProviderIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest { private final InMemoryLockProvider inMemoryLockProvider = new InMemoryLockProvider(); diff --git a/providers/jdbc/shedlock-provider-exposed/pom.xml b/providers/jdbc/shedlock-provider-exposed/pom.xml new file mode 100644 index 000000000..abaf6a766 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/pom.xml @@ -0,0 +1,155 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-exposed + ${project.groupId}:${project.artifactId} + + + 2.1.0 + 0.61.0 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + + org.jetbrains.exposed + exposed-core + ${exposed.version} + + + org.jetbrains.exposed + exposed-dao + ${exposed.version} + + + org.jetbrains.exposed + exposed-jdbc + ${exposed.version} + + + org.jetbrains.exposed + exposed-java-time + ${exposed.version} + + + + net.javacrumbs.shedlock + shedlock-test-support-jdbc + ${project.version} + test + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + org.mockito + mockito-core + ${mockito.ver} + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + ${jdk.version} + + + + + org.jetbrains.dokka + dokka-maven-plugin + 2.0.0 + + + package + + javadocJar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + java-compile + compile + + compile + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.exposed + + + + + + + + diff --git a/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedIntervalExpression.kt b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedIntervalExpression.kt new file mode 100644 index 000000000..c15859f35 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedIntervalExpression.kt @@ -0,0 +1,36 @@ +package net.javacrumbs.shedlock.provider.exposed + +import java.time.Duration +import java.time.LocalDateTime +import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.sql.QueryBuilder +import org.jetbrains.exposed.sql.vendors.SQLServerDialect +import org.jetbrains.exposed.sql.vendors.currentDialect + +internal fun Expression.plus(duration: Duration): Expression = + IntervalExpression(this, duration) + +private class IntervalExpression(private val expression: Expression, private val duration: Duration) : + Expression() { + override fun toQueryBuilder(queryBuilder: QueryBuilder) { + when (currentDialect) { + is SQLServerDialect -> { + queryBuilder.run { + append("(DATEADD(millisecond, ") + append(duration.toMillis().toString()) + append(", ") + expression.toQueryBuilder(queryBuilder) + append("))") + } + } + else -> { + queryBuilder.run { + append("(") + expression.toQueryBuilder(queryBuilder) + append(" + INTERVAL '${duration.toMillis() / 1000.0}' SECOND") + append(")") + } + } + } + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedLockProvider.kt b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedLockProvider.kt new file mode 100644 index 000000000..9d4111686 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedLockProvider.kt @@ -0,0 +1,6 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.support.StorageBasedLockProvider +import org.jetbrains.exposed.sql.Database + +class ExposedLockProvider(database: Database) : StorageBasedLockProvider(ExposedStorageAccessor(database)) diff --git a/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedStorageAccessor.kt b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedStorageAccessor.kt new file mode 100644 index 000000000..f15e895b4 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/ExposedStorageAccessor.kt @@ -0,0 +1,86 @@ +package net.javacrumbs.shedlock.provider.exposed + +import java.sql.SQLException +import java.sql.SQLIntegrityConstraintViolationException +import java.time.Duration +import java.time.LocalDateTime +import net.javacrumbs.shedlock.core.LockConfiguration +import net.javacrumbs.shedlock.support.AbstractStorageAccessor +import net.javacrumbs.shedlock.support.LockException +import org.jetbrains.exposed.sql.Case +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.javatime.CurrentDateTime +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.update + +internal class ExposedStorageAccessor(private val database: Database) : AbstractStorageAccessor() { + + private val now: Expression = CurrentDateTime + + override fun insertRecord(lockConfiguration: LockConfiguration): Boolean = + transaction(database) { + try { + Shedlock.insert { + it[name] = lockConfiguration.name + it[lockUntil] = nowPlus(lockConfiguration.lockAtMostFor) + it[lockedAt] = now + it[lockedBy] = hostname + } + true + } catch (_: SQLIntegrityConstraintViolationException) { + // lock record already exists + false + } catch (e: SQLException) { + logger.debug("Exception thrown when inserting record", e) + false + } + } + + override fun updateRecord(lockConfiguration: LockConfiguration): Boolean = + transaction(database) { + try { + Shedlock.update({ (Shedlock.name eq lockConfiguration.name) and (Shedlock.lockUntil lessEq now) }) { + it[lockUntil] = nowPlus(lockConfiguration.lockAtMostFor) + it[lockedAt] = now + it[lockedBy] = hostname + } > 0 + } catch (e: SQLException) { + throw LockException("Unexpected exception when locking", e) + } + } + + override fun unlock(lockConfiguration: LockConfiguration) { + transaction(database) { + try { + val lockAtLeastUntil = Shedlock.lockedAt.plus(lockConfiguration.lockAtLeastFor) + + Shedlock.update({ (Shedlock.name eq lockConfiguration.name) and (Shedlock.lockedBy eq hostname) }) { + it[lockUntil] = Case().When(lockAtLeastUntil greater now, lockAtLeastUntil).Else(now) + } + } catch (e: SQLException) { + throw LockException("Unexpected exception when unlocking", e) + } + } + } + + override fun extend(lockConfiguration: LockConfiguration): Boolean = + transaction(database) { + try { + Shedlock.update({ + (Shedlock.name eq lockConfiguration.name) and + (Shedlock.lockedBy eq hostname) and + (Shedlock.lockUntil greater now) + }) { + it[lockUntil] = nowPlus(lockConfiguration.lockAtMostFor) + } > 0 + } catch (e: SQLException) { + throw LockException("Unexpected exception when extending", e) + } + } + + private fun nowPlus(duration: Duration): Expression = now.plus(duration) +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/Shedlock.kt b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/Shedlock.kt new file mode 100644 index 000000000..47ed30eb9 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/main/kotlin/net/javacrumbs/shedlock/provider/exposed/Shedlock.kt @@ -0,0 +1,14 @@ +package net.javacrumbs.shedlock.provider.exposed + +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.javatime.datetime + +internal object Shedlock : Table() { + override val tableName: String = "shedlock" + val name = varchar("name", 64) + val lockUntil = datetime("lock_until") + val lockedAt = datetime("locked_at") + val lockedBy = varchar("locked_by", 255) + + override val primaryKey = PrimaryKey(name) +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/AbstractExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/AbstractExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..ea2bd1ca8 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/AbstractExposedLockProviderIntegrationTest.kt @@ -0,0 +1,33 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.support.StorageBasedLockProvider +import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest +import net.javacrumbs.shedlock.test.support.jdbc.DbConfig +import org.jetbrains.exposed.sql.Database +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS + +@TestInstance(PER_CLASS) +abstract class AbstractExposedLockProviderIntegrationTest( + private val dbConfig: DbConfig, + private val database: Database = Database.connect(dbConfig.dataSource), +) : AbstractJdbcLockProviderIntegrationTest() { + + override fun getDbConfig(): DbConfig = dbConfig + + override fun useDbTime(): Boolean = true + + override fun getLockProvider(): StorageBasedLockProvider = ExposedLockProvider(database) + + @BeforeAll + fun startDb() { + dbConfig.startDb() + } + + @AfterAll + fun shutdownDb() { + dbConfig.shutdownDb() + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/H2ExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/H2ExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..dbb806819 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/H2ExposedLockProviderIntegrationTest.kt @@ -0,0 +1,19 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.test.support.jdbc.H2Config +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.ExperimentalKeywordApi + +@OptIn(ExperimentalKeywordApi::class) +class H2ExposedLockProviderIntegrationTest : + AbstractExposedLockProviderIntegrationTest( + dbConfig = DB_CONFIG, + database = + Database.connect(DB_CONFIG.dataSource, databaseConfig = DatabaseConfig { preserveKeywordCasing = false }), + ) { + + private companion object { + private val DB_CONFIG = H2Config() + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MariaDbExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MariaDbExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..7d32a7991 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MariaDbExposedLockProviderIntegrationTest.kt @@ -0,0 +1,13 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.test.support.jdbc.MariaDbConfig + +class MariaDbExposedLockProviderIntegrationTest : AbstractExposedLockProviderIntegrationTest(dbConfig = DB_CONFIG) { + + private companion object { + private val DB_CONFIG = + object : MariaDbConfig() { + override fun nowExpression(): String = "current_timestamp(6)" + } + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MsSqlExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MsSqlExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..ddcf29562 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MsSqlExposedLockProviderIntegrationTest.kt @@ -0,0 +1,10 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.test.support.jdbc.MsSqlServerConfig + +class MsSqlExposedLockProviderIntegrationTest : AbstractExposedLockProviderIntegrationTest(dbConfig = DB_CONFIG) { + + private companion object { + private val DB_CONFIG = MsSqlServerConfig() + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MysqlExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MysqlExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..7d7021a63 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/MysqlExposedLockProviderIntegrationTest.kt @@ -0,0 +1,13 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.test.support.jdbc.MySqlConfig + +class MysqlExposedLockProviderIntegrationTest : AbstractExposedLockProviderIntegrationTest(dbConfig = DB_CONFIG) { + + private companion object { + private val DB_CONFIG = + object : MySqlConfig() { + override fun nowExpression(): String = "current_timestamp(6)" + } + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/PostgresExposedLockProviderIntegrationTest.kt b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/PostgresExposedLockProviderIntegrationTest.kt new file mode 100644 index 000000000..63c0282c5 --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/kotlin/net/javacrumbs/shedlock/provider/exposed/PostgresExposedLockProviderIntegrationTest.kt @@ -0,0 +1,15 @@ +package net.javacrumbs.shedlock.provider.exposed + +import net.javacrumbs.shedlock.test.support.jdbc.DbConfig +import net.javacrumbs.shedlock.test.support.jdbc.PostgresConfig + +class PostgresExposedLockProviderIntegrationTest : AbstractExposedLockProviderIntegrationTest(dbConfig = DB_CONFIG) { + private companion object { + private val DB_CONFIG: DbConfig = + object : PostgresConfig() { + override fun nowExpression(): String { + return "CURRENT_TIMESTAMP" + } + } + } +} diff --git a/providers/jdbc/shedlock-provider-exposed/src/test/resources/logback-test.xml b/providers/jdbc/shedlock-provider-exposed/src/test/resources/logback-test.xml new file mode 100644 index 000000000..c64bdcd7e --- /dev/null +++ b/providers/jdbc/shedlock-provider-exposed/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/providers/jdbc/shedlock-provider-jdbc-internal/pom.xml b/providers/jdbc/shedlock-provider-jdbc-internal/pom.xml index ddf735dd6..105a3c378 100644 --- a/providers/jdbc/shedlock-provider-jdbc-internal/pom.xml +++ b/providers/jdbc/shedlock-provider-jdbc-internal/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-jdbc-internal - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} diff --git a/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/AbstractJdbcStorageAccessor.java b/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/AbstractJdbcStorageAccessor.java index 0767dc834..d3afec7a2 100644 --- a/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/AbstractJdbcStorageAccessor.java +++ b/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/AbstractJdbcStorageAccessor.java @@ -1,117 +1,127 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.internal; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.AbstractStorageAccessor; -import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import static java.util.Objects.requireNonNull; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; import java.sql.Timestamp; import java.util.function.BiFunction; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; +import net.javacrumbs.shedlock.support.LockException; -import static java.util.Objects.requireNonNull; - -/** - * Internal class, please do not use. - */ +/** Internal class, please do not use. */ public abstract class AbstractJdbcStorageAccessor extends AbstractStorageAccessor { private final String tableName; - public AbstractJdbcStorageAccessor(@NonNull String tableName) { + public AbstractJdbcStorageAccessor(String tableName) { this.tableName = requireNonNull(tableName, "tableName can not be null"); } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { - // Try to insert if the record does not exist (not optimal, but the simplest platform agnostic way) + public boolean insertRecord(LockConfiguration lockConfiguration) { + // Try to insert if the record does not exist (not optimal, but the simplest + // platform agnostic + // way) String sql = "INSERT INTO " + tableName + "(name, lock_until, locked_at, locked_by) VALUES(?, ?, ?, ?)"; - return executeCommand(sql, statement -> { - statement.setString(1, lockConfiguration.getName()); - statement.setTimestamp(2, Timestamp.from(lockConfiguration.getLockAtMostUntil())); - statement.setTimestamp(3, Timestamp.from(ClockProvider.now())); - statement.setString(4, getHostname()); - int insertedRows = statement.executeUpdate(); - return insertedRows > 0; - }, this::handleInsertionException); + return executeCommand( + sql, + statement -> { + statement.setString(1, lockConfiguration.getName()); + statement.setTimestamp(2, Timestamp.from(lockConfiguration.getLockAtMostUntil())); + statement.setTimestamp(3, Timestamp.from(ClockProvider.now())); + statement.setString(4, getHostname()); + int insertedRows = statement.executeUpdate(); + return insertedRows > 0; + }, + this::handleInsertionException); } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { - String sql = "UPDATE " + tableName + " SET lock_until = ?, locked_at = ?, locked_by = ? WHERE name = ? AND lock_until <= ?"; - return executeCommand(sql, statement -> { - Timestamp now = Timestamp.from(ClockProvider.now()); - statement.setTimestamp(1, Timestamp.from(lockConfiguration.getLockAtMostUntil())); - statement.setTimestamp(2, now); - statement.setString(3, getHostname()); - statement.setString(4, lockConfiguration.getName()); - statement.setTimestamp(5, now); - int updatedRows = statement.executeUpdate(); - return updatedRows > 0; - }, this::handleUpdateException); + public boolean updateRecord(LockConfiguration lockConfiguration) { + String sql = "UPDATE " + tableName + + " SET lock_until = ?, locked_at = ?, locked_by = ? WHERE name = ? AND lock_until <= ?"; + return executeCommand( + sql, + statement -> { + Timestamp now = Timestamp.from(ClockProvider.now()); + statement.setTimestamp(1, Timestamp.from(lockConfiguration.getLockAtMostUntil())); + statement.setTimestamp(2, now); + statement.setString(3, getHostname()); + statement.setString(4, lockConfiguration.getName()); + statement.setTimestamp(5, now); + int updatedRows = statement.executeUpdate(); + return updatedRows > 0; + }, + this::handleUpdateException); } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { + public boolean extend(LockConfiguration lockConfiguration) { String sql = "UPDATE " + tableName + " SET lock_until = ? WHERE name = ? AND locked_by = ? AND lock_until > ? "; logger.debug("Extending lock={} until={}", lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil()); - return executeCommand(sql, statement -> { - statement.setTimestamp(1, Timestamp.from(lockConfiguration.getLockAtMostUntil())); - statement.setString(2, lockConfiguration.getName()); - statement.setString(3, getHostname()); - statement.setTimestamp(4, Timestamp.from(ClockProvider.now())); - return statement.executeUpdate() > 0; - }, this::handleUnlockException); + return executeCommand( + sql, + statement -> { + statement.setTimestamp(1, Timestamp.from(lockConfiguration.getLockAtMostUntil())); + statement.setString(2, lockConfiguration.getName()); + statement.setString(3, getHostname()); + statement.setTimestamp(4, Timestamp.from(ClockProvider.now())); + return statement.executeUpdate() > 0; + }, + this::handleUnlockException); } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { + public void unlock(LockConfiguration lockConfiguration) { String sql = "UPDATE " + tableName + " SET lock_until = ? WHERE name = ?"; - executeCommand(sql, statement -> { - statement.setTimestamp(1, Timestamp.from(lockConfiguration.getUnlockTime())); - statement.setString(2, lockConfiguration.getName()); - statement.executeUpdate(); - return null; - }, this::handleUnlockException); + executeCommand( + sql, + statement -> { + statement.setTimestamp(1, Timestamp.from(lockConfiguration.getUnlockTime())); + statement.setString(2, lockConfiguration.getName()); + statement.executeUpdate(); + return null; + }, + this::handleUnlockException); } protected abstract T executeCommand( - String sql, - SqlFunction body, - BiFunction exceptionHandler - ); + String sql, SqlFunction body, BiFunction exceptionHandler); boolean handleInsertionException(String sql, SQLException e) { if (e instanceof SQLIntegrityConstraintViolationException) { // lock record already exists } else { - // can not throw exception here, some drivers (Postgres) do not throw SQLIntegrityConstraintViolationException on duplicate key - // we will try update in the next step, so if there is another problem, an exception will be thrown there + // can not throw exception here, some drivers (Postgres) do not throw + // SQLIntegrityConstraintViolationException on duplicate key + // we will try update in the next step, so if there is another problem, an + // exception will be + // thrown there logger.debug("Exception thrown when inserting record", e); } return false; } boolean handleUpdateException(String sql, SQLException e) { + logger.debug("Unexpected exception when updating lock record", e); throw new LockException("Unexpected exception when locking", e); } @@ -124,4 +134,3 @@ public interface SqlFunction { R apply(T t) throws SQLException; } } - diff --git a/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/package-info.java b/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/package-info.java new file mode 100644 index 000000000..7fdf67ecb --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc-internal/src/main/java/net/javacrumbs/shedlock/provider/jdbc/internal/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.jdbc.internal; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/pom.xml b/providers/jdbc/shedlock-provider-jdbc-micronaut/pom.xml index a390407f0..ac4ef62d7 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/pom.xml +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-jdbc-micronaut - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,7 +21,7 @@ io.micronaut.data micronaut-data-tx - ${micronaut-data.ver} + ${micronaut4-data.version} @@ -30,6 +30,20 @@ ${project.version} test + + + io.micronaut.data + micronaut-data-spring-jdbc + ${micronaut4-data.version} + test + + + + io.micronaut.data + micronaut-data-tx-jdbc + ${micronaut4-data.version} + test + diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcLockProvider.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcLockProvider.java index 7d0f0f6cc..901d4fc19 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcLockProvider.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcLockProvider.java @@ -1,63 +1,48 @@ /** * Copyright 2009-2021 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.micronaut; import io.micronaut.transaction.TransactionOperations; -import io.micronaut.transaction.jdbc.DataSourceTransactionManager; -import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import javax.sql.DataSource; import java.sql.Connection; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; /** - * Lock provided by plain JDBC, using the Micronaut Data transaction manager. It uses a table that contains lock_name and locked_until. + * Lock provided by plain JDBC, using the Micronaut Data transaction manager. It + * uses a table that contains lock_name and locked_until. + * *

    - *
  1. - * Attempts to insert a new lock record. Since lock name is a primary key, it fails if the record already exists. As an optimization, - * we keep in-memory track of created lock records. - *
  2. - *
  3. - * If the insert succeeds (1 inserted row) we have the lock. - *
  4. - *
  5. - * If the insert failed due to duplicate key or we have skipped the insertion, we will try to update lock record using - * UPDATE tableName SET lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now - *
  6. - *
  7. - * If the update succeeded (1 updated row), we have the lock. If the update failed (0 updated rows) somebody else holds the lock - *
  8. - *
  9. - * When unlocking, lock_until is set to now. - *
  10. + *
  11. Attempts to insert a new lock record. Since lock name is a primary key, + * it fails if the record already exists. As an optimization, we keep in-memory + * track of created lock records. + *
  12. If the insert succeeds (1 inserted row) we have the lock. + *
  13. If the insert failed due to duplicate key or we have skipped the + * insertion, we will try to update lock record using UPDATE tableName SET + * lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now + *
  14. If the update succeeded (1 updated row), we have the lock. If the update + * failed (0 updated rows) somebody else holds the lock + *
  15. When unlocking, lock_until is set to now. *
*/ public class MicronautJdbcLockProvider extends StorageBasedLockProvider { private static final String DEFAULT_TABLE_NAME = "shedlock"; - public MicronautJdbcLockProvider(@NonNull DataSource datasource) { - this(new DataSourceTransactionManager(datasource), DEFAULT_TABLE_NAME); - } - - public MicronautJdbcLockProvider(@NonNull TransactionOperations transactionManager) { - this(transactionManager, DEFAULT_TABLE_NAME); + public MicronautJdbcLockProvider(TransactionOperations transactionOperations) { + this(transactionOperations, DEFAULT_TABLE_NAME); } - public MicronautJdbcLockProvider(@NonNull TransactionOperations transactionManager, @NonNull String tableName) { - super(new MicronautJdbcStorageAccessor(transactionManager, tableName)); + public MicronautJdbcLockProvider(TransactionOperations transactionOperations, String tableName) { + super(new MicronautJdbcStorageAccessor(transactionOperations, tableName)); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcStorageAccessor.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcStorageAccessor.java index 5745856a6..44629d993 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcStorageAccessor.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MicronautJdbcStorageAccessor.java @@ -1,45 +1,42 @@ /** * Copyright 2009-2021 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.micronaut; +import static java.util.Objects.requireNonNull; + import io.micronaut.transaction.TransactionDefinition; import io.micronaut.transaction.TransactionOperations; -import net.javacrumbs.shedlock.provider.jdbc.internal.AbstractJdbcStorageAccessor; -import net.javacrumbs.shedlock.support.annotation.NonNull; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.function.BiFunction; - -import static java.util.Objects.requireNonNull; +import net.javacrumbs.shedlock.provider.jdbc.internal.AbstractJdbcStorageAccessor; class MicronautJdbcStorageAccessor extends AbstractJdbcStorageAccessor { - private final TransactionOperations transactionManager; + private final TransactionOperations transactionOperations; private final TransactionDefinition.Propagation propagation = TransactionDefinition.Propagation.REQUIRES_NEW; - MicronautJdbcStorageAccessor(@NonNull TransactionOperations transactionManager, @NonNull String tableName) { + MicronautJdbcStorageAccessor(TransactionOperations transactionOperations, String tableName) { super(tableName); - this.transactionManager = requireNonNull(transactionManager, "transactionManager can not be null"); + this.transactionOperations = requireNonNull(transactionOperations, "transactionManager can not be null"); } @Override - protected T executeCommand(String sql, SqlFunction body, BiFunction exceptionHandler) { - return transactionManager.execute(TransactionDefinition.of(propagation), status -> { + protected T executeCommand( + String sql, SqlFunction body, BiFunction exceptionHandler) { + return transactionOperations.execute(TransactionDefinition.of(propagation), status -> { try (PreparedStatement statement = status.getConnection().prepareStatement(sql)) { return body.apply(statement); } catch (SQLException e) { diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/package-info.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/package-info.java new file mode 100644 index 000000000..f00b24bd5 --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/main/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.jdbc.micronaut; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/AbstractMicronautJdbcTest.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/AbstractMicronautJdbcTest.java index aff0ad90e..a7e9e048f 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/AbstractMicronautJdbcTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/AbstractMicronautJdbcTest.java @@ -1,5 +1,7 @@ package net.javacrumbs.shedlock.provider.jdbc.micronaut; +import io.micronaut.data.spring.jdbc.SpringJdbcConnectionOperations; +import io.micronaut.transaction.jdbc.DataSourceTransactionManager; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest; import org.junit.jupiter.api.AfterAll; @@ -25,6 +27,7 @@ protected boolean useDbTime() { @Override protected StorageBasedLockProvider getLockProvider() { - return new MicronautJdbcLockProvider(testUtils.getDatasource()); + return new MicronautJdbcLockProvider(new DataSourceTransactionManager( + getDatasource(), new SpringJdbcConnectionOperations(getDatasource()), null)); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/HsqlJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/HsqlJdbcLockProviderIntegrationTest.java index 57f73611f..c95a542ff 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/HsqlJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/HsqlJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009-2021 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.micronaut; diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MySqlJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MySqlJdbcLockProviderIntegrationTest.java index 67d845131..9db0944ab 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MySqlJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/MySqlJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009-2021 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.micronaut; diff --git a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/PostgresJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/PostgresJdbcLockProviderIntegrationTest.java index 2abb2b515..25273a770 100644 --- a/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/PostgresJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-micronaut/src/test/java/net/javacrumbs/shedlock/provider/jdbc/micronaut/PostgresJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009-2021 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc.micronaut; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/pom.xml b/providers/jdbc/shedlock-provider-jdbc-template/pom.xml index eb0e1e855..1e230872c 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/pom.xml +++ b/providers/jdbc/shedlock-provider-jdbc-template/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-jdbc-template - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProduct.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProduct.java new file mode 100644 index 000000000..2a0378c20 --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProduct.java @@ -0,0 +1,51 @@ +package net.javacrumbs.shedlock.provider.jdbctemplate; + +import java.util.Arrays; +import java.util.function.Function; +import java.util.function.Predicate; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration; +import net.javacrumbs.shedlock.support.annotation.Nullable; + +public enum DatabaseProduct { + POSTGRES_SQL("PostgreSQL"::equalsIgnoreCase, PostgresSqlServerTimeStatementsSource::new), + SQL_SERVER("Microsoft SQL Server"::equalsIgnoreCase, MsSqlServerTimeStatementsSource::new), + ORACLE("Oracle"::equalsIgnoreCase, OracleServerTimeStatementsSource::new), + MY_SQL("MySQL"::equalsIgnoreCase, MySqlServerTimeStatementsSource::new), + MARIA_DB("MariaDB"::equalsIgnoreCase, MySqlServerTimeStatementsSource::new), + HQL("HSQL Database Engine"::equalsIgnoreCase, HsqlServerTimeStatementsSource::new), + H2("H2"::equalsIgnoreCase, H2ServerTimeStatementsSource::new), + DB2(s -> s.substring(0, 3).equalsIgnoreCase("DB2"), Db2ServerTimeStatementsSource::new), + UNKNOWN(s -> false, configuration -> { + throw new UnsupportedOperationException("DB time is not supported for unknown database product"); + }); + + private final Predicate productMatcher; + + private final Function serverTimeStatementsSource; + + DatabaseProduct( + Predicate productMatcher, Function serverTimeStatementsSource) { + this.productMatcher = productMatcher; + this.serverTimeStatementsSource = serverTimeStatementsSource; + } + + SqlStatementsSource getDbTimeStatementSource(Configuration configuration) { + return serverTimeStatementsSource.apply(configuration); + } + + /** + * Searches for the right DatabaseProduct based on the ProductName returned from + * JDBC Connection Metadata + * + * @param productName + * Obtained from the JDBC connection. See + * java.sql.connection.getMetaData().getProductName(). + * @return The matching ProductName enum + */ + static DatabaseProduct matchProductName(@Nullable final String productName) { + return Arrays.stream(DatabaseProduct.values()) + .filter(databaseProduct -> databaseProduct.productMatcher.test(productName)) + .findFirst() + .orElse(UNKNOWN); + } +} diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2ServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2ServerTimeStatementsSource.java index 21c3c7d14..075e29d89 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2ServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2ServerTimeStatementsSource.java @@ -1,29 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class Db2ServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "(CURRENT TIMESTAMP - CURRENT TIMEZONE)"; - private static final String lockAtMostFor = "("+ now + " + :lockAtMostForMicros MICROSECONDS)"; + private static final String lockAtMostFor = "(" + now + " + :lockAtMostForMicros MICROSECONDS)"; Db2ServerTimeStatementsSource(JdbcTemplateLockProvider.Configuration configuration) { super(configuration); @@ -31,33 +26,40 @@ class Db2ServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = "(" + lockedAt() + "+ :lockAtLeastForMicros MICROSECONDS)"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull - Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForMicros", ((double) lockConfiguration.getLockAtMostFor().toNanos() / 1_000)); - params.put("lockAtLeastForMicros", ((double) lockConfiguration.getLockAtLeastFor().toNanos() / 1_000)); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForMicros", + lockConfiguration.getLockAtMostFor().toNanos() / 1_000, + "lockAtLeastForMicros", + lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2ServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2ServerTimeStatementsSource.java index 954e072cb..aa96eafc2 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2ServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2ServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class H2ServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "CURRENT_TIMESTAMP(3)"; @@ -31,33 +26,40 @@ class H2ServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = "TIMESTAMPADD(MICROSECOND, :lockAtLeastForMicros, " + lockedAt() + ")"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull - Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForMicros", lockConfiguration.getLockAtMostFor().toNanos() / 1_000); - params.put("lockAtLeastForMicros", lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForMicros", + lockConfiguration.getLockAtMostFor().toNanos() / 1_000, + "lockAtLeastForMicros", + lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlServerTimeStatementsSource.java index f6e786970..9dad1a17d 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class HsqlServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "CURRENT_TIMESTAMP(3)"; @@ -31,33 +26,40 @@ class HsqlServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = "TIMESTAMPADD(MICROSECOND, :lockAtLeastForMicros, " + lockedAt() + ")"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull - Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForMicros", lockConfiguration.getLockAtMostFor().toNanos() / 1_000); - params.put("lockAtLeastForMicros", lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForMicros", + lockConfiguration.getLockAtMostFor().toNanos() / 1_000, + "lockAtLeastForMicros", + lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProvider.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProvider.java index 9adbf9b8d..7b0986291 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProvider.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProvider.java @@ -1,112 +1,120 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static java.util.Objects.requireNonNull; + +import java.util.TimeZone; +import javax.sql.DataSource; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.support.Utils; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.javacrumbs.shedlock.support.annotation.Nullable; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; -import javax.sql.DataSource; -import java.util.TimeZone; - -import static java.util.Objects.requireNonNull; - /** - * Lock provided by JdbcTemplate. It uses a table that contains lock_name and locked_until. + * Lock provided by JdbcTemplate. It uses a table that contains lock_name and + * locked_until. + * *

    - *
  1. - * Attempts to insert a new lock record. Since lock name is a primary key, it fails if the record already exists. As an optimization, - * we keep in-memory track of created lock records. - *
  2. - *
  3. - * If the insert succeeds (1 inserted row) we have the lock. - *
  4. - *
  5. - * If the insert failed due to duplicate key or we have skipped the insertion, we will try to update lock record using - * UPDATE tableName SET lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now - *
  6. - *
  7. - * If the update succeeded (1 updated row), we have the lock. If the update failed (0 updated rows) somebody else holds the lock - *
  8. - *
  9. - * When unlocking, lock_until is set to now. - *
  10. + *
  11. Attempts to insert a new lock record. Since lock name is a primary key, + * it fails if the record already exists. As an optimization, we keep in-memory + * track of created lock records. + *
  12. If the insert succeeds (1 inserted row) we have the lock. + *
  13. If the insert failed due to duplicate key or we have skipped the + * insertion, we will try to update lock record using UPDATE tableName SET + * lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now + *
  14. If the update succeeded (1 updated row), we have the lock. If the update + * failed (0 updated rows) somebody else holds the lock + *
  15. When unlocking, lock_until is set to now. *
*/ public class JdbcTemplateLockProvider extends StorageBasedLockProvider { private static final String DEFAULT_TABLE_NAME = "shedlock"; - public JdbcTemplateLockProvider(@NonNull JdbcTemplate jdbcTemplate) { + public JdbcTemplateLockProvider(JdbcTemplate jdbcTemplate) { this(jdbcTemplate, (PlatformTransactionManager) null); } - public JdbcTemplateLockProvider(@NonNull JdbcTemplate jdbcTemplate, @Nullable PlatformTransactionManager transactionManager) { + public JdbcTemplateLockProvider( + JdbcTemplate jdbcTemplate, @Nullable PlatformTransactionManager transactionManager) { this(jdbcTemplate, transactionManager, DEFAULT_TABLE_NAME); } - public JdbcTemplateLockProvider(@NonNull JdbcTemplate jdbcTemplate, @NonNull String tableName) { + public JdbcTemplateLockProvider(JdbcTemplate jdbcTemplate, String tableName) { this(jdbcTemplate, null, tableName); } - public JdbcTemplateLockProvider(@NonNull DataSource dataSource) { + public JdbcTemplateLockProvider(DataSource dataSource) { this(new JdbcTemplate(dataSource)); } - public JdbcTemplateLockProvider(@NonNull DataSource dataSource, @NonNull String tableName) { + public JdbcTemplateLockProvider(DataSource dataSource, String tableName) { this(new JdbcTemplate(dataSource), tableName); } - public JdbcTemplateLockProvider(@NonNull JdbcTemplate jdbcTemplate, @Nullable PlatformTransactionManager transactionManager, @NonNull String tableName) { + public JdbcTemplateLockProvider( + JdbcTemplate jdbcTemplate, @Nullable PlatformTransactionManager transactionManager, String tableName) { this(Configuration.builder() - .withJdbcTemplate(jdbcTemplate) - .withTransactionManager(transactionManager) - .withTableName(tableName) - .build() - ); + .withJdbcTemplate(jdbcTemplate) + .withTransactionManager(transactionManager) + .withTableName(tableName) + .build()); } - public JdbcTemplateLockProvider(@NonNull Configuration configuration) { + public JdbcTemplateLockProvider(Configuration configuration) { super(new JdbcTemplateStorageAccessor(configuration)); } public static final class Configuration { private final JdbcTemplate jdbcTemplate; + + @Nullable + private final DatabaseProduct databaseProduct; + + @Nullable private final PlatformTransactionManager transactionManager; + private final String tableName; + + @Nullable private final TimeZone timeZone; + private final ColumnNames columnNames; private final String lockedByValue; private final boolean useDbTime; + + @Nullable private final Integer isolationLevel; + private final boolean throwUnexpectedException; + Configuration( - @NonNull JdbcTemplate jdbcTemplate, - @Nullable PlatformTransactionManager transactionManager, - @NonNull String tableName, - @Nullable TimeZone timeZone, - @NonNull ColumnNames columnNames, - @NonNull String lockedByValue, - boolean useDbTime, - @Nullable Integer isolationLevel) { + JdbcTemplate jdbcTemplate, + @Nullable DatabaseProduct databaseProduct, + @Nullable PlatformTransactionManager transactionManager, + String tableName, + @Nullable TimeZone timeZone, + ColumnNames columnNames, + String lockedByValue, + boolean useDbTime, + @Nullable Integer isolationLevel, + boolean throwUnexpectedException) { this.jdbcTemplate = requireNonNull(jdbcTemplate, "jdbcTemplate can not be null"); + this.databaseProduct = databaseProduct; this.transactionManager = transactionManager; this.tableName = requireNonNull(tableName, "tableName can not be null"); this.timeZone = timeZone; @@ -117,12 +125,19 @@ public static final class Configuration { throw new IllegalArgumentException("Can not set both useDbTime and timeZone"); } this.useDbTime = useDbTime; + this.throwUnexpectedException = throwUnexpectedException; } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } + @Nullable + public DatabaseProduct getDatabaseProduct() { + return databaseProduct; + } + + @Nullable public PlatformTransactionManager getTransactionManager() { return transactionManager; } @@ -131,6 +146,7 @@ public String getTableName() { return tableName; } + @Nullable public TimeZone getTimeZone() { return timeZone; } @@ -147,36 +163,54 @@ public boolean getUseDbTime() { return useDbTime; } + @Nullable public Integer getIsolationLevel() { return isolationLevel; } + public boolean isThrowUnexpectedException() { + return throwUnexpectedException; + } + public static Configuration.Builder builder() { return new Configuration.Builder(); } - public static final class Builder { private JdbcTemplate jdbcTemplate; + + @Nullable + private DatabaseProduct databaseProduct; + + @Nullable private PlatformTransactionManager transactionManager; + private String tableName = DEFAULT_TABLE_NAME; + + @Nullable private TimeZone timeZone; + private String lockedByValue = Utils.getHostname(); private ColumnNames columnNames = new ColumnNames("name", "lock_until", "locked_at", "locked_by"); + private boolean dbUpperCase = false; private boolean useDbTime = false; + + @Nullable private Integer isolationLevel; - public Builder withJdbcTemplate(@NonNull JdbcTemplate jdbcTemplate) { + private boolean throwUnexpectedException = false; + + public Builder withJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; return this; } - public Builder withTransactionManager(PlatformTransactionManager transactionManager) { + public Builder withTransactionManager(@Nullable PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; return this; } - public Builder withTableName(@NonNull String tableName) { + public Builder withTableName(String tableName) { this.tableName = tableName; return this; } @@ -191,6 +225,23 @@ public Builder withColumnNames(ColumnNames columnNames) { return this; } + public Builder withDbUpperCase(final boolean dbUpperCase) { + this.dbUpperCase = dbUpperCase; + return this; + } + + /** + * This is only needed if your database product can't be automatically detected. + * + * @param databaseProduct + * Database product + * @return ConfigurationBuilder + */ + public Builder withDatabaseProduct(final DatabaseProduct databaseProduct) { + this.databaseProduct = databaseProduct; + return this; + } + /** * Value stored in 'locked_by' column. Please use only for debugging purposes. */ @@ -205,19 +256,33 @@ public Builder usingDbTime() { } /** - * Sets the isolation level for ShedLock. See {@link java.sql.Connection} for constant definitions. - * for constant definitions + * Sets the isolation level for ShedLock. See {@link java.sql.Connection} for + * constant definitions. for constant definitions */ public Builder withIsolationLevel(int isolationLevel) { this.isolationLevel = isolationLevel; return this; } + public Builder withThrowUnexpectedException(boolean throwUnexpectedException) { + this.throwUnexpectedException = throwUnexpectedException; + return this; + } + public JdbcTemplateLockProvider.Configuration build() { - return new JdbcTemplateLockProvider.Configuration(jdbcTemplate, transactionManager, tableName, timeZone, columnNames, lockedByValue, useDbTime, isolationLevel); + return new JdbcTemplateLockProvider.Configuration( + jdbcTemplate, + databaseProduct, + transactionManager, + dbUpperCase ? tableName.toUpperCase() : tableName, + timeZone, + dbUpperCase ? columnNames.toUpperCase() : columnNames, + lockedByValue, + useDbTime, + isolationLevel, + throwUnexpectedException); } } - } public static final class ColumnNames { @@ -248,6 +313,10 @@ public String getLockedAt() { public String getLockedBy() { return lockedBy; } - } + private ColumnNames toUpperCase() { + return new ColumnNames( + name.toUpperCase(), lockUntil.toUpperCase(), lockedAt.toUpperCase(), lockedBy.toUpperCase()); + } + } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessor.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessor.java index 0130f3c89..d10c6cc71 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessor.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessor.java @@ -1,24 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static java.util.Objects.requireNonNull; + +import java.util.Map; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; @@ -32,26 +33,23 @@ import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.TransactionTemplate; -import java.util.Map; - -import static java.util.Objects.requireNonNull; - -/** - * Spring JdbcTemplate based implementation usable in JTA environment - */ +/** Spring JdbcTemplate based implementation usable in JTA environment */ class JdbcTemplateStorageAccessor extends AbstractStorageAccessor { private final NamedParameterJdbcTemplate jdbcTemplate; private final TransactionTemplate transactionTemplate; private final Configuration configuration; + + @Nullable private SqlStatementsSource sqlStatementsSource; - JdbcTemplateStorageAccessor(@NonNull Configuration configuration) { + JdbcTemplateStorageAccessor(Configuration configuration) { requireNonNull(configuration, "configuration can not be null"); this.jdbcTemplate = new NamedParameterJdbcTemplate(configuration.getJdbcTemplate()); this.configuration = configuration; - PlatformTransactionManager transactionManager = configuration.getTransactionManager() != null ? - configuration.getTransactionManager() : - new DataSourceTransactionManager(configuration.getJdbcTemplate().getDataSource()); + PlatformTransactionManager transactionManager = configuration.getTransactionManager() != null + ? configuration.getTransactionManager() + : new DataSourceTransactionManager( + configuration.getJdbcTemplate().getDataSource()); this.transactionTemplate = new TransactionTemplate(transactionManager); this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); @@ -62,33 +60,41 @@ class JdbcTemplateStorageAccessor extends AbstractStorageAccessor { } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean insertRecord(LockConfiguration lockConfiguration) { try { String sql = sqlStatementsSource().getInsertStatement(); return execute(sql, lockConfiguration); - } catch (DuplicateKeyException | ConcurrencyFailureException e) { + } catch (DuplicateKeyException | ConcurrencyFailureException | TransactionSystemException e) { + logger.debug("Duplicate key", e); return false; - } catch (DataIntegrityViolationException | BadSqlGrammarException | UncategorizedSQLException | TransactionSystemException e) { + } catch (DataIntegrityViolationException | BadSqlGrammarException | UncategorizedSQLException e) { + if (configuration.isThrowUnexpectedException()) { + throw e; + } logger.error("Unexpected exception", e); return false; } } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean updateRecord(LockConfiguration lockConfiguration) { String sql = sqlStatementsSource().getUpdateStatement(); try { return execute(sql, lockConfiguration); } catch (ConcurrencyFailureException e) { + logger.debug("Serialization exception", e); return false; - } catch (DataIntegrityViolationException | TransactionSystemException e) { + } catch (DataIntegrityViolationException | TransactionSystemException | UncategorizedSQLException e) { + if (configuration.isThrowUnexpectedException()) { + throw e; + } logger.error("Unexpected exception", e); return false; } } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { + public boolean extend(LockConfiguration lockConfiguration) { String sql = sqlStatementsSource().getExtendStatement(); logger.debug("Extending lock={} until={}", lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil()); @@ -96,13 +102,16 @@ public boolean extend(@NonNull LockConfiguration lockConfiguration) { } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { - try { - doUnlock(lockConfiguration); - } catch (ConcurrencyFailureException e) { - logger.info("Unlock failed due to TransactionSystemException - retrying"); - doUnlock(lockConfiguration); + public void unlock(LockConfiguration lockConfiguration) { + for (int i = 0; i < 10; i++) { + try { + doUnlock(lockConfiguration); + return; + } catch (ConcurrencyFailureException | TransactionSystemException e) { + logger.info("Unlock failed due to TransactionSystemException - retrying attempt {}", i + 1); + } } + logger.error("Unlock failed after 10 attempts"); } private void doUnlock(LockConfiguration lockConfiguration) { @@ -115,8 +124,7 @@ private boolean execute(String sql, LockConfiguration lockConfiguration) throws return transactionTemplate.execute(status -> jdbcTemplate.update(sql, params(lockConfiguration)) > 0); } - @NonNull - private Map params(@NonNull LockConfiguration lockConfiguration) { + private Map params(LockConfiguration lockConfiguration) { return sqlStatementsSource().params(lockConfiguration); } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerTimeStatementsSource.java index 39563d31c..0602589ba 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class MsSqlServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "SYSUTCDATETIME()"; @@ -31,32 +26,40 @@ class MsSqlServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = "DATEADD(millisecond, :lockAtLeastForMillis, " + lockedAt() + ")"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForMillis", lockConfiguration.getLockAtMostFor().toMillis()); - params.put("lockAtLeastForMillis", lockConfiguration.getLockAtLeastFor().toMillis()); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForMillis", + lockConfiguration.getLockAtMostFor().toMillis(), + "lockAtLeastForMillis", + lockConfiguration.getLockAtLeastFor().toMillis()); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlServerTimeStatementsSource.java index 171eeb25d..66ec21b79 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class MySqlServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "UTC_TIMESTAMP(3)"; @@ -31,32 +26,39 @@ class MySqlServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "INSERT IGNORE INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = "TIMESTAMPADD(MICROSECOND, :lockAtLeastForMicros, " + lockedAt() + ")"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = IF (" + lockAtLeastFor + " > " + now + " , " + lockAtLeastFor + ", " + now + ") WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = IF (" + lockAtLeastFor + " > " + now + " , " + + lockAtLeastFor + ", " + now + ") WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForMicros", lockConfiguration.getLockAtMostFor().toNanos() / 1_000); - params.put("lockAtLeastForMicros", lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForMicros", + lockConfiguration.getLockAtMostFor().toNanos() / 1_000, + "lockAtLeastForMicros", + lockConfiguration.getLockAtLeastFor().toNanos() / 1_000); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleServerTimeStatementsSource.java index aa71a69e1..05a011359 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class OracleServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "SYS_EXTRACT_UTC(SYSTIMESTAMP)"; @@ -33,33 +28,43 @@ class OracleServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; + return "MERGE INTO " + tableName() + " USING (SELECT 1 FROM dual) ON (" + name() + + " = :name) WHEN MATCHED THEN UPDATE SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + + " <= " + now + " WHEN NOT MATCHED THEN INSERT(" + name() + ", " + lockUntil() + ", " + lockedAt() + + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)"; } @Override public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + + ", " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= " + now; } @Override public String getUnlockStatement() { String lockAtLeastFor = lockedAt() + " + :lockAtLeastFor"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull - Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostFor", ((double) lockConfiguration.getLockAtMostFor().toMillis()) / millisecondsInDay); - params.put("lockAtLeastFor", ((double) lockConfiguration.getLockAtLeastFor().toMillis()) / millisecondsInDay); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostFor", + ((double) lockConfiguration.getLockAtMostFor().toMillis()) / millisecondsInDay, + "lockAtLeastFor", + ((double) lockConfiguration.getLockAtLeastFor().toMillis()) / millisecondsInDay); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlServerTimeStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlServerTimeStatementsSource.java index 445b01381..622857e7c 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlServerTimeStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlServerTimeStatementsSource.java @@ -1,25 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; - -import java.util.HashMap; import java.util.Map; +import net.javacrumbs.shedlock.core.LockConfiguration; class PostgresSqlServerTimeStatementsSource extends SqlStatementsSource { private static final String now = "timezone('utc', CURRENT_TIMESTAMP)"; @@ -31,13 +26,15 @@ class PostgresSqlServerTimeStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)" + - " ON CONFLICT (" + name() + ") DO UPDATE" + updateClause(); + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, " + lockAtMostFor + ", " + now + ", :lockedBy)" + " ON CONFLICT (" + name() + + ") DO UPDATE" + updateClause(); } - @NonNull private String updateClause() { - return " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + " = :lockedBy WHERE " + tableName() + "." + name() + " = :name AND " + tableName() + "." + lockUntil() + " <= " + now; + return " SET " + lockUntil() + " = " + lockAtMostFor + ", " + lockedAt() + " = " + now + ", " + lockedBy() + + " = :lockedBy WHERE " + tableName() + "." + name() + " = :name AND " + tableName() + "." + lockUntil() + + " <= " + now; } @Override @@ -48,21 +45,27 @@ public String getUpdateStatement() { @Override public String getUnlockStatement() { String lockAtLeastFor = lockedAt() + " + cast(:lockAtLeastForInterval as interval)"; - return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = CASE WHEN " + lockAtLeastFor + " > " + now + + " THEN " + lockAtLeastFor + " ELSE " + now + " END WHERE " + name() + " = :name AND " + lockedBy() + + " = :lockedBy"; } @Override public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor +" WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = " + lockAtMostFor + " WHERE " + name() + + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > " + now; } @Override - @NonNull Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("lockAtMostForInterval", lockConfiguration.getLockAtMostFor().toMillis() + " milliseconds"); - params.put("lockAtLeastForInterval", lockConfiguration.getLockAtLeastFor().toMillis() + " milliseconds"); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockedBy", + configuration.getLockedByValue(), + "lockAtMostForInterval", + lockConfiguration.getLockAtMostFor().toMillis() + " milliseconds", + "lockAtLeastForInterval", + lockConfiguration.getLockAtLeastFor().toMillis() + " milliseconds"); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlStatementsSource.java index a03e0893f..c40d44b8b 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSqlStatementsSource.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; @@ -22,9 +20,8 @@ class PostgresSqlStatementsSource extends SqlStatementsSource { @Override String getInsertStatement() { - return super.getInsertStatement() + " ON CONFLICT (" + name() + ") DO UPDATE " + - "SET " + lockUntil() + " = :lockUntil, " + lockedAt() + " = :now, " + lockedBy() + " = :lockedBy " + - "WHERE " + tableName() + "." + lockUntil() + " <= :now"; + return super.getInsertStatement() + " ON CONFLICT (" + name() + ") DO UPDATE " + "SET " + lockUntil() + + " = :lockUntil, " + lockedAt() + " = :now, " + lockedBy() + " = :lockedBy " + "WHERE " + tableName() + + "." + lockUntil() + " <= :now"; } - } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/SqlStatementsSource.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/SqlStatementsSource.java index c116bf3a9..a7300948b 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/SqlStatementsSource.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/SqlStatementsSource.java @@ -1,35 +1,31 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration; -import net.javacrumbs.shedlock.support.annotation.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.jdbc.core.ConnectionCallback; - import java.sql.Timestamp; import java.time.Instant; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.TimeZone; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.ConnectionCallback; class SqlStatementsSource { protected final Configuration configuration; @@ -41,41 +37,15 @@ class SqlStatementsSource { } static SqlStatementsSource create(Configuration configuration) { - String databaseProductName = getDatabaseProductName(configuration); + DatabaseProduct databaseProduct = getDatabaseProduct(configuration); if (configuration.getUseDbTime()) { - switch (databaseProductName) { - case "PostgreSQL": - logger.debug("Using PostgresSqlServerTimeStatementsSource"); - return new PostgresSqlServerTimeStatementsSource(configuration); - case "Microsoft SQL Server": - logger.debug("Using MsSqlServerTimeStatementsSource"); - return new MsSqlServerTimeStatementsSource(configuration); - case "Oracle": - logger.debug("Using OracleServerTimeStatementsSource"); - return new OracleServerTimeStatementsSource(configuration); - case "MySQL": - logger.debug("Using MySqlServerTimeStatementsSource"); - return new MySqlServerTimeStatementsSource(configuration); - case "MariaDB": - logger.debug("Using MySqlServerTimeStatementsSource (for MariaDB)"); - return new MySqlServerTimeStatementsSource(configuration); - case "HSQL Database Engine": - logger.debug("Using HsqlServerTimeStatementsSource"); - return new HsqlServerTimeStatementsSource(configuration); - case "H2": - logger.debug("Using H2ServerTimeStatementsSource"); - return new H2ServerTimeStatementsSource(configuration); - default: - if (databaseProductName.startsWith("DB2")) { - logger.debug("Using Db2ServerTimeStatementsSource"); - return new Db2ServerTimeStatementsSource(configuration); - } - throw new UnsupportedOperationException("DB time is not supported for '" + databaseProductName + "'"); - } + var statementsSource = databaseProduct.getDbTimeStatementSource(configuration); + logger.debug("Using {}", statementsSource.getClass().getSimpleName()); + return statementsSource; } else { - if ("PostgreSQL".equals(databaseProductName)) { - logger.debug("Using PostgresSqlServerTimeStatementsSource"); + if (Objects.equals(databaseProduct, DatabaseProduct.POSTGRES_SQL)) { + logger.debug("Using PostgresSqlStatementsSource"); return new PostgresSqlStatementsSource(configuration); } else { logger.debug("Using SqlStatementsSource"); @@ -84,27 +54,34 @@ static SqlStatementsSource create(Configuration configuration) { } } - private static String getDatabaseProductName(Configuration configuration) { + private static DatabaseProduct getDatabaseProduct(final Configuration configuration) { + if (configuration.getDatabaseProduct() != null) { + return configuration.getDatabaseProduct(); + } try { - return configuration.getJdbcTemplate().execute((ConnectionCallback) connection -> connection.getMetaData().getDatabaseProductName()); + String jdbcProductName = configuration.getJdbcTemplate().execute((ConnectionCallback) + connection -> connection.getMetaData().getDatabaseProductName()); + return DatabaseProduct.matchProductName(jdbcProductName); } catch (Exception e) { - logger.debug("Can not determine database product name " + e.getMessage()); - return "Unknown"; + logger.debug("Can not determine database product name {}", e.getMessage()); + return DatabaseProduct.UNKNOWN; } } - @NonNull - Map params(@NonNull LockConfiguration lockConfiguration) { - Map params = new HashMap<>(); - params.put("name", lockConfiguration.getName()); - params.put("lockUntil", timestamp(lockConfiguration.getLockAtMostUntil())); - params.put("now", timestamp(ClockProvider.now())); - params.put("lockedBy", configuration.getLockedByValue()); - params.put("unlockTime", timestamp(lockConfiguration.getUnlockTime())); - return params; + Map params(LockConfiguration lockConfiguration) { + return Map.of( + "name", + lockConfiguration.getName(), + "lockUntil", + timestamp(lockConfiguration.getLockAtMostUntil()), + "now", + timestamp(ClockProvider.now()), + "lockedBy", + configuration.getLockedByValue(), + "unlockTime", + timestamp(lockConfiguration.getUnlockTime())); } - @NonNull private Object timestamp(Instant time) { TimeZone timeZone = configuration.getTimeZone(); if (timeZone == null) { @@ -117,18 +94,19 @@ private Object timestamp(Instant time) { } } - String getInsertStatement() { - return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + ") VALUES(:name, :lockUntil, :now, :lockedBy)"; + return "INSERT INTO " + tableName() + "(" + name() + ", " + lockUntil() + ", " + lockedAt() + ", " + lockedBy() + + ") VALUES(:name, :lockUntil, :now, :lockedBy)"; } - public String getUpdateStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = :lockUntil, " + lockedAt() + " = :now, " + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= :now"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = :lockUntil, " + lockedAt() + " = :now, " + + lockedBy() + " = :lockedBy WHERE " + name() + " = :name AND " + lockUntil() + " <= :now"; } public String getExtendStatement() { - return "UPDATE " + tableName() + " SET " + lockUntil() + " = :lockUntil WHERE " + name() + " = :name AND " + lockedBy() + " = :lockedBy AND " + lockUntil() + " > :now"; + return "UPDATE " + tableName() + " SET " + lockUntil() + " = :lockUntil WHERE " + name() + " = :name AND " + + lockedBy() + " = :lockedBy AND " + lockUntil() + " > :now"; } public String getUnlockStatement() { diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/package-info.java b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/package-info.java new file mode 100644 index 000000000..ab03f5056 --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/main/java/net/javacrumbs/shedlock/provider/jdbctemplate/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.jdbctemplate; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateLockProviderIntegrationTest.java index 5a59dc18e..ba5a77a2b 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateLockProviderIntegrationTest.java @@ -1,20 +1,21 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest; import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; @@ -24,9 +25,6 @@ import org.junit.jupiter.api.TestInstance; import org.springframework.jdbc.core.JdbcTemplate; -import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - @TestInstance(PER_CLASS) public abstract class AbstractJdbcTemplateLockProviderIntegrationTest { private final DbConfig dbConfig; @@ -35,7 +33,6 @@ public AbstractJdbcTemplateLockProviderIntegrationTest(DbConfig dbConfig) { this.dbConfig = dbConfig; } - @BeforeAll public void startDb() { dbConfig.startDb(); @@ -45,6 +42,7 @@ public void startDb() { public void shutdownDb() { dbConfig.shutdownDb(); } + @Nested class ClientTime extends AbstractJdbcLockProviderIntegrationTest { @Override @@ -55,9 +53,8 @@ protected DbConfig getDbConfig() { @Override protected StorageBasedLockProvider getLockProvider() { return new JdbcTemplateLockProvider(builder() - .withJdbcTemplate(new JdbcTemplate(getDatasource())) - .build() - ); + .withJdbcTemplate(new JdbcTemplate(getDatasource())) + .build()); } @Override @@ -75,12 +72,10 @@ protected DbConfig getDbConfig() { @Override protected StorageBasedLockProvider getLockProvider() { - return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration - .builder() - .withJdbcTemplate(new JdbcTemplate(getDatasource())) - .usingDbTime() - .build() - ); + return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(new JdbcTemplate(getDatasource())) + .usingDbTime() + .build()); } @Override @@ -96,5 +91,3 @@ class StorageAccessor extends AbstractJdbcTemplateStorageAccessorTest { } } } - - diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateStorageAccessorTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateStorageAccessorTest.java index 044d5970e..4fd51ea66 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateStorageAccessorTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/AbstractJdbcTemplateStorageAccessorTest.java @@ -1,34 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static java.lang.Thread.sleep; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Timestamp; +import java.time.Duration; import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.JdbcTestUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import java.sql.Timestamp; -import java.time.Duration; - -import static java.lang.Thread.sleep; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; - public abstract class AbstractJdbcTemplateStorageAccessorTest { private static final String MY_LOCK = "my-lock"; @@ -58,15 +54,13 @@ void shouldNotUpdateOnInsertIfPreviousDidNotEndWhenUsingDbTime() { private void shouldNotUpdateOnInsertIfPreviousDidNotEnd(boolean usingDbTime) { JdbcTemplateStorageAccessor accessor = getAccessor(usingDbTime); - assertThat( - accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10))) - ).isEqualTo(true); + assertThat(accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10)))) + .isEqualTo(true); Timestamp originalLockValidity = testUtils.getLockedUntil(MY_LOCK); - assertThat( - accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10))) - ).isEqualTo(false); + assertThat(accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10)))) + .isEqualTo(false); assertThat(testUtils.getLockedUntil(MY_LOCK)).isEqualTo(originalLockValidity); } @@ -92,13 +86,14 @@ private void shouldNotUpdateOtherLockConfigurations(boolean usingDbTime) throws Timestamp otherLockLockedUntil = testUtils.getLockedUntil(OTHER_LOCK); // wait for a while so there will be a difference in the timestamp - // when system time is used seems there is no milliseconds in the timestamp so to make a difference we have to wait for at least a second + // when system time is used seems there is no milliseconds in the timestamp so + // to make a + // difference we have to wait for at least a second sleep(1000); - // act - assertThat(accessor.updateRecord(new LockConfiguration(now(), MY_LOCK, lockAtMostFor, Duration.ZERO))).isEqualTo(true); - + assertThat(accessor.updateRecord(new LockConfiguration(now(), MY_LOCK, lockAtMostFor, Duration.ZERO))) + .isEqualTo(true); // assert assertThat(testUtils.getLockedUntil(MY_LOCK)).isAfter(myLockLockedUntil); @@ -106,16 +101,13 @@ private void shouldNotUpdateOtherLockConfigurations(boolean usingDbTime) throws assertThat(testUtils.getLockedUntil(OTHER_LOCK)).isEqualTo(otherLockLockedUntil); } - @NonNull private LockConfiguration lockConfig(String myLock, Duration lockAtMostFor) { return new LockConfiguration(now(), myLock, lockAtMostFor, Duration.ZERO); } - @NonNull protected JdbcTemplateStorageAccessor getAccessor(boolean usingDbTime) { - JdbcTemplateLockProvider.Configuration.Builder builder = JdbcTemplateLockProvider - .Configuration.builder() - .withJdbcTemplate(testUtils.getJdbcTemplate()); + JdbcTemplateLockProvider.Configuration.Builder builder = + JdbcTemplateLockProvider.Configuration.builder().withJdbcTemplate(testUtils.getJdbcTemplate()); if (usingDbTime) { builder.usingDbTime(); } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProductTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProductTest.java new file mode 100644 index 000000000..d5d827829 --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/DatabaseProductTest.java @@ -0,0 +1,13 @@ +package net.javacrumbs.shedlock.provider.jdbctemplate; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; + +class DatabaseProductTest { + + @Test + void shouldMatchDb2() { + assertThat(DatabaseProduct.matchProductName("dB2 for zOS 456")).isEqualTo(DatabaseProduct.DB2); + } +} diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2JdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2JdbcTemplateLockProviderIntegrationTest.java index 155f527a6..c472578b3 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2JdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/Db2JdbcTemplateLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2JdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2JdbcTemplateLockProviderIntegrationTest.java index 48d10cce4..a66742b42 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2JdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/H2JdbcTemplateLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlJdbcTemplateLockProviderIntegrationTest.java index 75d2f408a..12ffb4b19 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/HsqlJdbcTemplateLockProviderIntegrationTest.java @@ -1,35 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.ColumnNames; -import net.javacrumbs.shedlock.test.support.jdbc.HsqlConfig; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.time.Duration; import java.util.Optional; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.ColumnNames; +import net.javacrumbs.shedlock.test.support.jdbc.HsqlConfig; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; public class HsqlJdbcTemplateLockProviderIntegrationTest extends AbstractJdbcTemplateLockProviderIntegrationTest { private static final HsqlConfig dbConfig = new HsqlConfig(); @@ -40,21 +37,21 @@ public HsqlJdbcTemplateLockProviderIntegrationTest() { @Test public void shouldBeAbleToSetCustomColumnNames() throws SQLException { - try ( - Connection conn = dbConfig.getDataSource().getConnection(); - Statement statement = conn.createStatement() - ) { - statement.execute("CREATE TABLE shdlck(n VARCHAR(64), lck_untl TIMESTAMP(3), lckd_at TIMESTAMP(3), lckd_by VARCHAR(255), PRIMARY KEY (n))"); + try (Connection conn = dbConfig.getDataSource().getConnection(); + Statement statement = conn.createStatement()) { + statement.execute( + "CREATE TABLE shdlck(n VARCHAR(64), lck_untl TIMESTAMP(3), lckd_at TIMESTAMP(3), lckd_by VARCHAR(255), PRIMARY KEY (n))"); } JdbcTemplateLockProvider provider = new JdbcTemplateLockProvider(builder() - .withTableName("shdlck") - .withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by")) - .withJdbcTemplate(new JdbcTemplate(dbConfig.getDataSource())) - .withLockedByValue("my-value") - .build()); - - Optional lock = provider.lock(new LockConfiguration(now(), "test", Duration.ofSeconds(10), Duration.ZERO)); + .withTableName("shdlck") + .withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by")) + .withJdbcTemplate(new JdbcTemplate(dbConfig.getDataSource())) + .withLockedByValue("my-value") + .build()); + + Optional lock = + provider.lock(new LockConfiguration(now(), "test", Duration.ofSeconds(10), Duration.ZERO)); lock.get().unlock(); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProviderTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProviderTest.java index 99dbdd63c..baefda825 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProviderTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateLockProviderTest.java @@ -1,38 +1,61 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.util.TimeZone; - +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; +import java.util.TimeZone; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; class JdbcTemplateLockProviderTest { @Test void shouldNotEnableBothTimezoneAndServerTime() { - assertThatThrownBy( - () -> JdbcTemplateLockProvider.Configuration.builder() - .withTimeZone(TimeZone.getTimeZone("Europe/Prague")) + assertThatThrownBy(() -> JdbcTemplateLockProvider.Configuration.builder() + .withTimeZone(TimeZone.getTimeZone("Europe/Prague")) + .withJdbcTemplate(mock(JdbcTemplate.class)) + .usingDbTime() + .build()) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldTableAndColumNamesUpperCase() { + final var config = JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(mock(JdbcTemplate.class)) + .withDbUpperCase(true) + .build(); + + assertThat(config.getTableName()).isUpperCase(); + assertThat(config.getColumnNames().getName()).isUpperCase(); + assertThat(config.getColumnNames().getLockedBy()).isUpperCase(); + assertThat(config.getColumnNames().getLockedAt()).isUpperCase(); + assertThat(config.getColumnNames().getLockUntil()).isUpperCase(); + } + + @Test + void shouldTableAndColumNamesLowerCaseByDefault() { + final var config = JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(mock(JdbcTemplate.class)) - .usingDbTime() - .build() - ).isInstanceOf(IllegalArgumentException.class); + .build(); + + assertThat(config.getTableName()).isLowerCase(); + assertThat(config.getColumnNames().getName()).isLowerCase(); + assertThat(config.getColumnNames().getLockedBy()).isLowerCase(); + assertThat(config.getColumnNames().getLockedAt()).isLowerCase(); + assertThat(config.getColumnNames().getLockUntil()).isLowerCase(); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessorTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessorTest.java index a489d2ea9..171777088 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessorTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/JdbcTemplateStorageAccessorTest.java @@ -1,41 +1,89 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; - +import static java.time.Duration.ZERO; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.time.Duration; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockConfiguration; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionSystemException; class JdbcTemplateStorageAccessorTest { + private final DataSource dataSource = mock(DataSource.class); + private final LockConfiguration lockConfiguration = + new LockConfiguration(now(), "name", Duration.ofSeconds(1), ZERO); + @Test void shouldDoLazyInit() { - DataSource dataSource = mock(DataSource.class); - new JdbcTemplateStorageAccessor( - JdbcTemplateLockProvider.Configuration - .builder() + + new JdbcTemplateStorageAccessor(JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) - .build() - ); + .build()); verifyNoInteractions(dataSource); } + @Test + void shouldCatchUnexpectedException() { + TransactionSystemException exception = new TransactionSystemException("test"); + JdbcTemplateStorageAccessor jdbcTemplateStorageAccessor = createJdbcTemplateStorageAccessor(exception, false); + + assertThat(jdbcTemplateStorageAccessor.updateRecord(lockConfiguration)).isFalse(); + } + + @Test + void shouldThrowUnexpectedException() { + TransactionSystemException exception = new TransactionSystemException("test"); + JdbcTemplateStorageAccessor jdbcTemplateStorageAccessor = createJdbcTemplateStorageAccessor(exception, true); + + assertThatThrownBy(() -> jdbcTemplateStorageAccessor.updateRecord(lockConfiguration)) + .isEqualTo(exception); + } + + @NotNull + private JdbcTemplateStorageAccessor createJdbcTemplateStorageAccessor( + TransactionSystemException exception, boolean throwUnexpectedException) { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + when(jdbcTemplate.getDataSource()).thenReturn(dataSource); + PlatformTransactionManager txManager = mock(PlatformTransactionManager.class); + + JdbcTemplateStorageAccessor jdbcTemplateStorageAccessor = + new JdbcTemplateStorageAccessor(JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(jdbcTemplate) + .withTransactionManager(txManager) + .withThrowUnexpectedException(throwUnexpectedException) + .build()); + + mockUpdateThrowsException(jdbcTemplate, exception); + return jdbcTemplateStorageAccessor; + } + + private static void mockUpdateThrowsException(JdbcTemplate jdbcTemplate, TransactionSystemException ex) { + when(jdbcTemplate.update(any(PreparedStatementCreator.class))).thenThrow(ex); + } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MariaDbJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MariaDbJdbcTemplateLockProviderIntegrationTest.java index 7a075dff2..6cde33edc 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MariaDbJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MariaDbJdbcTemplateLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerJdbcTemplateLockProviderIntegrationTest.java index ac16d7e97..f333af2f7 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MsSqlServerJdbcTemplateLockProviderIntegrationTest.java @@ -1,23 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; import net.javacrumbs.shedlock.test.support.jdbc.MsSqlServerConfig; -public class MsSqlServerJdbcTemplateLockProviderIntegrationTest extends AbstractJdbcTemplateLockProviderIntegrationTest { +public class MsSqlServerJdbcTemplateLockProviderIntegrationTest + extends AbstractJdbcTemplateLockProviderIntegrationTest { private static final MsSqlServerConfig dbConfig = new MsSqlServerConfig(); public MsSqlServerJdbcTemplateLockProviderIntegrationTest() { diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java index 8d8a2fca9..83a60ada3 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MultiTenancyLockProviderIntegrationTest.java @@ -1,25 +1,31 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.javacrumbs.shedlock.test.support.jdbc.H2Config; import net.javacrumbs.shedlock.test.support.jdbc.HsqlConfig; import net.javacrumbs.shedlock.test.support.jdbc.JdbcTestUtils; @@ -28,19 +34,10 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class MultiTenancyLockProviderIntegrationTest { public static final String LOCK_NAME = "lock_name"; - public static final LockConfiguration LOCK_CONFIGURATION = new LockConfiguration(now(), LOCK_NAME, Duration.ofSeconds(60), Duration.ZERO); + public static final LockConfiguration LOCK_CONFIGURATION = + new LockConfiguration(now(), LOCK_NAME, Duration.ofSeconds(60), Duration.ZERO); private static final JdbcTestUtils h2TestUtils; private static final JdbcTestUtils hsqlTestUtils; @@ -61,7 +58,8 @@ void shouldUseDifferDatabaseForEachTennant() { Optional lock1 = lockProvider.lock(LOCK_CONFIGURATION); assertThat(lock1).isNotEmpty(); assertThat(h2TestUtils.getLockInfo(LOCK_NAME).getLockUntil()).isAfter(ClockProvider.now()); - assertThatThrownBy(() -> hsqlTestUtils.getLockedUntil(LOCK_NAME)).isInstanceOf(EmptyResultDataAccessException.class); + assertThatThrownBy(() -> hsqlTestUtils.getLockedUntil(LOCK_NAME)) + .isInstanceOf(EmptyResultDataAccessException.class); lock1.get().unlock(); @@ -81,16 +79,18 @@ private LockProvider getLockProvider() { return new SampleLockProvider(h2TestUtils.getJdbcTemplate(), hsqlTestUtils.getJdbcTemplate()); } - private static abstract class MultiTenancyLockProvider implements LockProvider { + private abstract static class MultiTenancyLockProvider implements LockProvider { private final ConcurrentHashMap providers = new ConcurrentHashMap<>(); @Override - public @NonNull Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { String tenantName = getTenantName(lockConfiguration); - return providers.computeIfAbsent(tenantName, this::createLockProvider).lock(lockConfiguration); + return providers + .computeIfAbsent(tenantName, this::createLockProvider) + .lock(lockConfiguration); } - protected abstract LockProvider createLockProvider(String tenantName) ; + protected abstract LockProvider createLockProvider(String tenantName); protected abstract String getTenantName(LockConfiguration lockConfiguration); } @@ -107,19 +107,14 @@ private SampleLockProvider(JdbcTemplate jdbcTemplate1, JdbcTemplate jdbcTemplate this.jdbcTemplate2 = jdbcTemplate2; } - @Override protected LockProvider createLockProvider(String tenantName) { if (TENANT_1.equals(tenantName)) { - return new JdbcTemplateLockProvider(builder() - .withJdbcTemplate(jdbcTemplate1) - .build() - ); + return new JdbcTemplateLockProvider( + builder().withJdbcTemplate(jdbcTemplate1).build()); } else { - return new JdbcTemplateLockProvider(builder() - .withJdbcTemplate(jdbcTemplate2) - .build() - ); + return new JdbcTemplateLockProvider( + builder().withJdbcTemplate(jdbcTemplate2).build()); } } @@ -135,5 +130,3 @@ protected String getTenantName(LockConfiguration lockConfiguration) { } } } - - diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlJdbcTemplateLockProviderIntegrationTest.java index 54ae32fb3..edd12c8fc 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/MySqlJdbcTemplateLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; @@ -23,5 +21,4 @@ public class MySqlJdbcTemplateLockProviderIntegrationTest extends AbstractJdbcTe protected MySqlJdbcTemplateLockProviderIntegrationTest() { super(dbConfig); } - } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleJdbcTemplateLockProviderIntegrationTest.java index 0322e9f84..b35735977 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/OracleJdbcTemplateLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresJdbcTemplateLockProviderIntegrationTest.java index d4c59b11c..8a96c0aa9 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresJdbcTemplateLockProviderIntegrationTest.java @@ -1,40 +1,37 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.test.support.jdbc.PostgresConfig; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; +import static java.lang.Thread.sleep; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; +import static org.assertj.core.api.Assertions.assertThat; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.Timestamp; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.TimeZone; - -import static java.lang.Thread.sleep; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; -import static org.assertj.core.api.Assertions.assertThat; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.test.support.jdbc.PostgresConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; public class PostgresJdbcTemplateLockProviderIntegrationTest extends AbstractJdbcTemplateLockProviderIntegrationTest { private static final PostgresConfig dbConfig = new PostgresConfig(); @@ -59,24 +56,23 @@ void shouldHonorTimezone() { TimeZone originalTimezone = TimeZone.getDefault(); - DataSource datasource = dbConfig.getDataSource(); TimeZone.setDefault(timezone); try { JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource); - jdbcTemplate.execute("CREATE TABLE shedlock_tz(name VARCHAR(64), lock_until TIMESTAMP WITH TIME ZONE, locked_at TIMESTAMP WITH TIME ZONE, locked_by VARCHAR(255), PRIMARY KEY (name))"); + jdbcTemplate.execute( + "CREATE TABLE shedlock_tz(name VARCHAR(64), lock_until TIMESTAMP WITH TIME ZONE, locked_at TIMESTAMP WITH TIME ZONE, locked_by VARCHAR(255), PRIMARY KEY (name))"); JdbcTemplateLockProvider provider = new JdbcTemplateLockProvider(builder() - .withJdbcTemplate(new JdbcTemplate(datasource)) - .withTableName("shedlock_tz") - .withTimeZone(timezone) - .withIsolationLevel(Connection.TRANSACTION_SERIALIZABLE) - .build()); + .withJdbcTemplate(new JdbcTemplate(datasource)) + .withTableName("shedlock_tz") + .withTimeZone(timezone) + .withIsolationLevel(Connection.TRANSACTION_SERIALIZABLE) + .build()); - - provider.lock(new LockConfiguration(now, "timezone_test",Duration.ofSeconds(10), Duration.ZERO)); + provider.lock(new LockConfiguration(now, "timezone_test", Duration.ofSeconds(10), Duration.ZERO)); new JdbcTemplate(datasource).query("SELECT * FROM shedlock_tz where name='timezone_test'", rs -> { Timestamp timestamp = rs.getTimestamp("lock_until"); assertThat(timestamp.getTimezoneOffset()).isEqualTo(7 * 60); @@ -110,23 +106,21 @@ void shouldUpdateOnInsertAfterValidityOfPreviousEndedWhenUsingDbTime() throws In private void shouldUpdateOnInsertAfterValidityOfPreviousEnded(boolean usingDbTime) throws InterruptedException { JdbcTemplateStorageAccessor accessor = getAccessor(usingDbTime); - accessor.insertRecord(new LockConfiguration(now(), OTHER_LOCK, Duration.ofSeconds(5), Duration.ZERO)); Timestamp otherLockValidity = getTestUtils().getLockedUntil(OTHER_LOCK); - assertThat( - accessor.insertRecord(new LockConfiguration(now(), MY_LOCK, Duration.ofMillis(10), Duration.ZERO)) - ).isEqualTo(true); + assertThat(accessor.insertRecord( + new LockConfiguration(now(), MY_LOCK, Duration.ofMillis(10), Duration.ZERO))) + .isEqualTo(true); sleep(10); - assertThat( - accessor.insertRecord(new LockConfiguration(now(), MY_LOCK, Duration.ofMillis(10), Duration.ZERO)) - ).isEqualTo(true); + assertThat(accessor.insertRecord( + new LockConfiguration(now(), MY_LOCK, Duration.ofMillis(10), Duration.ZERO))) + .isEqualTo(true); // check that the other lock has not been affected by "my-lock" update assertThat(getTestUtils().getLockedUntil(OTHER_LOCK)).isEqualTo(otherLockValidity); } } - } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSerializableJdbcTemplateLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSerializableJdbcTemplateLockProviderIntegrationTest.java index ab854464c..cc6e09de2 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSerializableJdbcTemplateLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/PostgresSerializableJdbcTemplateLockProviderIntegrationTest.java @@ -1,20 +1,21 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; +import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; + +import java.sql.Connection; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest; import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; @@ -23,11 +24,8 @@ import org.junit.jupiter.api.BeforeAll; import org.springframework.jdbc.core.JdbcTemplate; -import java.sql.Connection; - -import static net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.Configuration.builder; - -public class PostgresSerializableJdbcTemplateLockProviderIntegrationTest extends AbstractJdbcLockProviderIntegrationTest { +public class PostgresSerializableJdbcTemplateLockProviderIntegrationTest + extends AbstractJdbcLockProviderIntegrationTest { private static final PostgresConfig dbConfig = new PostgresConfig(); @BeforeAll @@ -53,9 +51,7 @@ protected boolean useDbTime() { @Override protected StorageBasedLockProvider getLockProvider() { - return new JdbcTemplateLockProvider(builder() - .withJdbcTemplate(new JdbcTemplate(getDatasource())) - .build() - ); + return new JdbcTemplateLockProvider( + builder().withJdbcTemplate(new JdbcTemplate(getDatasource())).build()); } } diff --git a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/ServerTimeTest.java b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/ServerTimeTest.java index 96fd23509..3b094d8b0 100644 --- a/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/ServerTimeTest.java +++ b/providers/jdbc/shedlock-provider-jdbc-template/src/test/java/net/javacrumbs/shedlock/provider/jdbctemplate/ServerTimeTest.java @@ -1,34 +1,31 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbctemplate; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.test.support.jdbc.JdbcTestUtils; -import org.junit.jupiter.api.Test; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Optional; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.jdbc.JdbcTestUtils; +import org.junit.jupiter.api.Test; public interface ServerTimeTest { @@ -41,16 +38,20 @@ public interface ServerTimeTest { @Test default void lockUntilShouldBeInUtc() { Instant time = Instant.now(); - Optional lock = getLockProvider().lock(new LockConfiguration(now(), LOCK_NAME, Duration.ofSeconds(60), Duration.ZERO)); - assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()).isBetween(atUtc(time.plusSeconds(50)), atUtc(time.plusSeconds(70))); + Optional lock = + getLockProvider().lock(new LockConfiguration(now(), LOCK_NAME, Duration.ofSeconds(60), Duration.ZERO)); + assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()) + .isBetween(atUtc(time.plusSeconds(50)), atUtc(time.plusSeconds(70))); time = Instant.now(); lock.get().unlock(); - assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()).isBetween(atUtc(time.minusSeconds(10)), atUtc(time.plusSeconds(10))); + assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()) + .isBetween(atUtc(time.minusSeconds(10)), atUtc(time.plusSeconds(10))); time = Instant.now(); getLockProvider().lock(new LockConfiguration(now(), LOCK_NAME, Duration.ofSeconds(120), Duration.ZERO)); - assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()).isBetween(atUtc(time.plusSeconds(110)), atUtc(time.plusSeconds(130))); + assertThat(getTestUtils().getLockedUntil(LOCK_NAME).toLocalDateTime()) + .isBetween(atUtc(time.plusSeconds(110)), atUtc(time.plusSeconds(130))); } static LocalDateTime atUtc(Instant before) { diff --git a/providers/jdbc/shedlock-provider-jdbc/pom.xml b/providers/jdbc/shedlock-provider-jdbc/pom.xml index fd8043a5f..9dc280e52 100644 --- a/providers/jdbc/shedlock-provider-jdbc/pom.xml +++ b/providers/jdbc/shedlock-provider-jdbc/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-jdbc - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} diff --git a/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcLockProvider.java b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcLockProvider.java index 3f52ed20a..f93755ed3 100644 --- a/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcLockProvider.java +++ b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcLockProvider.java @@ -1,53 +1,44 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc; -import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; - import javax.sql.DataSource; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; /** - * Lock provided by plain JDBC. It uses a table that contains lock_name and locked_until. + * Lock provided by plain JDBC. It uses a table that contains lock_name and + * locked_until. + * *

    - *
  1. - * Attempts to insert a new lock record. Since lock name is a primary key, it fails if the record already exists. As an optimization, - * we keep in-memory track of created lock records. - *
  2. - *
  3. - * If the insert succeeds (1 inserted row) we have the lock. - *
  4. - *
  5. - * If the insert failed due to duplicate key or we have skipped the insertion, we will try to update lock record using - * UPDATE tableName SET lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now - *
  6. - *
  7. - * If the update succeeded (1 updated row), we have the lock. If the update failed (0 updated rows) somebody else holds the lock - *
  8. - *
  9. - * When unlocking, lock_until is set to now. - *
  10. + *
  11. Attempts to insert a new lock record. Since lock name is a primary key, + * it fails if the record already exists. As an optimization, we keep in-memory + * track of created lock records. + *
  12. If the insert succeeds (1 inserted row) we have the lock. + *
  13. If the insert failed due to duplicate key or we have skipped the + * insertion, we will try to update lock record using UPDATE tableName SET + * lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now + *
  14. If the update succeeded (1 updated row), we have the lock. If the update + * failed (0 updated rows) somebody else holds the lock + *
  15. When unlocking, lock_until is set to now. *
*/ public class JdbcLockProvider extends StorageBasedLockProvider { - public JdbcLockProvider(@NonNull DataSource datasource) { + public JdbcLockProvider(DataSource datasource) { this(datasource, "shedlock"); } - public JdbcLockProvider(@NonNull DataSource datasource, @NonNull String tableName) { + public JdbcLockProvider(DataSource datasource, String tableName) { super(new JdbcStorageAccessor(datasource, tableName)); } } diff --git a/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcStorageAccessor.java b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcStorageAccessor.java index aef34ed02..dd4982950 100644 --- a/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcStorageAccessor.java +++ b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/JdbcStorageAccessor.java @@ -1,45 +1,38 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc; -import net.javacrumbs.shedlock.provider.jdbc.internal.AbstractJdbcStorageAccessor; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import static java.util.Objects.requireNonNull; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.function.BiFunction; - -import static java.util.Objects.requireNonNull; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.provider.jdbc.internal.AbstractJdbcStorageAccessor; class JdbcStorageAccessor extends AbstractJdbcStorageAccessor { private final DataSource dataSource; - JdbcStorageAccessor(@NonNull DataSource dataSource, @NonNull String tableName) { + JdbcStorageAccessor(DataSource dataSource, String tableName) { super(tableName); this.dataSource = requireNonNull(dataSource, "dataSource can not be null"); } @Override protected T executeCommand( - String sql, - SqlFunction body, - BiFunction exceptionHandler - ) { + String sql, SqlFunction body, BiFunction exceptionHandler) { try (Connection connection = dataSource.getConnection()) { boolean originalAutocommit = connection.getAutoCommit(); if (!originalAutocommit) { diff --git a/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/package-info.java b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/package-info.java new file mode 100644 index 000000000..7c23cfd0e --- /dev/null +++ b/providers/jdbc/shedlock-provider-jdbc/src/main/java/net/javacrumbs/shedlock/provider/jdbc/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.jdbc; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/HsqlJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/HsqlJdbcLockProviderIntegrationTest.java index 6c18cbcd2..a7695efc1 100644 --- a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/HsqlJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/HsqlJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc; diff --git a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/MySqlJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/MySqlJdbcLockProviderIntegrationTest.java index c8f58a5be..aece61ba0 100644 --- a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/MySqlJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/MySqlJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc; diff --git a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/PostgresJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/PostgresJdbcLockProviderIntegrationTest.java index 6bf9c94d4..e2e08a993 100644 --- a/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/PostgresJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jdbc/src/test/java/net/javacrumbs/shedlock/provider/jdbc/PostgresJdbcLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jdbc; diff --git a/providers/jdbc/shedlock-provider-jooq/pom.xml b/providers/jdbc/shedlock-provider-jooq/pom.xml index 363e5d75b..f5ba05f23 100644 --- a/providers/jdbc/shedlock-provider-jooq/pom.xml +++ b/providers/jdbc/shedlock-provider-jooq/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-jooq - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,7 +21,7 @@ org.jooq jooq - 3.17.5 + 3.19.20 diff --git a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqLockProvider.java b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqLockProvider.java index 3d6811b00..301bf5593 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqLockProvider.java +++ b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqLockProvider.java @@ -1,27 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jooq; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; import org.jooq.DSLContext; - public class JooqLockProvider extends StorageBasedLockProvider { - public JooqLockProvider(@NonNull DSLContext dslContext) { + public JooqLockProvider(DSLContext dslContext) { super(new JooqStorageAccessor(dslContext)); } } diff --git a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqStorageAccessor.java b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqStorageAccessor.java index 4d7649d5c..5c1d5acb9 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqStorageAccessor.java +++ b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/JooqStorageAccessor.java @@ -1,25 +1,23 @@ package net.javacrumbs.shedlock.provider.jooq; +import static net.javacrumbs.shedlock.provider.jooq.Shedlock.SHEDLOCK; +import static org.jooq.impl.DSL.currentLocalDateTime; +import static org.jooq.impl.DSL.inline; +import static org.jooq.impl.DSL.localDateTimeAdd; +import static org.jooq.impl.DSL.when; + +import java.io.Serializable; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Map; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; -import net.javacrumbs.shedlock.support.annotation.NonNull; import org.jooq.DSLContext; import org.jooq.Field; import org.jooq.Record; import org.jooq.TableField; import org.jooq.types.DayToSecond; -import java.io.Serializable; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Map; - -import static net.javacrumbs.shedlock.provider.jooq.Shedlock.SHEDLOCK; -import static org.jooq.impl.DSL.currentLocalDateTime; -import static org.jooq.impl.DSL.inline; -import static org.jooq.impl.DSL.localDateTimeAdd; -import static org.jooq.impl.DSL.when; - class JooqStorageAccessor extends AbstractStorageAccessor { private final DSLContext dslContext; private final Shedlock t = SHEDLOCK; @@ -29,44 +27,61 @@ class JooqStorageAccessor extends AbstractStorageAccessor { } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { - return dslContext.transactionResult(tx -> tx.dsl().insertInto(t) - .set(data(lockConfiguration)) - .onConflictDoNothing() - .execute() > 0); + public boolean insertRecord(LockConfiguration lockConfiguration) { + return dslContext.transactionResult(tx -> tx.dsl() + .insertInto(t) + .set(data(lockConfiguration)) + .onConflictDoNothing() + .execute() + > 0); } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { - return dslContext.transactionResult(tx -> tx.dsl().update(t) - .set(data(lockConfiguration)) - .where(t.NAME.eq(lockConfiguration.getName()).and(t.LOCK_UNTIL.le(now()))) - .execute() > 0); + public boolean updateRecord(LockConfiguration lockConfiguration) { + return dslContext.transactionResult(tx -> tx.dsl() + .update(t) + .set(data(lockConfiguration)) + .where(t.NAME.eq(lockConfiguration.getName()).and(t.LOCK_UNTIL.le(now()))) + .execute() + > 0); } @Override public void unlock(LockConfiguration lockConfiguration) { - Field lockAtLeastFor = t.LOCKED_AT.add(DayToSecond.valueOf(lockConfiguration.getLockAtLeastFor())); - dslContext.transaction(tx -> tx.dsl().update(t).set(t.LOCK_UNTIL, when(lockAtLeastFor.gt(now()), lockAtLeastFor).otherwise(now())) - .where(t.NAME.eq(lockConfiguration.getName()).and(t.LOCKED_BY.eq(getHostname()))) - .execute()); + Field lockAtLeastFor = + t.LOCKED_AT.add(DayToSecond.valueOf(lockConfiguration.getLockAtLeastFor())); + dslContext.transaction(tx -> tx.dsl() + .update(t) + .set( + t.LOCK_UNTIL, + when(lockAtLeastFor.gt(now()), lockAtLeastFor).otherwise(now())) + .where(t.NAME.eq(lockConfiguration.getName()).and(t.LOCKED_BY.eq(getHostname()))) + .execute()); } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { - return dslContext.transactionResult(tx -> tx.dsl().update(t).set(t.LOCK_UNTIL, nowPlus(lockConfiguration.getLockAtMostFor())) - .where(t.NAME.eq(lockConfiguration.getName()).and(t.LOCKED_BY.eq(getHostname())).and(t.LOCK_UNTIL.gt(now()))) - .execute() > 0); + public boolean extend(LockConfiguration lockConfiguration) { + return dslContext.transactionResult(tx -> tx.dsl() + .update(t) + .set(t.LOCK_UNTIL, nowPlus(lockConfiguration.getLockAtMostFor())) + .where(t.NAME.eq(lockConfiguration.getName()) + .and(t.LOCKED_BY.eq(getHostname())) + .and(t.LOCK_UNTIL.gt(now()))) + .execute() + > 0); } - - private Map, Serializable> data(LockConfiguration lockConfiguration) { + private Map, Serializable> data( + LockConfiguration lockConfiguration) { return Map.of( - t.NAME, lockConfiguration.getName(), - t.LOCK_UNTIL, nowPlus(lockConfiguration.getLockAtMostFor()), - t.LOCKED_AT, now(), - t.LOCKED_BY, getHostname() - ); + t.NAME, + lockConfiguration.getName(), + t.LOCK_UNTIL, + nowPlus(lockConfiguration.getLockAtMostFor()), + t.LOCKED_AT, + now(), + t.LOCKED_BY, + getHostname()); } private Field now() { diff --git a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/Shedlock.java b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/Shedlock.java index 95a2a3187..dc330318d 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/Shedlock.java +++ b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/Shedlock.java @@ -1,6 +1,6 @@ package net.javacrumbs.shedlock.provider.jooq; - +import java.time.LocalDateTime; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Name; @@ -14,52 +14,39 @@ import org.jooq.impl.SQLDataType; import org.jooq.impl.TableImpl; -import java.time.LocalDateTime; - - -/** - * This class is generated by jOOQ. - */ -@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +/** This class is generated by jOOQ. */ +@SuppressWarnings({"all", "unchecked", "rawtypes"}) class Shedlock extends TableImpl { private static final long serialVersionUID = 1L; - /** - * The reference instance of public.shedlock - */ + /** The reference instance of public.shedlock */ public static final Shedlock SHEDLOCK = new Shedlock(); - public static final UniqueKey SHEDLOCK_PKEY = Internal.createUniqueKey(Shedlock.SHEDLOCK, DSL.name("shedlock_pkey"), new TableField[] { Shedlock.SHEDLOCK.NAME }, true); + public static final UniqueKey SHEDLOCK_PKEY = Internal.createUniqueKey( + Shedlock.SHEDLOCK, DSL.name("shedlock_pkey"), new TableField[] {Shedlock.SHEDLOCK.NAME}, true); - - /** - * The class holding records for this type - */ + /** The class holding records for this type */ @Override public Class getRecordType() { return Record.class; } - /** - * The column public.shedlock.name. - */ - public final TableField NAME = createField(DSL.name("name"), SQLDataType.VARCHAR(64).nullable(false), this, ""); + /** The column public.shedlock.name. */ + public final TableField NAME = + createField(DSL.name("name"), SQLDataType.VARCHAR(64).nullable(false), this, ""); - /** - * The column public.shedlock.lock_until. - */ - public final TableField LOCK_UNTIL = createField(DSL.name("lock_until"), SQLDataType.LOCALDATETIME(6).nullable(false), this, ""); + /** The column public.shedlock.lock_until. */ + public final TableField LOCK_UNTIL = + createField(DSL.name("lock_until"), SQLDataType.LOCALDATETIME(6).nullable(false), this, ""); - /** - * The column public.shedlock.locked_at. - */ - public final TableField LOCKED_AT = createField(DSL.name("locked_at"), SQLDataType.LOCALDATETIME(6).nullable(false), this, ""); + /** The column public.shedlock.locked_at. */ + public final TableField LOCKED_AT = + createField(DSL.name("locked_at"), SQLDataType.LOCALDATETIME(6).nullable(false), this, ""); - /** - * The column public.shedlock.locked_by. - */ - public final TableField LOCKED_BY = createField(DSL.name("locked_by"), SQLDataType.VARCHAR(255).nullable(false), this, ""); + /** The column public.shedlock.locked_by. */ + public final TableField LOCKED_BY = + createField(DSL.name("locked_by"), SQLDataType.VARCHAR(255).nullable(false), this, ""); private Shedlock(Name alias, Table aliased) { this(alias, aliased, null); @@ -69,23 +56,17 @@ private Shedlock(Name alias, Table aliased, Field[] parameters) { super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); } - /** - * Create an aliased public.shedlock table reference - */ + /** Create an aliased public.shedlock table reference */ public Shedlock(String alias) { this(DSL.name(alias), SHEDLOCK); } - /** - * Create an aliased public.shedlock table reference - */ + /** Create an aliased public.shedlock table reference */ public Shedlock(Name alias) { this(alias, SHEDLOCK); } - /** - * Create a public.shedlock table reference - */ + /** Create a public.shedlock table reference */ public Shedlock() { this(DSL.name("shedlock"), null); } @@ -94,7 +75,6 @@ public Shedlock(Table child, ForeignKey key) { super(child, key, SHEDLOCK); } - @Override public Shedlock as(String alias) { return new Shedlock(DSL.name(alias), this); @@ -109,34 +89,27 @@ public Shedlock as(Name alias) { public Shedlock as(Table alias) { return new Shedlock(alias.getQualifiedName(), this); } + @Override public UniqueKey getPrimaryKey() { return SHEDLOCK_PKEY; } - /** - * Rename this table - */ + /** Rename this table */ @Override public Shedlock rename(String name) { return new Shedlock(DSL.name(name), null); } - /** - * Rename this table - */ + /** Rename this table */ @Override public Shedlock rename(Name name) { return new Shedlock(name, null); } - /** - * Rename this table - */ + /** Rename this table */ @Override public Shedlock rename(Table name) { return new Shedlock(name.getQualifiedName(), null); } } - - diff --git a/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/package-info.java b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/package-info.java new file mode 100644 index 000000000..3a53d6608 --- /dev/null +++ b/providers/jdbc/shedlock-provider-jooq/src/main/java/net/javacrumbs/shedlock/provider/jooq/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.jooq; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/AbstractJooqLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/AbstractJooqLockProviderIntegrationTest.java index 2fb828793..9832f5da1 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/AbstractJooqLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/AbstractJooqLockProviderIntegrationTest.java @@ -1,20 +1,20 @@ /** * Copyright 2009 the original author or authors. - *

- * 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 + * + *

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 net.javacrumbs.shedlock.provider.jooq; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest; import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; @@ -24,8 +24,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - @TestInstance(PER_CLASS) public abstract class AbstractJooqLockProviderIntegrationTest extends AbstractJdbcLockProviderIntegrationTest { private final DbConfig dbConfig; @@ -52,7 +50,6 @@ protected boolean useDbTime() { return true; } - @BeforeAll public void startDb() { dbConfig.startDb(); @@ -63,5 +60,3 @@ public void shutdownDb() { dbConfig.shutdownDb(); } } - - diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/HsqlJooqLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/HsqlJooqLockProviderIntegrationTest.java index 274b22bf7..ee1337f3a 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/HsqlJooqLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/HsqlJooqLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jooq; @@ -26,6 +24,11 @@ public class HsqlJooqLockProviderIntegrationTest extends AbstractJooqLockProvide private static final DbConfig dbConfig = new HsqlConfig(); public HsqlJooqLockProviderIntegrationTest() { - super(dbConfig, DSL.using(dbConfig.getDataSource(), SQLDialect.HSQLDB, new Settings().withRenderNameCase(RenderNameCase.UPPER))); + super( + dbConfig, + DSL.using( + dbConfig.getDataSource(), + SQLDialect.HSQLDB, + new Settings().withRenderNameCase(RenderNameCase.UPPER))); } } diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MariaDbJooqLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MariaDbJooqLockProviderIntegrationTest.java index db7f79930..13f9cc875 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MariaDbJooqLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MariaDbJooqLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jooq; diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MySqlJooqLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MySqlJooqLockProviderIntegrationTest.java index 8f8e08f50..e9ce09a25 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MySqlJooqLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/MySqlJooqLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jooq; diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/PostgresJooqLockProviderIntegrationTest.java b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/PostgresJooqLockProviderIntegrationTest.java index 7770781c1..f010e6d6b 100644 --- a/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/PostgresJooqLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-provider-jooq/src/test/java/net/javacrumbs/shedlock/provider/jooq/PostgresJooqLockProviderIntegrationTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.jooq; diff --git a/providers/jdbc/shedlock-provider-jooq/src/test/resources/logback-test.xml b/providers/jdbc/shedlock-provider-jooq/src/test/resources/logback-test.xml new file mode 100644 index 000000000..c64bdcd7e --- /dev/null +++ b/providers/jdbc/shedlock-provider-jooq/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/providers/jdbc/shedlock-test-support-jdbc/pom.xml b/providers/jdbc/shedlock-test-support-jdbc/pom.xml index 6048d1ade..b5f8bde3f 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/pom.xml +++ b/providers/jdbc/shedlock-test-support-jdbc/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-test-support-jdbc - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,49 +21,49 @@ com.zaxxer HikariCP - 5.0.1 + 7.0.2 org.hsqldb hsqldb - 2.7.1 + 2.7.4 com.h2database h2 - 2.1.214 + 2.3.232 - mysql - mysql-connector-java - 8.0.30 + com.mysql + mysql-connector-j + 9.4.0 org.mariadb.jdbc mariadb-java-client - 3.0.8 + 3.5.5 com.microsoft.sqlserver mssql-jdbc - 11.2.1.jre8 + 13.2.0.jre11 com.oracle.database.jdbc - ojdbc8 - 21.7.0.0 + ojdbc11 + 23.9.0.25.07 com.ibm.db2 jcc - 11.5.8.0 + 12.1.2.0 @@ -105,7 +105,7 @@ org.postgresql postgresql - 42.5.0 + 42.7.7 @@ -124,7 +124,6 @@ org.junit.jupiter junit-jupiter - ${junit.ver} compile diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractContainerBasedDbConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractContainerBasedDbConfig.java index c60f5291c..1e878621b 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractContainerBasedDbConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractContainerBasedDbConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -24,8 +22,7 @@ abstract class AbstractContainerBasedDbConfig protected final T container; public AbstractContainerBasedDbConfig(T container) { - this.container = container - .withLogConsumer(outputFrame -> logger.debug(outputFrame.getUtf8String())); + this.container = container.withLogConsumer(outputFrame -> logger.debug(outputFrame.getUtf8String())); } @Override diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractDbConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractDbConfig.java index 2fd549f0e..5ee020674 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractDbConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractDbConfig.java @@ -1,28 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; import com.zaxxer.hikari.HikariDataSource; - import javax.sql.DataSource; +import net.javacrumbs.shedlock.support.annotation.Nullable; abstract class AbstractDbConfig implements DbConfig { + @Nullable private HikariDataSource dataSource; protected static final String TEST_SCHEMA_NAME = "shedlock_test"; + + @Nullable private Integer transactionIsolation; @Override @@ -46,9 +47,7 @@ public final void startDb() { dataSource = newDataSource; } - protected void doStartDb() { - - } + protected void doStartDb() {} @Override public final void shutdownDb() { @@ -56,9 +55,7 @@ public final void shutdownDb() { doShutdownDb(); } - protected void doShutdownDb() { - - } + protected void doShutdownDb() {} public void setTransactionIsolation(int transactionIsolation) { this.transactionIsolation = transactionIsolation; diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractJdbcLockProviderIntegrationTest.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractJdbcLockProviderIntegrationTest.java index f6b3ce579..d2dcfcc7f 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractJdbcLockProviderIntegrationTest.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/AbstractJdbcLockProviderIntegrationTest.java @@ -1,20 +1,26 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import javax.sql.DataSource; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.SimpleLock; @@ -24,15 +30,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -import static org.assertj.core.api.Assertions.assertThat; - public abstract class AbstractJdbcLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { protected JdbcTestUtils testUtils; @@ -53,22 +50,33 @@ public void cleanup() { @Override protected void assertUnlocked(String lockName) { JdbcTestUtils.LockInfo lockInfo = getLockInfo(lockName); - Instant now = useDbTime() ? lockInfo.getDbTime(): ClockProvider.now(); - assertThat(lockInfo.getLockUntil()).describedAs("is unlocked").isBeforeOrEqualTo(now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1)); + Instant now = useDbTime() ? lockInfo.getDbTime() : ClockProvider.now(); + assertThat(lockInfo.getLockUntil()) + .describedAs("is unlocked") + .isBeforeOrEqualTo(now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1)); } @Override protected void assertLocked(String lockName) { JdbcTestUtils.LockInfo lockInfo = getLockInfo(lockName); - Instant now = useDbTime() ? lockInfo.getDbTime(): ClockProvider.now(); + Instant now = useDbTime() ? lockInfo.getDbTime() : ClockProvider.now(); - assertThat(lockInfo.getLockUntil()).describedAs(getClass().getName() + " is locked").isAfter(now); + assertThat(lockInfo.getLockUntil()) + .describedAs(getClass().getName() + " is locked") + .isAfter(now); } @Test public void shouldCreateLockIfRecordAlreadyExists() { Timestamp previousLockTime = Timestamp.from(Instant.now().minus(1, ChronoUnit.DAYS)); - testUtils.getJdbcTemplate().update("INSERT INTO shedlock(name, lock_until, locked_at, locked_by) VALUES(?, ?, ?, ?)", LOCK_NAME1, previousLockTime, previousLockTime, "me"); + testUtils + .getJdbcTemplate() + .update( + "INSERT INTO shedlock(name, lock_until, locked_at, locked_by) VALUES(?, ?, ?, ?)", + LOCK_NAME1, + previousLockTime, + previousLockTime, + "me"); assertUnlocked(LOCK_NAME1); shouldCreateLock(); } @@ -81,7 +89,8 @@ public void fuzzTestShouldWorkWithTransaction() throws ExecutionException, Inter @Test @Disabled public void shouldNotFailIfKeyNameTooLong() { - LockConfiguration configuration = lockConfig("lock name that is too long Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + LockConfiguration configuration = lockConfig( + "lock name that is too long Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); Optional lock = getLockProvider().lock(configuration); assertThat(lock).isEmpty(); } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/Db2ServerConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/Db2ServerConfig.java index e093a0220..1c7565bcb 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/Db2ServerConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/Db2ServerConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -18,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.Db2Container; +import org.testcontainers.utility.DockerImageName; public final class Db2ServerConfig extends AbstractDbConfig { @@ -26,9 +25,9 @@ public final class Db2ServerConfig extends AbstractDbConfig { @Override protected void doStartDb() { - db2 = new Db2Container() - .acceptLicense() - .withLogConsumer(outputFrame -> logger.debug(outputFrame.getUtf8String())); + db2 = new Db2Container(DockerImageName.parse("icr.io/db2_community/db2")) + .acceptLicense() + .withLogConsumer(outputFrame -> logger.debug(outputFrame.getUtf8String())); db2.start(); } @@ -40,7 +39,6 @@ protected void doShutdownDb() { @Override public String getJdbcUrl() { return db2.getJdbcUrl(); - } @Override diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/DbConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/DbConfig.java index 964722220..2926b610b 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/DbConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/DbConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/H2Config.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/H2Config.java index cd0b73b38..5f744ed17 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/H2Config.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/H2Config.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/HsqlConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/HsqlConfig.java index 86e7db460..565cb2037 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/HsqlConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/HsqlConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/JdbcTestUtils.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/JdbcTestUtils.java index 834c05ede..8b9998b13 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/JdbcTestUtils.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/JdbcTestUtils.java @@ -1,25 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; import java.sql.Timestamp; import java.time.Instant; +import javax.sql.DataSource; +import org.springframework.jdbc.core.JdbcTemplate; public final class JdbcTestUtils { @@ -38,11 +35,16 @@ public Timestamp getLockedUntil(String lockName) { } public LockInfo getLockInfo(String lockName) { - return jdbcTemplate.query("SELECT name, lock_until, " + dbConfig.nowExpression() + " as db_time FROM shedlock WHERE name = ?", (rs, rowNum) -> new LockInfo( - rs.getString("name"), - rs.getTimestamp("lock_until").toInstant(), - rs.getTimestamp("db_time").toInstant() - ), lockName).get(0); + return jdbcTemplate + .query( + "SELECT name, lock_until, " + dbConfig.nowExpression() + + " as db_time FROM shedlock WHERE name = ?", + (rs, rowNum) -> new LockInfo( + rs.getString("name"), + rs.getTimestamp("lock_until").toInstant(), + rs.getTimestamp("db_time").toInstant()), + lockName) + .get(0); } public void clean() { @@ -82,11 +84,7 @@ public Instant getDbTime() { @Override public String toString() { - return "LockInfo{" + - "name='" + name + '\'' + - ", lockUntil=" + lockUntil + - ", dbTime=" + dbTime + - '}'; + return "LockInfo{" + "name='" + name + '\'' + ", lockUntil=" + lockUntil + ", dbTime=" + dbTime + '}'; } } } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MariaDbConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MariaDbConfig.java index 3edd63d40..cc14a35d4 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MariaDbConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MariaDbConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -20,10 +18,9 @@ public class MariaDbConfig extends AbstractContainerBasedDbConfig { public MariaDbConfig() { super(new MyMariaDbContainer() - .withDatabaseName(TEST_SCHEMA_NAME) - .withUsername("SA") - .withPassword("pass") - ); + .withDatabaseName(TEST_SCHEMA_NAME) + .withUsername("SA") + .withPassword("pass")); } @Override @@ -37,5 +34,8 @@ public String nowExpression() { } static class MyMariaDbContainer extends MariaDBContainer { + MyMariaDbContainer() { + super("mariadb:lts"); + } } } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MsSqlServerConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MsSqlServerConfig.java index 9f95423a5..382cfbd30 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MsSqlServerConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MsSqlServerConfig.java @@ -1,26 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; +import static org.testcontainers.containers.MSSQLServerContainer.MS_SQL_SERVER_PORT; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.MSSQLServerContainer; -import static org.testcontainers.containers.MSSQLServerContainer.MS_SQL_SERVER_PORT; - public final class MsSqlServerConfig extends AbstractContainerBasedDbConfig { private static final Logger LOGGER = LoggerFactory.getLogger(MsSqlServerConfig.class); @@ -45,7 +43,7 @@ public String nowExpression() { static class MyMSSQLServerContainer extends MSSQLServerContainer { MyMSSQLServerContainer() { - super("mcr.microsoft.com/mssql/server:2019-latest"); + super("mcr.microsoft.com/mssql/server:2022-latest"); withLogConsumer(it -> LOGGER.info(it.getUtf8String())); acceptLicense(); } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MySqlConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MySqlConfig.java index cd434ea97..d0156a316 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MySqlConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/MySqlConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -20,10 +18,10 @@ public class MySqlConfig extends AbstractContainerBasedDbConfig { public MySqlConfig() { super(new MyMySQLContainer() - .withDatabaseName(TEST_SCHEMA_NAME) - .withUsername("SA") - .withPassword("pass") - ); + .withDatabaseName(TEST_SCHEMA_NAME) + .withUsername("SA") + .withPassword("pass") + .withCommand("--default-authentication-plugin=mysql_native_password")); } @Override @@ -38,7 +36,7 @@ public String nowExpression() { static class MyMySQLContainer extends MySQLContainer { MyMySQLContainer() { - super("mysql:8"); + super("mysql:8.2"); } } } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/OracleServerConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/OracleServerConfig.java index 55eaaa1d0..4e5422fea 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/OracleServerConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/OracleServerConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -19,7 +17,7 @@ public final class OracleServerConfig extends AbstractContainerBasedDbConfig { public OracleServerConfig() { - super(new OracleContainer("gvenzl/oracle-xe:18-slim")); + super(new OracleContainer("gvenzl/oracle-xe:21-slim")); } @Override @@ -36,5 +34,4 @@ public String nowExpression() { public String getR2dbcUrl() { return "r2dbc:oracle://localhost:" + container.getOraclePort() + "/" + container.getDatabaseName(); } - } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/PostgresConfig.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/PostgresConfig.java index 941479311..64f859315 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/PostgresConfig.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/PostgresConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; @@ -20,9 +18,9 @@ public class PostgresConfig extends AbstractContainerBasedDbConfig { public PostgresConfig() { super(new MyPostgreSQLContainer() - .withDatabaseName(TEST_SCHEMA_NAME) - .withUsername("SA") - .withPassword("pass")); + .withDatabaseName(TEST_SCHEMA_NAME) + .withUsername("SA") + .withPassword("pass")); } @Override @@ -32,7 +30,7 @@ public String nowExpression() { static class MyPostgreSQLContainer extends PostgreSQLContainer { MyPostgreSQLContainer() { - super("postgres:15"); + super("postgres:17-alpine"); } } } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/TransactionalFuzzTester.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/TransactionalFuzzTester.java index 2915ab5b7..e784d1e43 100644 --- a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/TransactionalFuzzTester.java +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/TransactionalFuzzTester.java @@ -1,35 +1,34 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support.jdbc; +import java.util.concurrent.ExecutionException; +import javax.sql.DataSource; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.FuzzTester; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.support.TransactionTemplate; -import javax.sql.DataSource; -import java.util.concurrent.ExecutionException; - public class TransactionalFuzzTester { - public static void fuzzTestShouldWorkWithTransaction(LockProvider lockProvider, DataSource dataSource) throws ExecutionException, InterruptedException { + public static void fuzzTestShouldWorkWithTransaction(LockProvider lockProvider, DataSource dataSource) + throws ExecutionException, InterruptedException { new FuzzTester(lockProvider) { @Override protected Void task(int iterations, Job job) { - TransactionTemplate transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource)); + TransactionTemplate transactionTemplate = + new TransactionTemplate(new DataSourceTransactionManager(dataSource)); return transactionTemplate.execute(status -> super.task(iterations, job)); } diff --git a/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/package-info.java b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/package-info.java new file mode 100644 index 000000000..06ee0e2d5 --- /dev/null +++ b/providers/jdbc/shedlock-test-support-jdbc/src/main/java/net/javacrumbs/shedlock/test/support/jdbc/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.support.jdbc; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/memcached/shedlock-provider-memcached-spy/pom.xml b/providers/memcached/shedlock-provider-memcached-spy/pom.xml index 85d104563..4c8be27f6 100644 --- a/providers/memcached/shedlock-provider-memcached-spy/pom.xml +++ b/providers/memcached/shedlock-provider-memcached-spy/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-memcached-spy - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} 2.12.3 diff --git a/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProvider.java b/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProvider.java index 8a9d5e974..6147ae2d8 100644 --- a/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProvider.java +++ b/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProvider.java @@ -1,23 +1,21 @@ package net.javacrumbs.shedlock.provider.memcached.spy; +import static net.javacrumbs.shedlock.support.Utils.getHostname; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.spy.memcached.MemcachedClient; import net.spy.memcached.ops.OperationStatus; import net.spy.memcached.util.StringUtils; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; - /** * Lock Provider for Memcached * @@ -25,14 +23,10 @@ */ public class MemcachedLockProvider implements LockProvider { - /** - * KEY PREFIX - */ + /** KEY PREFIX */ private static final String KEY_PREFIX = "shedlock"; - /** - * ENV DEFAULT - */ + /** ENV DEFAULT */ private static final String ENV_DEFAULT = "default"; private final MemcachedClient client; @@ -41,26 +35,30 @@ public class MemcachedLockProvider implements LockProvider { /** * Create MemcachedLockProvider - * @param client Spy.memcached.MemcachedClient + * + * @param client + * Spy.memcached.MemcachedClient */ - public MemcachedLockProvider(@NonNull MemcachedClient client){ + public MemcachedLockProvider(MemcachedClient client) { this(client, ENV_DEFAULT); } /** * Create MemcachedLockProvider - * @param client Spy.memcached.MemcachedClient - * @param env is part of the key and thus makes sure there is not key conflict between multiple ShedLock instances - * running on the same memcached + * + * @param client + * Spy.memcached.MemcachedClient + * @param env + * is part of the key and thus makes sure there is not key conflict + * between multiple ShedLock instances running on the same memcached */ - public MemcachedLockProvider(@NonNull MemcachedClient client, @NonNull String env){ + public MemcachedLockProvider(MemcachedClient client, String env) { this.client = client; this.env = env; } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration){ + public Optional lock(LockConfiguration lockConfiguration) { long expireTime = getSecondUntil(lockConfiguration.getLockAtMostUntil()); String key = buildKey(lockConfiguration.getName(), this.env); OperationStatus status = client.add(key, (int) expireTime, buildValue()).getStatus(); @@ -70,10 +68,9 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration){ return Optional.empty(); } - private static long getSecondUntil(Instant instant) { long millis = Duration.between(ClockProvider.now(), instant).toMillis(); - return millis / 1000; + return millis / 1000; } static String buildKey(String lockName, String env) { @@ -86,16 +83,13 @@ private static String buildValue() { return String.format("ADDED:%s@%s", toIsoString(ClockProvider.now()), getHostname()); } - private static final class MemcachedLock extends AbstractSimpleLock { private final String key; private final MemcachedClient client; - private MemcachedLock(@NonNull String key, - @NonNull MemcachedClient client, - @NonNull LockConfiguration lockConfiguration) { + private MemcachedLock(String key, MemcachedClient client, LockConfiguration lockConfiguration) { super(lockConfiguration); this.key = key; this.client = client; @@ -110,12 +104,12 @@ protected void doUnlock() { throw new LockException("Can not remove node. " + status.getMessage()); } } else { - OperationStatus status = client.replace(key, (int) keepLockFor, buildValue()).getStatus(); + OperationStatus status = + client.replace(key, (int) keepLockFor, buildValue()).getStatus(); if (!status.isSuccess()) { throw new LockException("Can not replace node. " + status.getMessage()); } } } } - } diff --git a/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/package-info.java b/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/package-info.java new file mode 100644 index 000000000..d0e2f1ba4 --- /dev/null +++ b/providers/memcached/shedlock-provider-memcached-spy/src/main/java/net/javacrumbs/shedlock/provider/memcached/spy/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.memcached.spy; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedContainer.java b/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedContainer.java index e914c91bc..bddb45d8c 100644 --- a/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedContainer.java +++ b/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedContainer.java @@ -13,8 +13,6 @@ public class MemcachedContainer extends GenericContainer { public MemcachedContainer() { super(MEMCACHED_IMAGE.asCanonicalNameString()); - this.withExposedPorts(11211) - .withLogConsumer(frame -> LOGGER.info(frame.getUtf8String())); + this.withExposedPorts(11211).withLogConsumer(frame -> LOGGER.info(frame.getUtf8String())); } - } diff --git a/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProviderIntegrationTest.java b/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProviderIntegrationTest.java index ee238dc2c..969d30cd4 100644 --- a/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProviderIntegrationTest.java +++ b/providers/memcached/shedlock-provider-memcached-spy/src/test/java/net/javacrumbs/shedlock/provider/memcached/spy/MemcachedLockProviderIntegrationTest.java @@ -1,5 +1,11 @@ package net.javacrumbs.shedlock.provider.memcached.spy; +import static java.lang.Thread.sleep; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; @@ -11,14 +17,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.IOException; -import java.time.Duration; -import java.util.Optional; - -import static java.lang.Thread.sleep; -import static org.assertj.core.api.Assertions.assertThat; - - @Testcontainers public class MemcachedLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { @@ -33,14 +31,12 @@ public class MemcachedLockProviderIntegrationTest extends AbstractLockProviderIn @BeforeEach public void createLockProvider() throws IOException { - memcachedClient = new MemcachedClient( - AddrUtil.getAddresses(container.getContainerIpAddress() + ":" + container.getFirstMappedPort()) - ); + memcachedClient = + new MemcachedClient(AddrUtil.getAddresses(container.getHost() + ":" + container.getFirstMappedPort())); lockProvider = new MemcachedLockProvider(memcachedClient, ENV); } - @Override protected void assertUnlocked(String lockName) { assertThat(getLock(lockName)).isNull(); @@ -51,16 +47,13 @@ protected void assertLocked(String lockName) { assertThat(getLock(lockName)).isNotNull(); } - @Override @Test public void shouldTimeout() throws InterruptedException { this.doTestTimeout(Duration.ofSeconds(1)); } - /** - * memcached smallest unit is second. - */ + /** memcached smallest unit is second. */ @Override protected void doTestTimeout(Duration lockAtMostFor) throws InterruptedException { LockConfiguration configWithShortTimeout = lockConfig(LOCK_NAME1, lockAtMostFor, Duration.ZERO); @@ -70,12 +63,12 @@ protected void doTestTimeout(Duration lockAtMostFor) throws InterruptedException sleep(lockAtMostFor.toMillis() * 2); assertUnlocked(LOCK_NAME1); - Optional lock2 = getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofSeconds(1), Duration.ZERO)); + Optional lock2 = + getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofSeconds(1), Duration.ZERO)); assertThat(lock2).isNotEmpty(); lock2.get().unlock(); } - @Override protected LockProvider getLockProvider() { return lockProvider; @@ -84,5 +77,4 @@ protected LockProvider getLockProvider() { private String getLock(String lockName) { return (String) memcachedClient.get(MemcachedLockProvider.buildKey(lockName, ENV)); } - } diff --git a/providers/mongo/shedlock-provider-mongo-reactivestreams/pom.xml b/providers/mongo/shedlock-provider-mongo-reactivestreams/pom.xml index 9ffcea4fb..f02c3e9f1 100644 --- a/providers/mongo/shedlock-provider-mongo-reactivestreams/pom.xml +++ b/providers/mongo/shedlock-provider-mongo-reactivestreams/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-mongo-reactivestreams - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,13 +21,20 @@ org.mongodb mongodb-driver-reactivestreams - 4.7.2 + 5.5.1 - de.flapdoodle.embed - de.flapdoodle.embed.mongo - 3.4.11 + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + mongodb + ${test-containers.ver} test diff --git a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProvider.java b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProvider.java index 5d2100a40..84b6393a0 100644 --- a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProvider.java +++ b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProvider.java @@ -1,24 +1,31 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.mongo.reactivestreams; +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.gt; +import static com.mongodb.client.model.Filters.lte; +import static com.mongodb.client.model.Updates.combine; +import static com.mongodb.client.model.Updates.set; + import com.mongodb.MongoServerException; import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; @@ -26,24 +33,18 @@ import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; import net.javacrumbs.shedlock.support.Utils; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.bson.Document; import org.bson.conversions.Bson; import org.reactivestreams.Publisher; -import java.time.Instant; -import java.util.Optional; - -import static com.mongodb.client.model.Filters.and; -import static com.mongodb.client.model.Filters.eq; -import static com.mongodb.client.model.Filters.gt; -import static com.mongodb.client.model.Filters.lte; -import static com.mongodb.client.model.Updates.combine; -import static com.mongodb.client.model.Updates.set; - /** - * Distributed lock using Reactive MongoDB. Requires mongodb-driver-reactivestreams + * Distributed lock using Reactive MongoDB. Requires + * mongodb-driver-reactivestreams + * *

* It uses a collection that contains documents like this: + * *

  * {
  *    "_id" : "lock name",
@@ -53,22 +54,18 @@
  * }
  * 
* - * lockedAt and lockedBy are just for troubleshooting and are not read by the code + * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code * *
    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter _id == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  12. When unlocking, lock_until is set to now. *
*/ public class ReactiveStreamsMongoLockProvider implements ExtensibleLockProvider { @@ -81,9 +78,7 @@ public class ReactiveStreamsMongoLockProvider implements ExtensibleLockProvider private final String hostname; private final MongoCollection collection; - /** - * Uses Mongo to coordinate locks - */ + /** Uses Mongo to coordinate locks */ public ReactiveStreamsMongoLockProvider(MongoDatabase mongoDatabase) { this(mongoDatabase.getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME)); } @@ -91,37 +86,36 @@ public ReactiveStreamsMongoLockProvider(MongoDatabase mongoDatabase) { /** * Uses Mongo to coordinate locks * - * @param collection Mongo collection to be used + * @param collection + * Mongo collection to be used */ public ReactiveStreamsMongoLockProvider(MongoCollection collection) { this.collection = collection; this.hostname = Utils.getHostname(); } - @Override public Optional lock(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = combine( - set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()), - set(LOCKED_AT, now), - set(LOCKED_BY, hostname) - ); + set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()), set(LOCKED_AT, now), set(LOCKED_BY, hostname)); try { // There are three possible situations: // 1. The lock document does not exist yet - it is inserted - we have the lock - // 2. The lock document exists and lockUtil <= now - it is updated - we have the lock - // 3. The lock document exists and lockUtil > now - Duplicate key exception is thrown - execute(getCollection().findOneAndUpdate( - and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), - update, - new FindOneAndUpdateOptions().upsert(true) - )); + // 2. The lock document exists and lockUtil <= now - it is updated - we have the + // lock + // 3. The lock document exists and lockUtil > now - Duplicate key exception is + // thrown + execute(getCollection() + .findOneAndUpdate( + and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), + update, + new FindOneAndUpdateOptions().upsert(true))); return Optional.of(new ReactiveMongoLock(lockConfiguration, this)); } catch (MongoServerException e) { if (e.getCode() == 11000) { // duplicate key - //Upsert attempts to insert when there were no filter matches. - //This means there was a lock with matching ID with lockUntil > now. + // Upsert attempts to insert when there were no filter matches. + // This means there was a lock with matching ID with lockUntil > now. return Optional.empty(); } else { throw e; @@ -133,14 +127,10 @@ private Optional extend(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()); - Document updatedDocument = execute(getCollection().findOneAndUpdate( - and( - eq(ID, lockConfiguration.getName()), - gt(LOCK_UNTIL, now), - eq(LOCKED_BY, hostname) - ), - update - )); + Document updatedDocument = execute(getCollection() + .findOneAndUpdate( + and(eq(ID, lockConfiguration.getName()), gt(LOCK_UNTIL, now), eq(LOCKED_BY, hostname)), + update)); if (updatedDocument != null) { return Optional.of(new ReactiveMongoLock(lockConfiguration, this)); @@ -151,12 +141,13 @@ private Optional extend(LockConfiguration lockConfiguration) { private void unlock(LockConfiguration lockConfiguration) { // Set lockUtil to now or lockAtLeastUntil whichever is later - execute(getCollection().findOneAndUpdate( - eq(ID, lockConfiguration.getName()), - combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime())) - )); + execute(getCollection() + .findOneAndUpdate( + eq(ID, lockConfiguration.getName()), + combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime())))); } + @Nullable static T execute(Publisher command) { SingleLockableSubscriber subscriber = new SingleLockableSubscriber<>(); command.subscribe(subscriber); @@ -184,7 +175,8 @@ private Instant now() { private static final class ReactiveMongoLock extends AbstractSimpleLock { private final ReactiveStreamsMongoLockProvider mongoLockProvider; - private ReactiveMongoLock(LockConfiguration lockConfiguration, ReactiveStreamsMongoLockProvider mongoLockProvider) { + private ReactiveMongoLock( + LockConfiguration lockConfiguration, ReactiveStreamsMongoLockProvider mongoLockProvider) { super(lockConfiguration); this.mongoLockProvider = mongoLockProvider; } diff --git a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/SingleLockableSubscriber.java b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/SingleLockableSubscriber.java index 082054212..082c07f04 100644 --- a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/SingleLockableSubscriber.java +++ b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/main/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/SingleLockableSubscriber.java @@ -1,29 +1,28 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.mongo.reactivestreams; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import net.javacrumbs.shedlock.support.annotation.Nullable; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - /** - * Subscriber that expects a single result and allows locking until complete or error + * Subscriber that expects a single result and allows locking until complete or + * error * * @param */ @@ -58,17 +57,22 @@ public void onComplete() { latch.countDown(); } + @Nullable T getValue() { return value; } + @Nullable Throwable getError() { return error; } void await() { try { - latch.await(10, TimeUnit.SECONDS); + int timeout = 20; + if (!latch.await(timeout, TimeUnit.SECONDS)) { + this.error = new TimeoutException("Did not get response in " + timeout + " seconds."); + } } catch (InterruptedException e) { this.error = e; } diff --git a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/test/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProviderIntegrationTest.java b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/test/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProviderIntegrationTest.java index 662daec63..a9d524917 100644 --- a/providers/mongo/shedlock-provider-mongo-reactivestreams/src/test/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProviderIntegrationTest.java +++ b/providers/mongo/shedlock-provider-mongo-reactivestreams/src/test/java/net/javacrumbs/shedlock/provider/mongo/reactivestreams/ReactiveStreamsMongoLockProviderIntegrationTest.java @@ -1,29 +1,33 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.mongo.reactivestreams; +import static com.mongodb.client.model.Filters.eq; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.DEFAULT_SHEDLOCK_COLLECTION_NAME; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.ID; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCK_UNTIL; +import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.execute; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + import com.mongodb.client.result.DeleteResult; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoCollection; -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; +import java.util.Date; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest; @@ -32,48 +36,29 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.IOException; -import java.util.Date; - -import static com.mongodb.client.model.Filters.eq; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.DEFAULT_SHEDLOCK_COLLECTION_NAME; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.ID; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.LOCK_UNTIL; -import static net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider.execute; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; - +@Testcontainers public class ReactiveStreamsMongoLockProviderIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest { - private static final MongodStarter starter = MongodStarter.getDefaultInstance(); - private static final String DB_NAME = "db"; - private static MongodExecutable mongodExe; - private static MongodProcess mongod; + @Container + public static final MongoDBContainer container = new MongoDBContainer("mongo:6"); private static MongoClient mongo; @BeforeAll - public static void startMongo() throws IOException { - mongodExe = starter.prepare(MongodConfig.builder() - .version(Version.Main.V3_6) - .build()); - mongod = mongodExe.start(); - - mongo = MongoClients.create("mongodb://localhost:" + mongod.getConfig().net().getPort()); + public static void startMongo() { + mongo = MongoClients.create("mongodb://" + container.getHost() + ":" + container.getFirstMappedPort()); } @AfterAll public static void stopMongo() { mongo.close(); - mongod.stop(); - mongodExe.stop(); } - @BeforeEach public void cleanDb() { execute(mongo.getDatabase(DB_NAME).drop()); @@ -87,8 +72,8 @@ protected ExtensibleLockProvider getLockProvider() { @Override protected void assertUnlocked(String lockName) { Document lockDocument = getLockDocument(lockName); - assertThat((Date) lockDocument.get(LOCK_UNTIL)).isBeforeOrEqualsTo(now()); - assertThat((Date) lockDocument.get(LOCKED_AT)).isBeforeOrEqualsTo(now()); + assertThat((Date) lockDocument.get(LOCK_UNTIL)).isBeforeOrEqualTo(now()); + assertThat((Date) lockDocument.get(LOCKED_AT)).isBeforeOrEqualTo(now()); assertThat((String) lockDocument.get(LOCKED_BY)).isNotEmpty(); } @@ -100,7 +85,7 @@ private Date now() { protected void assertLocked(String lockName) { Document lockDocument = getLockDocument(lockName); assertThat((Date) lockDocument.get(LOCK_UNTIL)).isAfter(now()); - assertThat((Date) lockDocument.get(LOCKED_AT)).isBeforeOrEqualsTo(now()); + assertThat((Date) lockDocument.get(LOCKED_AT)).isBeforeOrEqualTo(now()); assertThat((String) lockDocument.get(LOCKED_BY)).isNotEmpty(); } diff --git a/providers/mongo/shedlock-provider-mongo/pom.xml b/providers/mongo/shedlock-provider-mongo/pom.xml index 38a5b51cc..7617358aa 100644 --- a/providers/mongo/shedlock-provider-mongo/pom.xml +++ b/providers/mongo/shedlock-provider-mongo/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-mongo - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -21,14 +21,7 @@ org.mongodb mongodb-driver-sync - 4.7.1 - - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - 3.4.11 - test + 5.5.1 @@ -44,6 +37,27 @@ ${logback.ver} test + + + org.testcontainers + testcontainers + ${test-containers.ver} + test + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + mongodb + ${test-containers.ver} + test + diff --git a/providers/mongo/shedlock-provider-mongo/src/main/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProvider.java b/providers/mongo/shedlock-provider-mongo/src/main/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProvider.java index e41f0cb65..d4861b6b1 100644 --- a/providers/mongo/shedlock-provider-mongo/src/main/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProvider.java +++ b/providers/mongo/shedlock-provider-mongo/src/main/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProvider.java @@ -1,24 +1,31 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.mongo; +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.gt; +import static com.mongodb.client.model.Filters.lte; +import static com.mongodb.client.model.Updates.combine; +import static com.mongodb.client.model.Updates.set; + import com.mongodb.MongoServerException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.FindOneAndUpdateOptions; +import java.time.Instant; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; @@ -28,20 +35,13 @@ import org.bson.Document; import org.bson.conversions.Bson; -import java.time.Instant; -import java.util.Optional; - -import static com.mongodb.client.model.Filters.and; -import static com.mongodb.client.model.Filters.eq; -import static com.mongodb.client.model.Filters.gt; -import static com.mongodb.client.model.Filters.lte; -import static com.mongodb.client.model.Updates.combine; -import static com.mongodb.client.model.Updates.set; - /** - * Distributed lock using MongoDB >= 2.6. Requires mongo-java-driver > 3.4.0 + * Distributed lock using MongoDB >= 2.6. Requires mongo-java-driver > + * 3.4.0 + * *

* It uses a collection that contains documents like this: + * *

  * {
  *    "_id" : "lock name",
@@ -51,22 +51,18 @@
  * }
  * 
* - * lockedAt and lockedBy are just for troubleshooting and are not read by the code + * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code * *
    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter _id == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  12. When unlocking, lock_until is set to now. *
*/ public class MongoLockProvider implements ExtensibleLockProvider { @@ -79,9 +75,7 @@ public class MongoLockProvider implements ExtensibleLockProvider { private final String hostname; private final MongoCollection collection; - /** - * Uses Mongo to coordinate locks - */ + /** Uses Mongo to coordinate locks */ public MongoLockProvider(MongoDatabase mongoDatabase) { this(mongoDatabase.getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME)); } @@ -89,37 +83,36 @@ public MongoLockProvider(MongoDatabase mongoDatabase) { /** * Uses Mongo to coordinate locks * - * @param collection Mongo collection to be used + * @param collection + * Mongo collection to be used */ public MongoLockProvider(MongoCollection collection) { this.collection = collection; this.hostname = Utils.getHostname(); } - @Override public Optional lock(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = combine( - set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()), - set(LOCKED_AT, now), - set(LOCKED_BY, hostname) - ); + set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()), set(LOCKED_AT, now), set(LOCKED_BY, hostname)); try { // There are three possible situations: // 1. The lock document does not exist yet - it is inserted - we have the lock - // 2. The lock document exists and lockUtil <= now - it is updated - we have the lock - // 3. The lock document exists and lockUtil > now - Duplicate key exception is thrown - getCollection().findOneAndUpdate( - and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), - update, - new FindOneAndUpdateOptions().upsert(true) - ); + // 2. The lock document exists and lockUtil <= now - it is updated - we have the + // lock + // 3. The lock document exists and lockUtil > now - Duplicate key exception is + // thrown + getCollection() + .findOneAndUpdate( + and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), + update, + new FindOneAndUpdateOptions().upsert(true)); return Optional.of(new MongoLock(lockConfiguration, this)); } catch (MongoServerException e) { if (e.getCode() == 11000) { // duplicate key - //Upsert attempts to insert when there were no filter matches. - //This means there was a lock with matching ID with lockUntil > now. + // Upsert attempts to insert when there were no filter matches. + // This means there was a lock with matching ID with lockUntil > now. return Optional.empty(); } else { throw e; @@ -131,14 +124,9 @@ private Optional extend(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()); - Document updatedDocument = getCollection().findOneAndUpdate( - and( - eq(ID, lockConfiguration.getName()), - gt(LOCK_UNTIL, now), - eq(LOCKED_BY, hostname) - ), - update - ); + Document updatedDocument = getCollection() + .findOneAndUpdate( + and(eq(ID, lockConfiguration.getName()), gt(LOCK_UNTIL, now), eq(LOCKED_BY, hostname)), update); if (updatedDocument != null) { return Optional.of(new MongoLock(lockConfiguration, this)); } else { @@ -148,10 +136,10 @@ private Optional extend(LockConfiguration lockConfiguration) { private void unlock(LockConfiguration lockConfiguration) { // Set lockUtil to now or lockAtLeastUntil whichever is later - getCollection().findOneAndUpdate( - eq(ID, lockConfiguration.getName()), - combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime())) - ); + getCollection() + .findOneAndUpdate( + eq(ID, lockConfiguration.getName()), + combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime()))); } private MongoCollection getCollection() { diff --git a/providers/mongo/shedlock-provider-mongo/src/test/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProviderIntegrationTest.java b/providers/mongo/shedlock-provider-mongo/src/test/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProviderIntegrationTest.java index c211ae423..d0203fa77 100644 --- a/providers/mongo/shedlock-provider-mongo/src/test/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProviderIntegrationTest.java +++ b/providers/mongo/shedlock-provider-mongo/src/test/java/net/javacrumbs/shedlock/provider/mongo/MongoLockProviderIntegrationTest.java @@ -1,29 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.mongo; +import static com.mongodb.client.model.Filters.eq; +import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.DEFAULT_SHEDLOCK_COLLECTION_NAME; +import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.ID; +import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCK_UNTIL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import com.mongodb.client.result.DeleteResult; -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; +import java.util.Date; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest; @@ -32,55 +35,38 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.IOException; -import java.util.Date; - -import static com.mongodb.client.model.Filters.eq; -import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.DEFAULT_SHEDLOCK_COLLECTION_NAME; -import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.ID; -import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.mongo.MongoLockProvider.LOCK_UNTIL; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; - +@Testcontainers public class MongoLockProviderIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest { - private static final MongodStarter starter = MongodStarter.getDefaultInstance(); private static final String DB_NAME = "db"; - private static MongodExecutable mongodExe; - private static MongodProcess mongod; + @Container + public static final MongoDBContainer container = new MongoDBContainer("mongo:6"); private static MongoClient mongo; @BeforeAll - public static void startMongo() throws IOException { - mongodExe = starter.prepare(MongodConfig.builder() - .version(Version.Main.V3_6) - .build()); - mongod = mongodExe.start(); - - mongo = MongoClients.create("mongodb://localhost:"+mongod.getConfig().net().getPort()); + public static void startMongo() { + mongo = MongoClients.create("mongodb://" + container.getHost() + ":" + container.getFirstMappedPort()); } @AfterAll public static void stopMongo() { mongo.close(); - mongod.stop(); - mongodExe.stop(); } - @BeforeEach public void cleanDb() { - mongo.getDatabase(DB_NAME).drop(); + getMongo().getDatabase(DB_NAME).drop(); } @Override protected ExtensibleLockProvider getLockProvider() { - return new MongoLockProvider(mongo.getDatabase(DB_NAME)); + return new MongoLockProvider(getMongo().getDatabase(DB_NAME)); } @Override @@ -104,7 +90,7 @@ protected void assertLocked(String lockName) { } private MongoCollection getLockCollection() { - return mongo.getDatabase(DB_NAME).getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME); + return getMongo().getDatabase(DB_NAME).getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME); } private Document getLockDocument(String lockName) { @@ -123,4 +109,8 @@ public void shouldLockWhenDocumentRemovedExternally() { assertThat(provider.lock(lockConfig(LOCK_NAME1))).isNotEmpty(); assertLocked(LOCK_NAME1); } + + private MongoClient getMongo() { + return mongo; + } } diff --git a/providers/neo4j/shedlock-provider-neo4j/pom.xml b/providers/neo4j/shedlock-provider-neo4j/pom.xml index dbc009b4d..47a2d78d9 100644 --- a/providers/neo4j/shedlock-provider-neo4j/pom.xml +++ b/providers/neo4j/shedlock-provider-neo4j/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-neo4j - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -20,7 +20,7 @@ org.neo4j.driver neo4j-java-driver - 5.2.0 + 5.28.9 @@ -32,7 +32,13 @@ org.testcontainers neo4j - 1.17.6 + ${test-containers.ver} + test + + + org.testcontainers + junit-jupiter + ${test-containers.ver} test diff --git a/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProvider.java b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProvider.java index 16a0bba97..71aff3260 100644 --- a/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProvider.java +++ b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProvider.java @@ -1,55 +1,48 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.neo4j; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.javacrumbs.shedlock.support.annotation.Nullable; import org.neo4j.driver.Driver; /** - * Lock provided by Neo4j Graph API. It uses a collection that stores each lock as a node. + * Lock provided by Neo4j Graph API. It uses a collection that stores each lock + * as a node. + * *

    - *
  1. - * Attempts to insert a new lock node. Since lock name has a unique constraint, it fails if the record already exists. - * As an optimization, we keep in-memory track of created lock nodes. - *
  2. - *
  3. - * If the insert succeeds (1 node inserted) we have the lock. - *
  4. - *
  5. - * If the insert failed due to duplicate key or we have skipped the insertion, we will try to update lock node using - * MATCH (lock:collectionName) WHERE name = $lockName AND lock_until <= $now SET lock_until = $lockUntil, locked_at = $now - * with some additional explicit node locking - *
  6. - *
  7. - * If the update succeeded (>1 property updated), we have the lock. If the update failed (<=1 properties updated) somebody else holds the lock - * or grabbed the lock in a data race caused by Neo4j's read-committed isolation level. - *
  8. - *
  9. - * When unlocking, lock_until is set to now. - *
  10. + *
  11. Attempts to insert a new lock node. Since lock name has a unique + * constraint, it fails if the record already exists. As an optimization, we + * keep in-memory track of created lock nodes. + *
  12. If the insert succeeds (1 node inserted) we have the lock. + *
  13. If the insert failed due to duplicate key or we have skipped the + * insertion, we will try to update lock node using MATCH (lock:collectionName) + * WHERE name = $lockName AND lock_until <= $now SET lock_until = $lockUntil, + * locked_at = $now with some additional explicit node locking + *
  14. If the update succeeded (>1 property updated), we have the lock. If + * the update failed (<=1 properties updated) somebody else holds the lock or + * grabbed the lock in a data race caused by Neo4j's read-committed isolation + * level. + *
  15. When unlocking, lock_until is set to now. *
*/ public class Neo4jLockProvider extends StorageBasedLockProvider { - public Neo4jLockProvider(@NonNull Driver driver) { + public Neo4jLockProvider(Driver driver) { this(driver, "shedlock", null); } - public Neo4jLockProvider(@NonNull Driver graphDatabaseService, @NonNull String collectionName, @Nullable String databaseName) { + public Neo4jLockProvider(Driver graphDatabaseService, String collectionName, @Nullable String databaseName) { super(new Neo4jStorageAccessor(graphDatabaseService, collectionName, databaseName)); } } diff --git a/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jStorageAccessor.java b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jStorageAccessor.java index 7d377cc5b..d4cd3e256 100644 --- a/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jStorageAccessor.java +++ b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jStorageAccessor.java @@ -1,25 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.neo4j; +import static java.util.Objects.requireNonNull; + +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; import net.javacrumbs.shedlock.support.annotation.Nullable; import org.neo4j.driver.Driver; import org.neo4j.driver.Result; @@ -27,19 +29,14 @@ import org.neo4j.driver.SessionConfig; import org.neo4j.driver.Transaction; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -import static java.util.Objects.requireNonNull; - class Neo4jStorageAccessor extends AbstractStorageAccessor { private final String collectionName; private final Driver driver; + + @Nullable private final String databaseName; - public Neo4jStorageAccessor(@NonNull Driver driver, @NonNull String collectionName, @Nullable String databaseName) { + public Neo4jStorageAccessor(Driver driver, String collectionName, @Nullable String databaseName) { this.collectionName = requireNonNull(collectionName, "collectionName can not be null"); this.driver = requireNonNull(driver, "driver can not be null"); this.databaseName = databaseName; @@ -47,90 +44,106 @@ public Neo4jStorageAccessor(@NonNull Driver driver, @NonNull String collectionNa } private void createLockNameUniqueConstraint() { - try ( - Session session = getSession(); - Transaction transaction = session.beginTransaction() - ) { - transaction.run(String.format("CREATE CONSTRAINT UNIQUE_%s_name IF NOT EXISTS ON (lock:%s) ASSERT lock.name IS UNIQUE", collectionName, collectionName)); + try (Session session = getSession(); + Transaction transaction = session.beginTransaction()) { + transaction.run(String.format( + "CREATE CONSTRAINT UNIQUE_%s_name IF NOT EXISTS FOR (lock:%s) REQUIRE lock.name IS UNIQUE", + collectionName, collectionName)); transaction.commit(); } } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { + public boolean insertRecord(LockConfiguration lockConfiguration) { // Try to insert if the record does not exists - String cypher = String.format("CREATE (lock:%s {name: $lockName, lock_until: $lockUntil, locked_at: $now, locked_by: $lockedBy })", collectionName); + String cypher = String.format( + "CYPHER runtime = slotted CREATE (lock:%s {name: $lockName, lock_until: $lockUntil, locked_at: $now, locked_by: $lockedBy })", + collectionName); Map parameters = createParameterMap(lockConfiguration); - return executeCommand(cypher, result -> { - int insertedNodes = result.consume().counters().nodesCreated(); - return insertedNodes > 0; - }, parameters, this::handleInsertionException); + return executeCommand( + cypher, + result -> { + int insertedNodes = result.consume().counters().nodesCreated(); + return insertedNodes > 0; + }, + parameters, + this::handleInsertionException); } - private Map createParameterMap(@NonNull LockConfiguration lockConfiguration) { - Map parameters = new HashMap<>(); - parameters.put("lockName", lockConfiguration.getName()); - parameters.put("lockedBy", getHostname()); - parameters.put("now", ClockProvider.now().toString()); - parameters.put("lockUntil", lockConfiguration.getLockAtMostUntil().toString()); - return parameters; + private Map createParameterMap(LockConfiguration lockConfiguration) { + return Map.of( + "lockName", + lockConfiguration.getName(), + "lockedBy", + getHostname(), + "now", + ClockProvider.now().toString(), + "lockUntil", + lockConfiguration.getLockAtMostUntil().toString()); } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { - String cypher = String.format("MATCH (lock:%s) " + - "WHERE lock.name = $lockName AND lock.lock_until <= $now " + - "SET lock._LOCK_ = true " + - "WITH lock as l " + - "WHERE l.lock_until <= $now " + - "SET l.lock_until = $lockUntil, l.locked_at = $now, l.locked_by = $lockedBy " + - "REMOVE l._LOCK_ ", collectionName); + public boolean updateRecord(LockConfiguration lockConfiguration) { + String cypher = String.format( + "CYPHER runtime = slotted MATCH (lock:%s) WHERE lock.name = $lockName AND lock.lock_until <= $now " + + "SET lock._LOCK_ = true WITH lock as l WHERE l.lock_until <= $now " + + "SET l.lock_until = $lockUntil, l.locked_at = $now, l.locked_by = $lockedBy " + + "REMOVE l._LOCK_ ", + collectionName); Map parameters = createParameterMap(lockConfiguration); - return executeCommand(cypher, statement -> { - int updatedProperties = statement.consume().counters().propertiesSet(); - return updatedProperties > 1; //ignore explicit lock when counting the updated properties - }, parameters, this::handleUpdateException); + return executeCommand( + cypher, + statement -> { + int updatedProperties = statement.consume().counters().propertiesSet(); + return updatedProperties > 1; // ignore explicit lock when counting the updated properties + }, + parameters, + this::handleUpdateException); } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { - String cypher = String.format("MATCH (lock:%s) " + - "WHERE lock.name = $lockName AND lock.locked_by = $lockedBy AND lock.lock_until > $now " + - "SET lock._LOCK_ = true " + - "WITH lock as l " + - "WHERE l.name = $lockName AND l.locked_by = $lockedBy AND l.lock_until > $now " + - "SET l.lock_until = $lockUntil " + - "REMOVE l._LOCK_ ", collectionName); + public boolean extend(LockConfiguration lockConfiguration) { + String cypher = String.format( + "CYPHER runtime = slotted MATCH (lock:%s) " + + "WHERE lock.name = $lockName AND lock.locked_by = $lockedBy AND lock.lock_until > $now " + + "SET lock._LOCK_ = true WITH lock as l " + + "WHERE l.name = $lockName AND l.locked_by = $lockedBy AND l.lock_until > $now " + + "SET l.lock_until = $lockUntil REMOVE l._LOCK_ ", + collectionName); Map parameters = createParameterMap(lockConfiguration); logger.debug("Extending lock={} until={}", lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil()); - return executeCommand(cypher, statement -> { - int updatedProperties = statement.consume().counters().propertiesSet(); - return updatedProperties > 1; //ignore the explicit lock when counting the updated properties - }, parameters, this::handleUnlockException); + return executeCommand( + cypher, + statement -> { + int updatedProperties = statement.consume().counters().propertiesSet(); + return updatedProperties > 1; // ignore the explicit lock when counting the updated properties + }, + parameters, + this::handleUnlockException); } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { - String cypher = String.format("MATCH (lock:%s) WHERE lock.name = $lockName " + - "SET lock.lock_until = $lockUntil ", collectionName); - Map parameters = new HashMap<>(); - parameters.put("lockName", lockConfiguration.getName()); - parameters.put("lockUntil", lockConfiguration.getUnlockTime().toString()); + public void unlock(LockConfiguration lockConfiguration) { + String cypher = String.format( + "CYPHER runtime = slotted MATCH (lock:%s) WHERE lock.name = $lockName SET lock.lock_until = $lockUntil", + collectionName); + Map parameters = Map.of( + "lockName", + lockConfiguration.getName(), + "lockUntil", + lockConfiguration.getUnlockTime().toString()); executeCommand(cypher, statement -> null, parameters, this::handleUnlockException); } private T executeCommand( - String cypher, - Function body, - Map parameters, - BiFunction exceptionHandler - ) { - try ( - Session session = getSession(); - Transaction transaction = session.beginTransaction() - ) { + String cypher, + Function body, + Map parameters, + BiFunction exceptionHandler) { + try (Session session = getSession(); + Transaction transaction = session.beginTransaction()) { Result result = transaction.run(cypher, parameters); T apply = body.apply(result); transaction.commit(); @@ -156,5 +169,4 @@ boolean handleUpdateException(String cypher, Exception e) { boolean handleUnlockException(String cypher, Exception e) { throw new LockException("Unexpected exception when unlocking", e); } - } diff --git a/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/package-info.java b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/package-info.java new file mode 100644 index 000000000..b9d04883f --- /dev/null +++ b/providers/neo4j/shedlock-provider-neo4j/src/main/java/net/javacrumbs/shedlock/provider/neo4j/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.neo4j; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/AbstractNeo4jLockProviderIntegrationTest.java b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/AbstractNeo4jLockProviderIntegrationTest.java new file mode 100644 index 000000000..9432aa8b7 --- /dev/null +++ b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/AbstractNeo4jLockProviderIntegrationTest.java @@ -0,0 +1,186 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.neo4j; + +import static java.lang.Thread.sleep; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import net.javacrumbs.shedlock.test.support.FuzzTester; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; +import org.slf4j.LoggerFactory; + +public abstract class AbstractNeo4jLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { + private static final String MY_LOCK = "my-lock"; + private static final String OTHER_LOCK = "other-lock"; + + protected abstract Neo4jTestUtils getNeo4jTestUtils(); + + @Override + protected StorageBasedLockProvider getLockProvider() { + return new Neo4jLockProvider(getNeo4jTestUtils().getDriver()); + } + + @AfterEach + public void cleanup() { + getNeo4jTestUtils().clean(); + } + + @Override + protected void assertUnlocked(String lockName) { + Neo4jTestUtils.LockInfo lockInfo = getLockInfo(lockName); + Instant now = ClockProvider.now(); + assertThat(lockInfo.getLockUntil()) + .describedAs("is unlocked") + .isBeforeOrEqualTo(now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1)); + } + + @Override + protected void assertLocked(String lockName) { + Neo4jTestUtils.LockInfo lockInfo = getLockInfo(lockName); + Instant now = ClockProvider.now(); + + assertThat(lockInfo.getLockUntil()) + .describedAs(getClass().getName() + " is locked") + .isAfter(now); + } + + @Test + public void shouldCreateLockIfRecordAlreadyExists() { + Map parameters = new HashMap<>(); + parameters.put("name", LOCK_NAME1); + parameters.put( + "previousLockTime", Instant.now().minus(1, ChronoUnit.DAYS).toString()); + parameters.put("lockedBy", "me"); + getNeo4jTestUtils() + .executeTransactionally( + "CREATE (lock:shedlock { name: $name, lock_until: $previousLockTime, locked_at: $previousLockTime, locked_by: $lockedBy })", + parameters, + null); + assertUnlocked(LOCK_NAME1); + shouldCreateLock(); + } + + @Test + public void fuzzTestShouldWorkWithTransaction() throws ExecutionException, InterruptedException { + new FuzzTester(getLockProvider()) { + @Override + protected Void task(int iterations, Job job) { + try (Session session = getNeo4jTestUtils().getDriver().session(); + Transaction transaction = session.beginTransaction()) { + super.task(iterations, job); + transaction.commit(); + return null; + } catch (Throwable e) { + LoggerFactory.getLogger(getClass()).error("Exception caught:", e); + return null; + } + } + + @Override + protected boolean shouldLog() { + return true; + } + }.doFuzzTest(); + } + + @Test + void shouldNotUpdateOnInsertIfPreviousDidNotEndWhenNotUsingDbTime() { + shouldNotUpdateOnInsertIfPreviousDidNotEnd(); + } + + @Test + void shouldNotUpdateOnInsertIfPreviousDidNotEndWhenUsingDbTime() { + shouldNotUpdateOnInsertIfPreviousDidNotEnd(); + } + + private void shouldNotUpdateOnInsertIfPreviousDidNotEnd() { + Neo4jStorageAccessor accessor = getAccessor(); + + assertThat(accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10)))) + .isEqualTo(true); + + Instant originalLockValidity = getLockedUntil(MY_LOCK); + + assertThat(accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10)))) + .isEqualTo(false); + + assertThat(getLockedUntil(MY_LOCK)).isEqualTo(originalLockValidity); + } + + @Test + void shouldNotUpdateOtherLockConfigurationsWhenNotUsingDbTime() throws InterruptedException { + shouldNotUpdateOtherLockConfigurations(); + } + + @Test + void shouldNotUpdateOtherLockConfigurationsWhenUsingDbTime() throws InterruptedException { + shouldNotUpdateOtherLockConfigurations(); + } + + private void shouldNotUpdateOtherLockConfigurations() throws InterruptedException { + Neo4jStorageAccessor accessor = getAccessor(); + + Duration lockAtMostFor = Duration.ofMillis(10); + assertThat(accessor.insertRecord(lockConfig(MY_LOCK, lockAtMostFor))).isEqualTo(true); + assertThat(accessor.insertRecord(lockConfig(OTHER_LOCK, lockAtMostFor))).isEqualTo(true); + + Instant myLockLockedUntil = getLockedUntil(MY_LOCK); + Instant otherLockLockedUntil = getLockedUntil(OTHER_LOCK); + + // wait for a while so there will be a difference in the timestamp + // when system time is used seems there is no milliseconds in the timestamp so + // to make a + // difference we have to wait for at least a second + sleep(1000); + + // act + assertThat(accessor.updateRecord(new LockConfiguration(now(), MY_LOCK, lockAtMostFor, Duration.ZERO))) + .isEqualTo(true); + + // assert + assertThat(getLockedUntil(MY_LOCK)).isAfter(myLockLockedUntil); + // check that the other lock has not been affected by "my-lock" update + assertThat(getLockedUntil(OTHER_LOCK)).isEqualTo(otherLockLockedUntil); + } + + private Instant getLockedUntil(String lockName) { + return getNeo4jTestUtils().getLockedUntil(lockName); + } + + private LockConfiguration lockConfig(String myLock, Duration lockAtMostFor) { + return new LockConfiguration(now(), myLock, lockAtMostFor, Duration.ZERO); + } + + private Neo4jStorageAccessor getAccessor() { + return new Neo4jStorageAccessor(getNeo4jTestUtils().getDriver(), "shedlock", null); + } + + protected Neo4jTestUtils.LockInfo getLockInfo(String lockName) { + return getNeo4jTestUtils().getLockInfo(lockName); + } +} diff --git a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/EnterpriseNeo4jLockProviderIntegrationTest.java b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/EnterpriseNeo4jLockProviderIntegrationTest.java new file mode 100644 index 000000000..ea8e53a95 --- /dev/null +++ b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/EnterpriseNeo4jLockProviderIntegrationTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.neo4j; + +import org.junit.jupiter.api.BeforeAll; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.GraphDatabase; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +public class EnterpriseNeo4jLockProviderIntegrationTest extends AbstractNeo4jLockProviderIntegrationTest { + private static Neo4jTestUtils testUtils; + + @Container + private static final MyNeo4jContainer container = new MyNeo4jContainer(); + + @BeforeAll + static void startDb() { + testUtils = new Neo4jTestUtils(GraphDatabase.driver(container.getBoltUrl(), AuthTokens.none())); + } + + @Override + protected Neo4jTestUtils getNeo4jTestUtils() { + return testUtils; + } + + private static class MyNeo4jContainer extends Neo4jContainer { + MyNeo4jContainer() { + super(DockerImageName.parse("neo4j").withTag("5.22.0-enterprise")); + addEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "eval"); + withoutAuthentication(); + } + } +} diff --git a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProviderIntegrationTest.java b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProviderIntegrationTest.java index 613535309..c1edf4127 100644 --- a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProviderIntegrationTest.java +++ b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jLockProviderIntegrationTest.java @@ -1,219 +1,46 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.neo4j; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; -import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; -import net.javacrumbs.shedlock.test.support.FuzzTester; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; -import org.slf4j.LoggerFactory; import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; +@Testcontainers +public class Neo4jLockProviderIntegrationTest extends AbstractNeo4jLockProviderIntegrationTest { + private static Neo4jTestUtils testUtils; -import static java.lang.Thread.sleep; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class Neo4jLockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { - protected Neo4jTestUtils testUtils; - protected MyNeo4jContainer container; - private Driver driver; - - private static final String MY_LOCK = "my-lock"; - private static final String OTHER_LOCK = "other-lock"; + @Container + private static final MyNeo4jContainer container = new MyNeo4jContainer(); @BeforeAll - public void startDb() { - container = new MyNeo4jContainer(); - container.start(); - driver = GraphDatabase.driver(container.getBoltUrl(), AuthTokens.none()); - } - - @AfterAll - public void shutDownDb() { - container.stop(); + static void startDb() { + testUtils = new Neo4jTestUtils(GraphDatabase.driver(container.getBoltUrl(), AuthTokens.none())); } @Override - protected StorageBasedLockProvider getLockProvider() { - return new Neo4jLockProvider(testUtils.getDriver()); - } - - @BeforeEach - public void initTestUtils() { - testUtils = new Neo4jTestUtils(driver); - } - - @AfterEach - public void cleanup() { - testUtils.clean(); - } - - @Override - protected void assertUnlocked(String lockName) { - Neo4jTestUtils.LockInfo lockInfo = getLockInfo(lockName); - Instant now = ClockProvider.now(); - assertThat(lockInfo.getLockUntil()).describedAs("is unlocked").isBeforeOrEqualTo(now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1)); - } - - @Override - protected void assertLocked(String lockName) { - Neo4jTestUtils.LockInfo lockInfo = getLockInfo(lockName); - Instant now = ClockProvider.now(); - - assertThat(lockInfo.getLockUntil()).describedAs(getClass().getName() + " is locked").isAfter(now); - } - - @Test - public void shouldCreateLockIfRecordAlreadyExists() { - Map parameters = new HashMap<>(); - parameters.put("name", LOCK_NAME1); - parameters.put("previousLockTime", Instant.now().minus(1, ChronoUnit.DAYS).toString()); - parameters.put("lockedBy", "me"); - testUtils.executeTransactionally("CREATE (lock:shedlock { name: $name, lock_until: $previousLockTime, locked_at: $previousLockTime, locked_by: $lockedBy })", parameters, null); - assertUnlocked(LOCK_NAME1); - shouldCreateLock(); - } - - @Test - public void fuzzTestShouldWorkWithTransaction() throws ExecutionException, InterruptedException { - new FuzzTester(getLockProvider()) { - @Override - protected Void task(int iterations, Job job) { - try ( - Session session = testUtils.getDriver().session(); - Transaction transaction = session.beginTransaction() - ) { - super.task(iterations, job); - transaction.commit(); - return null; - } catch (Throwable e) { - LoggerFactory.getLogger(getClass()) - .error("Exception caught:", e); - return null; - } - } - - @Override - protected boolean shouldLog() { - return true; - } - }.doFuzzTest(); - } - - - @Test - void shouldNotUpdateOnInsertIfPreviousDidNotEndWhenNotUsingDbTime() { - shouldNotUpdateOnInsertIfPreviousDidNotEnd(); - } - - @Test - void shouldNotUpdateOnInsertIfPreviousDidNotEndWhenUsingDbTime() { - shouldNotUpdateOnInsertIfPreviousDidNotEnd(); - } - - private void shouldNotUpdateOnInsertIfPreviousDidNotEnd() { - Neo4jStorageAccessor accessor = getAccessor(); - - assertThat( - accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10))) - ).isEqualTo(true); - - Instant originalLockValidity = testUtils.getLockedUntil(MY_LOCK); - - assertThat( - accessor.insertRecord(lockConfig(MY_LOCK, Duration.ofSeconds(10))) - ).isEqualTo(false); - - assertThat(testUtils.getLockedUntil(MY_LOCK)).isEqualTo(originalLockValidity); - } - - @Test - void shouldNotUpdateOtherLockConfigurationsWhenNotUsingDbTime() throws InterruptedException { - shouldNotUpdateOtherLockConfigurations(); - } - - @Test - void shouldNotUpdateOtherLockConfigurationsWhenUsingDbTime() throws InterruptedException { - shouldNotUpdateOtherLockConfigurations(); - } - - private void shouldNotUpdateOtherLockConfigurations() throws InterruptedException { - Neo4jStorageAccessor accessor = getAccessor(); - - Duration lockAtMostFor = Duration.ofMillis(10); - assertThat(accessor.insertRecord(lockConfig(MY_LOCK, lockAtMostFor))).isEqualTo(true); - assertThat(accessor.insertRecord(lockConfig(OTHER_LOCK, lockAtMostFor))).isEqualTo(true); - - Instant myLockLockedUntil = testUtils.getLockedUntil(MY_LOCK); - Instant otherLockLockedUntil = testUtils.getLockedUntil(OTHER_LOCK); - - // wait for a while so there will be a difference in the timestamp - // when system time is used seems there is no milliseconds in the timestamp so to make a difference we have to wait for at least a second - sleep(1000); - - - // act - assertThat(accessor.updateRecord(new LockConfiguration(now(), MY_LOCK, lockAtMostFor, Duration.ZERO))).isEqualTo(true); - - - // assert - assertThat(testUtils.getLockedUntil(MY_LOCK)).isAfter(myLockLockedUntil); - // check that the other lock has not been affected by "my-lock" update - assertThat(testUtils.getLockedUntil(OTHER_LOCK)).isEqualTo(otherLockLockedUntil); - } - - @NonNull - private LockConfiguration lockConfig(String myLock, Duration lockAtMostFor) { - return new LockConfiguration(now(), myLock, lockAtMostFor, Duration.ZERO); - } - - @NonNull - protected Neo4jStorageAccessor getAccessor() { - return new Neo4jStorageAccessor(this.testUtils.getDriver(), "shedlock", null); - } - - protected Neo4jTestUtils.LockInfo getLockInfo(String lockName) { - return testUtils.getLockInfo(lockName); + protected Neo4jTestUtils getNeo4jTestUtils() { + return testUtils; } private static class MyNeo4jContainer extends Neo4jContainer { MyNeo4jContainer() { - super(DockerImageName.parse("neo4j").withTag("4.4.6")); + super(DockerImageName.parse("neo4j").withTag("5.22.0")); withoutAuthentication(); } } diff --git a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jTestUtils.java b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jTestUtils.java index afc5b5f83..e911d2b3b 100644 --- a/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jTestUtils.java +++ b/providers/neo4j/shedlock-provider-neo4j/src/test/java/net/javacrumbs/shedlock/provider/neo4j/Neo4jTestUtils.java @@ -1,31 +1,28 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.neo4j; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; +import static java.util.Collections.singletonMap; import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.function.Function; - -import static java.util.Collections.singletonMap; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; public final class Neo4jTestUtils { @@ -39,7 +36,8 @@ public void executeTransactionally(String query) { executeTransactionally(query, new HashMap<>(), null); } - public T executeTransactionally(String query, Map parameters, Function resultTransformer) { + public T executeTransactionally( + String query, Map parameters, Function resultTransformer) { T transformedResult = null; try (Session session = driver.session()) { Transaction transaction = session.beginTransaction(); @@ -54,24 +52,26 @@ public T executeTransactionally(String query, Map parameters public Instant getLockedUntil(String lockName) { Map parameters = singletonMap("lockName", lockName); - return executeTransactionally("MATCH (lock:shedlock) WHERE lock.name = $lockName return lock.lock_until", - parameters, result -> result.stream() - .findFirst() - .map(it -> Instant.parse(it.get("lock.lock_until").asString())) - .orElse(null)); + return executeTransactionally( + "MATCH (lock:shedlock) WHERE lock.name = $lockName return lock.lock_until", + parameters, + result -> result.stream() + .findFirst() + .map(it -> Instant.parse(it.get("lock.lock_until").asString())) + .orElse(null)); } public LockInfo getLockInfo(String lockName) { Map parameters = singletonMap("lockName", lockName); - return executeTransactionally("MATCH (lock:shedlock) WHERE lock.name = $lockName RETURN lock.name, lock.lock_until, localdatetime() as db_time ", parameters, result -> - result.stream() - .findFirst() - .map(it -> new LockInfo( - it.get("lock.name").asString(), - Instant.parse(it.get("lock.lock_until").asString()) - ) - ) - ).orElse(null); + return executeTransactionally( + "MATCH (lock:shedlock) WHERE lock.name = $lockName RETURN lock.name, lock.lock_until, localdatetime() as db_time ", + parameters, + result -> result.stream() + .findFirst() + .map(it -> new LockInfo( + it.get("lock.name").asString(), + Instant.parse(it.get("lock.lock_until").asString())))) + .orElse(null); } public void clean() { diff --git a/providers/opensearch/shedlock-provider-opensearch-java/pom.xml b/providers/opensearch/shedlock-provider-opensearch-java/pom.xml new file mode 100644 index 000000000..86d9c197d --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch-java/pom.xml @@ -0,0 +1,78 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-opensearch-java + ${project.groupId}:${project.artifactId} + + + 2.23.0 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + org.opensearch.client + opensearch-java + ${opensearch.java.client.version} + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.opensearch + opensearch-testcontainers + 2.1.3 + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.opensearch.java + + + + + + + + + diff --git a/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProvider.java b/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProvider.java new file mode 100644 index 000000000..0157a2b88 --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProvider.java @@ -0,0 +1,215 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.opensearch.java; + +import static java.net.HttpURLConnection.HTTP_CONFLICT; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.support.Utils.getHostname; + +import java.io.IOException; +import java.time.Instant; +import java.util.Map; +import java.util.Optional; +import net.javacrumbs.shedlock.core.AbstractSimpleLock; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.support.LockException; +import org.opensearch.client.json.JsonData; +import org.opensearch.client.opensearch.OpenSearchClient; +import org.opensearch.client.opensearch._types.InlineScript; +import org.opensearch.client.opensearch._types.OpenSearchException; +import org.opensearch.client.opensearch._types.Refresh; +import org.opensearch.client.opensearch._types.Result; +import org.opensearch.client.opensearch._types.Script; +import org.opensearch.client.opensearch.core.UpdateRequest; +import org.opensearch.client.opensearch.core.UpdateRequest.Builder; +import org.opensearch.client.opensearch.core.UpdateResponse; +import org.opensearch.client.transport.httpclient5.ResponseException; + +/** + * Lock using OpenSearch >= . Requires opensearch-rest-high-level-client > + * 1.1.0 + * + *

+ * It uses a collection that contains documents like this: + * + *

+ * {
+ *    "name" : "lock name",
+ *    "lockUntil" :  {
+ *      "type":   "date",
+ *      "format": "epoch_millis"
+ *    },
+ *    "lockedAt" : {
+ *      "type":   "date",
+ *      "format": "epoch_millis"
+ *    }:
+ *    "lockedBy" : "hostname"
+ * }
+ * 
+ * + *

+ * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code + * + *

    + *
  1. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  2. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  3. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  4. When unlocking, lock_until is set to now. + *
+ */ +public class OpenSearchLockProvider implements LockProvider { + static final String SCHEDLOCK_DEFAULT_INDEX = "shedlock"; + static final String LOCK_UNTIL = "lockUntil"; + static final String LOCKED_AT = "lockedAt"; + static final String LOCKED_BY = "lockedBy"; + static final String NAME = "name"; + + private static final String UPDATE_SCRIPT = "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + + ") { " + "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + "ctx._source." + LOCKED_AT + + " = params." + LOCKED_AT + "; " + "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + + "} else { " + "ctx.op = 'none' " + "}"; + private static final String UNLOCK_UPDATE_SCRIPT = "ctx._source.lockUntil = params.unlockTime"; + private static final String PAINLESS_SCRIPT_LANG = "painless"; + private static final String UNLOCK_TIME = "unlockTime"; + + private final OpenSearchClient openSearchClient; + private final String hostname; + private final String index; + + public OpenSearchLockProvider(OpenSearchClient openSearchClient) { + this(openSearchClient, SCHEDLOCK_DEFAULT_INDEX); + } + + public OpenSearchLockProvider(OpenSearchClient openSearchClient, String index) { + this.openSearchClient = openSearchClient; + this.index = index; + this.hostname = getHostname(); + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + Instant now = now(); + UpdateRequest updateRequest = createUpdateRequest(lockConfiguration, now); + + try { + UpdateResponse updateResponse = openSearchClient.update(updateRequest, Object.class); + + return updateResponse.result() == Result.NoOp + ? Optional.empty() + : Optional.of(new OpenSearchSimpleLock(lockConfiguration)); + } catch (IOException | OpenSearchException e) { + if (isResponseExceptionWithConflictStatus(e) || isOpenSearchExceptionWithConflictStatus(e)) { + return Optional.empty(); + } + + throw new LockException("Unexpected exception occurred", e); + } + } + + private static boolean isResponseExceptionWithConflictStatus(Exception e) { + return e instanceof ResponseException ex && ex.status() == HTTP_CONFLICT; + } + + private static boolean isOpenSearchExceptionWithConflictStatus(Exception e) { + return e instanceof OpenSearchException ex && ex.status() == HTTP_CONFLICT; + } + + private UpdateRequest createUpdateRequest(LockConfiguration lockConfiguration, Instant now) { + + Map lockObject = + lockObject(lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil(), now); + + return new Builder<>() + .index(index) + .script(createUpdateScript(lockConfiguration, now)) + .id(lockConfiguration.getName()) + .refresh(Refresh.True) + .upsert(lockObject) + .build(); + } + + private Script createUpdateScript(LockConfiguration lockConfiguration, Instant now) { + Map updateScriptParams = + updateScriptParams(lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil(), now); + + InlineScript inlineScript = InlineScript.of(builder -> + builder.source(UPDATE_SCRIPT).params(updateScriptParams).lang(PAINLESS_SCRIPT_LANG)); + + return Script.of(scriptBuilder -> scriptBuilder.inline(inlineScript)); + } + + private Map lockObject(String name, Instant lockUntil, Instant lockedAt) { + return Map.of( + NAME, + name, + LOCKED_BY, + hostname, + LOCKED_AT, + lockedAt.toEpochMilli(), + LOCK_UNTIL, + lockUntil.toEpochMilli()); + } + + private Map updateScriptParams(String name, Instant lockUntil, Instant lockedAt) { + return Map.of( + NAME, JsonData.of(name), + LOCKED_BY, JsonData.of(hostname), + LOCKED_AT, JsonData.of(lockedAt.toEpochMilli()), + LOCK_UNTIL, JsonData.of(lockUntil.toEpochMilli())); + } + + private final class OpenSearchSimpleLock extends AbstractSimpleLock { + + private OpenSearchSimpleLock(LockConfiguration lockConfiguration) { + super(lockConfiguration); + } + + @Override + public void doUnlock() { + + Map unlockParams = Map.of( + UNLOCK_TIME, JsonData.of(lockConfiguration.getUnlockTime().toEpochMilli())); + + InlineScript inlineScript = InlineScript.of(builder -> + builder.source(UNLOCK_UPDATE_SCRIPT).params(unlockParams).lang(PAINLESS_SCRIPT_LANG)); + + Script unlockScript = Script.of(scriptBuilder -> scriptBuilder.inline(inlineScript)); + + UpdateRequest unlockUpdateRequest = + new org.opensearch.client.opensearch.core.UpdateRequest.Builder<>() + .index(index) + .script(unlockScript) + .id(lockConfiguration.getName()) + .refresh(Refresh.True) + .build(); + + try { + openSearchClient.update(unlockUpdateRequest, Object.class); + } catch (IOException | OpenSearchException e) { + throwLockException(e); + } + } + + private void throwLockException(Exception e) { + throw new LockException("Unexpected exception occurred", e); + } + } +} diff --git a/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/package-info.java b/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/package-info.java new file mode 100644 index 000000000..908ede4fd --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch-java/src/main/java/net/javacrumbs/shedlock/provider/opensearch/java/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.opensearch.java; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/opensearch/shedlock-provider-opensearch-java/src/test/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProviderTest.java b/providers/opensearch/shedlock-provider-opensearch-java/src/test/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProviderTest.java new file mode 100644 index 000000000..b11c4dd2d --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch-java/src/test/java/net/javacrumbs/shedlock/provider/opensearch/java/OpenSearchLockProviderTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.opensearch.java; + +import static java.time.Instant.now; +import static java.time.Instant.ofEpochMilli; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider.LOCK_UNTIL; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider.NAME; +import static net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Date; +import java.util.Map; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.core5.http.HttpHost; +import org.junit.jupiter.api.BeforeEach; +import org.opensearch.client.opensearch.OpenSearchClient; +import org.opensearch.client.opensearch.core.GetRequest; +import org.opensearch.client.opensearch.core.GetResponse; +import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.httpclient5.ApacheHttpClient5TransportBuilder; +import org.opensearch.testcontainers.OpensearchContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +public class OpenSearchLockProviderTest extends AbstractLockProviderIntegrationTest { + + @Container + private static final OpensearchContainer container = + new OpensearchContainer<>(DockerImageName.parse("opensearchproject/opensearch:2")); + + private OpenSearchClient openSearchClient; + private OpenSearchLockProvider lockProvider; + + @BeforeEach + public void setUp() { + openSearchClient = openSearchClient(); + lockProvider = new OpenSearchLockProvider(openSearchClient); + } + + @Override + protected LockProvider getLockProvider() { + return lockProvider; + } + + @Override + @SuppressWarnings("unchecked") + protected void assertUnlocked(String lockName) { + GetRequest getRequest = + GetRequest.of(builder -> builder.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); + + try { + GetResponse objectGetResponse = openSearchClient.get(getRequest, Object.class); + Map sourceData = (Map) objectGetResponse.source(); + assert sourceData != null; + assertThat(new Date((Long) sourceData.get(LOCK_UNTIL))).isBeforeOrEqualTo(now()); + assertThat(new Date((Long) sourceData.get(LOCKED_AT))).isBeforeOrEqualTo(now()); + assertThat((String) sourceData.get(LOCKED_BY)).isNotBlank(); + assertThat((String) sourceData.get(NAME)).isEqualTo(lockName); + } catch (IOException e) { + fail("Call to embedded OS failed."); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void assertLocked(String lockName) { + GetRequest getRequest = + GetRequest.of(builder -> builder.index(SCHEDLOCK_DEFAULT_INDEX).id(lockName)); + + try { + GetResponse getResponse = openSearchClient.get(getRequest, Object.class); + Map sourceData = (Map) getResponse.source(); + + assert sourceData != null; + assertThat(ofEpochMilli((Long) sourceData.get(LOCK_UNTIL))).isAfter(now()); + assertThat(ofEpochMilli((Long) sourceData.get(LOCKED_AT))).isBeforeOrEqualTo(now()); + assertThat((String) sourceData.get(LOCKED_BY)).isNotBlank(); + assertThat((String) sourceData.get(NAME)).isEqualTo(lockName); + } catch (IOException e) { + fail("Call to embedded OS failed."); + } + } + + private OpenSearchClient openSearchClient() { + OpenSearchTransport openSearchTransport = openSearchTransport(); + return new OpenSearchClient(openSearchTransport); + } + + private static OpenSearchTransport openSearchTransport() { + HttpHost httpHost; + try { + httpHost = HttpHost.create(container.getHttpHostAddress()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + BasicCredentialsProvider basicCredentialsProvider = getBasicCredentialsProvider(httpHost); + + return ApacheHttpClient5TransportBuilder.builder(httpHost) + .setHttpClientConfigCallback( + httpClientBuilder -> getClientBuilder(httpClientBuilder, basicCredentialsProvider)) + .build(); + } + + private static HttpAsyncClientBuilder getClientBuilder( + HttpAsyncClientBuilder httpClientBuilder, CredentialsProvider basicCredentialsProvider) { + return httpClientBuilder + .setDefaultCredentialsProvider(basicCredentialsProvider) + .setConnectionManager( + PoolingAsyncClientConnectionManagerBuilder.create().build()); + } + + private static BasicCredentialsProvider getBasicCredentialsProvider(HttpHost httpHost) { + BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider(); + basicCredentialsProvider.setCredentials( + new AuthScope(httpHost), new UsernamePasswordCredentials("admin", "admin".toCharArray())); + return basicCredentialsProvider; + } +} diff --git a/providers/opensearch/shedlock-provider-opensearch-java/src/test/resources/logback.xml b/providers/opensearch/shedlock-provider-opensearch-java/src/test/resources/logback.xml new file mode 100644 index 000000000..a35e40a2f --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch-java/src/test/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/opensearch/shedlock-provider-opensearch/pom.xml b/providers/opensearch/shedlock-provider-opensearch/pom.xml index c3007139e..c02772428 100644 --- a/providers/opensearch/shedlock-provider-opensearch/pom.xml +++ b/providers/opensearch/shedlock-provider-opensearch/pom.xml @@ -3,16 +3,16 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-opensearch - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - 2.4.0 + 2.19.2 @@ -48,6 +48,13 @@ ${logback.ver} test + + + org.opensearch + opensearch-testcontainers + 2.1.3 + test + diff --git a/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProvider.java b/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProvider.java index da1c47600..b44824cdf 100644 --- a/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProvider.java +++ b/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProvider.java @@ -1,52 +1,49 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.opensearch; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.support.Utils.getHostname; +import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; + +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; import org.opensearch.OpenSearchException; import org.opensearch.action.DocWriteResponse; import org.opensearch.action.update.UpdateRequest; import org.opensearch.action.update.UpdateResponse; import org.opensearch.client.RequestOptions; import org.opensearch.client.RestHighLevelClient; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import org.opensearch.script.Script; import org.opensearch.script.ScriptType; -import java.io.IOException; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; - /** - * Lock using OpenSearch >= . - * Requires opensearch-rest-high-level-client > 1.1.0 + * Lock using OpenSearch >= . Requires opensearch-rest-high-level-client > + * 1.1.0 + * *

* It uses a collection that contains documents like this: + * *

  * {
  *    "name" : "lock name",
@@ -61,25 +58,25 @@
  *    "lockedBy" : "hostname"
  * }
  * 
+ * *

- * lockedAt and lockedBy are just for troubleshooting and are not read by the code + * lockedAt and lockedBy are just for troubleshooting and are not read by the + * code * *

    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter _id == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter _id == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated document), we have the lock. If the + * update failed (0 updated documents) somebody else holds the lock + *
  12. When unlocking, lock_until is set to now. *
+ * + * @deprecated Use shedlock-provider-opensearch-java module */ +@Deprecated(forRemoval = true) public class OpenSearchLockProvider implements LockProvider { static final String SCHEDLOCK_DEFAULT_INDEX = "shedlock"; static final String LOCK_UNTIL = "lockUntil"; @@ -87,44 +84,33 @@ public class OpenSearchLockProvider implements LockProvider { static final String LOCKED_BY = "lockedBy"; static final String NAME = "name"; - - private static final String UPDATE_SCRIPT = - "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + ") { " + - "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + - "ctx._source." + LOCKED_AT + " = params." + LOCKED_AT + "; " + - "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + - "} else { " + - "ctx.op = 'none' " + - "}"; + private static final String UPDATE_SCRIPT = "if (ctx._source." + LOCK_UNTIL + " <= " + "params." + LOCKED_AT + + ") { " + "ctx._source." + LOCKED_BY + " = params." + LOCKED_BY + "; " + "ctx._source." + LOCKED_AT + + " = params." + LOCKED_AT + "; " + "ctx._source." + LOCK_UNTIL + " = params." + LOCK_UNTIL + "; " + + "} else { " + "ctx.op = 'none' " + "}"; private final RestHighLevelClient highLevelClient; private final String hostname; private final String index; - private OpenSearchLockProvider(@NonNull RestHighLevelClient highLevelClient, @NonNull String index) { + public OpenSearchLockProvider(RestHighLevelClient highLevelClient, String index) { this.highLevelClient = highLevelClient; this.hostname = getHostname(); this.index = index; } - public OpenSearchLockProvider(@NonNull RestHighLevelClient highLevelClient) { + public OpenSearchLockProvider(RestHighLevelClient highLevelClient) { this(highLevelClient, SCHEDLOCK_DEFAULT_INDEX); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { try { - Map lockObject = lockObject(lockConfiguration.getName(), - lockConfiguration.getLockAtMostUntil(), - now()); + Map lockObject = + lockObject(lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil(), now()); UpdateRequest ur = updateRequest(lockConfiguration) - .script(new Script(ScriptType.INLINE, - "painless", - UPDATE_SCRIPT, - lockObject) - ) - .upsert(lockObject); + .script(new Script(ScriptType.INLINE, "painless", UPDATE_SCRIPT, lockObject)) + .upsert(lockObject); UpdateResponse res = highLevelClient.update(ur, RequestOptions.DEFAULT); if (res.getResult() != DocWriteResponse.Result.NOOP) { return Optional.of(new OpenSearchSimpleLock(lockConfiguration)); @@ -140,20 +126,20 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration) { } } - private UpdateRequest updateRequest(@NonNull LockConfiguration lockConfiguration) { - return new UpdateRequest() - .index(index) - .id(lockConfiguration.getName()) - .setRefreshPolicy(IMMEDIATE); + private UpdateRequest updateRequest(LockConfiguration lockConfiguration) { + return new UpdateRequest().index(index).id(lockConfiguration.getName()).setRefreshPolicy(IMMEDIATE); } private Map lockObject(String name, Instant lockUntil, Instant lockedAt) { - Map lock = new HashMap<>(); - lock.put(NAME, name); - lock.put(LOCKED_BY, hostname); - lock.put(LOCKED_AT, lockedAt.toEpochMilli()); - lock.put(LOCK_UNTIL, lockUntil.toEpochMilli()); - return lock; + return Map.of( + NAME, + name, + LOCKED_BY, + hostname, + LOCKED_AT, + lockedAt.toEpochMilli(), + LOCK_UNTIL, + lockUntil.toEpochMilli()); } private final class OpenSearchSimpleLock extends AbstractSimpleLock { @@ -167,10 +153,13 @@ public void doUnlock() { // Set lockUtil to now or lockAtLeastUntil whichever is later try { UpdateRequest ur = updateRequest(lockConfiguration) - .script(new Script(ScriptType.INLINE, - "painless", - "ctx._source.lockUntil = params.unlockTime", - Collections.singletonMap("unlockTime", lockConfiguration.getUnlockTime().toEpochMilli()))); + .script(new Script( + ScriptType.INLINE, + "painless", + "ctx._source.lockUntil = params.unlockTime", + Collections.singletonMap( + "unlockTime", + lockConfiguration.getUnlockTime().toEpochMilli()))); highLevelClient.update(ur, RequestOptions.DEFAULT); } catch (IOException | OpenSearchException e) { throw new LockException("Unexpected exception occurred", e); diff --git a/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/package-info.java b/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/package-info.java new file mode 100644 index 000000000..f9f213c19 --- /dev/null +++ b/providers/opensearch/shedlock-provider-opensearch/src/main/java/net/javacrumbs/shedlock/provider/opensearch/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.opensearch; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/container/OpenSearchContainer.java b/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/container/OpenSearchContainer.java deleted file mode 100644 index 9e968ff7c..000000000 --- a/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/container/OpenSearchContainer.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.javacrumbs.container; - -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.images.builder.ImageFromDockerfile; -import org.testcontainers.utility.Base58; - -import java.time.Duration; - -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; - -public class OpenSearchContainer extends GenericContainer { - - static final int OPENSEARCH_DEFAULT_PORT = 9200; - static final int OPENSEARCH_DEFAULT_TCP_PORT = 9300; - public OpenSearchContainer(String dockerImageName) { - super(dockerImageName); - } - - private ImageFromDockerfile prepareImage(String imageName) { - return new ImageFromDockerfile() - .withDockerfileFromBuilder(builder -> { - builder.from(imageName); - }); - } - - @Override - protected void configure() { - withNetworkAliases("opensearch-" + Base58.randomString(6)); - withEnv("discovery.type", "single-node"); - addExposedPorts(OPENSEARCH_DEFAULT_PORT, OPENSEARCH_DEFAULT_TCP_PORT); - setWaitStrategy(new HttpWaitStrategy() - .forPort(OPENSEARCH_DEFAULT_PORT) - .forStatusCodeMatching(response -> response == HTTP_OK || response == HTTP_UNAUTHORIZED) - .withStartupTimeout(Duration.ofMinutes(2))); - setImage(prepareImage(getDockerImageName())); - } - - public String getHttpHostAddress() { - return getHost() + ":" + getMappedPort(OPENSEARCH_DEFAULT_PORT); - } -} diff --git a/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProviderTest.java b/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProviderTest.java index 0893f6d99..e61e4b40c 100644 --- a/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProviderTest.java +++ b/providers/opensearch/shedlock-provider-opensearch/src/test/java/net/javacrumbs/shedlock/provider/opensearch/OpenSearchLockProviderTest.java @@ -1,21 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.opensearch; -import net.javacrumbs.container.OpenSearchContainer; +import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCKED_AT; +import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCKED_BY; +import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCK_UNTIL; +import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.NAME; +import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; import org.apache.http.HttpHost; @@ -26,38 +34,24 @@ import org.opensearch.client.RequestOptions; import org.opensearch.client.RestClient; import org.opensearch.client.RestHighLevelClient; +import org.opensearch.testcontainers.OpensearchContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; - -import java.io.IOException; -import java.time.Duration; -import java.util.Date; -import java.util.Map; - -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCKED_AT; -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCKED_BY; -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.LOCK_UNTIL; -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.NAME; -import static net.javacrumbs.shedlock.provider.opensearch.OpenSearchLockProvider.SCHEDLOCK_DEFAULT_INDEX; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import org.testcontainers.utility.DockerImageName; @Testcontainers public class OpenSearchLockProviderTest extends AbstractLockProviderIntegrationTest { @Container - private static final OpenSearchContainer container = new OpenSearchContainer("opensearchproject/opensearch:1.1.0") - .withStartupTimeout(Duration.ofMinutes(2)) - .withEnv("plugins.security.disabled", "true") - .withStartupAttempts(2); + private static final OpensearchContainer container = + new OpensearchContainer<>(DockerImageName.parse("opensearchproject/opensearch:2")); + private RestHighLevelClient highLevelClient; private OpenSearchLockProvider lockProvider; @BeforeEach public void setUp() { - highLevelClient = new RestHighLevelClient( - RestClient.builder(HttpHost.create(container.getHttpHostAddress())) - ); + highLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create(container.getHttpHostAddress()))); lockProvider = new OpenSearchLockProvider(highLevelClient); } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/pom.xml b/providers/r2dbc/shedlock-provider-r2dbc/pom.xml index 23c309b08..afb6e0ec3 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/pom.xml +++ b/providers/r2dbc/shedlock-provider-r2dbc/pom.xml @@ -5,12 +5,16 @@ net.javacrumbs.shedlock shedlock-parent - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml shedlock-provider-r2dbc - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} + + + 1.0.0.RELEASE + @@ -22,12 +26,13 @@ io.r2dbc r2dbc-spi + ${r2dbc.version} io.projectreactor reactor-core - 3.5.0 + 3.7.9 @@ -41,50 +46,57 @@ org.postgresql r2dbc-postgresql + + 1.0.7.RELEASE test io.r2dbc r2dbc-mssql + ${r2dbc.version} test - dev.miku - r2dbc-mysql - 0.8.2.RELEASE + com.github.jasync-sql + jasync-r2dbc-mysql + 2.2.4 test com.oracle.database.r2dbc oracle-r2dbc + 1.3.0 test com.oracle.database.jdbc - ojdbc8 - 21.7.0.0 + ojdbc11 + 23.9.0.25.07 test io.r2dbc r2dbc-h2 + ${r2dbc.version} test org.mariadb r2dbc-mariadb + 1.3.0 test io.r2dbc r2dbc-pool + ${r2dbc.version} test @@ -96,18 +108,6 @@ - - - - io.r2dbc - r2dbc-bom - Borca-SR1 - import - pom - - - - diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcStorageAccessor.java b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcStorageAccessor.java index 1f9de49ef..4e7eb4c4b 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcStorageAccessor.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcStorageAccessor.java @@ -15,105 +15,129 @@ */ package net.javacrumbs.shedlock.provider.r2dbc; +import static java.util.Objects.requireNonNull; + import io.r2dbc.spi.R2dbcDataIntegrityViolationException; import io.r2dbc.spi.Statement; +import java.time.Instant; +import java.util.function.BiFunction; +import java.util.function.Function; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.support.AbstractStorageAccessor; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -import java.time.Instant; -import java.util.function.BiFunction; -import java.util.function.Function; - -import static java.util.Objects.requireNonNull; - -/** - * Internal class, please do not use. - */ +/** Internal class, please do not use. */ abstract class AbstractR2dbcStorageAccessor extends AbstractStorageAccessor { private final String tableName; - public AbstractR2dbcStorageAccessor(@NonNull String tableName) { + public AbstractR2dbcStorageAccessor(String tableName) { this.tableName = requireNonNull(tableName, "tableName can not be null"); } @Override - public boolean insertRecord(@NonNull LockConfiguration lockConfiguration) { - return Boolean.TRUE.equals(Mono.from(insertRecordReactive(lockConfiguration)).block()); + public boolean insertRecord(LockConfiguration lockConfiguration) { + return Boolean.TRUE.equals( + Mono.from(insertRecordReactive(lockConfiguration)).block()); } @Override - public boolean updateRecord(@NonNull LockConfiguration lockConfiguration) { - return Boolean.TRUE.equals(Mono.from(updateRecordReactive(lockConfiguration)).block()); + public boolean updateRecord(LockConfiguration lockConfiguration) { + return Boolean.TRUE.equals( + Mono.from(updateRecordReactive(lockConfiguration)).block()); } @Override - public boolean extend(@NonNull LockConfiguration lockConfiguration) { + public boolean extend(LockConfiguration lockConfiguration) { return Boolean.TRUE.equals(Mono.from(extendReactive(lockConfiguration)).block()); } @Override - public void unlock(@NonNull LockConfiguration lockConfiguration) { + public void unlock(LockConfiguration lockConfiguration) { Mono.from(unlockReactive(lockConfiguration)).block(); } - public Publisher insertRecordReactive(@NonNull LockConfiguration lockConfiguration) { - // Try to insert if the record does not exist (not optimal, but the simplest platform agnostic way) - String sql = "INSERT INTO " + tableName + "(name, lock_until, locked_at, locked_by) VALUES(" + toParameter(1, "name") + ", " + toParameter(2, "lock_until") + ", " + toParameter(3, "locked_at") + ", " + toParameter(4, "locked_by") + ")"; - return executeCommand(sql, statement -> { - bind(statement, 0, "name", lockConfiguration.getName()); - bind(statement, 1, "lock_until", lockConfiguration.getLockAtMostUntil()); - bind(statement, 2, "locked_at", ClockProvider.now()); - bind(statement, 3, "locked_by", getHostname()); - return Mono.from(statement.execute()).flatMap(it -> Mono.from(it.getRowsUpdated())).map(it -> it > 0); - }, this::handleInsertionException); + public Publisher insertRecordReactive(LockConfiguration lockConfiguration) { + // Try to insert if the record does not exist (not optimal, but the simplest + // platform agnostic + // way) + String sql = "INSERT INTO " + tableName + "(name, lock_until, locked_at, locked_by) VALUES(" + + toParameter(1, "name") + ", " + toParameter(2, "lock_until") + ", " + toParameter(3, "locked_at") + + ", " + toParameter(4, "locked_by") + ")"; + return executeCommand( + sql, + statement -> { + bind(statement, 0, "name", lockConfiguration.getName()); + bind(statement, 1, "lock_until", lockConfiguration.getLockAtMostUntil()); + bind(statement, 2, "locked_at", ClockProvider.now()); + bind(statement, 3, "locked_by", getHostname()); + return Mono.from(statement.execute()) + .flatMap(it -> Mono.from(it.getRowsUpdated())) + .map(it -> it > 0); + }, + this::handleInsertionException); } - public Publisher updateRecordReactive(@NonNull LockConfiguration lockConfiguration) { - String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + ", locked_at = " + toParameter(2, "locked_at") + ", locked_by = " + toParameter(3, "locked_by") + " WHERE name = " + toParameter(4, "name") + " AND lock_until <= " + toParameter(5, "now"); - return executeCommand(sql, statement -> { - Instant now = ClockProvider.now(); - bind(statement, 0, "lock_until", lockConfiguration.getLockAtMostUntil()); - bind(statement, 1, "locked_at", now); - bind(statement, 2, "locked_by", getHostname()); - bind(statement, 3, "name", lockConfiguration.getName()); - bind(statement, 4, "now", now); - return Mono.from(statement.execute()).flatMap(it -> Mono.from(it.getRowsUpdated())).map(it -> it > 0); - }, this::handleUpdateException); + public Publisher updateRecordReactive(LockConfiguration lockConfiguration) { + String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + ", locked_at = " + + toParameter(2, "locked_at") + ", locked_by = " + toParameter(3, "locked_by") + " WHERE name = " + + toParameter(4, "name") + " AND lock_until <= " + toParameter(5, "now"); + return executeCommand( + sql, + statement -> { + Instant now = ClockProvider.now(); + bind(statement, 0, "lock_until", lockConfiguration.getLockAtMostUntil()); + bind(statement, 1, "locked_at", now); + bind(statement, 2, "locked_by", getHostname()); + bind(statement, 3, "name", lockConfiguration.getName()); + bind(statement, 4, "now", now); + return Mono.from(statement.execute()) + .flatMap(it -> Mono.from(it.getRowsUpdated())) + .map(it -> it > 0); + }, + this::handleUpdateException); } - public Publisher extendReactive(@NonNull LockConfiguration lockConfiguration) { - String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + " WHERE name = " + toParameter(2, "name") + " AND locked_by = " + toParameter(3, "locked_by") + " AND lock_until > " + toParameter(4, "now"); + public Publisher extendReactive(LockConfiguration lockConfiguration) { + String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + " WHERE name = " + + toParameter(2, "name") + " AND locked_by = " + toParameter(3, "locked_by") + " AND lock_until > " + + toParameter(4, "now"); logger.debug("Extending lock={} until={}", lockConfiguration.getName(), lockConfiguration.getLockAtMostUntil()); - return executeCommand(sql, statement -> { - bind(statement, 0, "lock_until", lockConfiguration.getLockAtMostUntil()); - bind(statement, 1, "name", lockConfiguration.getName()); - bind(statement, 2, "locked_by", getHostname()); - bind(statement, 3, "now", ClockProvider.now()); - return Mono.from(statement.execute()).flatMap(it -> Mono.from(it.getRowsUpdated())).map(it -> it > 0); - }, this::handleUnlockException); + return executeCommand( + sql, + statement -> { + bind(statement, 0, "lock_until", lockConfiguration.getLockAtMostUntil()); + bind(statement, 1, "name", lockConfiguration.getName()); + bind(statement, 2, "locked_by", getHostname()); + bind(statement, 3, "now", ClockProvider.now()); + return Mono.from(statement.execute()) + .flatMap(it -> Mono.from(it.getRowsUpdated())) + .map(it -> it > 0); + }, + this::handleUnlockException); } - public Publisher unlockReactive(@NonNull LockConfiguration lockConfiguration) { - String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + " WHERE name = " + toParameter(2, "name"); - return executeCommand(sql, statement -> { - bind(statement, 0, "lock_until", lockConfiguration.getUnlockTime()); - bind(statement, 1, "name", lockConfiguration.getName()); - return Mono.from(statement.execute()).flatMap(it -> Mono.from(it.getRowsUpdated())).then(); - }, (s, t) -> handleUnlockException(s, t).then()); + public Publisher unlockReactive(LockConfiguration lockConfiguration) { + String sql = "UPDATE " + tableName + " SET lock_until = " + toParameter(1, "lock_until") + " WHERE name = " + + toParameter(2, "name"); + return executeCommand( + sql, + statement -> { + bind(statement, 0, "lock_until", lockConfiguration.getUnlockTime()); + bind(statement, 1, "name", lockConfiguration.getName()); + return Mono.from(statement.execute()) + .flatMap(it -> Mono.from(it.getRowsUpdated())) + .then(); + }, + (s, t) -> handleUnlockException(s, t).then()); } protected abstract Mono executeCommand( - String sql, - Function> body, - BiFunction> exceptionHandler - ); + String sql, Function> body, BiFunction> exceptionHandler); protected abstract String toParameter(int index, String name); @@ -123,8 +147,11 @@ Mono handleInsertionException(String sql, Throwable e) { if (e instanceof R2dbcDataIntegrityViolationException) { // lock record already exists } else { - // can not throw exception here, some drivers (Postgres) do not throw SQLIntegrityConstraintViolationException on duplicate key - // we will try update in the next step, su if there is another problem, an exception will be thrown there + // can not throw exception here, some drivers (Postgres) do not throw + // SQLIntegrityConstraintViolationException on duplicate key + // we will try update in the next step, su if there is another problem, an + // exception will be + // thrown there logger.debug("Exception thrown when inserting record", e); } return Mono.just(false); diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcAdapter.java b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcAdapter.java index 727673b4a..de36a9a8a 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcAdapter.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcAdapter.java @@ -1,8 +1,6 @@ package net.javacrumbs.shedlock.provider.r2dbc; import io.r2dbc.spi.Statement; -import net.javacrumbs.shedlock.support.annotation.NonNull; - import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -11,37 +9,24 @@ abstract class R2dbcAdapter { private static final String MSSQL_NAME = "Microsoft SQL Server"; private static final String MYSQL_NAME = "MySQL"; + private static final String JASYNC_MYSQL_NAME = "Jasync-MySQL"; private static final String MARIA_NAME = "MariaDB"; private static final String ORACLE_NAME = "Oracle Database"; - static R2dbcAdapter create(@NonNull String driver) { - switch (driver) { - case MSSQL_NAME: - return new DefaultR2dbcAdapter( - (index, name) -> "@" + name, - R2dbcAdapter::toLocalDate, - R2dbcAdapter::bindByName - ); - case MYSQL_NAME: - case MARIA_NAME: - return new DefaultR2dbcAdapter( - (index, name) -> "?", - R2dbcAdapter::toLocalDate, - R2dbcAdapter::bindByIndex - ); - case ORACLE_NAME: - return new DefaultR2dbcAdapter( - (index, name) -> ":" + name, - R2dbcAdapter::toLocalDate, - R2dbcAdapter::bindByName - ); - default: - return new DefaultR2dbcAdapter( - (index, name) -> "$" + index, - R2dbcAdapter::toInstant, - R2dbcAdapter::bindByIndex - ); - } + static R2dbcAdapter create(String driver) { + return switch (driver) { + case MSSQL_NAME -> + new DefaultR2dbcAdapter( + (index, name) -> "@" + name, R2dbcAdapter::toLocalDate, R2dbcAdapter::bindByName); + case MYSQL_NAME, JASYNC_MYSQL_NAME, MARIA_NAME -> + new DefaultR2dbcAdapter((index, name) -> "?", R2dbcAdapter::toLocalDate, R2dbcAdapter::bindByIndex); + case ORACLE_NAME -> + new DefaultR2dbcAdapter( + (index, name) -> ":" + name, R2dbcAdapter::toLocalDate, R2dbcAdapter::bindByName); + default -> + new DefaultR2dbcAdapter( + (index, name) -> "$" + index, R2dbcAdapter::toInstant, R2dbcAdapter::bindByIndex); + }; } private static Instant toInstant(Instant date) { @@ -55,11 +40,11 @@ private static LocalDateTime toLocalDate(Instant date) { private static void bindByName(Statement statement, int index, String name, Object value) { statement.bind(name, value); } + private static void bindByIndex(Statement statement, int index, String name, Object value) { statement.bind(index, value); } - protected abstract String toParameter(int index, String name); public abstract void bind(Statement statement, int index, String name, Object value); @@ -70,10 +55,7 @@ private static class DefaultR2dbcAdapter extends R2dbcAdapter { private final ValueBinder binder; private DefaultR2dbcAdapter( - @NonNull ParameterResolver parameterResolver, - @NonNull Function dateConverter, - @NonNull ValueBinder binder - ) { + ParameterResolver parameterResolver, Function dateConverter, ValueBinder binder) { this.parameterResolver = parameterResolver; this.dateConverter = dateConverter; this.binder = binder; diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcLockProvider.java b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcLockProvider.java index f0cf43917..0dcca64c8 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcLockProvider.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcLockProvider.java @@ -17,36 +17,30 @@ import io.r2dbc.spi.ConnectionFactory; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; -import net.javacrumbs.shedlock.support.annotation.NonNull; /** - * Lock provided by plain R2DBC SPI. It uses a table that contains lock_name and locked_until. + * Lock provided by plain R2DBC SPI. It uses a table that contains lock_name and + * locked_until. + * *

    - *
  1. - * Attempts to insert a new lock record. Since lock name is a primary key, it fails if the record already exists. As an optimization, - * we keep in-memory track of created lock records. - *
  2. - *
  3. - * If the insert succeeds (1 inserted row) we have the lock. - *
  4. - *
  5. - * If the insert failed due to duplicate key or we have skipped the insertion, we will try to update lock record using - * UPDATE tableName SET lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now - *
  6. - *
  7. - * If the update succeeded (1 updated row), we have the lock. If the update failed (0 updated rows) somebody else holds the lock - *
  8. - *
  9. - * When unlocking, lock_until is set to now. - *
  10. + *
  11. Attempts to insert a new lock record. Since lock name is a primary key, + * it fails if the record already exists. As an optimization, we keep in-memory + * track of created lock records. + *
  12. If the insert succeeds (1 inserted row) we have the lock. + *
  13. If the insert failed due to duplicate key or we have skipped the + * insertion, we will try to update lock record using UPDATE tableName SET + * lock_until = :lockUntil WHERE name = :lockName AND lock_until <= :now + *
  14. If the update succeeded (1 updated row), we have the lock. If the update + * failed (0 updated rows) somebody else holds the lock + *
  15. When unlocking, lock_until is set to now. *
*/ public class R2dbcLockProvider extends StorageBasedLockProvider { - public R2dbcLockProvider(@NonNull ConnectionFactory connectionFactory) { + public R2dbcLockProvider(ConnectionFactory connectionFactory) { this(connectionFactory, "shedlock"); } - public R2dbcLockProvider(@NonNull ConnectionFactory connectionFactory, @NonNull String tableName) { + public R2dbcLockProvider(ConnectionFactory connectionFactory, String tableName) { super(new R2dbcStorageAccessor(connectionFactory, tableName)); } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcStorageAccessor.java b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcStorageAccessor.java index 5d392d2ec..013683b9e 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcStorageAccessor.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/R2dbcStorageAccessor.java @@ -15,45 +15,43 @@ */ package net.javacrumbs.shedlock.provider.r2dbc; +import static java.util.Objects.requireNonNull; + import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.Statement; -import net.javacrumbs.shedlock.support.annotation.NonNull; -import reactor.core.publisher.Mono; - import java.util.function.BiFunction; import java.util.function.Function; - -import static java.util.Objects.requireNonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import reactor.core.publisher.Mono; class R2dbcStorageAccessor extends AbstractR2dbcStorageAccessor { private final ConnectionFactory connectionFactory; + + @Nullable private R2dbcAdapter adapter; - R2dbcStorageAccessor(@NonNull ConnectionFactory connectionFactory, @NonNull String tableName) { + R2dbcStorageAccessor(ConnectionFactory connectionFactory, String tableName) { super(tableName); this.connectionFactory = requireNonNull(connectionFactory, "dataSource can not be null"); } @Override protected Mono executeCommand( - String sql, - Function> body, - BiFunction> exceptionHandler - ) { + String sql, Function> body, BiFunction> exceptionHandler) { return Mono.usingWhen( - Mono.from(connectionFactory.create()).doOnNext(it -> it.setAutoCommit(true)), - conn -> body.apply(conn.createStatement(sql)).onErrorResume(throwable -> exceptionHandler.apply(sql, throwable)), - Connection::close, - (connection, throwable) -> Mono.from(connection.close()).then(exceptionHandler.apply(sql, throwable)), - connection -> Mono.from(connection.close()).then() - ); + Mono.from(connectionFactory.create()).doOnNext(it -> it.setAutoCommit(true)), + conn -> body.apply(conn.createStatement(sql)) + .onErrorResume(throwable -> exceptionHandler.apply(sql, throwable)), + Connection::close, + (connection, throwable) -> Mono.from(connection.close()).then(exceptionHandler.apply(sql, throwable)), + connection -> Mono.from(connection.close()).then()); } @Override protected String toParameter(int index, String name) { - return getAdapter().toParameter(index, name); + return getAdapter().toParameter(index, name); } @Override diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/package-info.java b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/package-info.java new file mode 100644 index 000000000..fff6a2f9b --- /dev/null +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/main/java/net/javacrumbs/shedlock/provider/r2dbc/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.r2dbc; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcTest.java index f16c184e2..b325e92e4 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/AbstractR2dbcTest.java @@ -1,51 +1,48 @@ package net.javacrumbs.shedlock.provider.r2dbc; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + import io.r2dbc.pool.ConnectionPool; import io.r2dbc.pool.ConnectionPoolConfiguration; import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; +import java.time.Duration; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import net.javacrumbs.shedlock.test.support.jdbc.AbstractJdbcLockProviderIntegrationTest; +import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; -import java.time.Duration; - -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - @TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class AbstractR2dbcTest extends AbstractJdbcLockProviderIntegrationTest { + private final DbConfig dbConfig; private ConnectionFactory connectionFactory; + AbstractR2dbcTest(DbConfig dbConfig) { + this.dbConfig = dbConfig; + } + @BeforeAll public void startDb() { getDbConfig().startDb(); ConnectionFactory cf = ConnectionFactories.get( - ConnectionFactoryOptions - .parse(getDbConfig().getR2dbcUrl()) - .mutate() - .option(USER, getDbConfig().getUsername()) - .option(PASSWORD, getDbConfig().getPassword()) - .build() - ); - - if (usePool()) { + ConnectionFactoryOptions.parse(getDbConfig().getR2dbcUrl()) + .mutate() + .option(USER, getDbConfig().getUsername()) + .option(PASSWORD, getDbConfig().getPassword()) + .build()); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(cf) + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(cf) .maxIdleTime(Duration.ofMillis(1000)) .maxSize(20) .build(); - connectionFactory = new ConnectionPool(configuration); - } else { - // Oracle does not support pool - https://github.com/oracle/oracle-r2dbc/issues/29 - connectionFactory = cf; - } + connectionFactory = new ConnectionPool(configuration); } @AfterAll @@ -67,7 +64,8 @@ protected ConnectionFactory connectionFactory() { return connectionFactory; } - protected boolean usePool() { - return true; + @Override + public DbConfig getDbConfig() { + return dbConfig; } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MariaR2dbcLockProviderIntegrationTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MariaR2dbcLockProviderIntegrationTest.java index c9e6fbc1e..559258d76 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MariaR2dbcLockProviderIntegrationTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MariaR2dbcLockProviderIntegrationTest.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.r2dbc; -import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.MariaDbConfig; +import org.junit.jupiter.api.Disabled; +@Disabled("No R2DBC 1.0.0 compatible driver") public class MariaR2dbcLockProviderIntegrationTest extends AbstractR2dbcTest { - private static final DbConfig dbConfig = new MariaDbConfig(); - @Override - protected DbConfig getDbConfig() { - return dbConfig; + public MariaR2dbcLockProviderIntegrationTest() { + super(new MariaDbConfig()); } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MsSqlR2dbcLockProviderIntegrationTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MsSqlR2dbcLockProviderIntegrationTest.java index 2dd484d41..6f7b845a3 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MsSqlR2dbcLockProviderIntegrationTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MsSqlR2dbcLockProviderIntegrationTest.java @@ -1,28 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.r2dbc; -import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.MsSqlServerConfig; +import org.junit.jupiter.api.Disabled; +@Disabled("Maven build does not finish when enabled") public class MsSqlR2dbcLockProviderIntegrationTest extends AbstractR2dbcTest { - private static final DbConfig dbConfig = new MsSqlServerConfig(); - - @Override - protected DbConfig getDbConfig() { - return dbConfig; + public MsSqlR2dbcLockProviderIntegrationTest() { + super(new MsSqlServerConfig()); } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MySqlR2dbcLockProviderIntegrationTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MySqlR2dbcLockProviderIntegrationTest.java index 3ae40fead..b073b22e1 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MySqlR2dbcLockProviderIntegrationTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/MySqlR2dbcLockProviderIntegrationTest.java @@ -1,28 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.r2dbc; -import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.MySqlConfig; public class MySqlR2dbcLockProviderIntegrationTest extends AbstractR2dbcTest { - private static final DbConfig dbConfig = new MySqlConfig(); - - @Override - protected DbConfig getDbConfig() { - return dbConfig; + public MySqlR2dbcLockProviderIntegrationTest() { + super(new MySqlConfig()); } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/OracleR2dbcLockProviderIntegrationTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/OracleR2dbcLockProviderIntegrationTest.java index ac8c1bade..10e0cccb1 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/OracleR2dbcLockProviderIntegrationTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/OracleR2dbcLockProviderIntegrationTest.java @@ -1,33 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.r2dbc; -import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.OracleServerConfig; public class OracleR2dbcLockProviderIntegrationTest extends AbstractR2dbcTest { - private static final DbConfig dbConfig = new OracleServerConfig(); - - @Override - protected DbConfig getDbConfig() { - return dbConfig; - } - - @Override - protected boolean usePool() { - return false; + public OracleR2dbcLockProviderIntegrationTest() { + super(new OracleServerConfig()); } } diff --git a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/PostgresR2dbcLockProviderIntegrationTest.java b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/PostgresR2dbcLockProviderIntegrationTest.java index d8897bffb..a3901977a 100644 --- a/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/PostgresR2dbcLockProviderIntegrationTest.java +++ b/providers/r2dbc/shedlock-provider-r2dbc/src/test/java/net/javacrumbs/shedlock/provider/r2dbc/PostgresR2dbcLockProviderIntegrationTest.java @@ -1,28 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.r2dbc; -import net.javacrumbs.shedlock.test.support.jdbc.DbConfig; import net.javacrumbs.shedlock.test.support.jdbc.PostgresConfig; public class PostgresR2dbcLockProviderIntegrationTest extends AbstractR2dbcTest { - private static final DbConfig dbConfig = new PostgresConfig(); - - @Override - protected DbConfig getDbConfig() { - return dbConfig; + public PostgresR2dbcLockProviderIntegrationTest() { + super(new PostgresConfig()); } } diff --git a/providers/redis/shedlock-provider-redis-jedis4/pom.xml b/providers/redis/shedlock-provider-redis-jedis4/pom.xml index b3a234a01..3078be97c 100644 --- a/providers/redis/shedlock-provider-redis-jedis4/pom.xml +++ b/providers/redis/shedlock-provider-redis-jedis4/pom.xml @@ -3,45 +3,39 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-redis-jedis4 - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} net.javacrumbs.shedlock - shedlock-core + shedlock-support-redis ${project.version} redis.clients jedis - 4.3.0 + 6.1.0 + net.javacrumbs.shedlock - shedlock-test-support + shedlock-test-support-redis ${project.version} test - com.playtika.testcontainers - embedded-redis - 2.2.12 - test - - - - org.testcontainers - junit-jupiter - ${test-containers.ver} + org.springframework + spring-beans + ${spring.version} test diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProvider.java b/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProvider.java index 5d8d65c79..908f05cf0 100644 --- a/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProvider.java +++ b/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProvider.java @@ -1,178 +1,170 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.redis.jedis4; -import net.javacrumbs.shedlock.core.AbstractSimpleLock; -import net.javacrumbs.shedlock.core.ClockProvider; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.DEFAULT_KEY_PREFIX; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.ENV_DEFAULT; +import static redis.clients.jedis.params.SetParams.setParams; + +import java.util.List; +import java.util.Optional; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockTemplate; import redis.clients.jedis.Jedis; import redis.clients.jedis.commands.JedisCommands; import redis.clients.jedis.params.SetParams; import redis.clients.jedis.util.Pool; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; -import static redis.clients.jedis.params.SetParams.setParams; - /** - * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking mechanism. + * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking + * mechanism. + * *

- * See https://redis.io/commands/set + * See Set command */ -public class JedisLockProvider implements LockProvider { - - private static final String KEY_PREFIX = "job-lock"; - private static final String ENV_DEFAULT = "default"; +public class JedisLockProvider implements ExtensibleLockProvider { - private final JedisTemplate jedisTemplate; - private final String environment; + private final InternalRedisLockProvider internalRedisLockProvider; - public JedisLockProvider(@NonNull Pool jedisPool) { + public JedisLockProvider(Pool jedisPool) { this(jedisPool, ENV_DEFAULT); } /** * Creates JedisLockProvider * - * @param jedisPool Jedis connection pool - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis + * @param jedisPool + * Jedis connection pool + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis */ - public JedisLockProvider(@NonNull Pool jedisPool, @NonNull String environment) { - this.jedisTemplate = new JedisPoolTemplate(jedisPool); - this.environment = environment; + public JedisLockProvider(Pool jedisPool, String environment) { + this(jedisPool, environment, false); } /** * Creates JedisLockProvider * - * @param jedisCommands implementation of JedisCommands. - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis + * @param jedisPool + * Jedis connection pool + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param safeUpdate When set to true and the lock is held for more than lockAtMostFor, and the lock + * is already held by somebody else, we don't release/extend the lock. */ - public JedisLockProvider(@NonNull JedisCommands jedisCommands, @NonNull String environment) { - this.jedisTemplate = new JedisCommandsTemplate(jedisCommands); - this.environment = environment; + public JedisLockProvider(Pool jedisPool, String environment, boolean safeUpdate) { + this.internalRedisLockProvider = new InternalRedisLockProvider( + new JedisPoolTemplate(jedisPool), environment, DEFAULT_KEY_PREFIX, safeUpdate); } - @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { - long expireTime = getMsUntil(lockConfiguration.getLockAtMostUntil()); - - String key = buildKey(lockConfiguration.getName(), this.environment); - - String rez = jedisTemplate.set(key, buildValue(), setParams().nx().px(expireTime)); - - if ("OK".equals(rez)) { - return Optional.of(new RedisLock(key, jedisTemplate, lockConfiguration)); - } + /** + * Creates JedisLockProvider + * + * @param jedisCommands + * implementation of JedisCommands. + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + */ + public JedisLockProvider(JedisCommands jedisCommands, String environment) { + this(jedisCommands, environment, false); + } - return Optional.empty(); + /** + * Creates JedisLockProvider + * + * @param jedisCommands + * implementation of JedisCommands. + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param safeUpdate When set to true and the lock is held for more than lockAtMostFor, and the lock + * is already held by somebody else, we don't release/extend the lock. + */ + public JedisLockProvider(JedisCommands jedisCommands, String environment, boolean safeUpdate) { + this.internalRedisLockProvider = new InternalRedisLockProvider( + new JedisCommandsTemplate(jedisCommands), environment, DEFAULT_KEY_PREFIX, safeUpdate); } - private static final class RedisLock extends AbstractSimpleLock { - private final String key; - private final JedisTemplate jedisTemplate; + @Override + public Optional lock(LockConfiguration lockConfiguration) { + return internalRedisLockProvider.lock(lockConfiguration); + } - private RedisLock(String key, JedisTemplate jedisTemplate, LockConfiguration lockConfiguration) { - super(lockConfiguration); - this.key = key; - this.jedisTemplate = jedisTemplate; + private record JedisPoolTemplate(Pool jedisPool) implements InternalRedisLockTemplate { + @Override + public boolean setIfAbsent(String key, String value, long expirationMs) { + return set(key, value, setParams().nx().px(expirationMs)); } @Override - public void doUnlock() { - long keepLockFor = getMsUntil(lockConfiguration.getLockAtLeastUntil()); - - // lock at least until is in the past - if (keepLockFor <= 0) { - try { - jedisTemplate.del(key); - } catch (Exception e) { - throw new LockException("Can not remove node", e); - } - } else { - jedisTemplate.set(key, buildValue(), setParams().xx().px(keepLockFor)); - } + public boolean setIfPresent(String key, String value, long expirationMs) { + return set(key, value, setParams().xx().px(expirationMs)); } - } - - private static long getMsUntil(Instant instant) { - return Duration.between(ClockProvider.now(), instant).toMillis(); - } - - static String buildKey(String lockName, String env) { - return String.format("%s:%s:%s", KEY_PREFIX, env, lockName); - } - - private static String buildValue() { - return String.format("ADDED:%s@%s", toIsoString(ClockProvider.now()), getHostname()); - } - private interface JedisTemplate { - String set(String key, String value, SetParams setParams); - - void del(String key); - } - - private static class JedisPoolTemplate implements JedisTemplate { - private final Pool jedisPool; - - private JedisPoolTemplate(Pool jedisPool) { - this.jedisPool = jedisPool; + private boolean set(String key, String value, SetParams params) { + try (Jedis jedis = jedisPool.getResource()) { + return "OK".equals(jedis.set(key, value, params)); + } } @Override - public String set(String key, String value, SetParams setParams) { + public Object eval(String script, String key, String... values) { try (Jedis jedis = jedisPool.getResource()) { - return jedis.set(key, value, setParams); + return jedis.eval(script, List.of(key), List.of(values)); } } @Override - public void del(String key) { + public void delete(String key) { try (Jedis jedis = jedisPool.getResource()) { jedis.del(key); } } } - private static class JedisCommandsTemplate implements JedisTemplate { - private final JedisCommands jedisCommands; + private record JedisCommandsTemplate(JedisCommands jedisCommands) implements InternalRedisLockTemplate { + @Override + public boolean setIfAbsent(String key, String value, long expirationMs) { + return set(key, value, setParams().nx().px(expirationMs)); + } + + @Override + public boolean setIfPresent(String key, String value, long expirationMs) { + return set(key, value, setParams().xx().px(expirationMs)); + } - private JedisCommandsTemplate(JedisCommands jedisCommands) { - this.jedisCommands = jedisCommands; + private boolean set(String key, String value, SetParams params) { + return "OK".equals(jedisCommands.set(key, value, params)); } @Override - public String set(String key, String value, SetParams setParams) { - return jedisCommands.set(key, value, setParams); + public Object eval(String script, String key, String... values) { + return jedisCommands.eval(script, List.of(key), List.of(values)); } @Override - public void del(String key) { + public void delete(String key) { jedisCommands.del(key); } } diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/package-info.java b/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/package-info.java new file mode 100644 index 000000000..718a56dca --- /dev/null +++ b/providers/redis/shedlock-provider-redis-jedis4/src/main/java/net/javacrumbs/shedlock/provider/redis/jedis4/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.redis.jedis4; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProviderIntegrationTest.java index c6d255f28..497a809a3 100644 --- a/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProviderIntegrationTest.java +++ b/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/JedisLockProviderIntegrationTest.java @@ -1,22 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.redis.jedis4; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; +import static net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer.ENV; +import static net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer.PORT; + +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.provider.redis.testsupport.AbstractRedisIntegrationTest; +import net.javacrumbs.shedlock.provider.redis.testsupport.AbstractRedisSafeUpdateIntegrationTest; +import net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.testcontainers.junit.jupiter.Container; @@ -26,79 +29,104 @@ import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; -import static net.javacrumbs.shedlock.provider.redis.jedis4.RedisContainer.ENV; -import static net.javacrumbs.shedlock.provider.redis.jedis4.RedisContainer.PORT; -import static org.assertj.core.api.Assertions.assertThat; - @Testcontainers -public class JedisLockProviderIntegrationTest { +public class JedisLockProviderIntegrationTest { @Container public static final RedisContainer redis = new RedisContainer(PORT); @Nested - class Cluster extends AbstractLockProviderIntegrationTest { - private LockProvider lockProvider; + class Cluster extends AbstractRedisIntegrationTest { + private ExtensibleLockProvider lockProvider; private JedisCluster jedisCluster; @BeforeEach public void createLockProvider() { - jedisCluster = new JedisCluster(new HostAndPort(redis.getContainerIpAddress(), redis.getFirstMappedPort())); + jedisCluster = new JedisCluster(new HostAndPort(redis.getHost(), redis.getFirstMappedPort())); lockProvider = new JedisLockProvider(jedisCluster, ENV); } + @Override - protected void assertUnlocked(String lockName) { - assertThat(getLock(lockName)).isNull(); + protected String getLock(String lockName) { + return jedisCluster.get(buildKey(lockName, ENV)); } @Override - protected void assertLocked(String lockName) { - assertThat(getLock(lockName)).isNotNull(); + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; } + } + + @Nested + class ClusterSafeUpdate extends AbstractRedisSafeUpdateIntegrationTest { + private ExtensibleLockProvider lockProvider; - private String getLock(String lockName) { - return jedisCluster.get(JedisLockProvider.buildKey(lockName, ENV)); + private JedisCluster jedisCluster; + + @BeforeEach + public void createLockProvider() { + jedisCluster = new JedisCluster(new HostAndPort(redis.getHost(), redis.getFirstMappedPort())); + lockProvider = new JedisLockProvider(jedisCluster, ENV, true); } @Override - protected LockProvider getLockProvider() { + protected String getLock(String lockName) { + return jedisCluster.get(buildKey(lockName, ENV)); + } + + @Override + protected ExtensibleLockProvider getLockProvider() { return lockProvider; } } @Nested - class Pool extends AbstractLockProviderIntegrationTest { - private LockProvider lockProvider; + class Pool extends AbstractRedisIntegrationTest { + private ExtensibleLockProvider lockProvider; private JedisPool jedisPool; @BeforeEach public void createLockProvider() { - jedisPool = new JedisPool(redis.getContainerIpAddress(), redis.getMappedPort(PORT)); + jedisPool = new JedisPool(redis.getHost(), redis.getMappedPort(PORT)); lockProvider = new JedisLockProvider(jedisPool, ENV); } @Override - protected void assertUnlocked(String lockName) { + protected String getLock(String lockName) { try (Jedis jedis = jedisPool.getResource()) { - assertThat(getLock(lockName, jedis)).isNull(); + return jedis.get(buildKey(lockName, ENV)); } } @Override - protected void assertLocked(String lockName) { - try (Jedis jedis = jedisPool.getResource()) { - assertThat(getLock(lockName, jedis)).isNotNull(); - } + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; + } + } + + @Nested + class PoolSafeUpdate extends AbstractRedisSafeUpdateIntegrationTest { + private ExtensibleLockProvider lockProvider; + + private JedisPool jedisPool; + + @BeforeEach + public void createLockProvider() { + jedisPool = new JedisPool(redis.getHost(), redis.getMappedPort(PORT)); + lockProvider = new JedisLockProvider(jedisPool, ENV, true); } - private String getLock(String lockName, Jedis jedis) { - return jedis.get(JedisLockProvider.buildKey(lockName, ENV)); + @Override + protected String getLock(String lockName) { + try (Jedis jedis = jedisPool.getResource()) { + return jedis.get(buildKey(lockName, ENV)); + } } @Override - protected LockProvider getLockProvider() { + protected ExtensibleLockProvider getLockProvider() { return lockProvider; } } diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/RedisContainer.java b/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/RedisContainer.java deleted file mode 100644 index b761dacff..000000000 --- a/providers/redis/shedlock-provider-redis-jedis4/src/test/java/net/javacrumbs/shedlock/provider/redis/jedis4/RedisContainer.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.provider.redis.jedis4; - -import com.playtika.test.redis.RedisProperties; -import com.playtika.test.redis.wait.RedisClusterStatusCheck; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.FixedHostPortGenericContainer; -import org.testcontainers.utility.MountableFile; - -class RedisContainer extends FixedHostPortGenericContainer { - - private static final Logger LOGGER = LoggerFactory.getLogger(RedisContainer.class); - - final static int PORT = 6379; - - final static String ENV = "test"; - - - public RedisContainer(int hostPort) { - super("redis:5-alpine"); - - RedisProperties properties = new RedisProperties(); - properties.host = "localhost"; - properties.port = hostPort; - properties.requirepass = false; - - this.withFixedExposedPort(hostPort, PORT) - .withExposedPorts(PORT) - .withLogConsumer(frame -> LOGGER.info(frame.getUtf8String())) - .withCopyFileToContainer(MountableFile.forClasspathResource("redis.conf"), "/data/redis.conf") - .withCopyFileToContainer(MountableFile.forClasspathResource("nodes.conf"), "/data/nodes.conf") - .waitingFor(new RedisClusterStatusCheck(properties)) - //.waitingFor(new RedisStatusCheck(properties)) - .withCommand("redis-server", "/data/redis.conf"); - } -} diff --git a/providers/redis/shedlock-provider-redis-lettuce/pom.xml b/providers/redis/shedlock-provider-redis-lettuce/pom.xml new file mode 100644 index 000000000..3651418be --- /dev/null +++ b/providers/redis/shedlock-provider-redis-lettuce/pom.xml @@ -0,0 +1,74 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-redis-lettuce + ${project.groupId}:${project.artifactId} + + + + net.javacrumbs.shedlock + shedlock-support-redis + ${project.version} + + + + io.lettuce + lettuce-core + 6.8.0.RELEASE + + + + net.javacrumbs.shedlock + shedlock-test-support-redis + ${project.version} + test + + + + redis.clients + jedis + 6.1.0 + test + + + + org.springframework + spring-beans + ${spring.version} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.redis.lettuce + + + + + + + + + diff --git a/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProvider.java b/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProvider.java new file mode 100644 index 000000000..d94b72fcb --- /dev/null +++ b/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProvider.java @@ -0,0 +1,104 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.redis.lettuce; + +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.DEFAULT_KEY_PREFIX; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.ENV_DEFAULT; + +import io.lettuce.core.ScriptOutputType; +import io.lettuce.core.SetArgs; +import io.lettuce.core.api.StatefulRedisConnection; +import java.util.Optional; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockTemplate; + +/** + * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking + * mechanism. + * + *

+ * See Set command + */ +public class LettuceLockProvider implements ExtensibleLockProvider { + + private final InternalRedisLockProvider internalRedisLockProvider; + + public LettuceLockProvider(StatefulRedisConnection connection) { + this(connection, ENV_DEFAULT); + } + + /** + * Creates LettuceLockProvider + * + * @param connection StatefulRedisConnection + * @param environment environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + */ + public LettuceLockProvider(StatefulRedisConnection connection, String environment) { + this(connection, environment, false); + } + + /** + * Creates LettuceLockProvider + * + * @param connection StatefulRedisConnection + * @param environment environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param safeUpdate When set to true and the lock is held for more than lockAtMostFor, and the lock + * is already held by somebody else, we don't release/extend the lock. + */ + public LettuceLockProvider( + StatefulRedisConnection connection, String environment, boolean safeUpdate) { + this.internalRedisLockProvider = new InternalRedisLockProvider( + new LettuceRedisLockTemplate(connection), environment, DEFAULT_KEY_PREFIX, safeUpdate); + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + return internalRedisLockProvider.lock(lockConfiguration); + } + + private record LettuceRedisLockTemplate(StatefulRedisConnection connection) + implements InternalRedisLockTemplate { + + @Override + public boolean setIfAbsent(String key, String value, long expirationMs) { + return set(key, value, SetArgs.Builder.nx().px(expirationMs)); + } + + @Override + public boolean setIfPresent(String key, String value, long expirationMs) { + return set(key, value, SetArgs.Builder.xx().px(expirationMs)); + } + + private boolean set(String key, String value, SetArgs args) { + return "OK".equals(connection.sync().set(key, value, args)); + } + + @Override + public Object eval(String script, String key, String... values) { + return connection.sync().eval(script, ScriptOutputType.INTEGER, new String[] {key}, values); + } + + @Override + public void delete(String key) { + connection.sync().del(key); + } + } +} diff --git a/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/package-info.java b/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/package-info.java new file mode 100644 index 000000000..aeaa2278a --- /dev/null +++ b/providers/redis/shedlock-provider-redis-lettuce/src/main/java/net/javacrumbs/shedlock/provider/redis/lettuce/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.redis.lettuce; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/redis/shedlock-provider-redis-lettuce/src/test/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-lettuce/src/test/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProviderIntegrationTest.java new file mode 100644 index 000000000..9aa1d9f2b --- /dev/null +++ b/providers/redis/shedlock-provider-redis-lettuce/src/test/java/net/javacrumbs/shedlock/provider/redis/lettuce/LettuceLockProviderIntegrationTest.java @@ -0,0 +1,109 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.redis.lettuce; + +import static net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer.ENV; +import static net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer.PORT; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.StatefulRedisConnection; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.provider.redis.testsupport.AbstractRedisIntegrationTest; +import net.javacrumbs.shedlock.provider.redis.testsupport.AbstractRedisSafeUpdateIntegrationTest; +import net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +public class LettuceLockProviderIntegrationTest { + + @Container + public static final RedisContainer redis = new RedisContainer(PORT); + + private static RedisClient createClient() { + String uri = String.format("redis://%s:%d", redis.getHost(), redis.getFirstMappedPort()); + return RedisClient.create(uri); + } + + @Nested + class Cluster extends AbstractRedisIntegrationTest { + + private ExtensibleLockProvider lockProvider; + private StatefulRedisConnection connection; + + @BeforeEach + public void createLockProvider() { + connection = createClient().connect(); + lockProvider = new LettuceLockProvider(connection, ENV); + } + + @Override + protected String getLock(String lockName) { + return connection.sync().get(buildKey(lockName, ENV)); + } + + @Override + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; + } + } + + @Nested + class Pool extends AbstractRedisIntegrationTest { + + private ExtensibleLockProvider lockProvider; + private StatefulRedisConnection connection; + + @BeforeEach + public void createLockProvider() { + connection = createClient().connect(); + lockProvider = new LettuceLockProvider(connection, ENV); + } + + @Override + protected String getLock(String lockName) { + return connection.sync().get(buildKey(lockName, ENV)); + } + + @Override + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; + } + } + + @Nested + class ClusterSafeUpdate extends AbstractRedisSafeUpdateIntegrationTest { + + private ExtensibleLockProvider lockProvider; + private StatefulRedisConnection connection; + + @BeforeEach + public void createLockProvider() { + connection = createClient().connect(); + lockProvider = new LettuceLockProvider(connection, ENV, true); + } + + @Override + protected String getLock(String lockName) { + return connection.sync().get(buildKey(lockName, ENV)); + } + + @Override + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; + } + } +} diff --git a/providers/redis/shedlock-provider-redis-lettuce/src/test/resources/logback.xml b/providers/redis/shedlock-provider-redis-lettuce/src/test/resources/logback.xml new file mode 100644 index 000000000..8ee4ac478 --- /dev/null +++ b/providers/redis/shedlock-provider-redis-lettuce/src/test/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/redis/shedlock-provider-redis-spring/pom.xml b/providers/redis/shedlock-provider-redis-spring/pom.xml index 285f9f6e1..7a077bb23 100644 --- a/providers/redis/shedlock-provider-redis-spring/pom.xml +++ b/providers/redis/shedlock-provider-redis-spring/pom.xml @@ -3,66 +3,60 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-redis-spring - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} net.javacrumbs.shedlock - shedlock-core + shedlock-support-redis ${project.version} org.springframework.data spring-data-redis - 3.0.0 + 3.5.3 io.projectreactor reactor-core - 3.5.0 + 3.7.9 true + net.javacrumbs.shedlock - shedlock-test-support + shedlock-test-support-redis ${project.version} test - - com.github.kstyrc - embedded-redis - 0.6 - test - - redis.clients jedis - 4.3.0 + 6.1.0 test io.lettuce lettuce-core - 6.2.1.RELEASE + 6.8.0.RELEASE test org.redisson - redisson-spring-data-20 - 3.18.0 + redisson-spring-data-32 + 3.51.0 test diff --git a/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/ReactiveRedisLockProvider.java b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/ReactiveRedisLockProvider.java index 7c4346c0d..4313e6e19 100644 --- a/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/ReactiveRedisLockProvider.java +++ b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/ReactiveRedisLockProvider.java @@ -1,143 +1,103 @@ package net.javacrumbs.shedlock.provider.redis.spring; -import net.javacrumbs.shedlock.core.AbstractSimpleLock; -import net.javacrumbs.shedlock.core.ClockProvider; +import static java.lang.Boolean.TRUE; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.DEFAULT_KEY_PREFIX; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.ENV_DEFAULT; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockTemplate; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveStringRedisTemplate; - -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; +import org.springframework.data.redis.core.script.DefaultRedisScript; /** - * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking mechanism. - * See https://redis.io/commands/set + * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking + * mechanism. See https://redis.io/commands/set */ public class ReactiveRedisLockProvider implements LockProvider { - private static final String KEY_PREFIX_DEFAULT = "job-lock"; - private static final String ENV_DEFAULT = "default"; + private final InternalRedisLockProvider internalRedisLockProvider; - private final ReactiveStringRedisTemplate redisTemplate; - private final String environment; - private final String keyPrefix; - - public ReactiveRedisLockProvider(@NonNull ReactiveRedisConnectionFactory redisConn) { + public ReactiveRedisLockProvider(ReactiveRedisConnectionFactory redisConn) { this(redisConn, ENV_DEFAULT); } /** * Creates ReactiveRedisLockProvider * - * @param redisConn ReactiveRedisConnectionFactory - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis + * @param redisConn + * ReactiveRedisConnectionFactory + * @param environment + * environment is part of the key and thus makes sure there is no + * key conflict between multiple ShedLock instances running on the + * same Redis */ - public ReactiveRedisLockProvider(@NonNull ReactiveRedisConnectionFactory redisConn, @NonNull String environment) { - this(redisConn, environment, KEY_PREFIX_DEFAULT); + public ReactiveRedisLockProvider(ReactiveRedisConnectionFactory redisConn, String environment) { + this(redisConn, environment, DEFAULT_KEY_PREFIX); } /** * Creates ReactiveRedisLockProvider * - * @param redisConn ReactiveRedisConnectionFactory - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis - * @param keyPrefix prefix of the key in Redis. + * @param redisConn + * ReactiveRedisConnectionFactory + * @param environment + * environment is part of the key and thus makes sure there is no + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param keyPrefix + * prefix of the key in Redis. */ - public ReactiveRedisLockProvider(@NonNull ReactiveRedisConnectionFactory redisConn, @NonNull String environment, @NonNull String keyPrefix) { + public ReactiveRedisLockProvider(ReactiveRedisConnectionFactory redisConn, String environment, String keyPrefix) { this(new ReactiveStringRedisTemplate(redisConn), environment, keyPrefix); } /** * Create ReactiveRedisLockProvider * - * @param redisTemplate ReactiveStringRedisTemplate - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis - * @param keyPrefix prefix of the key in Redis. + * @param redisTemplate + * ReactiveStringRedisTemplate + * @param environment + * environment is part of the key and thus makes sure there is no + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param keyPrefix + * prefix of the key in Redis. */ - public ReactiveRedisLockProvider(@NonNull ReactiveStringRedisTemplate redisTemplate, @NonNull String environment, @NonNull String keyPrefix) { - this.redisTemplate = redisTemplate; - this.environment = environment; - this.keyPrefix = keyPrefix; + public ReactiveRedisLockProvider(ReactiveStringRedisTemplate redisTemplate, String environment, String keyPrefix) { + this.internalRedisLockProvider = new InternalRedisLockProvider( + new ReactiveRedisLockTemplate(redisTemplate), environment, keyPrefix, false); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { - Instant now = ClockProvider.now(); - String key = ReactiveRedisLock.createKey(keyPrefix, environment, lockConfiguration.getName()); - String value = ReactiveRedisLock.createValue(now); - Duration expirationTime = Duration.between(now, lockConfiguration.getLockAtMostUntil()); - Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(key, value, expirationTime).block(); - if (Boolean.TRUE.equals(lockResult)) { - return Optional.of(new ReactiveRedisLock(key, redisTemplate, lockConfiguration)); - } - return Optional.empty(); - } - - private static final class ReactiveRedisLock extends AbstractSimpleLock { - private final String key; - private final ReactiveStringRedisTemplate redisTemplate; - - private static String createKey(String keyPrefix, String environment, String lockName) { - return String.format("%s:%s:%s", keyPrefix, environment, lockName); - } - - private static String createValue(Instant now) { - return String.format("ADDED:%s@%s", toIsoString(now), getHostname()); - } - - private ReactiveRedisLock(String key, ReactiveStringRedisTemplate redisTemplate, LockConfiguration lockConfiguration) { - super(lockConfiguration); - this.key = key; - this.redisTemplate = redisTemplate; - } - - @Override - protected void doUnlock() { - Instant now = ClockProvider.now(); - Duration expirationTime = Duration.between(now, lockConfiguration.getLockAtLeastUntil()); - if (expirationTime.isNegative() || expirationTime.isZero()) { - try { - redisTemplate.delete(key).block(); - } catch (Exception e) { - throw new LockException("Can not remove node", e); - } - } else { - String value = createValue(now); - redisTemplate.opsForValue().setIfPresent(key, value, expirationTime).block(); - } - } + public Optional lock(LockConfiguration lockConfiguration) { + return internalRedisLockProvider.lock(lockConfiguration); } public static class Builder { private final ReactiveStringRedisTemplate redisTemplate; private String environment = ENV_DEFAULT; - private String keyPrefix = KEY_PREFIX_DEFAULT; + private String keyPrefix = DEFAULT_KEY_PREFIX; - public Builder(@NonNull ReactiveRedisConnectionFactory redisConnectionFactory) { + public Builder(ReactiveRedisConnectionFactory redisConnectionFactory) { this.redisTemplate = new ReactiveStringRedisTemplate(redisConnectionFactory); } - public Builder(@NonNull ReactiveStringRedisTemplate redisTemplate) { + public Builder(ReactiveStringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } - public ReactiveRedisLockProvider.Builder environment(@NonNull String environment) { + public ReactiveRedisLockProvider.Builder environment(String environment) { this.environment = environment; return this; } - public ReactiveRedisLockProvider.Builder keyPrefix(@NonNull String keyPrefix) { + public ReactiveRedisLockProvider.Builder keyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; return this; } @@ -146,4 +106,39 @@ public ReactiveRedisLockProvider build() { return new ReactiveRedisLockProvider(redisTemplate, environment, keyPrefix); } } + + private record ReactiveRedisLockTemplate(ReactiveStringRedisTemplate redisTemplate) + implements InternalRedisLockTemplate { + + @Override + public boolean setIfAbsent(String key, String value, long expirationMs) { + return TRUE + == redisTemplate + .opsForValue() + .setIfAbsent(key, value, Duration.ofMillis(expirationMs)) + .block(); + } + + @Override + public boolean setIfPresent(String key, String value, long expirationMs) { + return TRUE + == redisTemplate + .opsForValue() + .setIfPresent(key, value, Duration.ofMillis(expirationMs)) + .block(); + } + + @Override + public Object eval(String script, String key, String... values) { + return redisTemplate + .execute(new DefaultRedisScript<>(script, Integer.class), List.of(key), List.of(values)) + .next() + .block(); + } + + @Override + public void delete(String key) { + redisTemplate.delete(key).block(); + } + } } diff --git a/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/RedisLockProvider.java b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/RedisLockProvider.java index a2beedde4..00793e7a1 100644 --- a/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/RedisLockProvider.java +++ b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/RedisLockProvider.java @@ -1,181 +1,183 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.redis.spring; -import net.javacrumbs.shedlock.core.AbstractSimpleLock; -import net.javacrumbs.shedlock.core.ClockProvider; +import static java.lang.Boolean.TRUE; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.DEFAULT_KEY_PREFIX; +import static net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider.ENV_DEFAULT; +import static org.springframework.data.redis.connection.RedisStringCommands.SetOption.SET_IF_ABSENT; +import static org.springframework.data.redis.connection.RedisStringCommands.SetOption.SET_IF_PRESENT; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; -import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockProvider; +import net.javacrumbs.shedlock.provider.redis.support.InternalRedisLockTemplate; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStringCommands.SetOption; +import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializer; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import static java.lang.Boolean.TRUE; -import static net.javacrumbs.shedlock.support.Utils.getHostname; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; -import static org.springframework.data.redis.connection.RedisStringCommands.SetOption.SET_IF_ABSENT; - /** - * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking mechanism. - * See https://redis.io/commands/set + * Uses Redis's `SET resource-name anystring NX PX max-lock-ms-time` as locking + * mechanism. See https://redis.io/commands/set */ -public class RedisLockProvider implements LockProvider { - private static final String KEY_PREFIX_DEFAULT = "job-lock"; - private static final String ENV_DEFAULT = "default"; - - private final StringRedisTemplate redisTemplate; - private final String environment; - private final String keyPrefix; +public class RedisLockProvider implements ExtensibleLockProvider { + private final InternalRedisLockProvider internalRedisLockProvider; - public RedisLockProvider(@NonNull RedisConnectionFactory redisConn) { + public RedisLockProvider(RedisConnectionFactory redisConn) { this(redisConn, ENV_DEFAULT); } /** * Creates RedisLockProvider * - * @param redisConn RedisConnectionFactory - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis + * @param redisConn + * RedisConnectionFactory + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis */ - public RedisLockProvider(@NonNull RedisConnectionFactory redisConn, @NonNull String environment) { - this(redisConn, environment, KEY_PREFIX_DEFAULT); + public RedisLockProvider(RedisConnectionFactory redisConn, String environment) { + this(redisConn, environment, DEFAULT_KEY_PREFIX); } /** * Creates RedisLockProvider * - * @param redisConn RedisConnectionFactory - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis - * @param keyPrefix prefix of the key in Redis. + * @param redisConn + * RedisConnectionFactory + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param keyPrefix + * prefix of the key in Redis. */ - public RedisLockProvider(@NonNull RedisConnectionFactory redisConn, @NonNull String environment, @NonNull String keyPrefix) { + public RedisLockProvider(RedisConnectionFactory redisConn, String environment, String keyPrefix) { this(new StringRedisTemplate(redisConn), environment, keyPrefix); } /** * Create RedisLockProvider * - * @param redisTemplate StringRedisTemplate - * @param environment environment is part of the key and thus makes sure there is not key conflict between - * multiple ShedLock instances running on the same Redis - * @param keyPrefix prefix of the key in Redis. + * @param redisTemplate + * StringRedisTemplate + * @param environment + * environment is part of the key and thus makes sure there is not + * key conflict between multiple ShedLock instances running on the + * same Redis + * @param keyPrefix + * prefix of the key in Redis. */ - public RedisLockProvider(@NonNull StringRedisTemplate redisTemplate, @NonNull String environment, @NonNull String keyPrefix) { - this.redisTemplate = redisTemplate; - this.environment = environment; - this.keyPrefix = keyPrefix; + public RedisLockProvider(StringRedisTemplate redisTemplate, String environment, String keyPrefix) { + this(redisTemplate, environment, keyPrefix, false); } - @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { - String key = buildKey(lockConfiguration.getName()); - Expiration expiration = getExpiration(lockConfiguration.getLockAtMostUntil()); - if (TRUE.equals(tryToSetExpiration(redisTemplate, key, expiration, SET_IF_ABSENT))) { - return Optional.of(new RedisLock(key, redisTemplate, lockConfiguration)); - } else { - return Optional.empty(); - } - } - - private static Expiration getExpiration(Instant until) { - return Expiration.from(getMsUntil(until), TimeUnit.MILLISECONDS); + RedisLockProvider(StringRedisTemplate redisTemplate, String environment, String keyPrefix, boolean safeUpdate) { + this.internalRedisLockProvider = new InternalRedisLockProvider( + new SpringRedisLockTemplate(redisTemplate), environment, keyPrefix, safeUpdate); } - private static long getMsUntil(Instant until) { - return Duration.between(ClockProvider.now(), until).toMillis(); + @Override + public Optional lock(LockConfiguration lockConfiguration) { + return internalRedisLockProvider.lock(lockConfiguration); } - private static final class RedisLock extends AbstractSimpleLock { - - private final String key; + public static class Builder { private final StringRedisTemplate redisTemplate; + private String environment = ENV_DEFAULT; + private String keyPrefix = DEFAULT_KEY_PREFIX; + private boolean safeUpdate = false; + + public Builder(RedisConnectionFactory redisConnectionFactory) { + this.redisTemplate = new StringRedisTemplate(redisConnectionFactory); + } - private RedisLock(String key, StringRedisTemplate redisTemplate, LockConfiguration lockConfiguration) { - super(lockConfiguration); - this.key = key; + public Builder(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } - @Override - public void doUnlock() { - Expiration keepLockFor = getExpiration(lockConfiguration.getLockAtLeastUntil()); - // lock at least until is in the past - if (keepLockFor.getExpirationTimeInMilliseconds() <= 0) { - try { - redisTemplate.delete(key); - } catch (Exception e) { - throw new LockException("Can not remove node", e); - } - } else { - tryToSetExpiration(this.redisTemplate, key, keepLockFor, SetOption.SET_IF_PRESENT); - } + public Builder environment(String environment) { + this.environment = environment; + return this; } - } + public Builder keyPrefix(String keyPrefix) { + this.keyPrefix = keyPrefix; + return this; + } - String buildKey(String lockName) { - return String.format("%s:%s:%s", keyPrefix, environment, lockName); - } + /** + * @param safeUpdate When set to true and the lock is held for more than lockAtMostFor, and the lock + * is already held by somebody else, we don't release/extend the lock. + */ + public Builder safeUpdate(boolean safeUpdate) { + this.safeUpdate = safeUpdate; + return this; + } - private static Boolean tryToSetExpiration(StringRedisTemplate template, String key, Expiration expiration, SetOption option) { - return template.execute(connection -> { - byte[] serializedKey = ((RedisSerializer) template.getKeySerializer()).serialize(key); - byte[] serializedValue = ((RedisSerializer) template.getValueSerializer()).serialize(String.format("ADDED:%s@%s", toIsoString(ClockProvider.now()), getHostname())); - return connection.set(serializedKey, serializedValue, expiration, option); - }, false); + public RedisLockProvider build() { + return new RedisLockProvider(redisTemplate, environment, keyPrefix, safeUpdate); + } } - public static class Builder { - private final StringRedisTemplate redisTemplate; - private String environment = ENV_DEFAULT; - private String keyPrefix = KEY_PREFIX_DEFAULT; + private record SpringRedisLockTemplate(StringRedisTemplate template) implements InternalRedisLockTemplate { - public Builder(@NonNull RedisConnectionFactory redisConnectionFactory) { - this.redisTemplate = new StringRedisTemplate(redisConnectionFactory); + @Override + public boolean setIfAbsent(String key, String value, long expirationMs) { + return set(key, value, expirationMs, SET_IF_ABSENT); } - public Builder(@NonNull StringRedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; + @Override + public boolean setIfPresent(String key, String value, long expirationMs) { + return set(key, value, expirationMs, SET_IF_PRESENT); } - public Builder environment(@NonNull String environment) { - this.environment = environment; - return this; + private boolean set(String key, String value, long expirationMs, RedisStringCommands.SetOption setOption) { + return TRUE + == template.execute( + connection -> { + byte[] serializedKey = + ((RedisSerializer) template.getKeySerializer()).serialize(key); + byte[] serializedValue = + ((RedisSerializer) template.getValueSerializer()).serialize(value); + return connection + .stringCommands() + .set( + serializedKey, + serializedValue, + Expiration.from(expirationMs, TimeUnit.MILLISECONDS), + setOption); + }, + false); } - public Builder keyPrefix(@NonNull String keyPrefix) { - this.keyPrefix = keyPrefix; - return this; + @Override + public Object eval(String script, String key, String... values) { + return template.execute(new DefaultRedisScript<>(script, Integer.class), List.of(key), (Object[]) values); } - public RedisLockProvider build() { - return new RedisLockProvider(redisTemplate, environment, keyPrefix); + @Override + public void delete(String key) { + template.delete(key); } } } diff --git a/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/package-info.java b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/package-info.java new file mode 100644 index 000000000..b7089d0b0 --- /dev/null +++ b/providers/redis/shedlock-provider-redis-spring/src/main/java/net/javacrumbs/shedlock/provider/redis/spring/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.redis.spring; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractReactiveRedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractReactiveRedisLockProviderIntegrationTest.java index 48ffb9e6a..3a6a9c727 100644 --- a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractReactiveRedisLockProviderIntegrationTest.java +++ b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractReactiveRedisLockProviderIntegrationTest.java @@ -1,24 +1,24 @@ package net.javacrumbs.shedlock.provider.redis.spring; +import static org.assertj.core.api.Assertions.assertThat; + import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import static org.assertj.core.api.Assertions.assertThat; - public abstract class AbstractReactiveRedisLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { private final ReactiveRedisLockProvider lockProvider; private final ReactiveStringRedisTemplate redisTemplate; - private final static String ENV = "test"; - private final static String KEY_PREFIX = "test-prefix"; + private static final String ENV = "test"; + private static final String KEY_PREFIX = "test-prefix"; public AbstractReactiveRedisLockProviderIntegrationTest(ReactiveRedisConnectionFactory connectionFactory) { lockProvider = new ReactiveRedisLockProvider.Builder(connectionFactory) - .environment(ENV) - .keyPrefix(KEY_PREFIX) - .build(); + .environment(ENV) + .keyPrefix(KEY_PREFIX) + .build(); redisTemplate = new ReactiveStringRedisTemplate(connectionFactory); } diff --git a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractRedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractRedisLockProviderIntegrationTest.java deleted file mode 100644 index 7b6b22cf3..000000000 --- a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractRedisLockProviderIntegrationTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.provider.redis.spring; - -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -public abstract class AbstractRedisLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { - - private final RedisLockProvider lockProvider; - private final StringRedisTemplate redisTemplate; - - private final static String ENV = "test"; - private final static String KEY_PREFIX = "test-prefix"; - - - public AbstractRedisLockProviderIntegrationTest(RedisConnectionFactory connectionFactory) { - lockProvider = new RedisLockProvider.Builder(connectionFactory) - .environment(ENV) - .keyPrefix(KEY_PREFIX) - .build(); - - redisTemplate = new StringRedisTemplate(connectionFactory); - } - - @Override - protected LockProvider getLockProvider() { - return lockProvider; - } - - @Override - protected void assertUnlocked(String lockName) { - assertThat(redisTemplate.hasKey(buildKey(lockName))).isFalse(); - } - - private String buildKey(String lockName) { - return lockProvider.buildKey(lockName); - } - - @Override - protected void assertLocked(String lockName) { - assertThat(redisTemplate.getExpire(buildKey(lockName))).isGreaterThan(0); - } -} diff --git a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractSpringRedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractSpringRedisLockProviderIntegrationTest.java new file mode 100644 index 000000000..cb1592005 --- /dev/null +++ b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/AbstractSpringRedisLockProviderIntegrationTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.redis.spring; + +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.provider.redis.testsupport.AbstractRedisIntegrationTest; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; + +public abstract class AbstractSpringRedisLockProviderIntegrationTest extends AbstractRedisIntegrationTest { + + private final RedisLockProvider lockProvider; + private final StringRedisTemplate redisTemplate; + + private static final String ENV = "test"; + private static final String KEY_PREFIX = "test-prefix"; + + public AbstractSpringRedisLockProviderIntegrationTest( + RedisConnectionFactory connectionFactory, boolean safeUpdate) { + lockProvider = new RedisLockProvider.Builder(connectionFactory) + .environment(ENV) + .keyPrefix(KEY_PREFIX) + .safeUpdate(safeUpdate) + .build(); + + redisTemplate = new StringRedisTemplate(connectionFactory); + } + + @Override + protected ExtensibleLockProvider getLockProvider() { + return lockProvider; + } + + @Override + protected String getLock(String lockName) { + return redisTemplate.opsForValue().get(buildKey(lockName)); + } + + private String buildKey(String lockName) { + return buildKey(lockName, KEY_PREFIX, ENV); + } +} diff --git a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/SpringRedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/SpringRedisLockProviderIntegrationTest.java index a33b1d0dc..eeac178a9 100644 --- a/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/SpringRedisLockProviderIntegrationTest.java +++ b/providers/redis/shedlock-provider-redis-spring/src/test/java/net/javacrumbs/shedlock/provider/redis/spring/SpringRedisLockProviderIntegrationTest.java @@ -1,97 +1,95 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.redis.spring; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import static net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer.PORT; + +import net.javacrumbs.shedlock.provider.redis.testsupport.RedisContainer; import org.junit.jupiter.api.Nested; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import redis.embedded.RedisServer; - -import java.io.IOException; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +@Testcontainers public class SpringRedisLockProviderIntegrationTest { - private static RedisServer redisServer; - - private final static int PORT = 6381; - protected final static String HOST = "localhost"; - + @Container + public static final RedisContainer redis = new RedisContainer(PORT); - @BeforeAll - public static void startRedis() throws IOException { - redisServer = new RedisServer(PORT); - redisServer.start(); - } - - @AfterAll - public static void stopRedis() { - redisServer.stop(); + @Nested + class Jedis extends AbstractSpringRedisLockProviderIntegrationTest { + public Jedis() { + super(createJedisConnectionFactory(), false); + } } @Nested - class Jedis extends AbstractRedisLockProviderIntegrationTest { - public Jedis() { - super(createJedisConnectionFactory()); + class JedisSafeUpdate extends AbstractSpringRedisLockProviderIntegrationTest { + public JedisSafeUpdate() { + super(createJedisConnectionFactory(), true); } } private static RedisConnectionFactory createJedisConnectionFactory() { - JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); - jedisConnectionFactory.setHostName(HOST); - jedisConnectionFactory.setPort(PORT); + JedisConnectionFactory jedisConnectionFactory = + new JedisConnectionFactory(new RedisStandaloneConfiguration(redis.getHost(), PORT)); jedisConnectionFactory.afterPropertiesSet(); return jedisConnectionFactory; } @Nested - class Letucce extends AbstractRedisLockProviderIntegrationTest { - public Letucce() { - super(createLettuceConnectionFactory()); + class LettuceSafeUpdate extends AbstractSpringRedisLockProviderIntegrationTest { + public LettuceSafeUpdate() { + super(createLettuceConnectionFactory(), true); + } + } + + @Nested + class Lettuce extends AbstractSpringRedisLockProviderIntegrationTest { + public Lettuce() { + super(createLettuceConnectionFactory(), false); } } @Nested - class ReactiveLetucce extends AbstractReactiveRedisLockProviderIntegrationTest { - public ReactiveLetucce() { + class ReactiveLettuce extends AbstractReactiveRedisLockProviderIntegrationTest { + public ReactiveLettuce() { super(createLettuceConnectionFactory()); } } private static LettuceConnectionFactory createLettuceConnectionFactory() { - LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(HOST, PORT); + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redis.getHost(), PORT); lettuceConnectionFactory.afterPropertiesSet(); return lettuceConnectionFactory; } @Nested - class Redisson extends AbstractRedisLockProviderIntegrationTest { + class Redisson extends AbstractSpringRedisLockProviderIntegrationTest { public Redisson() { - super(createRedissonConnectionFactory()); + super(createRedissonConnectionFactory(), false); } } private static RedisConnectionFactory createRedissonConnectionFactory() { Config config = new Config(); - config.useSingleServer() - .setAddress("redis://" + HOST + ":" + PORT); + config.useSingleServer().setAddress("redis://" + redis.getHost() + ":" + PORT); RedissonClient redisson = org.redisson.Redisson.create(config); return new RedissonConnectionFactory(redisson); } diff --git a/providers/redis/shedlock-support-redis/pom.xml b/providers/redis/shedlock-support-redis/pom.xml new file mode 100644 index 000000000..c4136e2c5 --- /dev/null +++ b/providers/redis/shedlock-support-redis/pom.xml @@ -0,0 +1,39 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-support-redis + ${project.groupId}:${project.artifactId} + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.redis.support + + + + + + + + diff --git a/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockProvider.java b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockProvider.java new file mode 100644 index 000000000..dd3bca303 --- /dev/null +++ b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockProvider.java @@ -0,0 +1,164 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.redis.support; + +import static net.javacrumbs.shedlock.support.Utils.getHostname; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import net.javacrumbs.shedlock.core.AbstractSimpleLock; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import net.javacrumbs.shedlock.support.LockException; + +/** + * Common implementation of RedisLockProvider. Internal class, please don't use directly. + */ +public class InternalRedisLockProvider implements ExtensibleLockProvider { + + public static final String DEFAULT_KEY_PREFIX = "job-lock"; + public static final String ENV_DEFAULT = "default"; + + private final InternalRedisLockTemplate redisLockTemplate; + private final String environment; + private final String keyPrefix; + private final boolean safeUpdate; + + /* + * https://redis.io/docs/latest/develop/use/patterns/distributed-locks/ + * */ + private static final String delLuaScript = + """ + if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) + else + return 0 + end + """; + + private static final String updLuaScript = + """ + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('pexpire', KEYS[1], ARGV[2]) + else + return 0 + end + """; + + public InternalRedisLockProvider( + InternalRedisLockTemplate redisLockTemplate, String environment, String keyPrefix, boolean safeUpdate) { + this.redisLockTemplate = redisLockTemplate; + this.environment = environment; + this.keyPrefix = keyPrefix; + this.safeUpdate = safeUpdate; + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + long expireTime = getMsUntil(lockConfiguration.getLockAtMostUntil()); + + String key = buildKey(lockConfiguration.getName(), keyPrefix, this.environment); + String uniqueLockValue = buildValue(); + + if (createLock(key, uniqueLockValue, expireTime)) { + return Optional.of(new RedisLock(key, uniqueLockValue, this, lockConfiguration)); + } + + return Optional.empty(); + } + + private Optional extend(RedisLock currentLock, LockConfiguration lockConfiguration) { + long expireTime = getMsUntil(lockConfiguration.getLockAtMostUntil()); + + if (setKeyExpiration(currentLock, expireTime)) { + return Optional.of(new RedisLock(currentLock.key, currentLock.value, this, lockConfiguration)); + } + + return Optional.empty(); + } + + private boolean setKeyExpiration(RedisLock currentLock, long expiration) { + if (safeUpdate) { + return redisLockTemplate + .eval(updLuaScript, currentLock.key, currentLock.value, String.valueOf(expiration)) + .equals(1L); + } else { + return redisLockTemplate.setIfPresent(currentLock.key, currentLock.value, expiration); + } + } + + private boolean createLock(String key, String value, long expirationMs) { + return redisLockTemplate.setIfAbsent(key, value, expirationMs); + } + + private void deleteLock(String key, String value) { + if (safeUpdate) { + redisLockTemplate.eval(delLuaScript, key, value); + } else { + redisLockTemplate.delete(key); + } + } + + private static final class RedisLock extends AbstractSimpleLock { + private final String key; + private final String value; + private final InternalRedisLockProvider lockProvider; + + private RedisLock( + String key, String value, InternalRedisLockProvider lockProvider, LockConfiguration lockConfiguration) { + super(lockConfiguration); + this.key = key; + this.value = value; + this.lockProvider = lockProvider; + } + + @Override + public void doUnlock() { + long keepLockFor = getMsUntil(lockConfiguration.getLockAtLeastUntil()); + + // lock at least until is in the past + if (keepLockFor <= 0) { + try { + lockProvider.deleteLock(key, value); + } catch (Exception e) { + throw new LockException("Can not remove node", e); + } + } else { + lockProvider.setKeyExpiration(this, keepLockFor); + } + } + + @Override + protected Optional doExtend(LockConfiguration newConfiguration) { + return lockProvider.extend(this, newConfiguration); + } + } + + private static long getMsUntil(Instant instant) { + return Duration.between(ClockProvider.now(), instant).toMillis(); + } + + private static String buildKey(String lockName, String keyPrefix, String env) { + return String.format("%s:%s:%s", keyPrefix, env, lockName); + } + + private static String buildValue() { + return String.format("ADDED:%s@%s:%s", toIsoString(ClockProvider.now()), getHostname(), UUID.randomUUID()); + } +} diff --git a/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockTemplate.java b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockTemplate.java new file mode 100644 index 000000000..933401475 --- /dev/null +++ b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/InternalRedisLockTemplate.java @@ -0,0 +1,14 @@ +package net.javacrumbs.shedlock.provider.redis.support; + +/** + * Abstraction of Redis operations used by ShedLock. Internal class, please don't use directly. + */ +public interface InternalRedisLockTemplate { + boolean setIfAbsent(String key, String value, long expirationMs); + + boolean setIfPresent(String key, String value, long expirationMs); + + Object eval(String script, String key, String... values); + + void delete(String key); +} diff --git a/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/package-info.java b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/package-info.java new file mode 100644 index 000000000..44e0e274e --- /dev/null +++ b/providers/redis/shedlock-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/support/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.redis.support; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/redis/shedlock-test-support-redis/pom.xml b/providers/redis/shedlock-test-support-redis/pom.xml new file mode 100644 index 000000000..ad2b702ef --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/pom.xml @@ -0,0 +1,35 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-test-support-redis + ${project.groupId}:${project.artifactId} + + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + + + + com.playtika.testcontainers + embedded-redis + 3.1.15 + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + + + + diff --git a/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisIntegrationTest.java b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisIntegrationTest.java new file mode 100644 index 000000000..8f387dc16 --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisIntegrationTest.java @@ -0,0 +1,34 @@ +package net.javacrumbs.shedlock.provider.redis.testsupport; + +import static org.assertj.core.api.Assertions.assertThat; + +import net.javacrumbs.shedlock.support.annotation.Nullable; +import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest; + +/** + * The fix for this use-case only exists in Redis LockProvider implementations. + * When we will fix this in all LockProviders, we can move this test to the base class, removing the need for this class. + */ +public abstract class AbstractRedisIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest { + + @Override + protected void assertLocked(String lockName) { + assertThat(getLock(lockName)).isNotNull(); + } + + @Override + protected void assertUnlocked(String lockName) { + assertThat(getLock(lockName)).isNull(); + } + + @Nullable + protected abstract String getLock(String lockName); + + protected String buildKey(String lockName, String env) { + return String.format("%s:%s:%s", "job-lock", env, lockName); + } + + protected String buildKey(String lockName, String keyPrefix, String env) { + return String.format("%s:%s:%s", keyPrefix, env, lockName); + } +} diff --git a/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisSafeUpdateIntegrationTest.java b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisSafeUpdateIntegrationTest.java new file mode 100644 index 000000000..b953862a1 --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/AbstractRedisSafeUpdateIntegrationTest.java @@ -0,0 +1,35 @@ +package net.javacrumbs.shedlock.provider.redis.testsupport; + +import static java.time.Duration.ZERO; +import static java.time.Duration.ofSeconds; + +import net.javacrumbs.shedlock.core.SimpleLock; +import org.junit.jupiter.api.Test; + +/** + * The fix for this use-case only exists in Redis LockProvider implementations. + * When we will fix this in all LockProviders, we can move this test to the base class, removing the need for this class. + */ +public abstract class AbstractRedisSafeUpdateIntegrationTest extends AbstractRedisIntegrationTest { + + @Test + public void unlockingAfterExpirationShouldBeNoOp() { + int lockDurationSeconds = 2; + SimpleLock lock1 = lock(ofSeconds(lockDurationSeconds)); + sleepFor(ofSeconds(lockDurationSeconds + 1)); + lock(ofSeconds(lockDurationSeconds)); + lock1.unlock(); + assertLocked(LOCK_NAME1); + } + + @Test + public void extendAfterExpirationShouldBeNoOp() { + int lockDurationSeconds = 2; + SimpleLock lock1 = lock(ofSeconds(lockDurationSeconds)); + sleepFor(ofSeconds(lockDurationSeconds + 1)); + lock(ofSeconds(lockDurationSeconds)); + lock1.extend(ofSeconds(lockDurationSeconds * 5), ZERO); + sleepFor(ofSeconds(lockDurationSeconds + 1)); + assertUnlocked(LOCK_NAME1); + } +} diff --git a/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/RedisContainer.java b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/RedisContainer.java new file mode 100644 index 000000000..447ae3855 --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/RedisContainer.java @@ -0,0 +1,48 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.provider.redis.testsupport; + +import com.playtika.testcontainer.redis.RedisProperties; +import com.playtika.testcontainer.redis.wait.RedisClusterStatusCheck; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.FixedHostPortGenericContainer; +import org.testcontainers.utility.MountableFile; + +public class RedisContainer extends FixedHostPortGenericContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedisContainer.class); + + public static final int PORT = 6379; + + public static final String ENV = "test"; + + public RedisContainer(int hostPort) { + super("redis:7.2.3-alpine"); + + RedisProperties properties = new RedisProperties(); + properties.host = "localhost"; + properties.port = hostPort; + properties.requirepass = false; + + this.withFixedExposedPort(hostPort, PORT) + .withExposedPorts(PORT) + .withLogConsumer(frame -> LOGGER.info(frame.getUtf8String())) + .withCopyFileToContainer(MountableFile.forClasspathResource("redis.conf"), "/data/redis.conf") + .withCopyFileToContainer(MountableFile.forClasspathResource("nodes.conf"), "/data/nodes.conf") + .waitingFor(new RedisClusterStatusCheck(properties)) + // .waitingFor(new RedisStatusCheck(properties)) + .withCommand("redis-server", "/data/redis.conf"); + } +} diff --git a/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/package-info.java b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/package-info.java new file mode 100644 index 000000000..66ff3cbf2 --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/src/main/java/net/javacrumbs/shedlock/provider/redis/testsupport/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.redis.testsupport; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/redis/shedlock-test-support-redis/src/main/resources/logback.xml b/providers/redis/shedlock-test-support-redis/src/main/resources/logback.xml new file mode 100644 index 000000000..8ee4ac478 --- /dev/null +++ b/providers/redis/shedlock-test-support-redis/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/test/resources/nodes.conf b/providers/redis/shedlock-test-support-redis/src/main/resources/nodes.conf similarity index 100% rename from providers/redis/shedlock-provider-redis-jedis4/src/test/resources/nodes.conf rename to providers/redis/shedlock-test-support-redis/src/main/resources/nodes.conf diff --git a/providers/redis/shedlock-provider-redis-jedis4/src/test/resources/redis.conf b/providers/redis/shedlock-test-support-redis/src/main/resources/redis.conf similarity index 100% rename from providers/redis/shedlock-provider-redis-jedis4/src/test/resources/redis.conf rename to providers/redis/shedlock-test-support-redis/src/main/resources/redis.conf diff --git a/providers/s3/shedlock-provider-s3/pom.xml b/providers/s3/shedlock-provider-s3/pom.xml new file mode 100644 index 000000000..3a9ca032e --- /dev/null +++ b/providers/s3/shedlock-provider-s3/pom.xml @@ -0,0 +1,77 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-s3 + ${project.groupId}:${project.artifactId} + + + 1.12.788 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + com.amazonaws + aws-java-sdk-s3 + ${aws-java-sdk-s3.version} + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + localstack + ${test-containers.ver} + test + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.s3 + + + + + + + + diff --git a/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/Lock.java b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/Lock.java new file mode 100644 index 000000000..d62371f6e --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/Lock.java @@ -0,0 +1,5 @@ +package net.javacrumbs.shedlock.provider.s3; + +import java.time.Instant; + +record Lock(Instant lockUntil, Instant lockedAt, String lockedBy, String eTag) {} diff --git a/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3LockProvider.java b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3LockProvider.java new file mode 100644 index 000000000..050fc8c5c --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3LockProvider.java @@ -0,0 +1,31 @@ +package net.javacrumbs.shedlock.provider.s3; + +import com.amazonaws.services.s3.AmazonS3; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; + +/** + * Lock provider implementation for S3. + */ +public class S3LockProvider extends StorageBasedLockProvider { + + /** + * Constructs an S3LockProvider. + * + * @param s3Client Amazon S3 client used to interact with the S3 bucket. + * @param bucketName The name of the S3 bucket where locks are stored. + * @param objectPrefix The prefix of the S3 object lock. + */ + public S3LockProvider(AmazonS3 s3Client, String bucketName, String objectPrefix) { + super(new S3StorageAccessor(s3Client, bucketName, objectPrefix)); + } + + /** + * Constructs an S3LockProvider. + * + * @param s3Client Amazon S3 client used to interact with the S3 bucket. + * @param bucketName The name of the S3 bucket where locks are stored. + */ + public S3LockProvider(AmazonS3 s3Client, String bucketName) { + this(s3Client, bucketName, "shedlock/"); + } +} diff --git a/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3StorageAccessor.java b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3StorageAccessor.java new file mode 100644 index 000000000..8c66f8286 --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/S3StorageAccessor.java @@ -0,0 +1,193 @@ +package net.javacrumbs.shedlock.provider.s3; + +import static net.javacrumbs.shedlock.core.ClockProvider.now; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.PutObjectResult; +import java.io.ByteArrayInputStream; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; + +/** + * Implementation of StorageAccessor for S3 as a lock storage backend. + * Manages locks using S3 objects with metadata for expiration and conditional writes. + */ +class S3StorageAccessor extends AbstractStorageAccessor { + + private static final String LOCK_UNTIL = "lockUntil"; + private static final String LOCKED_AT = "lockedAt"; + private static final String LOCKED_BY = "lockedBy"; + private static final int PRECONDITION_FAILED = 412; + + private final AmazonS3 s3Client; + private final String bucketName; + private final String objectPrefix; + + public S3StorageAccessor(AmazonS3 s3Client, String bucketName, String objectPrefix) { + this.s3Client = s3Client; + this.bucketName = bucketName; + this.objectPrefix = objectPrefix; + } + + /** + * Finds the lock in the S3 bucket. + */ + Optional find(String name, String action) { + try { + ObjectMetadata metadata = getExistingMetadata(name); + Instant lockUntil = Instant.parse(metadata.getUserMetaDataOf(LOCK_UNTIL)); + Instant lockedAt = Instant.parse(metadata.getUserMetaDataOf(LOCKED_AT)); + String lockedBy = metadata.getUserMetaDataOf(LOCKED_BY); + String eTag = metadata.getETag(); + + logger.debug("Lock found. action: {}, name: {}, lockUntil: {}, e-tag: {}", action, name, lockUntil, eTag); + return Optional.of(new Lock(lockUntil, lockedAt, lockedBy, eTag)); + } catch (AmazonServiceException e) { + if (e.getStatusCode() == 404) { + logger.debug("Lock not found. action: {}, name: {}", action, name); + return Optional.empty(); + } + throw e; + } + } + + @Override + public boolean insertRecord(LockConfiguration lockConfiguration) { + String name = lockConfiguration.getName(); + if (find(name, "insertRecord").isPresent()) { + logger.debug("Lock already exists. name: {}", name); + return false; + } + + try { + var lockContent = getLockContent(); + ObjectMetadata metadata = createMetadata(lockConfiguration.getLockAtMostUntil(), now(), getHostname()); + metadata.setContentLength(lockContent.length); + + PutObjectRequest request = + new PutObjectRequest(bucketName, objectName(name), new ByteArrayInputStream(lockContent), metadata); + request.putCustomRequestHeader("If-None-Match", "*"); + + s3Client.putObject(request); + logger.debug("Lock created successfully. name: {}, metadata: {}", name, metadata.getUserMetadata()); + return true; + } catch (AmazonServiceException e) { + if (e.getStatusCode() == PRECONDITION_FAILED) { + logger.debug("Lock already in use. name: {}", name); + } else { + logger.warn("Failed to create lock. name: {}", name, e); + } + return false; + } + } + + @Override + public boolean updateRecord(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "updateRecord"); + if (lock.isEmpty()) { + logger.warn("Update skipped. Lock not found. name: {}, lock: {}", lockConfiguration.getName(), lock); + return false; + } + if (lock.get().lockUntil().isAfter(now())) { + logger.debug("Update skipped. Lock still valid. name: {}, lock: {}", lockConfiguration.getName(), lock); + return false; + } + + ObjectMetadata newMetadata = createMetadata(lockConfiguration.getLockAtMostUntil(), now(), getHostname()); + return replaceObjectMetadata( + lockConfiguration.getName(), newMetadata, lock.get().eTag(), "updateRecord"); + } + + @Override + public void unlock(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "unlock"); + if (lock.isEmpty()) { + logger.warn("Unlock skipped. Lock not found. name: {}, lock: {}", lockConfiguration.getName(), lock); + return; + } + + updateUntil(lockConfiguration.getName(), lock.get(), lockConfiguration.getUnlockTime(), "unlock"); + } + + @Override + public boolean extend(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "extend"); + if (lock.isEmpty() + || lock.get().lockUntil().isBefore(now()) + || !lock.get().lockedBy().equals(getHostname())) { + logger.debug( + "Extend skipped. Lock invalid or not owned by host. name: {}, lock: {}", + lockConfiguration.getName(), + lock); + return false; + } + + return updateUntil(lockConfiguration.getName(), lock.get(), lockConfiguration.getLockAtMostUntil(), "extend"); + } + + private boolean updateUntil(String name, Lock lock, Instant until, String action) { + ObjectMetadata existingMetadata = getExistingMetadata(name); + ObjectMetadata newMetadata = + createMetadata(until, Instant.parse(existingMetadata.getUserMetaDataOf(LOCKED_AT)), getHostname()); + + return replaceObjectMetadata(name, newMetadata, lock.eTag(), action); + } + + private ObjectMetadata getExistingMetadata(String name) { + return s3Client.getObjectMetadata(bucketName, objectName(name)); + } + + private boolean replaceObjectMetadata(String name, ObjectMetadata newMetadata, String eTag, String action) { + var lockContent = getLockContent(); + newMetadata.setContentLength(lockContent.length); + + PutObjectRequest request = + new PutObjectRequest(bucketName, objectName(name), new ByteArrayInputStream(lockContent), newMetadata); + request.putCustomRequestHeader("If-Match", eTag); + + try { + PutObjectResult response = s3Client.putObject(request); + logger.debug( + "Lock {} successfully. name: {}, old e-tag: {}, new e-tag: {}", + action, + name, + eTag, + response.getETag()); + return true; + } catch (AmazonServiceException e) { + if (e.getStatusCode() == PRECONDITION_FAILED) { + logger.debug("Lock not exists to {}. name: {}, e-tag {}", action, name, eTag); + } else { + logger.warn("Failed to {} lock. name: {}", action, name, e); + } + return false; + } + } + + private static byte[] getLockContent() { + var uuid = UUID.randomUUID(); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb.array(); + } + + private ObjectMetadata createMetadata(Instant lockUntil, Instant lockedAt, String lockedBy) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.addUserMetadata(LOCK_UNTIL, lockUntil.toString()); + metadata.addUserMetadata(LOCKED_AT, lockedAt.toString()); + metadata.addUserMetadata(LOCKED_BY, lockedBy); + return metadata; + } + + private String objectName(String name) { + return objectPrefix + name; + } +} diff --git a/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/package-info.java b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/package-info.java new file mode 100644 index 000000000..fd06ffe41 --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/main/java/net/javacrumbs/shedlock/provider/s3/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.s3; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/s3/shedlock-provider-s3/src/test/java/net/javacrumbs/shedlock/provider/s3/S3LockProviderIntegrationTest.java b/providers/s3/shedlock-provider-s3/src/test/java/net/javacrumbs/shedlock/provider/s3/S3LockProviderIntegrationTest.java new file mode 100644 index 000000000..0b0038d29 --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/test/java/net/javacrumbs/shedlock/provider/s3/S3LockProviderIntegrationTest.java @@ -0,0 +1,91 @@ +package net.javacrumbs.shedlock.provider.s3; + +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +/** + * Integration test uses local instance of LocalStack S3 running on localhost at + * port 9042 using bucket shedlock and folder shedlock + * + * @see net.javacrumbs.shedlock.provider.s3.S3LockProvider + */ +@Testcontainers +public class S3LockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { + + @Container + static final MyLocalStackS3Container localStackS3 = new MyLocalStackS3Container(); + + private static AmazonS3 s3Client; + private static final String BUCKET_NAME = "my-bucket"; + private static final String OBJECT_PREFIX = "prefix"; + + @BeforeAll + public static void startLocalStackS3() { + s3Client = AmazonS3ClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration( + localStackS3.getEndpoint().toString(), localStackS3.getRegion())) + .withCredentials(new AWSStaticCredentialsProvider( + new BasicAWSCredentials(localStackS3.getAccessKey(), localStackS3.getSecretKey()))) + .build(); + } + + @BeforeEach + public void before() { + s3Client.createBucket(BUCKET_NAME); + } + + @AfterEach + public void after() { + s3Client.listObjects(BUCKET_NAME).getObjectSummaries().forEach(obj -> { + s3Client.deleteObject(BUCKET_NAME, obj.getKey()); + }); + } + + @Override + protected StorageBasedLockProvider getLockProvider() { + return new S3LockProvider(s3Client, BUCKET_NAME, OBJECT_PREFIX); + } + + @Override + protected void assertUnlocked(String lockName) { + Lock lock = findLock(lockName); + assertThat(lock.lockUntil()).isBefore(now()); + assertThat(lock.lockedAt()).isBefore(now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected void assertLocked(String lockName) { + Lock lock = findLock(lockName); + assertThat(lock.lockUntil()).isAfter(now()); + assertThat(lock.lockedAt()).isBefore(now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + private Lock findLock(String lockName) { + return new S3StorageAccessor(s3Client, BUCKET_NAME, OBJECT_PREFIX) + .find(lockName, "test") + .get(); + } + + private static class MyLocalStackS3Container extends LocalStackContainer { + public MyLocalStackS3Container() { + super(DockerImageName.parse("localstack/localstack:s3-latest")); + } + } +} diff --git a/providers/s3/shedlock-provider-s3/src/test/resources/logback-test.xml b/providers/s3/shedlock-provider-s3/src/test/resources/logback-test.xml new file mode 100644 index 000000000..8ee4ac478 --- /dev/null +++ b/providers/s3/shedlock-provider-s3/src/test/resources/logback-test.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/s3/shedlock-provider-s3v2/pom.xml b/providers/s3/shedlock-provider-s3v2/pom.xml new file mode 100644 index 000000000..86648980d --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/pom.xml @@ -0,0 +1,77 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-s3v2 + ${project.groupId}:${project.artifactId} + + + 2.32.31 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + software.amazon.awssdk + s3 + ${aws-java-sdk2-s3.version} + + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + + org.testcontainers + localstack + ${test-containers.ver} + test + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.s3v2 + + + + + + + + diff --git a/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/Lock.java b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/Lock.java new file mode 100644 index 000000000..fd900b548 --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/Lock.java @@ -0,0 +1,5 @@ +package net.javacrumbs.shedlock.provider.s3v2; + +import java.time.Instant; + +record Lock(Instant lockUntil, Instant lockedAt, String lockedBy, String eTag) {} diff --git a/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProvider.java b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProvider.java new file mode 100644 index 000000000..26c3bdbca --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProvider.java @@ -0,0 +1,31 @@ +package net.javacrumbs.shedlock.provider.s3v2; + +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import software.amazon.awssdk.services.s3.S3Client; + +/** + * Lock provider implementation for S3. + */ +public class S3LockProvider extends StorageBasedLockProvider { + + /** + * Constructs an S3LockProvider. + * + * @param s3Client S3 client used to interact with the S3 bucket. + * @param bucketName The name of the S3 bucket where locks are stored. + * @param objectPrefix The prefix of the S3 object lock. + */ + public S3LockProvider(S3Client s3Client, String bucketName, String objectPrefix) { + super(new S3StorageAccessor(s3Client, bucketName, objectPrefix)); + } + + /** + * Constructs an S3LockProvider. + * + * @param s3Client S3 client used to interact with the S3 bucket. + * @param bucketName The name of the S3 bucket where locks are stored. + */ + public S3LockProvider(S3Client s3Client, String bucketName) { + this(s3Client, bucketName, "shedlock/"); + } +} diff --git a/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3StorageAccessor.java b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3StorageAccessor.java new file mode 100644 index 000000000..84c5204b8 --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/S3StorageAccessor.java @@ -0,0 +1,204 @@ +package net.javacrumbs.shedlock.provider.s3v2; + +import static net.javacrumbs.shedlock.core.ClockProvider.now; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; + +/** + * Implementation of StorageAccessor for S3 as a lock storage backend. + * Manages locks using S3 objects with metadata for expiration and conditional writes. + */ +class S3StorageAccessor extends AbstractStorageAccessor { + + private static final String LOCK_UNTIL = "lock-until"; + private static final String LOCKED_AT = "locked-at"; + private static final String LOCKED_BY = "locked-by"; + private static final int PRECONDITION_FAILED = 412; + + private final S3Client s3Client; + private final String bucketName; + private final String objectPrefix; + + public S3StorageAccessor(S3Client s3Client, String bucketName, String objectPrefix) { + this.s3Client = s3Client; + this.bucketName = bucketName; + this.objectPrefix = objectPrefix; + } + + /** + * Finds the lock in the S3 bucket. + */ + Optional find(String name, String action) { + try { + HeadObjectResponse metadataResponse = getExistingMetadata(name); + + Map metadata = metadataResponse.metadata(); + + Instant lockUntil = Instant.parse(metadata.get(LOCK_UNTIL)); + Instant lockedAt = Instant.parse(metadata.get(LOCKED_AT)); + String lockedBy = metadata.get(LOCKED_BY); + String eTag = metadataResponse.eTag(); + + logger.debug("Lock found. action: {}, name: {}, lockUntil: {}, e-tag: {}", action, name, lockUntil, eTag); + return Optional.of(new Lock(lockUntil, lockedAt, lockedBy, eTag)); + } catch (AwsServiceException e) { + if (e.statusCode() == 404) { + logger.debug("Lock not found. action: {}, name: {}", action, name); + return Optional.empty(); + } + throw e; + } + } + + @Override + public boolean insertRecord(LockConfiguration lockConfiguration) { + String name = lockConfiguration.getName(); + if (find(name, "insertRecord").isPresent()) { + logger.debug("Lock already exists. name: {}", name); + return false; + } + + try { + Map metadata = createMetadata(lockConfiguration.getLockAtMostUntil(), now(), getHostname()); + + PutObjectRequest request = PutObjectRequest.builder() + .bucket(bucketName) + .key(objectName(name)) + .metadata(metadata) + .ifNoneMatch("*") + .build(); + + s3Client.putObject(request, getLockContent()); + logger.debug("Lock created successfully. name: {}, metadata: {}", name, metadata); + return true; + } catch (AwsServiceException e) { + if (e.statusCode() == PRECONDITION_FAILED) { + logger.debug("Lock already in use. name: {}", name); + } else { + logger.warn("Failed to create lock. name: {}", name, e); + } + return false; + } + } + + @Override + public boolean updateRecord(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "updateRecord"); + if (lock.isEmpty()) { + logger.warn("Update skipped. Lock not found. name: {}, lock: {}", lockConfiguration.getName(), lock); + return false; + } + if (lock.get().lockUntil().isAfter(now())) { + logger.debug("Update skipped. Lock still valid. name: {}, lock: {}", lockConfiguration.getName(), lock); + return false; + } + + Map newMetadata = createMetadata(lockConfiguration.getLockAtMostUntil(), now(), getHostname()); + return replaceObjectMetadata( + lockConfiguration.getName(), newMetadata, lock.get().eTag(), "updateRecord"); + } + + @Override + public void unlock(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "unlock"); + if (lock.isEmpty()) { + logger.warn("Unlock skipped. Lock not found. name: {}, lock: {}", lockConfiguration.getName(), lock); + return; + } + + updateUntil(lockConfiguration.getName(), lock.get(), lockConfiguration.getUnlockTime(), "unlock"); + } + + @Override + public boolean extend(LockConfiguration lockConfiguration) { + Optional lock = find(lockConfiguration.getName(), "extend"); + if (lock.isEmpty() + || lock.get().lockUntil().isBefore(now()) + || !lock.get().lockedBy().equals(getHostname())) { + logger.debug( + "Extend skipped. Lock invalid or not owned by host. name: {}, lock: {}", + lockConfiguration.getName(), + lock); + return false; + } + + return updateUntil(lockConfiguration.getName(), lock.get(), lockConfiguration.getLockAtMostUntil(), "extend"); + } + + private boolean updateUntil(String name, Lock lock, Instant until, String action) { + var existingMetadata = getExistingMetadata(name); + + Map newMetadata = + createMetadata(until, Instant.parse(existingMetadata.metadata().get(LOCKED_AT)), getHostname()); + + return replaceObjectMetadata(name, newMetadata, lock.eTag(), action); + } + + private HeadObjectResponse getExistingMetadata(String name) { + return s3Client.headObject(HeadObjectRequest.builder() + .bucket(bucketName) + .key(objectName(name)) + .build()); + } + + private boolean replaceObjectMetadata(String name, Map newMetadata, String eTag, String action) { + PutObjectRequest request = PutObjectRequest.builder() + .bucket(bucketName) + .key(objectName(name)) + .metadata(newMetadata) + .ifMatch(eTag) + .build(); + + try { + PutObjectResponse response = s3Client.putObject(request, getLockContent()); + logger.debug( + "Lock {} successfully. name: {}, old e-tag: {}, new e-tag: {}", + action, + name, + eTag, + response.eTag()); + return true; + } catch (AwsServiceException e) { + if (e.statusCode() == PRECONDITION_FAILED) { + logger.debug("Lock not exists to {}. name: {}, e-tag {}", action, name, eTag); + } else { + logger.warn("Failed to {} lock. name: {}", action, name, e); + } + return false; + } + } + + private static RequestBody getLockContent() { + var uuid = UUID.randomUUID(); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return RequestBody.fromBytes(bb.array()); + } + + private Map createMetadata(Instant lockUntil, Instant lockedAt, String lockedBy) { + Map metadata = new HashMap<>(); + metadata.put(LOCK_UNTIL, lockUntil.toString()); + metadata.put(LOCKED_AT, lockedAt.toString()); + metadata.put(LOCKED_BY, lockedBy); + return metadata; + } + + private String objectName(String name) { + return objectPrefix + name; + } +} diff --git a/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/package-info.java b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/package-info.java new file mode 100644 index 000000000..51c3fb125 --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/main/java/net/javacrumbs/shedlock/provider/s3v2/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.s3v2; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/s3/shedlock-provider-s3v2/src/test/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProviderIntegrationTest.java b/providers/s3/shedlock-provider-s3v2/src/test/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProviderIntegrationTest.java new file mode 100644 index 000000000..cd8ce6cde --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/test/java/net/javacrumbs/shedlock/provider/s3v2/S3LockProviderIntegrationTest.java @@ -0,0 +1,99 @@ +package net.javacrumbs.shedlock.provider.s3v2; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsRequest; + +/** + * Integration test uses local instance of LocalStack S3 running on localhost at + * port 9042 using bucket shedlock and folder shedlock + * + * @see net.javacrumbs.shedlock.provider.s3v2.S3LockProvider + */ +@Testcontainers +public class S3LockProviderIntegrationTest extends AbstractStorageBasedLockProviderIntegrationTest { + + @Container + static final MyLocalStackS3Container localStackS3 = new MyLocalStackS3Container(); + + private static S3Client s3Client; + private static final String BUCKET_NAME = "my-bucket"; + private static final String OBJECT_PREFIX = "prefix"; + + @BeforeAll + public static void startLocalStackS3() { + s3Client = S3Client.builder() + .endpointOverride(URI.create(localStackS3.getEndpoint().toString())) + .credentialsProvider( + () -> AwsBasicCredentials.create(localStackS3.getAccessKey(), localStackS3.getSecretKey())) + .region(Region.of(localStackS3.getRegion())) + .build(); + } + + @BeforeEach + public void before() { + s3Client.createBucket(CreateBucketRequest.builder().bucket(BUCKET_NAME).build()); + } + + @AfterEach + public void after() { + var listObjectsResponse = s3Client.listObjects( + ListObjectsRequest.builder().bucket(BUCKET_NAME).build()); + + listObjectsResponse.contents().forEach(s3Object -> { + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(BUCKET_NAME) + .key(s3Object.key()) + .build()); + }); + } + + @Override + protected StorageBasedLockProvider getLockProvider() { + return new S3LockProvider(s3Client, BUCKET_NAME, OBJECT_PREFIX); + } + + @Override + protected void assertUnlocked(String lockName) { + Lock lock = findLock(lockName); + assertThat(lock.lockUntil()).isBefore(ClockProvider.now()); + assertThat(lock.lockedAt()).isBefore(ClockProvider.now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected void assertLocked(String lockName) { + Lock lock = findLock(lockName); + assertThat(lock.lockUntil()).isAfter(ClockProvider.now()); + assertThat(lock.lockedAt()).isBefore(ClockProvider.now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + private Lock findLock(String lockName) { + return new S3StorageAccessor(s3Client, BUCKET_NAME, OBJECT_PREFIX) + .find(lockName, "test") + .get(); + } + + private static class MyLocalStackS3Container extends LocalStackContainer { + public MyLocalStackS3Container() { + super(DockerImageName.parse("localstack/localstack:s3-latest")); + } + } +} diff --git a/providers/s3/shedlock-provider-s3v2/src/test/resources/logback-test.xml b/providers/s3/shedlock-provider-s3v2/src/test/resources/logback-test.xml new file mode 100644 index 000000000..8ee4ac478 --- /dev/null +++ b/providers/s3/shedlock-provider-s3v2/src/test/resources/logback-test.xml @@ -0,0 +1,29 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/providers/spanner/shedlock-provider-spanner/pom.xml b/providers/spanner/shedlock-provider-spanner/pom.xml new file mode 100644 index 000000000..4c8fa2447 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/pom.xml @@ -0,0 +1,76 @@ + + + + shedlock-parent + net.javacrumbs.shedlock + 6.10.1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + + shedlock-provider-spanner + ${project.groupId}:${project.artifactId} + + + 6.99.0 + + + + + net.javacrumbs.shedlock + shedlock-core + ${project.version} + + + + com.google.cloud + google-cloud-spanner + ${google-cloud-spanner.version} + + + + + net.javacrumbs.shedlock + shedlock-test-support + ${project.version} + test + + + org.testcontainers + gcloud + ${test-containers.ver} + test + + + org.testcontainers + junit-jupiter + ${test-containers.ver} + test + + + ch.qos.logback + logback-classic + ${logback.ver} + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + net.javacrumbs.shedlock.provider.spanner + + + + + + + + + diff --git a/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProvider.java b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProvider.java new file mode 100644 index 000000000..db83dbce3 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProvider.java @@ -0,0 +1,213 @@ +package net.javacrumbs.shedlock.provider.spanner; + +import static java.util.Objects.requireNonNull; + +import com.google.cloud.spanner.DatabaseClient; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import net.javacrumbs.shedlock.support.Utils; + +/** + * A lock provider for Google Cloud Spanner. + * This provider uses Spanner as the backend storage for the locks. + */ +public class SpannerLockProvider extends StorageBasedLockProvider { + + /** + * Constructs a new {@code SpannerLockProvider} with the provided {@link DatabaseClient}. + * + * @param databaseClient the client for interacting with Google Cloud Spanner. + */ + public SpannerLockProvider(DatabaseClient databaseClient) { + this(Configuration.builder().withDatabaseClient(databaseClient).build()); + } + + /** + * Constructs a new {@code SpannerLockProvider} using the specified configuration. + * + * @param configuration configuration for the provider. + */ + public SpannerLockProvider(Configuration configuration) { + super(new SpannerStorageAccessor(configuration)); + } + + /** + * Configuration class for {@code SpannerLockProvider}. + * It holds configuration details required to create an instance of {@code SpannerLockProvider}. + */ + public static final class Configuration { + private final DatabaseClient databaseClient; + private final String hostname; + private final TableConfiguration tableConfiguration; + + /** + * Constructs a {@code Configuration} with the builder pattern. + * + * @param builder the {@code Builder} object. + */ + private Configuration(Builder builder) { + databaseClient = requireNonNull(builder.databaseClient, "databaseClient must be set"); + tableConfiguration = builder.tableConfiguration; + hostname = builder.hostName; + } + + public static Builder builder() { + return new Builder(); + } + + public DatabaseClient getDatabaseClient() { + return databaseClient; + } + + public String getHostname() { + return hostname; + } + + public TableConfiguration getTableConfiguration() { + return tableConfiguration; + } + + /** + * Builder for {@link Configuration}. It provides defaults for table configuration and hostname. + * A default {@link TableConfiguration} and host name are used if not explicitly specified. + */ + public static final class Builder { + private DatabaseClient databaseClient; + + // Default host name is obtained from the Utils class if not specified. + private String hostName = Utils.getHostname(); + + // Default table configuration if not specified by the user of the builder. + private TableConfiguration tableConfiguration = TableConfiguration.builder() + .withTableName("shedlock") + .withLockName("name") + .withLockedBy("locked_by") + .withLockedAt("locked_at") + .withLockUntil("lock_until") + .build(); + + private Builder() {} + + public Builder withDatabaseClient(DatabaseClient databaseClient) { + this.databaseClient = databaseClient; + return this; + } + + public Builder withHostName(String hostName) { + this.hostName = hostName; + return this; + } + + public Builder withTableConfiguration(TableConfiguration tableConfiguration) { + this.tableConfiguration = tableConfiguration; + return this; + } + + /** + * Builds the {@link Configuration} with the provided parameters. If the table configuration or + * hostname are not set, it will default to a pre-defined table configuration for ShedLock and + * the local hostname. + * + * @return A new instance of {@link Configuration} with the set parameters. + */ + public Configuration build() { + return new Configuration(this); + } + } + } + + /** + * Class representing the table configuration for the lock provider. + */ + public static final class TableConfiguration { + + private final String tableName; + private final String lockName; + private final String lockUntil; + private final String lockedAt; + private final String lockedBy; + + /** + * Constructs a {@code TableConfiguration} using the builder pattern. + * + * @param builder the {@code Builder} for the table configuration. + */ + private TableConfiguration(Builder builder) { + tableName = requireNonNull(builder.tableName, "tableName must be set"); + lockName = requireNonNull(builder.lockName, "lockName must be set"); + lockUntil = requireNonNull(builder.lockUntil, "lockUntil must be set"); + lockedAt = requireNonNull(builder.lockedAt, "lockedAt must be set"); + lockedBy = requireNonNull(builder.lockedBy, "lockedBy must be set"); + } + + public static Builder builder() { + return new Builder(); + } + + public String getTableName() { + return tableName; + } + + public String getLockName() { + return lockName; + } + + public String getLockUntil() { + return lockUntil; + } + + public String getLockedAt() { + return lockedAt; + } + + public String getLockedBy() { + return lockedBy; + } + + /** + * Builder for creating {@code TableConfiguration} instances. + */ + public static final class Builder { + private String tableName; + private String lockName; + private String lockUntil; + private String lockedAt; + private String lockedBy; + + private Builder() {} + + public Builder withTableName(String tableName) { + this.tableName = tableName; + return this; + } + + public Builder withLockName(String lockNameColumn) { + this.lockName = lockNameColumn; + return this; + } + + public Builder withLockUntil(String lockUntilColumn) { + this.lockUntil = lockUntilColumn; + return this; + } + + public Builder withLockedAt(String lockedAtColumn) { + this.lockedAt = lockedAtColumn; + return this; + } + + public Builder withLockedBy(String lockedByColumn) { + this.lockedBy = lockedByColumn; + return this; + } + + /** + * Builds the {@code TableConfiguration} object. + * + * @return a new {@code TableConfiguration} instance. + */ + public TableConfiguration build() { + return new TableConfiguration(this); + } + } + } +} diff --git a/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerStorageAccessor.java b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerStorageAccessor.java new file mode 100644 index 000000000..7bf84f393 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/SpannerStorageAccessor.java @@ -0,0 +1,174 @@ +package net.javacrumbs.shedlock.provider.spanner; + +import static com.google.cloud.Timestamp.now; +import static com.google.cloud.spanner.Mutation.newInsertBuilder; +import static com.google.cloud.spanner.Mutation.newUpdateBuilder; + +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.Key; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Mutation.WriteBuilder; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.TransactionContext; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.provider.spanner.SpannerLockProvider.TableConfiguration; +import net.javacrumbs.shedlock.support.AbstractStorageAccessor; + +/** + * Accessor for managing lock records within a Google Spanner database. + * This class is responsible for inserting, updating, extending, and unlocking + * lock records using Spanner's transactions. + */ +public class SpannerStorageAccessor extends AbstractStorageAccessor { + + private final String table; + private final String name; + private final String lockedBy; + private final String lockUntil; + private final String lockedAt; + private final String hostname; + private final DatabaseClient databaseClient; + + /** + * Constructs a {@code SpannerStorageAccessor} using the specified configuration. + * + * @param configuration The lock provider configuration. + */ + public SpannerStorageAccessor(SpannerLockProvider.Configuration configuration) { + TableConfiguration tableConfiguration = configuration.getTableConfiguration(); + this.lockUntil = tableConfiguration.getLockUntil(); + this.lockedAt = tableConfiguration.getLockedAt(); + this.lockedBy = tableConfiguration.getLockedBy(); + this.table = tableConfiguration.getTableName(); + this.name = tableConfiguration.getLockName(); + this.databaseClient = configuration.getDatabaseClient(); + this.hostname = configuration.getHostname(); + } + + /** + * Attempts to insert a lock record into the Spanner table. + * + * @param lockConfiguration The lock configuration. + * @return {@code true} if the lock was successfully inserted, otherwise {@code false}. + */ + @Override + public boolean insertRecord(LockConfiguration lockConfiguration) { + return Boolean.TRUE.equals( + databaseClient.readWriteTransaction().run(tx -> findLock(tx, lockConfiguration.getName()) + .map(lock -> false) // Lock already exists, so we return false. + .orElseGet(() -> { + tx.buffer(buildMutation(lockConfiguration, newInsertBuilder(table))); + return true; + }))); + } + + /** + * Attempts to update an existing lock record in the Spanner table. + * + * @param lockConfiguration The lock configuration. + * @return {@code true} if the lock was successfully updated, otherwise {@code false}. + */ + @Override + public boolean updateRecord(LockConfiguration lockConfiguration) { + return Boolean.TRUE.equals( + databaseClient.readWriteTransaction().run(tx -> findLock(tx, lockConfiguration.getName()) + .filter(lock -> lock.lockedUntil().compareTo(now()) <= 0) + .map(lock -> { + tx.buffer(buildMutation(lockConfiguration, newUpdateBuilder(table))); + return true; + }) + .orElse(false))); + } + + private Mutation buildMutation(LockConfiguration lockConfiguration, WriteBuilder builder) { + return builder.set(name) + .to(lockConfiguration.getName()) + .set(lockUntil) + .to(toTimestamp(lockConfiguration.getLockAtMostUntil())) + .set(lockedAt) + .to(now()) + .set(lockedBy) + .to(hostname) + .build(); + } + + /** + * Extends the lock until time of an existing lock record if the current host holds the lock. + * + * @param lockConfiguration The lock configuration. + * @return {@code true} if the lock was successfully extended, otherwise {@code false}. + */ + @Override + public boolean extend(LockConfiguration lockConfiguration) { + return Boolean.TRUE.equals( + databaseClient.readWriteTransaction().run(tx -> findLock(tx, lockConfiguration.getName()) + .filter(lock -> hostname.equals(lock.lockedBy())) + .filter(lock -> lock.lockedUntil().compareTo(now()) > 0) + .map(lock -> { + tx.buffer(newUpdateBuilder(table) + .set(name) + .to(lockConfiguration.getName()) + .set(lockUntil) + .to(toTimestamp(lockConfiguration.getLockAtMostUntil())) + .build()); + return true; + }) + .orElse(false))); + } + + /** + * Unlocks the lock by updating the lock record's lock until time to the unlock time. + * + * @param lockConfiguration The lock configuration to unlock. + */ + @Override + public void unlock(LockConfiguration lockConfiguration) { + databaseClient.readWriteTransaction().run(tx -> { + findLock(tx, lockConfiguration.getName()) + .filter(lock -> hostname.equals(lock.lockedBy())) + .ifPresent(lock -> tx.buffer(newUpdateBuilder(table) + .set(name) + .to(lockConfiguration.getName()) + .set(lockUntil) + .to(toTimestamp(lockConfiguration.getUnlockTime())) + .build())); + return null; // need a return to commit the transaction + }); + } + + /** + * Finds the lock in the Spanner table. + * + * @param tx The tx context to use for the read. + * @param lockName The name of the lock to find. + * @return An {@code Optional} containing the lock if found, otherwise empty. + */ + Optional findLock(TransactionContext tx, String lockName) { + return Optional.ofNullable(tx.readRow(table, Key.of(lockName), List.of(name, lockUntil, lockedBy, lockedAt))) + .map(this::newLock); + } + + Lock newLock(Struct row) { + return new Lock( + row.getString(name), row.getString(lockedBy), row.getTimestamp(lockedAt), row.getTimestamp(lockUntil)); + } + + /** + * Converts {@code Instant} to {@code Timestamp}. + * + * @param instant The instant to convert. + * @return The corresponding {@code Timestamp}. + */ + private Timestamp toTimestamp(Instant instant) { + return Timestamp.ofTimeSecondsAndNanos(instant.getEpochSecond(), instant.getNano()); + } + + /** + * Inner class representing a lock record from Spanner. + */ + record Lock(String lockName, String lockedBy, Timestamp lockedAt, Timestamp lockedUntil) {} +} diff --git a/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/package-info.java b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/package-info.java new file mode 100644 index 000000000..bf87614e0 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/main/java/net/javacrumbs/shedlock/provider/spanner/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.spanner; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/AbstractSpannerStorageBasedLockProviderIntegrationTest.java b/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/AbstractSpannerStorageBasedLockProviderIntegrationTest.java new file mode 100644 index 000000000..160e60b53 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/AbstractSpannerStorageBasedLockProviderIntegrationTest.java @@ -0,0 +1,122 @@ +package net.javacrumbs.shedlock.provider.spanner; + +import com.google.cloud.NoCredentials; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Instance; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfigId; +import com.google.cloud.spanner.InstanceId; +import com.google.cloud.spanner.InstanceInfo; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Statement; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import net.javacrumbs.shedlock.provider.spanner.SpannerLockProvider.Configuration; +import net.javacrumbs.shedlock.test.support.AbstractStorageBasedLockProviderIntegrationTest; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.SpannerEmulatorContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +public abstract class AbstractSpannerStorageBasedLockProviderIntegrationTest + extends AbstractStorageBasedLockProviderIntegrationTest { + + private static final String SPANNER_EMULATOR_IMAGE = "gcr.io/cloud-spanner-emulator/emulator:1.5.12"; + private static final String PROJECT_NAME = "test-project"; + private static final String INSTANCE_NAME = "test-instance"; + private static final String DATABASE_NAME = "test-db"; + + private static DatabaseClient databaseClient; + + @Container + public static final SpannerEmulatorContainer emulator = + new SpannerEmulatorContainer(DockerImageName.parse(SPANNER_EMULATOR_IMAGE)); + + protected static SpannerStorageAccessor accessor; + + @BeforeAll + public static void setUpSpanner() { + Spanner spanner = createSpannerService(); + InstanceId instanceId = createInstance(spanner); + DatabaseId databaseId = createDatabase(spanner); + databaseClient = spanner.getDatabaseClient(databaseId); + Configuration configuration = + Configuration.builder().withDatabaseClient(databaseClient).build(); + accessor = new SpannerStorageAccessor(configuration); + } + + static DatabaseClient getDatabaseClient() { + return databaseClient; + } + + private static Spanner createSpannerService() { + SpannerOptions options = SpannerOptions.newBuilder() + .setEmulatorHost(emulator.getEmulatorGrpcEndpoint()) + .setCredentials(NoCredentials.getInstance()) + .setProjectId(PROJECT_NAME) + .build(); + + return options.getService(); + } + + private static InstanceId createInstance(Spanner spanner) { + InstanceConfigId instanceConfig = InstanceConfigId.of(PROJECT_NAME, "emulator-config"); + InstanceId instanceId = InstanceId.of(PROJECT_NAME, INSTANCE_NAME); + InstanceAdminClient insAdminClient = spanner.getInstanceAdminClient(); + try { + Instance instance = insAdminClient + .createInstance(InstanceInfo.newBuilder(instanceId) + .setNodeCount(1) + .setDisplayName("Test instance") + .setInstanceConfigId(instanceConfig) + .build()) + .get(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException("Failed creating Spanner instance.", e); + } + return instanceId; + } + + private static DatabaseId createDatabase(Spanner spanner) { + DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + try { + Database database = dbAdminClient + .createDatabase(INSTANCE_NAME, DATABASE_NAME, List.of(getShedlockDdl())) + .get(); + return database.getId(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed creating Spanner database.", e); + } + } + + Optional nonTransactionFindLock(String lockName) { + return Optional.ofNullable(databaseClient + .singleUse() + .executeQuery(Statement.newBuilder("SELECT * FROM shedlock WHERE name = @name") + .bind("name") + .to(lockName) + .build())) + .filter(ResultSet::next) + .map(ResultSet::getCurrentRowAsStruct) + .map(accessor::newLock); + } + + private static String getShedlockDdl() { + return """ + CREATE TABLE shedlock ( + name STRING(64) NOT NULL, + lock_until TIMESTAMP NOT NULL, + locked_at TIMESTAMP NOT NULL, + locked_by STRING(255) NOT NULL + ) PRIMARY KEY (name) + """; + } +} diff --git a/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProviderIntegrationTest.java b/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProviderIntegrationTest.java new file mode 100644 index 000000000..074879cba --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/test/java/net/javacrumbs/shedlock/provider/spanner/SpannerLockProviderIntegrationTest.java @@ -0,0 +1,56 @@ +package net.javacrumbs.shedlock.provider.spanner; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.KeySet; +import com.google.cloud.spanner.Mutation; +import java.time.Instant; +import java.util.List; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.support.StorageBasedLockProvider; +import org.junit.jupiter.api.AfterEach; + +class SpannerLockProviderIntegrationTest extends AbstractSpannerStorageBasedLockProviderIntegrationTest { + + @AfterEach + void cleanUp() { + cleanLockTable(); + } + + @Override + protected StorageBasedLockProvider getLockProvider() { + return new SpannerLockProvider(getDatabaseClient()); + } + + @Override + protected void assertUnlocked(String lockName) { + SpannerStorageAccessor.Lock lock = findLock(lockName); + + assertThat(toInstant(lock.lockedUntil())).isBefore(ClockProvider.now()); + assertThat(toInstant(lock.lockedAt())).isBefore(ClockProvider.now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + @Override + protected void assertLocked(String lockName) { + SpannerStorageAccessor.Lock lock = findLock(lockName); + + assertThat(toInstant(lock.lockedUntil())).isAfter(ClockProvider.now()); + assertThat(toInstant(lock.lockedAt())).isBefore(ClockProvider.now()); + assertThat(lock.lockedBy()).isNotEmpty(); + } + + private SpannerStorageAccessor.Lock findLock(String lockName) { + return nonTransactionFindLock(lockName).get(); + } + + private void cleanLockTable() { + List mutations = List.of(Mutation.delete("shedlock", KeySet.all())); + getDatabaseClient().write(mutations); + } + + private Instant toInstant(Timestamp timestamp) { + return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); + } +} diff --git a/providers/spanner/shedlock-provider-spanner/src/test/resources/logback-test.xml b/providers/spanner/shedlock-provider-spanner/src/test/resources/logback-test.xml new file mode 100644 index 000000000..b984b9ab5 --- /dev/null +++ b/providers/spanner/shedlock-provider-spanner/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/providers/zookeeper/shedlock-provider-zookeeper-curator/pom.xml b/providers/zookeeper/shedlock-provider-zookeeper-curator/pom.xml index e1f226fbd..6773ff3f0 100644 --- a/providers/zookeeper/shedlock-provider-zookeeper-curator/pom.xml +++ b/providers/zookeeper/shedlock-provider-zookeeper-curator/pom.xml @@ -3,16 +3,16 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-provider-zookeeper-curator - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} - 5.4.0 + 5.9.0 diff --git a/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProvider.java b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProvider.java index 1c6c72213..b892b6613 100644 --- a/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProvider.java +++ b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProvider.java @@ -1,27 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.zookeeper.curator; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.support.Utils.toIsoString; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.LockException; -import net.javacrumbs.shedlock.support.annotation.NonNull; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.utils.PathUtils; import org.apache.zookeeper.CreateMode; @@ -30,17 +35,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; -import java.time.format.DateTimeParseException; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.requireNonNull; -import static net.javacrumbs.shedlock.support.Utils.toIsoString; - /** - * Locks kept using ZooKeeper. When locking, creates a PERSISTENT node with node name = lock_name and value containing lock data, - * when unlocking, keeps the node and changes node data to release the lock. + * Locks kept using ZooKeeper. When locking, creates a PERSISTENT node with node + * name = lock_name and value containing lock data, when unlocking, keeps the + * node and changes node data to release the lock. */ public class ZookeeperCuratorLockProvider implements LockProvider { public static final String DEFAULT_PATH = "/shedlock"; @@ -49,18 +47,17 @@ public class ZookeeperCuratorLockProvider implements LockProvider { private static final Logger logger = LoggerFactory.getLogger(ZookeeperCuratorLockProvider.class); - public ZookeeperCuratorLockProvider(@NonNull CuratorFramework client) { + public ZookeeperCuratorLockProvider(CuratorFramework client) { this(client, DEFAULT_PATH); } - public ZookeeperCuratorLockProvider(@NonNull CuratorFramework client, @NonNull String path) { + public ZookeeperCuratorLockProvider(CuratorFramework client, String path) { this.client = requireNonNull(client); this.path = PathUtils.validatePath(path); } @Override - @NonNull - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { String nodePath = getNodePath(lockConfiguration.getName()); try { @@ -84,9 +81,12 @@ public Optional lock(@NonNull LockConfiguration lockConfiguration) { } } - private Optional tryLock(LockConfiguration lockConfiguration, String nodePath, Stat stat) throws Exception { + private Optional tryLock(LockConfiguration lockConfiguration, String nodePath, Stat stat) + throws Exception { try { - client.setData().withVersion(stat.getVersion()).forPath(nodePath, serialize(lockConfiguration.getLockAtMostUntil())); + client.setData() + .withVersion(stat.getVersion()) + .forPath(nodePath, serialize(lockConfiguration.getLockAtMostUntil())); return Optional.of(new CuratorLock(nodePath, client, lockConfiguration)); } catch (KeeperException.BadVersionException e) { logger.trace("Node value can not be set, must have been set by a parallel process"); @@ -96,7 +96,10 @@ private Optional tryLock(LockConfiguration lockConfiguration, String private boolean createNode(LockConfiguration lockConfiguration, String nodePath) { try { - client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodePath, serialize(lockConfiguration.getLockAtMostUntil())); + client.create() + .creatingParentsIfNeeded() + .withMode(CreateMode.PERSISTENT) + .forPath(nodePath, serialize(lockConfiguration.getLockAtMostUntil())); return true; } catch (KeeperException.NodeExistsException e) { return false; @@ -110,7 +113,7 @@ boolean isLocked(String nodePath) throws Exception { return isLocked(data); } - private boolean isLocked(byte[] data) { + private boolean isLocked(@Nullable byte[] data) { if (data == null || data.length == 0) { // most likely created by previous version of the library return true; diff --git a/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/package-info.java b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/package-info.java new file mode 100644 index 000000000..0b725b212 --- /dev/null +++ b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/main/java/net/javacrumbs/shedlock/provider/zookeeper/curator/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.provider.zookeeper.curator; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/providers/zookeeper/shedlock-provider-zookeeper-curator/src/test/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProviderIntegrationTest.java b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/test/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProviderIntegrationTest.java index 81b2520e6..7719c392e 100644 --- a/providers/zookeeper/shedlock-provider-zookeeper-curator/src/test/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProviderIntegrationTest.java +++ b/providers/zookeeper/shedlock-provider-zookeeper-curator/src/test/java/net/javacrumbs/shedlock/provider/zookeeper/curator/ZookeeperCuratorLockProviderIntegrationTest.java @@ -1,20 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.provider.zookeeper.curator; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.test.support.AbstractLockProviderIntegrationTest; @@ -27,11 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - public class ZookeeperCuratorLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { private TestingServer zkTestServer; private CuratorFramework client; @@ -51,9 +48,11 @@ public void stopZookeeper() throws IOException { } private CuratorFramework newClient() { - CuratorFramework client = CuratorFrameworkFactory.builder().namespace("MyApp") - .retryPolicy(new RetryOneTime(2000)) - .connectString(zkTestServer.getConnectString()).build(); + CuratorFramework client = CuratorFrameworkFactory.builder() + .namespace("MyApp") + .retryPolicy(new RetryOneTime(2000)) + .connectString(zkTestServer.getConnectString()) + .build(); client.start(); return client; } @@ -78,7 +77,8 @@ protected LockProvider getLockProvider() { @Override protected void assertUnlocked(String lockName) { try { - assertThat(zookeeperCuratorLockProvider.isLocked(getNodePath(lockName))).isFalse(); + assertThat(zookeeperCuratorLockProvider.isLocked(getNodePath(lockName))) + .isFalse(); } catch (Exception e) { throw new IllegalStateException(e); } @@ -87,7 +87,8 @@ protected void assertUnlocked(String lockName) { @Override protected void assertLocked(String lockName) { try { - assertThat(zookeeperCuratorLockProvider.isLocked(getNodePath(lockName))).isTrue(); + assertThat(zookeeperCuratorLockProvider.isLocked(getNodePath(lockName))) + .isTrue(); } catch (Exception e) { throw new IllegalStateException(e); } diff --git a/shedlock-bom/pom.xml b/shedlock-bom/pom.xml index 036dec963..7cd483868 100644 --- a/shedlock-bom/pom.xml +++ b/shedlock-bom/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT 4.0.0 BOM for ShedLock modules shedlock-bom - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} pom @@ -21,7 +21,12 @@ net.javacrumbs.shedlock - shedlock-micronaut + shedlock-micronaut4 + ${project.version} + + + net.javacrumbs.shedlock + shedlock-cdi ${project.version} @@ -44,6 +49,26 @@ shedlock-provider-jdbc-template ${project.version} + + net.javacrumbs.shedlock + shedlock-provider-jdbc + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-jdbc-internal + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-jooq + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-exposed + ${project.version} + net.javacrumbs.shedlock shedlock-provider-jdbc-micronaut @@ -69,11 +94,6 @@ shedlock-provider-neo4j ${project.version} - - net.javacrumbs.shedlock - shedlock-provider-elasticsearch - ${project.version} - net.javacrumbs.shedlock shedlock-provider-opensearch @@ -81,7 +101,7 @@ net.javacrumbs.shedlock - shedlock-provider-couchbase-javaclient + shedlock-provider-opensearch-java ${project.version} @@ -96,7 +116,7 @@ net.javacrumbs.shedlock - shedlock-provider-redis-jedis + shedlock-support-redis ${project.version} @@ -106,12 +126,12 @@ net.javacrumbs.shedlock - shedlock-provider-redis-spring + shedlock-provider-redis-lettuce ${project.version} net.javacrumbs.shedlock - shedlock-provider-dynamodb + shedlock-provider-redis-spring ${project.version} @@ -149,7 +169,41 @@ shedlock-provider-memcached-spy ${project.version} + + net.javacrumbs.shedlock + shedlock-provider-datastore + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-spanner + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-s3 + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-s3v2 + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-elasticsearch8 + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-elasticsearch9 + ${project.version} + + + net.javacrumbs.shedlock + shedlock-provider-firestore + ${project.version} + - diff --git a/shedlock-core/pom.xml b/shedlock-core/pom.xml index aea985cb3..4dc6afe5e 100644 --- a/shedlock-core/pom.xml +++ b/shedlock-core/pom.xml @@ -3,12 +3,12 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT 4.0.0 shedlock-core - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} 2.13.3 @@ -30,7 +30,6 @@ org.junit.jupiter junit-jupiter - ${junit.ver} test @@ -54,7 +53,7 @@ org.jmock jmock-junit5 - 2.12.0 + 2.13.1 test @@ -75,6 +74,13 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + net.javacrumbs.shedlock + + diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/AbstractSimpleLock.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/AbstractSimpleLock.java index 8d0dd04a5..fbba062f0 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/AbstractSimpleLock.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/AbstractSimpleLock.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; @@ -38,7 +36,8 @@ public final void unlock() { @Override public Optional extend(Duration lockAtMostFor, Duration lockAtLeastFor) { checkValidity(); - Optional result = doExtend(new LockConfiguration(ClockProvider.now(), lockConfiguration.getName(), lockAtMostFor, lockAtLeastFor)); + Optional result = doExtend( + new LockConfiguration(ClockProvider.now(), lockConfiguration.getName(), lockAtMostFor, lockAtLeastFor)); valid = false; return result; } @@ -49,7 +48,8 @@ protected Optional doExtend(LockConfiguration newConfiguration) { private void checkValidity() { if (!valid) { - throw new IllegalStateException("Lock " + lockConfiguration.getName() + " is not valid, it has already been unlocked or extended"); + throw new IllegalStateException( + "Lock " + lockConfiguration.getName() + " is not valid, it has already been unlocked or extended"); } } } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ClockProvider.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ClockProvider.java index b2ef45af9..be84e7d8d 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ClockProvider.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ClockProvider.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; @@ -19,9 +17,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; -/** - * Enables to change Clock for all ShedLock classes - */ +/** Enables to change Clock for all ShedLock classes */ public class ClockProvider { private static Clock clock = Clock.systemUTC(); diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockManager.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockManager.java index 8daefd580..e87a3b241 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockManager.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockManager.java @@ -1,31 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; - import static java.util.Objects.requireNonNull; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Default implementation {@link LockManager} implementation. - */ +/** Default implementation {@link LockManager} implementation. */ public class DefaultLockManager implements LockManager { private static final Logger logger = LoggerFactory.getLogger(DefaultLockManager.class); @@ -36,7 +30,8 @@ public DefaultLockManager(LockProvider lockProvider, LockConfigurationExtractor this(new DefaultLockingTaskExecutor(lockProvider), lockConfigurationExtractor); } - public DefaultLockManager(LockingTaskExecutor lockingTaskExecutor, LockConfigurationExtractor lockConfigurationExtractor) { + public DefaultLockManager( + LockingTaskExecutor lockingTaskExecutor, LockConfigurationExtractor lockConfigurationExtractor) { this.lockingTaskExecutor = requireNonNull(lockingTaskExecutor); this.lockConfigurationExtractor = requireNonNull(lockConfigurationExtractor); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutor.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutor.java index a46fcb017..3b694bfa0 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutor.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutor.java @@ -1,32 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.shedlock.core.LockAssert.alreadyLockedBy; import java.time.Instant; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static java.util.Objects.requireNonNull; -import static net.javacrumbs.shedlock.core.LockAssert.alreadyLockedBy; - -/** - * Default {@link LockingTaskExecutor} implementation. - */ +/** Default {@link LockingTaskExecutor} implementation. */ public class DefaultLockingTaskExecutor implements LockingTaskExecutor { private static final Logger logger = LoggerFactory.getLogger(DefaultLockingTaskExecutor.class); private final LockProvider lockProvider; @@ -49,25 +44,29 @@ public void executeWithLock(Runnable task, LockConfiguration lockConfig) { @Override public void executeWithLock(Task task, LockConfiguration lockConfig) throws Throwable { - executeWithLock(() -> { - task.call(); - return null; - }, lockConfig); + executeWithLock( + () -> { + task.call(); + return null; + }, + lockConfig); } @Override public TaskResult executeWithLock(TaskWithResult task, LockConfiguration lockConfig) throws Throwable { - Optional lock = lockProvider.lock(lockConfig); String lockName = lockConfig.getName(); - if (alreadyLockedBy(lockName)) { logger.debug("Already locked '{}'", lockName); return TaskResult.result(task.call()); - } else if (lock.isPresent()) { + } + + Optional lock = lockProvider.lock(lockConfig); + if (lock.isPresent()) { try { LockAssert.startLock(lockName); LockExtender.startLock(lock.get()); - logger.debug("Locked '{}', lock will be held at most until {}", lockName, lockConfig.getLockAtMostUntil()); + logger.debug( + "Locked '{}', lock will be held at most until {}", lockName, lockConfig.getLockAtMostUntil()); return TaskResult.result(task.call()); } finally { LockAssert.endLock(); @@ -75,7 +74,8 @@ public TaskResult executeWithLock(TaskWithResult task, LockConfigurati if (activeLock != null) { activeLock.unlock(); } else { - // This should never happen, but I do not know any better way to handle the null case. + // This should never happen, but I do not know any better way to handle the null + // case. logger.warn("No active lock, please report this as a bug."); lock.get().unlock(); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ExtensibleLockProvider.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ExtensibleLockProvider.java index 26902164f..cd9b43b7b 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ExtensibleLockProvider.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/ExtensibleLockProvider.java @@ -1,22 +1,17 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -/** - * Marker interface for a LockProvider that supports lock extension. - */ -public interface ExtensibleLockProvider extends LockProvider { -} +/** Marker interface for a LockProvider that supports lock extension. */ +public interface ExtensibleLockProvider extends LockProvider {} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockAssert.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockAssert.java index 019f495b3..04b787a2e 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockAssert.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockAssert.java @@ -1,67 +1,88 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; +import java.util.Deque; +import java.util.LinkedList; + /** - * Asserts lock presence. The Spring ecosystem is so complicated, so one can not be sure that the lock is applied. This class - * makes sure that the task is indeed locked. + * Asserts lock presence. The Spring ecosystem is so complicated, so one can not + * be sure that the lock is applied. This class makes sure that the task is + * indeed locked. + * *

- * If you use AOP with Kotlin, it does not have to work due to final methods, if you use TaskExecutor wrapper, it can be - * broken by Sleuth,. + * If you use AOP with Kotlin, it does not have to work due to final methods, if + * you use TaskExecutor wrapper, it can be broken by Sleuth,. */ public final class LockAssert { - private static final ThreadLocal currentLockName = ThreadLocal.withInitial(() -> null); + // using null initial value so new LinkedList is not created every time we call + // alreadyLockedBy + private static final ThreadLocal> activeLocksTL = ThreadLocal.withInitial(() -> null); - private LockAssert() { } + private LockAssert() {} static void startLock(String name) { - currentLockName.set(name); + activeLocks().add(name); } static boolean alreadyLockedBy(String name) { - return name.equals(currentLockName.get()); + Deque activeLocks = activeLocksTL.get(); + return activeLocks != null && activeLocks.contains(name); } static void endLock() { - currentLockName.remove(); + Deque activeLocks = activeLocks(); + activeLocks.removeLast(); + if (activeLocks.isEmpty()) { + activeLocksTL.remove(); + } + } + + private static Deque activeLocks() { + if (activeLocksTL.get() == null) { + activeLocksTL.set(new LinkedList<>()); + } + return activeLocksTL.get(); } - /** - * Throws an exception if the lock is not present. - */ + /** Throws an exception if the lock is not present. */ public static void assertLocked() { - if (currentLockName.get() == null) { + Deque activeLocks = activeLocksTL.get(); + if (activeLocks == null || activeLocks.isEmpty()) { throw new IllegalStateException("The task is not locked."); } } public static class TestHelper { + + private static final String TEST_LOCK_NAME = "net.javacrumbs.shedlock.core.test-lock"; + /** - * If pass is set to true, all LockAssert.assertLocked calls in current thread will pass. - * To be used in unit tests only - * - * + * If pass is set to true, all LockAssert.assertLocked calls in current thread + * will pass. To be used in unit tests only * LockAssert.TestHelper.makeAllAssertsPass(true) * */ public static void makeAllAssertsPass(boolean pass) { if (pass) { - LockAssert.startLock("net.javacrumbs.shedlock.core.test-lock"); + if (!LockAssert.alreadyLockedBy(TEST_LOCK_NAME)) { + LockAssert.startLock(TEST_LOCK_NAME); + } } else { - LockAssert.endLock(); + if (LockAssert.alreadyLockedBy(TEST_LOCK_NAME)) { + LockAssert.endLock(); + } } } } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfiguration.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfiguration.java index a2d1511b5..fe315c0e9 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfiguration.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfiguration.java @@ -1,48 +1,48 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + import java.time.Duration; import java.time.Instant; import java.util.Objects; -import static net.javacrumbs.shedlock.core.ClockProvider.now; - -/** - * Lock configuration. - */ +/** Lock configuration. */ public class LockConfiguration { private final Instant createdAt; private final String name; /** - * The lock is held until this duration passes, after that it's automatically released (the process holding it has most likely - * died without releasing the lock) Can be ignored by providers which can detect dead processes (like Zookeeper) + * The lock is held until this duration passes, after that it's automatically + * released (the process holding it has most likely died without releasing the + * lock) Can be ignored by providers which can detect dead processes (like + * Zookeeper) */ private final Duration lockAtMostFor; /** - * The lock will be held at least this duration even if the task holding the lock finishes earlier. + * The lock will be held at least this duration even if the task holding the + * lock finishes earlier. */ private final Duration lockAtLeastFor; /** - * Creates LockConfiguration. There are two types of lock providers. One that uses "db time" which requires relative - * values of lockAtMostFor and lockAtLeastFor (currently it's only JdbcTemplateLockProvider). Second type of + * Creates LockConfiguration. There are two types of lock providers. One that + * uses "db time" which requires relative values of lockAtMostFor and + * lockAtLeastFor (currently it's only JdbcTemplateLockProvider). Second type of * lock provider uses absolute time calculated from `createdAt`. * * @param createdAt @@ -66,7 +66,6 @@ public LockConfiguration(Instant createdAt, String name, Duration lockAtMostFor, } } - public String getName() { return name; } @@ -79,9 +78,7 @@ public Instant getLockAtLeastUntil() { return createdAt.plus(lockAtLeastFor); } - /** - * Returns either now or lockAtLeastUntil whichever is later. - */ + /** Returns either now or lockAtLeastUntil whichever is later. */ public Instant getUnlockTime() { Instant now = now(); Instant lockAtLeastUntil = getLockAtLeastUntil(); @@ -98,10 +95,7 @@ public Duration getLockAtMostFor() { @Override public String toString() { - return "LockConfiguration{" + - "name='" + name + '\'' + - ", lockAtMostFor=" + lockAtMostFor + - ", lockAtLeastFor=" + lockAtLeastFor + - '}'; + return "LockConfiguration{" + "name='" + name + '\'' + ", lockAtMostFor=" + lockAtMostFor + ", lockAtLeastFor=" + + lockAtLeastFor + '}'; } } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfigurationExtractor.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfigurationExtractor.java index 7e312b27b..a0934fcba 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfigurationExtractor.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockConfigurationExtractor.java @@ -1,25 +1,21 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; import java.util.Optional; -/** - * Extracts lock parameters from the task. - */ +/** Extracts lock parameters from the task. */ public interface LockConfigurationExtractor { - Optional getLockConfiguration(Runnable task); + Optional getLockConfiguration(Runnable task); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockExtender.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockExtender.java index b313825fb..aa670398a 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockExtender.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockExtender.java @@ -1,41 +1,60 @@ package net.javacrumbs.shedlock.core; -import net.javacrumbs.shedlock.support.annotation.Nullable; - import java.time.Duration; +import java.util.Deque; +import java.util.LinkedList; import java.util.Optional; +import net.javacrumbs.shedlock.support.annotation.Nullable; public final class LockExtender { - private static final ThreadLocal activeLock = ThreadLocal.withInitial(() -> null); + // Using deque here instead of a simple thread local to be able to handle nested + // locks. + private static final ThreadLocal> activeLocks = ThreadLocal.withInitial(LinkedList::new); - private LockExtender() { } + private LockExtender() {} /** - * Extends active lock. Is based on a thread local variable so it might not work in case of async processing. + * Extends active lock. Is based on a thread local variable, so it might not + * work in case of async processing. In case of nested locks, extends the + * innermost lock. * - * @throws LockCanNotBeExtendedException when the lock can not be extended due to expired lock - * @throws NoActiveLockException when there is no active lock in the thread local - * @throws UnsupportedOperationException when the LockProvider does not support lock extension. + * @throws LockCanNotBeExtendedException + * when the lock can not be extended due to expired lock + * @throws NoActiveLockException + * when there is no active lock in the thread local + * @throws UnsupportedOperationException + * when the LockProvider does not support lock extension. */ public static void extendActiveLock(Duration lockAtMostFor, Duration lockAtLeastFor) { - SimpleLock lock = activeLock.get(); + SimpleLock lock = locks().peekLast(); if (lock == null) throw new NoActiveLockException(); Optional newLock = lock.extend(lockAtMostFor, lockAtLeastFor); if (newLock.isPresent()) { - activeLock.set(newLock.get()); + // removing and adding here should be safe as it's a thread local variable and + // the changes are + // only visible in the current thread. + locks().removeLast(); + locks().addLast(newLock.get()); } else { throw new LockCanNotBeExtendedException(); } } + private static Deque locks() { + return activeLocks.get(); + } + static void startLock(SimpleLock lock) { - activeLock.set(lock); + locks().addLast(lock); } @Nullable static SimpleLock endLock() { - SimpleLock lock = activeLock.get(); - activeLock.remove(); + SimpleLock lock = locks().pollLast(); + // we want to clean up the thread local variable when there are no locks + if (locks().isEmpty()) { + activeLocks.remove(); + } return lock; } @@ -47,7 +66,8 @@ public LockExtensionException(String message) { public static class NoActiveLockException extends LockExtensionException { public NoActiveLockException() { - super("No active lock in current thread, please make sure that you execute LockExtender.extendActiveLock in locked context."); + super( + "No active lock in current thread, please make sure that you execute LockExtender.extendActiveLock in locked context."); } } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockManager.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockManager.java index 7c17367c8..dd9e25967 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockManager.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockManager.java @@ -1,23 +1,19 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -/** - * Executes task if not locked. - */ +/** Executes task if not locked. */ public interface LockManager { void executeWithLock(Runnable task); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockProvider.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockProvider.java index 104016680..500b47c5c 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockProvider.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockProvider.java @@ -1,30 +1,26 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; import java.util.Optional; -/** - * Provides lock implementation. - */ +/** Provides lock implementation. */ public interface LockProvider { /** - * @return If empty optional has been returned, lock could not be acquired. The lock - * has to be released by the callee. + * @return If empty optional has been returned, lock could not be acquired. The + * lock has to be released by the callee. */ Optional lock(LockConfiguration lockConfiguration); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockableRunnable.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockableRunnable.java index dd09147cc..283c44d05 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockableRunnable.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockableRunnable.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockingTaskExecutor.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockingTaskExecutor.java index 45101b1f8..7a1a4f831 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockingTaskExecutor.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/LockingTaskExecutor.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; @@ -18,16 +16,12 @@ import net.javacrumbs.shedlock.support.annotation.Nullable; public interface LockingTaskExecutor { - /** - * Executes task if it's not already running. - */ + /** Executes task if it's not already running. */ void executeWithLock(Runnable task, LockConfiguration lockConfig); void executeWithLock(Task task, LockConfiguration lockConfig) throws Throwable; - /** - * Executes task. - */ + /** Executes task. */ default TaskResult executeWithLock(TaskWithResult task, LockConfiguration lockConfig) throws Throwable { throw new UnsupportedOperationException(); } @@ -45,6 +39,7 @@ interface TaskWithResult { final class TaskResult { private final boolean executed; + @Nullable private final T result; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/SimpleLock.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/SimpleLock.java index 9845f7e3b..2fbef0027 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/SimpleLock.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/core/SimpleLock.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; @@ -21,21 +19,26 @@ public interface SimpleLock { /** - * Unlocks the lock. Once you unlock it, you should not use for any other operation. + * Unlocks the lock. Once you unlock it, you should not use for any other + * operation. * - * @throws IllegalStateException if the lock has already been unlocked or extended + * @throws IllegalStateException + * if the lock has already been unlocked or extended */ void unlock(); /** - * Extends the lock. If the lock can be extended a new lock is returned. After calling extend, no other operation - * can be called on current lock. + * Extends the lock. If the lock can be extended a new lock is returned. After + * calling extend, no other operation can be called on current lock. + * *

* This method is NOT supported by all lock providers. * * @return a new lock or empty optional if the lock can not be extended - * @throws IllegalStateException if the lock has already been unlocked or extended - * @throws UnsupportedOperationException if the lock extension is not supported by LockProvider. + * @throws IllegalStateException + * if the lock has already been unlocked or extended + * @throws UnsupportedOperationException + * if the lock extension is not supported by LockProvider. */ default Optional extend(Duration lockAtMostFor, Duration lockAtLeastFor) { throw new UnsupportedOperationException(); diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/AbstractStorageAccessor.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/AbstractStorageAccessor.java index efe1ab76f..dbd3f798a 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/AbstractStorageAccessor.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/AbstractStorageAccessor.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/KeepAliveLockProvider.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/KeepAliveLockProvider.java index 221bfc3c8..d40eae94e 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/KeepAliveLockProvider.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/KeepAliveLockProvider.java @@ -1,5 +1,13 @@ package net.javacrumbs.shedlock.support; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static net.javacrumbs.shedlock.core.ClockProvider.now; + +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; @@ -8,23 +16,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static net.javacrumbs.shedlock.core.ClockProvider.now; - /** - * LockProvider that keeps the lock `alive`. In the middle of lockAtMostFor period tries to extend the lock for - * lockAtMostFor period. For example, if the lockAtMostFor is 10 minutes the lock is extended every 5 minutes for 10 minutes - * until the lock is released. If the process dies, the lock is automatically released after lockAtMostFor period as usual. + * LockProvider that keeps the lock `alive`. In the middle of lockAtMostFor + * period tries to extend the lock for lockAtMostFor period. For example, if the + * lockAtMostFor is 10 minutes the lock is extended every 5 minutes for 10 + * minutes until the lock is released. If the process dies, the lock is + * automatically released after lockAtMostFor period as usual. * - * Does not support lockAtMostFor shorter than 30s. The reason is that with short (subsecond) lockAtMostFor time the - * time when we attmpt to extend the lock is too close to the expiration time and the lock can expire before we are able to extend it. + *

+ * Does not support lockAtMostFor shorter than 30s. The reason is that + * with short (subsecond) lockAtMostFor time the time when we attmpt to extend + * the lock is too close to the expiration time and the lock can expire before + * we are able to extend it. * + *

* Wraps ExtensibleLockProvider that implements the actual locking. */ public class KeepAliveLockProvider implements LockProvider { @@ -38,7 +43,8 @@ public KeepAliveLockProvider(ExtensibleLockProvider wrapped, ScheduledExecutorSe this(wrapped, executorService, Duration.ofSeconds(30)); } - KeepAliveLockProvider(ExtensibleLockProvider wrapped, ScheduledExecutorService executorService, Duration minimalLockAtMostFor) { + KeepAliveLockProvider( + ExtensibleLockProvider wrapped, ScheduledExecutorService executorService, Duration minimalLockAtMostFor) { this.wrapped = wrapped; this.executorService = executorService; this.minimalLockAtMostFor = minimalLockAtMostFor; @@ -47,7 +53,8 @@ public KeepAliveLockProvider(ExtensibleLockProvider wrapped, ScheduledExecutorSe @Override public Optional lock(LockConfiguration lockConfiguration) { if (lockConfiguration.getLockAtMostFor().compareTo(minimalLockAtMostFor) < 0) { - throw new IllegalArgumentException("Can not use KeepAliveLockProvider with lockAtMostFor shorter than " + minimalLockAtMostFor); + throw new IllegalArgumentException( + "Can not use KeepAliveLockProvider with lockAtMostFor shorter than " + minimalLockAtMostFor); } Optional lock = wrapped.lock(lockConfiguration); return lock.map(simpleLock -> new KeepAliveLock(lockConfiguration, simpleLock, executorService)); @@ -61,7 +68,8 @@ private static class KeepAliveLock extends AbstractSimpleLock { private boolean active = true; private Instant currentLockAtMostUntil; - private KeepAliveLock(LockConfiguration lockConfiguration, SimpleLock lock, ScheduledExecutorService executorService) { + private KeepAliveLock( + LockConfiguration lockConfiguration, SimpleLock lock, ScheduledExecutorService executorService) { super(lockConfiguration); this.lock = lock; this.lockExtensionPeriod = lockConfiguration.getLockAtMostFor().dividedBy(2); @@ -70,22 +78,23 @@ private KeepAliveLock(LockConfiguration lockConfiguration, SimpleLock lock, Sche long extensionPeriodMs = lockExtensionPeriod.toMillis(); this.future = executorService.scheduleAtFixedRate( - this::extendForNextPeriod, - extensionPeriodMs, - extensionPeriodMs, - MILLISECONDS - ); + this::extendForNextPeriod, extensionPeriodMs, extensionPeriodMs, MILLISECONDS); } private void extendForNextPeriod() { - // We can have a race-condition when we extend the lock but the `lock` field is accessed before we update it. + // We can have a race-condition when we extend the lock but the `lock` field is + // accessed + // before we update it. synchronized (this) { if (!active) { return; } if (currentLockAtMostUntil.isBefore(now())) { - // Failsafe for cases when we are not able to extend the lock and it expires before the extension - // In such case someone else might have already obtained the lock so we can't extend it. + // Failsafe for cases when we are not able to extend the lock and it expires + // before the + // extension + // In such case someone else might have already obtained the lock so we can't + // extend it. stop(); return; } @@ -94,10 +103,14 @@ private void extendForNextPeriod() { remainingLockAtLeastFor = Duration.ZERO; } currentLockAtMostUntil = now().plus(lockConfiguration.getLockAtMostFor()); - Optional extendedLock = lock.extend(lockConfiguration.getLockAtMostFor(), remainingLockAtLeastFor); + Optional extendedLock = + lock.extend(lockConfiguration.getLockAtMostFor(), remainingLockAtLeastFor); if (extendedLock.isPresent()) { lock = extendedLock.get(); - logger.trace("Lock {} extended for {}", lockConfiguration.getName(), lockConfiguration.getLockAtMostFor()); + logger.trace( + "Lock {} extended for {}", + lockConfiguration.getName(), + lockConfiguration.getLockAtMostFor()); } else { logger.warn("Can't extend lock {}", lockConfiguration.getName()); stop(); @@ -113,6 +126,7 @@ private void stop() { @Override protected void doUnlock() { synchronized (this) { + logger.trace("Unlocking lock {}", lockConfiguration.getName()); stop(); lock.unlock(); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockException.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockException.java index cd39fbb94..4fda2f038 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockException.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockException.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockRecordRegistry.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockRecordRegistry.java index 523d51dd2..fe469e768 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockRecordRegistry.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/LockRecordRegistry.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; @@ -20,8 +18,9 @@ import java.util.WeakHashMap; /** - * Some LockProviders have to decide if a new record has to be created or an old one updated. - * This class helps them keep track of existing lock records, so they know if a lock record exists. + * Some LockProviders have to decide if a new record has to be created or an old + * one updated. This class helps them keep track of existing lock records, so + * they know if a lock record exists. */ class LockRecordRegistry { private final Set lockRecords = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); @@ -34,7 +33,6 @@ void removeLockRecord(String lockName) { lockRecords.remove(lockName); } - public boolean lockRecordRecentlyCreated(String lockName) { return lockRecords.contains(lockName); } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageAccessor.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageAccessor.java index 5767a9348..363ca0e4b 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageAccessor.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageAccessor.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; @@ -21,16 +19,19 @@ public interface StorageAccessor { /** * Inserts a record, if it does not already exists. If it exists, returns false. * - * @param lockConfiguration LockConfiguration + * @param lockConfiguration + * LockConfiguration * @return true if inserted */ boolean insertRecord(LockConfiguration lockConfiguration); /** - * Tries to update the lock record. If there is already a valid lock record (the lock is held by someone else) - * update should not do anything and this method returns false. + * Tries to update the lock record. If there is already a valid lock record (the + * lock is held by someone else) update should not do anything and this method + * returns false. * - * @param lockConfiguration LockConfiguration + * @param lockConfiguration + * LockConfiguration * @return true if updated */ boolean updateRecord(LockConfiguration lockConfiguration); diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageBasedLockProvider.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageBasedLockProvider.java index cec1afb80..c539f9a92 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageBasedLockProvider.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/StorageBasedLockProvider.java @@ -1,45 +1,40 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; +import java.util.Optional; import net.javacrumbs.shedlock.core.AbstractSimpleLock; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.SimpleLock; -import java.util.Optional; - /** * Distributed lock using abstract storage + * *

- * It uses a table/collection that contains ID = lock name and a field locked_until. + * It uses a table/collection that contains ID = lock name and a field + * locked_until. + * *

    - *
  1. - * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record - * has been inserted, returns lock. - *
  2. - *
  3. - * We will try to update lock record using filter ID == name AND lock_until <= now - *
  4. - *
  5. - * If the update succeeded (1 updated row/document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock - *
  6. - *
  7. - * When unlocking, lock_until is set to now. - *
  8. + *
  9. Attempts to insert a new lock record. As an optimization, we keep + * in-memory track of created lock records. If the record has been inserted, + * returns lock. + *
  10. We will try to update lock record using filter ID == name AND lock_until + * <= now + *
  11. If the update succeeded (1 updated row/document), we have the lock. If + * the update failed (0 updated documents) somebody else holds the lock + *
  12. When unlocking, lock_until is set to now. *
*/ public class StorageBasedLockProvider implements ExtensibleLockProvider { @@ -50,9 +45,7 @@ protected StorageBasedLockProvider(StorageAccessor storageAccessor) { this.storageAccessor = storageAccessor; } - /** - * Clears cache of existing lock records. - */ + /** Clears cache of existing lock records. */ public void clearCache() { lockRecordRegistry.clear(); } @@ -81,7 +74,9 @@ protected boolean doLock(LockConfiguration lockConfiguration) { // we were able to create the record, we have the lock return true; } - // we were not able to create the record, it already exists, let's put it to the cache so we do not try again + // we were not able to create the record, it already exists, let's put it to the + // cache so we + // do not try again lockRecordRegistry.addLockRecord(name); } @@ -90,9 +85,13 @@ protected boolean doLock(LockConfiguration lockConfiguration) { return storageAccessor.updateRecord(lockConfiguration); } catch (Exception e) { // There are some users that start the app before they have the DB ready. - // If they use JDBC, insertRecord returns false, the record is stored in the recordRegistry - // and the insert is not attempted again. We are assuming that the DB still does not exist - // when update is attempted. Unlike insert, update throws the exception, and we clear the cache here. + // If they use JDBC, insertRecord returns false, the record is stored in the + // recordRegistry + // and the insert is not attempted again. We are assuming that the DB still does + // not exist + // when update is attempted. Unlike insert, update throws the exception, and we + // clear the + // cache here. if (tryToCreateLockRecord) { lockRecordRegistry.removeLockRecord(name); } @@ -122,5 +121,4 @@ public Optional doExtend(LockConfiguration newConfig) { } } } - } diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/Utils.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/Utils.java index db36a80c2..8e861ad23 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/Utils.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/Utils.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; @@ -26,10 +24,13 @@ public final class Utils { /** * A {@link DateTimeFormatter} like {@link DateTimeFormatter#ISO_INSTANT} with - * the exception that it always appends exactly three fractional digits (nano seconds). + * the exception that it always appends exactly three fractional digits (nano + * seconds). + * *

- * This is required in order to guarantee natural sorting, which enables us to use - * <= comparision in queries. + * This is required in order to guarantee natural sorting, which enables us to + * use <= + * comparision in queries. * *

      * 2018-12-07T12:30:37.000Z
@@ -37,10 +38,12 @@ public final class Utils {
      * 2018-12-07T12:30:37.819Z
      * 2018-12-07T12:30:37.820Z
      * 
+ * *

- * When using variable fractional digit count as done in {@link DateTimeFormatter#ISO_INSTANT ISO_INSTANT} - * and {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO_OFFSET_DATE_TIME} the following sorting - * occurs: + * When using variable fractional digit count as done in + * {@link DateTimeFormatter#ISO_INSTANT ISO_INSTANT} and + * {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO_OFFSET_DATE_TIME} the + * following sorting occurs: * *

      * 2018-12-07T12:30:37.819Z
@@ -49,18 +52,17 @@ public final class Utils {
      * 2018-12-07T12:30:37Z
      * 
* - * @see natural sorting of ISO 8601 time format + * @see natural sorting of ISO + * 8601 time format */ private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendInstant(3) - .toFormatter(); - + .parseCaseInsensitive() + .appendInstant(3) + .toFormatter(); private static final String hostname = initHostname(); - private Utils() { - } + private Utils() {} public static String getHostname() { return hostname; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNull.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNull.java index 193b30515..8accacde5 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNull.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNull.java @@ -1,29 +1,28 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support.annotation; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; /** - * A common annotation to declare that annotated elements cannot be {@code null}. + * A common annotation to declare that annotated elements cannot be + * {@code null}. * * @see Nullable */ @@ -31,5 +30,4 @@ @Retention(RetentionPolicy.RUNTIME) @Nonnull @TypeQualifierNickname -public @interface NonNull { -} +public @interface NonNull {} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullApi.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullApi.java index 1c90870ff..2ed18240a 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullApi.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullApi.java @@ -1,21 +1,23 @@ package net.javacrumbs.shedlock.support.annotation; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; /** - * A common annotation to declare that parameters and return values - * are to be considered as non-nullable by default for a given package. + * A common annotation to declare that parameters and return values are to be + * considered as non-nullable by default for a given package. * - *

Leverages JSR-305 meta-annotations to indicate nullability in Java to common + *

+ * Leverages JSR-305 meta-annotations to indicate nullability in Java to common * tools with JSR-305 support and used by Kotlin to infer nullability of API. * - *

Should be used at package level in association with {@link Nullable} + *

+ * Should be used at package level in association with {@link Nullable} * annotations at parameter and return value level. * * @author Sebastien Deleuze @@ -29,5 +31,4 @@ @Documented @Nonnull @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) -public @interface NonNullApi { -} +public @interface NonNullApi {} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullFields.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullFields.java index d805ac304..ebb7d00b3 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullFields.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/NonNullFields.java @@ -1,21 +1,23 @@ package net.javacrumbs.shedlock.support.annotation; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; /** * A common annotation to declare that fields are to be considered as * non-nullable by default for a given package. * - *

Leverages JSR-305 meta-annotations to indicate nullability in Java to common - * tools with JSR-305 support and used by Kotlin to infer nullability API. + *

+ * Leverages JSR-305 meta-annotations to indicate nullability in Java to common + * tools with JSR-305 support and used by Kotlin to infer nullability API. * - *

Should be used at package level in association with {@link Nullable} + *

+ * Should be used at package level in association with {@link Nullable} * annotations at field level. * * @author Sebastien Deleuze @@ -28,5 +30,4 @@ @Documented @Nonnull @TypeQualifierDefault(ElementType.FIELD) -public @interface NonNullFields { -} +public @interface NonNullFields {} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/Nullable.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/Nullable.java index 6e4514940..c32cb8b6e 100644 --- a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/Nullable.java +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/Nullable.java @@ -1,35 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support.annotation; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; -import javax.annotation.meta.When; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; /** - * A common annotation to declare that annotated elements can be {@code null} under - * some circumstance. + * A common annotation to declare that annotated elements can be {@code null} + * under some circumstance. */ @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Nonnull(when = When.MAYBE) @TypeQualifierNickname -public @interface Nullable { -} +public @interface Nullable {} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/package-info.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/package-info.java new file mode 100644 index 000000000..07cfef0f2 --- /dev/null +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/support/annotation/package-info.java @@ -0,0 +1,3 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.support.annotation; diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/SimpleLockWithConfiguration.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/SimpleLockWithConfiguration.java new file mode 100644 index 000000000..f95e3c4d1 --- /dev/null +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/SimpleLockWithConfiguration.java @@ -0,0 +1,8 @@ +package net.javacrumbs.shedlock.util; + +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; + +public interface SimpleLockWithConfiguration extends SimpleLock { + LockConfiguration getLockConfiguration(); +} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapper.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapper.java new file mode 100644 index 000000000..0359d5d3f --- /dev/null +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapper.java @@ -0,0 +1,79 @@ +package net.javacrumbs.shedlock.util; + +import static java.util.Collections.newSetFromMap; +import static java.util.Collections.synchronizedSet; + +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; + +/** + * Wraps a LockProvider and keeps track of active locks. You can use the getActiveLocks() method + * to obtain the list of active locks. Unlocking the returned locks will most likely lead to unpredictable + * results - the lock will be released another node can get the lock while the original task is still running. + */ +public class TrackingLockProviderWrapper implements LockProvider { + private final LockProvider wrapped; + private final Set activeLocks = synchronizedSet(newSetFromMap(new IdentityHashMap<>())); + + public TrackingLockProviderWrapper(LockProvider wrapped) { + this.wrapped = wrapped; + } + + @Override + public Optional lock(LockConfiguration lockConfiguration) { + Optional result = wrapped.lock(lockConfiguration); + if (result.isPresent()) { + SimpleLock wrappedLock = new SimpleLockWrapper(result.get(), lockConfiguration); + activeLocks.add(wrappedLock); + return Optional.of(wrappedLock); + } else { + return Optional.empty(); + } + } + + public Collection getActiveLocks() { + return Collections.unmodifiableSet(activeLocks); + } + + private class SimpleLockWrapper implements SimpleLockWithConfiguration { + private final SimpleLock wrappedLock; + private final LockConfiguration lockConfiguration; + private final AtomicBoolean locked = new AtomicBoolean(true); + + private SimpleLockWrapper(SimpleLock wrappedLock, LockConfiguration lockConfiguration) { + this.wrappedLock = wrappedLock; + this.lockConfiguration = lockConfiguration; + } + + @Override + public void unlock() { + try { + // Unlocking only once - unlocking twice is dangerous as it's likely that the second unlock will + // unlock a lock held by another process + if (locked.compareAndSet(true, false)) { + wrappedLock.unlock(); + } + } finally { + activeLocks.remove(this); + } + } + + @Override + public Optional extend(Duration lockAtMostFor, Duration lockAtLeastFor) { + return wrappedLock.extend(lockAtMostFor, lockAtLeastFor); + } + + @Override + public LockConfiguration getLockConfiguration() { + return lockConfiguration; + } + } +} diff --git a/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/package-info.java b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/package-info.java new file mode 100644 index 000000000..a3c1253d3 --- /dev/null +++ b/shedlock-core/src/main/java/net/javacrumbs/shedlock/util/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.util; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockManagerTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockManagerTest.java index 346d48a06..bf698323a 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockManagerTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockManagerTest.java @@ -1,27 +1,18 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; - -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; - -import java.time.Duration; -import java.util.Optional; - import static net.javacrumbs.shedlock.core.ClockProvider.now; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -29,16 +20,22 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + class DefaultLockManagerTest { - private static final LockConfiguration LOCK_CONFIGURATION = new LockConfiguration(now(),"name", Duration.ofSeconds(10), Duration.ZERO); + private static final LockConfiguration LOCK_CONFIGURATION = + new LockConfiguration(now(), "name", Duration.ofSeconds(10), Duration.ZERO); private final LockProvider lockProvider = mock(LockProvider.class); private final LockConfigurationExtractor lockConfigurationExtractor = mock(LockConfigurationExtractor.class); private final Runnable task = mock(Runnable.class); private final SimpleLock lock = mock(SimpleLock.class); - private final DefaultLockManager defaultLockManager = new DefaultLockManager(lockProvider, lockConfigurationExtractor); - + private final DefaultLockManager defaultLockManager = + new DefaultLockManager(lockProvider, lockConfigurationExtractor); @Test void noConfigNoLock() { @@ -69,5 +66,4 @@ void doNotExecuteIfAlreadyLocked() { defaultLockManager.executeWithLock(task); verifyNoInteractions(task); } - } diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutorTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutorTest.java index c03e46b34..d2c32098d 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutorTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/DefaultLockingTaskExecutorTest.java @@ -1,54 +1,69 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import net.javacrumbs.shedlock.core.LockingTaskExecutor.TaskResult; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - import static net.javacrumbs.shedlock.core.ClockProvider.now; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import net.javacrumbs.shedlock.core.LockingTaskExecutor.TaskResult; +import org.junit.jupiter.api.Test; + class DefaultLockingTaskExecutorTest { private final LockProvider lockProvider = mock(LockProvider.class); private final DefaultLockingTaskExecutor executor = new DefaultLockingTaskExecutor(lockProvider); - private final LockConfiguration lockConfig = new LockConfiguration(now(),"test", Duration.ofSeconds(100), Duration.ZERO); + private final LockConfiguration lockConfig = + new LockConfiguration(now(), "test", Duration.ofSeconds(100), Duration.ZERO); + private final LockConfiguration lockConfig2 = + new LockConfiguration(now(), "test2", Duration.ofSeconds(100), Duration.ZERO); @Test void lockShouldBeReentrant() { - when(lockProvider.lock(lockConfig)) - .thenReturn(Optional.of(mock(SimpleLock.class))) - .thenReturn(Optional.empty()); + mockLockFor(lockConfig); AtomicBoolean called = new AtomicBoolean(false); - executor.executeWithLock((Runnable) () -> executor.executeWithLock((Runnable) () -> called.set(true), lockConfig), lockConfig); + executor.executeWithLock( + (Runnable) () -> executor.executeWithLock((Runnable) () -> called.set(true), lockConfig), lockConfig); assertThat(called.get()).isTrue(); } + @Test + void lockShouldBeReentrantForMultipleDifferentLocks() { + mockLockFor(lockConfig); + mockLockFor(lockConfig2); + + AtomicBoolean called = new AtomicBoolean(false); + + executor.executeWithLock( + (Runnable) () -> executor.executeWithLock((Runnable) () -> called.set(true), lockConfig2), lockConfig); + + assertThat(called.get()).isTrue(); + } + + private void mockLockFor(LockConfiguration lockConfig2) { + when(lockProvider.lock(lockConfig2)).thenReturn(Optional.of(mock(SimpleLock.class))); + } + @Test void shouldExecuteWithResult() throws Throwable { - when(lockProvider.lock(lockConfig)) - .thenReturn(Optional.of(mock(SimpleLock.class))); + mockLockFor(lockConfig); TaskResult result = executor.executeWithLock(() -> "result", lockConfig); assertThat(result.wasExecuted()).isTrue(); diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockAssertTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockAssertTest.java index 43cefc1e7..af1622160 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockAssertTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockAssertTest.java @@ -1,30 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Optional; - import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.core.LockAssert.alreadyLockedBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.util.Optional; +import org.junit.jupiter.api.Test; + class LockAssertTest { @Test @@ -34,15 +33,38 @@ void assertLockedShouldFailIfLockNotHeld() { @Test void assertLockedShouldNotFailIfLockHeld() { - LockConfiguration lockConfiguration = new LockConfiguration(now(), "test", Duration.ofSeconds(10), Duration.ZERO); + LockConfiguration lockConfiguration = + new LockConfiguration(now(), "test", Duration.ofSeconds(10), Duration.ZERO); LockProvider lockProvider = mock(LockProvider.class); when(lockProvider.lock(lockConfiguration)).thenReturn(Optional.of(mock(SimpleLock.class))); - new DefaultLockingTaskExecutor(lockProvider).executeWithLock( - (Runnable) LockAssert::assertLocked, - lockConfiguration - ); + new DefaultLockingTaskExecutor(lockProvider) + .executeWithLock((Runnable) LockAssert::assertLocked, lockConfiguration); + } + + @Test + void shouldWorkWithNestedLocks() { + LockAssert.startLock("outer"); + assertThat(alreadyLockedBy("outer")).isTrue(); + assertThat(alreadyLockedBy("inner")).isFalse(); + LockAssert.assertLocked(); + + LockAssert.startLock("inner"); + assertThat(alreadyLockedBy("inner")).isTrue(); + assertThat(alreadyLockedBy("outer")).isTrue(); + LockAssert.assertLocked(); + + LockAssert.endLock(); + assertThat(alreadyLockedBy("inner")).isFalse(); + assertThat(alreadyLockedBy("outer")).isTrue(); + LockAssert.assertLocked(); + + LockAssert.endLock(); + assertThat(alreadyLockedBy("inner")).isFalse(); + assertThat(alreadyLockedBy("outer")).isFalse(); + + assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); } @Test @@ -53,4 +75,20 @@ void assertShouldNotFailIfConfiguredForTests() { LockAssert.TestHelper.makeAllAssertsPass(false); assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); } + + @Test + void makeAllAssertsPassShouldNotFail() { + LockAssert.TestHelper.makeAllAssertsPass(false); + } + + @Test + void shouldNotStartMoreThanOneTestLock() { + assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); + LockAssert.TestHelper.makeAllAssertsPass(true); + LockAssert.TestHelper.makeAllAssertsPass(true); + + LockAssert.TestHelper.makeAllAssertsPass(false); + + assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); + } } diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockConfigurationTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockConfigurationTest.java index 650b8718a..0c005cec7 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockConfigurationTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockConfigurationTest.java @@ -1,48 +1,46 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import org.junit.jupiter.api.Test; - import static java.time.Duration.ZERO; import static java.time.Duration.ofMillis; import static java.time.Duration.ofSeconds; import static net.javacrumbs.shedlock.core.ClockProvider.now; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; class LockConfigurationTest { @Test void lockAtLeastUntilShouldBeBeforeOrEqualsToLockAtMostUntil() { new LockConfiguration(now(), "name", ZERO, ZERO); - new LockConfiguration(now(),"name", ofMillis(1), ZERO); + new LockConfiguration(now(), "name", ofMillis(1), ZERO); - assertThatThrownBy(() -> new LockConfiguration(now(),"name", ZERO, ofMillis(1))).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new LockConfiguration(now(), "name", ZERO, ofMillis(1))) + .isInstanceOf(IllegalArgumentException.class); } @Test void lockAtMostUntilHasToBeInTheFuture() { - assertThatThrownBy(() -> new LockConfiguration(now(),"name", ofSeconds(-1), ZERO)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new LockConfiguration(now(), "name", ofSeconds(-1), ZERO)) + .isInstanceOf(IllegalArgumentException.class); } - @Test void nameShouldNotBeEmpty() { - assertThatThrownBy(() -> new LockConfiguration(now(),"", ofSeconds(5), ofSeconds(5))).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new LockConfiguration(now(), "", ofSeconds(5), ofSeconds(5))) + .isInstanceOf(IllegalArgumentException.class); } - } diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockExtenderTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockExtenderTest.java index 178570271..0d2ee1c3c 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockExtenderTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/LockExtenderTest.java @@ -1,33 +1,31 @@ package net.javacrumbs.shedlock.core; -import net.javacrumbs.shedlock.core.LockExtender.LockCanNotBeExtendedException; -import net.javacrumbs.shedlock.core.LockExtender.NoActiveLockException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - import static java.time.Duration.ZERO; import static java.time.Duration.ofSeconds; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockExtender.LockCanNotBeExtendedException; +import net.javacrumbs.shedlock.core.LockExtender.NoActiveLockException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class LockExtenderTest { private final LockProvider lockProvider = mock(LockProvider.class); private final SimpleLock lock = mock(SimpleLock.class); - private final SimpleLock newLock = mock(SimpleLock.class); + private final SimpleLock newLock = mock(SimpleLock.class); private final DefaultLockingTaskExecutor executor = new DefaultLockingTaskExecutor(lockProvider); private final LockConfiguration configuration = new LockConfiguration(Instant.now(), "test", ofSeconds(1), ZERO); private final Duration extendBy = ofSeconds(1); @BeforeEach void configureLockProvider() { - when(lockProvider.lock(any())).thenReturn(Optional.of(lock)); + when(lockProvider.lock(configuration)).thenReturn(Optional.of(lock)); } @Test @@ -40,6 +38,20 @@ void shouldExtendActiveLock() { verify(lock).extend(extendBy, ZERO); } + @Test + void shouldExtendNestedLock() { + LockConfiguration innerConfiguration = new LockConfiguration(Instant.now(), "test2", ofSeconds(1), ZERO); + SimpleLock innerLock = mock(SimpleLock.class); + when(lockProvider.lock(innerConfiguration)).thenReturn(Optional.of(innerLock)); + when(innerLock.extend(extendBy, ZERO)).thenReturn(Optional.of(newLock)); + + Runnable innerTask = () -> LockExtender.extendActiveLock(extendBy, ZERO); + Runnable outerTask = () -> executor.executeWithLock(innerTask, innerConfiguration); + executor.executeWithLock(outerTask, configuration); + + verify(innerLock).extend(extendBy, ZERO); + } + @Test void shouldExtendActiveLockTwice() { when(lock.extend(extendBy, ZERO)).thenReturn(Optional.of(newLock)); @@ -63,12 +75,12 @@ void shouldFailIfLockCanNotBeExtended() { Runnable task = () -> LockExtender.extendActiveLock(extendBy, ZERO); assertThatThrownBy(() -> executor.executeWithLock(task, configuration)) - .isInstanceOf(LockCanNotBeExtendedException.class); + .isInstanceOf(LockCanNotBeExtendedException.class); } @Test void shouldFailIfNoActiveLock() { assertThatThrownBy(() -> LockExtender.extendActiveLock(ofSeconds(1), ofSeconds(0))) - .isInstanceOf(NoActiveLockException.class); + .isInstanceOf(NoActiveLockException.class); } } diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProvider.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProvider.java index 69c1fe08c..708b0e194 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProvider.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProvider.java @@ -1,34 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import net.javacrumbs.shedlock.support.annotation.NonNull; - import java.util.Optional; import java.util.concurrent.locks.ReentrantLock; /** - * Lock provider based on {@link java.util.concurrent.locks.ReentrantLock}. Only one task per - * SimpleLockProvider can be running. Useful mainly for testing. + * Lock provider based on {@link java.util.concurrent.locks.ReentrantLock}. Only + * one task per SimpleLockProvider can be running. Useful mainly for testing. */ public class ReentrantLockProvider implements LockProvider { private final ReentrantLock lock = new ReentrantLock(); @Override - public Optional lock(@NonNull LockConfiguration lockConfiguration) { + public Optional lock(LockConfiguration lockConfiguration) { if (lock.tryLock()) { return Optional.of(lock::unlock); } else { diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProviderTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProviderTest.java index f0eda7a46..71b1a2d24 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProviderTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/core/ReentrantLockProviderTest.java @@ -1,22 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.core; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.time.Duration; import java.util.Optional; @@ -26,23 +27,21 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ReentrantLockProviderTest { private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10); private final LockProvider lockProvider = new ReentrantLockProvider(); private final LockConfigurationExtractor lockConfigurationExtractor = mock(LockConfigurationExtractor.class); private final LockManager lockManager = new DefaultLockManager(lockProvider, lockConfigurationExtractor); - private final LockConfiguration configuration = new LockConfiguration(now(),"test", Duration.ofSeconds(60), Duration.ZERO); + private final LockConfiguration configuration = + new LockConfiguration(now(), "test", Duration.ofSeconds(60), Duration.ZERO); @BeforeEach void configureMocks() { - when(lockConfigurationExtractor.getLockConfiguration(any(Runnable.class))).thenReturn(Optional.of(configuration)); + when(lockConfigurationExtractor.getLockConfiguration(any(Runnable.class))) + .thenReturn(Optional.of(configuration)); } @Test @@ -65,8 +64,10 @@ void shouldNotExecuteTwiceAtTheSameTime() throws ExecutionException, Interrupted assertThat(runningTasks.decrementAndGet()).isEqualTo(0); executedTasks.incrementAndGet(); }; - ScheduledFuture scheduledFuture1 = executor.schedule(new LockableRunnable(task, lockManager), 1, TimeUnit.MILLISECONDS); - ScheduledFuture scheduledFuture2 = executor.schedule(new LockableRunnable(task, lockManager), 1, TimeUnit.MILLISECONDS); + ScheduledFuture scheduledFuture1 = + executor.schedule(new LockableRunnable(task, lockManager), 1, TimeUnit.MILLISECONDS); + ScheduledFuture scheduledFuture2 = + executor.schedule(new LockableRunnable(task, lockManager), 1, TimeUnit.MILLISECONDS); scheduledFuture1.get(); scheduledFuture2.get(); @@ -81,5 +82,4 @@ private void sleep(int millis) { throw new RuntimeException(e); } } - } diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/KeepAliveLockProviderTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/KeepAliveLockProviderTest.java index f6f105bb9..2fcd7f97a 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/KeepAliveLockProviderTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/KeepAliveLockProviderTest.java @@ -1,15 +1,5 @@ package net.javacrumbs.shedlock.support; -import net.javacrumbs.shedlock.core.ExtensibleLockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.SimpleLock; -import org.jmock.lib.concurrent.DeterministicScheduler; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Optional; - import static java.time.Duration.ZERO; import static java.time.Duration.ofMillis; import static java.time.Duration.ofSeconds; @@ -23,11 +13,21 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.util.Optional; +import net.javacrumbs.shedlock.core.ExtensibleLockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.SimpleLock; +import org.jmock.lib.concurrent.DeterministicScheduler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class KeepAliveLockProviderTest { private final ExtensibleLockProvider wrappedProvider = mock(ExtensibleLockProvider.class); private final DeterministicScheduler scheduler = new DeterministicScheduler(); private final KeepAliveLockProvider provider = new KeepAliveLockProvider(wrappedProvider, scheduler, ofSeconds(1)); - private final LockConfiguration lockConfiguration = new LockConfiguration(now(), "lock", ofSeconds(3), ofSeconds(2)); + private final LockConfiguration lockConfiguration = + new LockConfiguration(now(), "lock", ofSeconds(3), ofSeconds(2)); private final SimpleLock originalLock = mock(SimpleLock.class); @BeforeEach @@ -81,7 +81,7 @@ void shouldCancelIfCanNotExtend() { @Test void shouldFailForShortLockAtMostFor() { assertThatThrownBy(() -> provider.lock(new LockConfiguration(now(), "short", ofMillis(100), ZERO))) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } private void tickMs(int i) { diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/LockRecordRegistryTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/LockRecordRegistryTest.java index 19700b15a..453e62b25 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/LockRecordRegistryTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/LockRecordRegistryTest.java @@ -1,25 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class LockRecordRegistryTest { private static final String NAME = "name"; diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/StorageBasedLockProviderTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/StorageBasedLockProviderTest.java index 651bbb4bc..a14a9360a 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/StorageBasedLockProviderTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/StorageBasedLockProviderTest.java @@ -1,26 +1,18 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; -import net.javacrumbs.shedlock.core.LockConfiguration; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; - import static net.javacrumbs.shedlock.core.ClockProvider.now; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -31,8 +23,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import net.javacrumbs.shedlock.core.LockConfiguration; +import org.junit.jupiter.api.Test; + class StorageBasedLockProviderTest { - private static final LockConfiguration LOCK_CONFIGURATION = new LockConfiguration(now(),"name", Duration.of(5, ChronoUnit.MINUTES), Duration.ZERO); + private static final LockConfiguration LOCK_CONFIGURATION = + new LockConfiguration(now(), "name", Duration.of(5, ChronoUnit.MINUTES), Duration.ZERO); private static final LockException LOCK_EXCEPTION = new LockException("Test"); private final StorageAccessor storageAccessor = mock(StorageAccessor.class); diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/UtilsTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/UtilsTest.java index e1ed7b84b..aa873fa0a 100644 --- a/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/UtilsTest.java +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/support/UtilsTest.java @@ -1,27 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.support; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - import static net.javacrumbs.shedlock.support.Utils.toIsoString; import static org.assertj.core.api.Assertions.assertThat; +import java.time.Instant; +import org.junit.jupiter.api.Test; class UtilsTest { diff --git a/shedlock-core/src/test/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapperTest.java b/shedlock-core/src/test/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapperTest.java new file mode 100644 index 000000000..696e8403c --- /dev/null +++ b/shedlock-core/src/test/java/net/javacrumbs/shedlock/util/TrackingLockProviderWrapperTest.java @@ -0,0 +1,84 @@ +package net.javacrumbs.shedlock.util; + +import static java.time.Duration.ZERO; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import org.junit.jupiter.api.Test; + +class TrackingLockProviderWrapperTest { + private final LockProvider wrapped = mock(LockProvider.class); + private final TrackingLockProviderWrapper wrapper = new TrackingLockProviderWrapper(wrapped); + + @Test + void shouldTrackLocks() { + LockConfiguration configA = config("a"); + LockConfiguration configB = config("b"); + LockConfiguration configC = config("c"); + SimpleLock lockA = mock(SimpleLock.class); + SimpleLock lockB = mock(SimpleLock.class); + + when(wrapped.lock(eq(configA))).thenReturn(Optional.of(lockA)); + when(wrapped.lock(eq(configB))).thenReturn(Optional.of(lockB)); + when(wrapped.lock(eq(configC))).thenReturn(Optional.empty()); + + Optional a = wrapper.lock(configA); + assertThat(a).isPresent(); + assertThat(((SimpleLockWithConfiguration) a.get()).getLockConfiguration()) + .isEqualTo(configA); + + Optional b = wrapper.lock(configB); + assertThat(b).isPresent(); + + assertThat(wrapper.lock(configC)).isNotPresent(); + + assertThat(wrapper.getActiveLocks()).containsExactlyInAnyOrder(a.get(), b.get()); + + a.get().unlock(); + b.get().extend(Duration.ofSeconds(10), ZERO); + + Collection activeLocks = wrapper.getActiveLocks(); + assertThat(activeLocks).containsExactlyInAnyOrder(b.get()); + activeLocks.forEach(SimpleLock::unlock); + + assertThat(wrapper.getActiveLocks()).isEmpty(); + + verify(lockA).unlock(); + verify(lockB).unlock(); + verify(lockB).extend(Duration.ofSeconds(10), ZERO); + } + + @Test + void shouldUnlockOnlyOnce() { + LockConfiguration configA = config("a"); + SimpleLock lockA = mock(SimpleLock.class); + + when(wrapped.lock(eq(configA))).thenReturn(Optional.of(lockA)); + + Optional a = wrapper.lock(configA); + assertThat(a).isPresent(); + + Collection activeLocks = wrapper.getActiveLocks(); + assertThat(activeLocks).containsExactlyInAnyOrder(a.get()); + + a.get().unlock(); + a.get().unlock(); + + verify(lockA, times(1)).unlock(); + } + + private LockConfiguration config(String name) { + return new LockConfiguration(Instant.now(), name, Duration.ofSeconds(10), ZERO); + } +} diff --git a/shedlock-test-support/pom.xml b/shedlock-test-support/pom.xml index 14187b342..3acc75a6d 100644 --- a/shedlock-test-support/pom.xml +++ b/shedlock-test-support/pom.xml @@ -3,12 +3,12 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT 4.0.0 shedlock-test-support - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -27,7 +27,6 @@ org.junit.jupiter junit-jupiter - ${junit.ver} compile diff --git a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractExtensibleLockProviderIntegrationTest.java b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractExtensibleLockProviderIntegrationTest.java index b8874859f..d2ca825c0 100644 --- a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractExtensibleLockProviderIntegrationTest.java +++ b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractExtensibleLockProviderIntegrationTest.java @@ -1,33 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support; +import static java.time.Duration.ZERO; +import static java.time.Duration.ofSeconds; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.Optional; import net.javacrumbs.shedlock.core.ExtensibleLockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.Test; -import java.time.Duration; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public abstract class AbstractExtensibleLockProviderIntegrationTest extends AbstractLockProviderIntegrationTest { - private final Duration originalLockDuration = Duration.ofSeconds(2); + private final Duration originalLockDuration = ofSeconds(2); @Override protected abstract ExtensibleLockProvider getLockProvider(); @@ -36,13 +35,25 @@ public abstract class AbstractExtensibleLockProviderIntegrationTest extends Abst public void shouldBeAbleToExtendLock() { SimpleLock lock = lock(originalLockDuration); - Optional newLock = lock.extend(Duration.ofSeconds(10), Duration.ZERO); - assertThat(newLock).isNotEmpty(); + SimpleLock newLock = extendLock(lock); // wait for the original lock to be released sleepFor(originalLockDuration); assertLocked(LOCK_NAME1); - newLock.get().unlock(); + newLock.unlock(); + assertUnlocked(LOCK_NAME1); + } + + @Test + public void shouldBeAbleToExtendMultipleTimes() { + SimpleLock lock = lock(originalLockDuration); + + SimpleLock newLock = extendLock(extendLock(extendLock(lock))); + + // wait for the original lock to be released + sleepFor(originalLockDuration); + assertLocked(LOCK_NAME1); + newLock.unlock(); assertUnlocked(LOCK_NAME1); } @@ -51,54 +62,57 @@ public void shouldNotBeAbleToExtendUnlockedLock() { SimpleLock lock = lock(originalLockDuration); lock.unlock(); assertUnlocked(LOCK_NAME1); - assertInvalidLock(() -> lock.extend(Duration.ofSeconds(10), Duration.ZERO)); + assertInvalidLock(() -> lock.extend(ofSeconds(10), ZERO)); } @Test public void shouldNotBeAbleToExtendExpiredLock() { - Optional lock = getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(1), Duration.ZERO)); - sleepFor(Duration.ofMillis(1)); + Optional lock = getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(2), ZERO)); assertThat(lock).isNotEmpty(); + sleepFor(Duration.ofMillis(4)); - Optional newLock = lock.get().extend(Duration.ofSeconds(10), Duration.ZERO); + Optional newLock = lock.get().extend(ofSeconds(10), ZERO); assertThat(newLock).isEmpty(); assertUnlocked(LOCK_NAME1); } - @Test public void shouldBeAbleToExtendAtLeast() { - SimpleLock lock = lock(Duration.ofSeconds(10)); + SimpleLock lock = lock(ofSeconds(10)); - SimpleLock newLock = extendLock(lock); + SimpleLock newLock = extendLock(lock, ofSeconds(9)); newLock.unlock(); assertLocked(LOCK_NAME1); } @Test public void lockCanNotBeExtendedTwice() { - SimpleLock lock = lock(Duration.ofSeconds(10)); - extendLock(lock); + SimpleLock lock = lock(ofSeconds(10)); + extendLock(lock, ofSeconds(9)); - assertInvalidLock(() -> lock.extend(Duration.ofSeconds(10), Duration.ofSeconds(9))); + assertInvalidLock(() -> lock.extend(ofSeconds(10), ofSeconds(9))); } @Test public void lockCanNotBeUnlockedAfterExtending() { - SimpleLock lock = lock(Duration.ofSeconds(10)); - extendLock(lock); + SimpleLock lock = lock(ofSeconds(10)); + extendLock(lock, ofSeconds(9)); assertInvalidLock(lock::unlock); } private SimpleLock extendLock(SimpleLock lock) { - Optional newLock = lock.extend(Duration.ofSeconds(10), Duration.ofSeconds(9)); + return extendLock(lock, ZERO); + } + + private SimpleLock extendLock(SimpleLock lock, Duration lockAtLeastFor) { + Optional newLock = lock.extend(ofSeconds(10), lockAtLeastFor); assertThat(newLock).isNotEmpty(); return newLock.get(); } - private SimpleLock lock(Duration lockAtMostFor) { - Optional lock = getLockProvider().lock(lockConfig(LOCK_NAME1, lockAtMostFor, Duration.ZERO)); + protected SimpleLock lock(Duration lockAtMostFor) { + Optional lock = getLockProvider().lock(lockConfig(LOCK_NAME1, lockAtMostFor, ZERO)); assertThat(lock).isNotEmpty(); assertLocked(LOCK_NAME1); return lock.get(); diff --git a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractLockProviderIntegrationTest.java b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractLockProviderIntegrationTest.java index 9ff2b1453..75f09ffaa 100644 --- a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractLockProviderIntegrationTest.java +++ b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractLockProviderIntegrationTest.java @@ -1,35 +1,33 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.core.SimpleLock; -import org.junit.jupiter.api.Test; +import static java.lang.Thread.sleep; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; - -import static java.lang.Thread.sleep; -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.time.temporal.ChronoUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; public abstract class AbstractLockProviderIntegrationTest { protected final String LOCK_NAME1 = UUID.randomUUID().toString(); @@ -97,25 +95,25 @@ protected void doTestTimeout(Duration lockAtMostFor) throws InterruptedException sleep(lockAtMostFor.toMillis() * 2); assertUnlocked(LOCK_NAME1); - Optional lock2 = getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(50), Duration.ZERO)); + Optional lock2 = + getLockProvider().lock(lockConfig(LOCK_NAME1, Duration.ofMillis(50), Duration.ZERO)); assertThat(lock2).isNotEmpty(); lock2.get().unlock(); } - @Test public void shouldBeAbleToLockRightAfterUnlock() { LockConfiguration lockConfiguration = lockConfig(LOCK_NAME1); for (int i = 0; i < 10; i++) { Optional lock = getLockProvider().lock(lockConfiguration); - assertThat(lock).describedAs("Successfully locked").isNotEmpty(); + assertThat(lock).describedAs("Successfully locked " + i).isNotEmpty(); assertThat(getLockProvider().lock(lockConfiguration)).isEmpty(); - assertThat(lock).isNotEmpty(); lock.get().unlock(); } } @Test + @Disabled public void fuzzTestShouldPass() throws ExecutionException, InterruptedException { new FuzzTester(getLockProvider()).doFuzzTest(); } @@ -126,20 +124,26 @@ public void shouldLockAtLeastFor() throws InterruptedException { } protected void doTestShouldLockAtLeastFor(int sleepForMs) throws InterruptedException { - // Lock for LOCK_AT_LEAST_FOR - we do not expect the lock to be released before this time - Optional lock1 = getLockProvider().lock(lockConfig(LOCK_NAME1, LOCK_AT_LEAST_FOR.multipliedBy(2), LOCK_AT_LEAST_FOR)); + // Lock for LOCK_AT_LEAST_FOR - we do not expect the lock to be released before + // this time + Optional lock1 = + getLockProvider().lock(lockConfig(LOCK_NAME1, LOCK_AT_LEAST_FOR.multipliedBy(2), LOCK_AT_LEAST_FOR)); assertThat(lock1).describedAs("Should be locked").isNotEmpty(); lock1.get().unlock(); // Even though we have unlocked the lock, it will be held for some time - assertThat(getLockProvider().lock(lockConfig(LOCK_NAME1))).describedAs(getClass().getName() + "Can not acquire lock, grace period did not pass yet").isEmpty(); + assertThat(getLockProvider().lock(lockConfig(LOCK_NAME1))) + .describedAs(getClass().getName() + "Can not acquire lock, grace period did not pass yet") + .isEmpty(); // Let's wait for the lock to be automatically released sleep(LOCK_AT_LEAST_FOR.toMillis() + sleepForMs); // Should be able to acquire now Optional lock3 = getLockProvider().lock(lockConfig(LOCK_NAME1)); - assertThat(lock3).describedAs(getClass().getName() + "Can acquire the lock after grace period").isNotEmpty(); + assertThat(lock3) + .describedAs(getClass().getName() + "Can acquire the lock after grace period") + .isNotEmpty(); lock3.get().unlock(); } @@ -157,5 +161,5 @@ protected static LockConfiguration lockConfig(String name) { protected static LockConfiguration lockConfig(String name, Duration lockAtMostFor, Duration lockAtLeastFor) { return new LockConfiguration(ClockProvider.now(), name, lockAtMostFor, lockAtLeastFor); - } + } } diff --git a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractStorageBasedLockProviderIntegrationTest.java b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractStorageBasedLockProviderIntegrationTest.java index 660dbbba3..59bc53570 100644 --- a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractStorageBasedLockProviderIntegrationTest.java +++ b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/AbstractStorageBasedLockProviderIntegrationTest.java @@ -1,35 +1,32 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.support.StorageBasedLockProvider; import org.junit.jupiter.api.Test; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - -public abstract class AbstractStorageBasedLockProviderIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest { +public abstract class AbstractStorageBasedLockProviderIntegrationTest + extends AbstractExtensibleLockProviderIntegrationTest { @Override protected abstract StorageBasedLockProvider getLockProvider(); - @Test public void lockShouldSurviveCacheClearingInTheMiddle() { StorageBasedLockProvider provider = getLockProvider(); diff --git a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/FuzzTester.java b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/FuzzTester.java index 9c9664407..0b9bafa5c 100644 --- a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/FuzzTester.java +++ b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/FuzzTester.java @@ -1,72 +1,66 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.support; -import net.javacrumbs.shedlock.core.ClockProvider; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.core.SimpleLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.range; +import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; - -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.util.stream.Collectors.toList; -import static java.util.stream.IntStream.range; -import static org.assertj.core.api.Assertions.assertThat; +import java.util.concurrent.atomic.AtomicInteger; +import net.javacrumbs.shedlock.core.ClockProvider; +import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.LockingTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Increments counter from several threads coordinating using lock provided under test. + * Increments counter from several threads coordinating using lock provided + * under test. */ public class FuzzTester { private static final int THREADS = 8; public static final int SHORT_ITERATION = 10; - private final LockProvider lockProvider; - private final Duration sleepFor; private final Duration lockAtMostFor; private final int iterations; + private final LockingTaskExecutor lockingTaskExecutor; + private final Logger logger = LoggerFactory.getLogger(getClass()); public FuzzTester(LockProvider lockProvider) { this(lockProvider, Duration.ofMillis(1), Duration.of(5, MINUTES), 100); } - public FuzzTester( - LockProvider lockProvider, - Duration sleepFor, - Duration lockAtMostFor, - int iterations - ) { - this.lockProvider = lockProvider; + public FuzzTester(LockProvider lockProvider, Duration sleepFor, Duration lockAtMostFor, int iterations) { this.sleepFor = sleepFor; this.lockAtMostFor = lockAtMostFor; this.iterations = iterations; + this.lockingTaskExecutor = new DefaultLockingTaskExecutor(lockProvider); } public void doFuzzTest() throws InterruptedException, ExecutionException { @@ -76,9 +70,9 @@ public void doFuzzTest() throws InterruptedException, ExecutionException { Job job1 = new Job("lock1", lockAtMostFor); Job job2 = new Job("lock2", lockAtMostFor); - List> tasks = range(0, THREADS).mapToObj(i -> (Callable) () -> - task(iters[i], i % 2 == 0 ? job1 : job2)).collect(toList() - ); + List> tasks = range(0, THREADS) + .mapToObj(i -> (Callable) () -> task(iters[i], i % 2 == 0 ? job1 : job2)) + .collect(toList()); waitForIt(executor.invokeAll(tasks)); assertThat(job2.getCounter()).isEqualTo(THREADS / 2 * iterations); @@ -94,17 +88,20 @@ private void waitForIt(List> futures) throws InterruptedException, protected Void task(int iterations, Job job) { try { - for (int i = 0; i < iterations;) { - Optional lock = lockProvider.lock(job.getLockConfiguration()); - if (lock.isPresent()) { - int n = job.getCounter(); - if (shouldLog()) logger.debug("action=getLock value={} i={}", n, i); - sleep(); - if (shouldLog()) logger.debug("action=setCounter value={} i={}", n + 1, i); - job.setCounter(n + 1); - lock.get().unlock(); - i++; - } + for (AtomicInteger i = new AtomicInteger(0); i.get() < iterations; ) { + lockingTaskExecutor.executeWithLock( + (Runnable) () -> { + int n = job.getCounter(); + if (shouldLog()) logger.debug("action=getLock value={} i={}", n, i); + sleep(); + if (shouldLog()) logger.debug("action=setCounter value={} i={}", n + 1, i); + // counter is shared variable. If locking does not work, this overwrites the + // value + // set by another thread + job.setCounter(n + 1); + i.incrementAndGet(); + }, + job.getLockConfiguration()); } logger.debug("action=finished"); return null; @@ -142,11 +139,7 @@ protected static class Job { public LockConfiguration getLockConfiguration() { return new LockConfiguration( - ClockProvider.now(), - lockName, - lockAtMostFor, - Duration.of(5, ChronoUnit.MILLIS) - ); + ClockProvider.now(), lockName, lockAtMostFor, Duration.of(5, ChronoUnit.MILLIS)); } public int getCounter() { @@ -158,5 +151,3 @@ public void setCounter(int counter) { } } } - - diff --git a/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/package-info.java b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/package-info.java new file mode 100644 index 000000000..ea6a62f0e --- /dev/null +++ b/shedlock-test-support/src/main/java/net/javacrumbs/shedlock/test/support/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.support; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/shedlock-spring/pom.xml b/spring/shedlock-spring/pom.xml index e940e5ddd..134b22714 100644 --- a/spring/shedlock-spring/pom.xml +++ b/spring/shedlock-spring/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../pom.xml 4.0.0 shedlock-spring - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -25,13 +25,12 @@ org.aspectj aspectjrt - 1.9.9.1 + 1.9.24 true org.junit.jupiter junit-jupiter - ${junit.ver} test diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/ExtendedLockConfigurationExtractor.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/ExtendedLockConfigurationExtractor.java index d2f6e83e1..818d16162 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/ExtendedLockConfigurationExtractor.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/ExtendedLockConfigurationExtractor.java @@ -1,29 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring; -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockConfigurationExtractor; - import java.lang.reflect.Method; import java.util.Optional; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.core.LockConfigurationExtractor; public interface ExtendedLockConfigurationExtractor extends LockConfigurationExtractor { - /** - * Extracts lock configuration for given method - */ - Optional getLockConfiguration(Object object, Method method); + /** Extracts lock configuration for given method */ + Optional getLockConfiguration(Object object, Method method, Object[] parameterValues); } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/LockableTaskScheduler.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/LockableTaskScheduler.java index 96ebaa93a..81250f71a 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/LockableTaskScheduler.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/LockableTaskScheduler.java @@ -1,35 +1,34 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring; -import net.javacrumbs.shedlock.core.LockManager; -import net.javacrumbs.shedlock.core.LockableRunnable; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.Trigger; +import static java.util.Objects.requireNonNull; import java.time.Duration; import java.time.Instant; import java.util.Date; import java.util.concurrent.ScheduledFuture; - -import static java.util.Objects.requireNonNull; +import net.javacrumbs.shedlock.core.LockManager; +import net.javacrumbs.shedlock.core.LockableRunnable; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.Trigger; /** - * Wraps a all tasks to {@link LockableRunnable} and delegates all calls to a {@link TaskScheduler}. + * Wraps a all tasks to {@link LockableRunnable} and delegates all calls to a + * {@link TaskScheduler}. */ public class LockableTaskScheduler implements TaskScheduler, DisposableBean { private final TaskScheduler taskScheduler; @@ -41,6 +40,7 @@ public LockableTaskScheduler(TaskScheduler taskScheduler, LockManager lockManage } @Override + @Nullable public ScheduledFuture schedule(Runnable task, Trigger trigger) { return taskScheduler.schedule(wrap(task), trigger); } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/EnableSchedulerLock.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/EnableSchedulerLock.java index 09bfc8d8f..656cd736f 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/EnableSchedulerLock.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/EnableSchedulerLock.java @@ -1,29 +1,26 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.annotation; -import net.javacrumbs.shedlock.spring.aop.SchedulerLockConfigurationSelector; -import org.springframework.context.annotation.AdviceMode; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import net.javacrumbs.shedlock.spring.aop.SchedulerLockConfigurationSelector; +import org.springframework.context.annotation.AdviceMode; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -31,51 +28,63 @@ public @interface EnableSchedulerLock { enum InterceptMode { /** - * Default mode when a TaskScheduler is wrapped in a proxy to ensure locking. Only applies lock - * if the {@link net.javacrumbs.shedlock.spring.annotation.SchedulerLock} annotated method is called using scheduler. + * TaskScheduler is wrapped in a proxy to ensure locking. + * Only applies lock if the + * {@link net.javacrumbs.shedlock.spring.annotation.SchedulerLock} annotated + * method is called using scheduler. + * + * @deprecated Use PROXY_METHOD. Requires a reflection hack to work well with Spring 6.2. */ + @Deprecated(forRemoval = true) PROXY_SCHEDULER, /** * Scheduled method is proxied to ensure locking. Lock is created every time - * {@link net.javacrumbs.shedlock.spring.annotation.SchedulerLock} annotated is called (even if it is NOT called using Spring scheduler) + * {@link net.javacrumbs.shedlock.spring.annotation.SchedulerLock} annotated is + * called (even if it is NOT called using Spring scheduler) */ PROXY_METHOD } - /** - * Mode of integration, either TaskScheduler is wrapped in a proxy or the scheduled method is proxied to ensure locking + * Mode of integration, either TaskScheduler is wrapped in a proxy or the + * scheduled method is proxied to ensure locking * - * @see Modes of Spring integration + * @see Modes + * of Spring integration */ InterceptMode interceptMode() default InterceptMode.PROXY_METHOD; - /** - * Default value how long the lock should be kept in case the machine which obtained the lock died before releasing it. - * Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S. - * This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes. - * Set this to some value much higher than normal task duration. Can be overridden in each ScheduledLock annotation. - + * Default value how long the lock should be kept in case the machine which + * obtained the lock died before releasing it. Can be either time with suffix + * like 10s or ISO8601 duration as described in + * {@link java.time.Duration#parse(CharSequence)}, for example PT30S. This is + * just a fallback, under normal circumstances the lock is released as soon the + * tasks finishes. Set this to some value much higher than normal task duration. + * Can be overridden in each ScheduledLock annotation. */ String defaultLockAtMostFor(); - /** - * The lock will be held at least for this duration. - * Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S. Can be used if you really need to execute the task - * at most once in given period of time. If the duration of the task is shorter than clock difference between nodes, the task can - * be theoretically executed more than once (one node after another). By setting this parameter, you can make sure that the - * lock will be kept at least for given period of time. Can be overridden in each ScheduledLock annotation. + * The lock will be held at least for this duration. Can be either time with + * suffix like 10s or ISO8601 duration as described in + * {@link java.time.Duration#parse(CharSequence)}, for example PT30S. Can be + * used if you really need to execute the task at most once in given period of + * time. If the duration of the task is shorter than clock difference between + * nodes, the task can be theoretically executed more than once (one node after + * another). By setting this parameter, you can make sure that the lock will be + * kept at least for given period of time. Can be overridden in each + * ScheduledLock annotation. */ String defaultLockAtLeastFor() default "PT0S"; - /** - * Since 3.0.0 use {@link #interceptMode()} to configure the intercept mode. Had to be renamed to make it compatible - * with Spring AOP infrastructure. Sorry. + * Since 3.0.0 use {@link #interceptMode()} to configure the intercept mode. Had + * to be renamed to make it compatible with Spring AOP infrastructure. Sorry. * + *

* Indicate how advice should be applied. */ AdviceMode mode() default AdviceMode.PROXY; @@ -87,9 +96,11 @@ enum InterceptMode { boolean proxyTargetClass() default false; /** - * Indicate the ordering of the execution of the locking advisor - * when multiple advices are applied at a specific joinpoint. - *

The default is {@link Ordered#LOWEST_PRECEDENCE}. + * Indicate the ordering of the execution of the locking advisor when multiple + * advices are applied at a specific joinpoint. + * + *

+ * The default is {@link Ordered#LOWEST_PRECEDENCE}. */ int order() default Ordered.LOWEST_PRECEDENCE; } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/LockProviderToUse.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/LockProviderToUse.java new file mode 100644 index 000000000..156e9826e --- /dev/null +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/LockProviderToUse.java @@ -0,0 +1,16 @@ +package net.javacrumbs.shedlock.spring.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows to disambiguate between lock providers. For performance and compatibility reasons, the annotation has no effect + * if there is only one lock provider in the application context. Can be applied to method, type or package. + */ +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface LockProviderToUse { + String value(); +} diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/SchedulerLock.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/SchedulerLock.java index 1a073748e..c032937de 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/SchedulerLock.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/SchedulerLock.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.annotation; @@ -23,28 +21,31 @@ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface SchedulerLock { - /** - * Lock name. - */ + /** Lock name. */ String name() default ""; - /** - * How long the lock should be kept in case the machine which obtained the lock died before releasing it. - * This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes. + * How long the lock should be kept in case the machine which obtained the lock + * died before releasing it. This is just a fallback, under normal circumstances + * the lock is released as soon the tasks finishes. * - * Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S. + *

+ * Can be either time with suffix like 10s or ISO8601 duration as described in + * {@link java.time.Duration#parse(CharSequence)}, for example PT30S. */ String lockAtMostFor() default ""; - /** - * The lock will be held at least for given duration. Can be used if you really need to execute the task - * at most once in given period of time. If the duration of the task is shorter than clock difference between nodes, the task can - * be theoretically executed more than once (one node after another). By setting this parameter, you can make sure that the - * lock will be kept at least for given period of time. + * The lock will be held at least for given duration. Can be used if you really + * need to execute the task at most once in given period of time. If the + * duration of the task is shorter than clock difference between nodes, the task + * can be theoretically executed more than once (one node after another). By + * setting this parameter, you can make sure that the lock will be kept at least + * for given period of time. * - * Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S. + *

+ * Can be either time with suffix like 10s or ISO8601 duration as described in + * {@link java.time.Duration#parse(CharSequence)}, for example PT30S. */ String lockAtLeastFor() default ""; } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/package-info.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/package-info.java new file mode 100644 index 000000000..35e40dffc --- /dev/null +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/annotation/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.spring.annotation; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/AbstractLockConfiguration.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/AbstractLockConfiguration.java index 0491f1cba..fd8146421 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/AbstractLockConfiguration.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/AbstractLockConfiguration.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; @@ -26,10 +24,10 @@ abstract class AbstractLockConfiguration implements ImportAware { @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.annotationAttributes = AnnotationAttributes.fromMap( - importMetadata.getAnnotationAttributes(EnableSchedulerLock.class.getName(), false)); + importMetadata.getAnnotationAttributes(EnableSchedulerLock.class.getName(), false)); if (this.annotationAttributes == null) { throw new IllegalArgumentException( - "@EnableSchedulerLock is not present on importing class " + importMetadata.getClassName()); + "@EnableSchedulerLock is not present on importing class " + importMetadata.getClassName()); } } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/BeanNameSelectingLockProviderSupplier.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/BeanNameSelectingLockProviderSupplier.java new file mode 100644 index 000000000..f9f989b2a --- /dev/null +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/BeanNameSelectingLockProviderSupplier.java @@ -0,0 +1,49 @@ +package net.javacrumbs.shedlock.spring.aop; + +import java.lang.reflect.Method; +import java.util.Map; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.core.annotation.AnnotationUtils; + +class BeanNameSelectingLockProviderSupplier implements LockProviderSupplier { + private final ListableBeanFactory beanFactory; + + BeanNameSelectingLockProviderSupplier(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public LockProvider supply(Object target, Method method, Object[] parameterValues) { + LockProviderToUse annotation = findAnnotation(target, method); + if (annotation == null) { + throw noUniqueBeanDefinitionException(); + } + return beanFactory.getBean(annotation.value(), LockProvider.class); + } + + private NoUniqueBeanDefinitionException noUniqueBeanDefinitionException() { + Map lockProviders = beanFactory.getBeansOfType(LockProvider.class); + return new NoUniqueBeanDefinitionException( + LockProvider.class, + lockProviders.size(), + "Multiple LockProviders found (" + String.join(", ", lockProviders.keySet()) + + "), use @LockProviderToUse to disambiguate."); + } + + @Nullable + private LockProviderToUse findAnnotation(Object target, Method method) { + LockProviderToUse annotation = AnnotationUtils.findAnnotation(method, LockProviderToUse.class); + if (annotation != null) { + return annotation; + } + annotation = AnnotationUtils.findAnnotation(target.getClass(), LockProviderToUse.class); + if (annotation != null) { + return annotation; + } + return method.getDeclaringClass().getPackage().getAnnotation(LockProviderToUse.class); + } +} diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockConfigurationExtractorConfiguration.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockConfigurationExtractorConfiguration.java index 2c1272f25..c01a92826 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockConfigurationExtractorConfiguration.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockConfigurationExtractorConfiguration.java @@ -1,40 +1,49 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import java.time.Duration; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; +import net.javacrumbs.shedlock.support.annotation.Nullable; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringValueResolver; -import java.time.Duration; - -/** - * Defines ExtendedLockConfigurationExtractor bean. - */ +/** Defines ExtendedLockConfigurationExtractor bean. */ @Configuration -class LockConfigurationExtractorConfiguration extends AbstractLockConfiguration implements EmbeddedValueResolverAware { +class LockConfigurationExtractorConfiguration extends AbstractLockConfiguration + implements EmbeddedValueResolverAware, BeanFactoryAware { private final StringToDurationConverter durationConverter = StringToDurationConverter.INSTANCE; + @Nullable private StringValueResolver resolver; + @Nullable + private BeanFactory beanFactory; + @Bean ExtendedLockConfigurationExtractor lockConfigurationExtractor() { - return new SpringLockConfigurationExtractor(defaultLockAtMostForDuration(), defaultLockAtLeastForDuration(), resolver, durationConverter); + return new SpringLockConfigurationExtractor( + defaultLockAtMostForDuration(), + defaultLockAtLeastForDuration(), + resolver, + durationConverter, + beanFactory); } private Duration defaultLockAtLeastForDuration() { @@ -65,4 +74,9 @@ protected String getStringFromAnnotation(String name) { public void setEmbeddedValueResolver(StringValueResolver resolver) { this.resolver = resolver; } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockProviderSupplier.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockProviderSupplier.java new file mode 100644 index 000000000..78cead2e3 --- /dev/null +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockProviderSupplier.java @@ -0,0 +1,25 @@ +package net.javacrumbs.shedlock.spring.aop; + +import java.lang.reflect.Method; +import net.javacrumbs.shedlock.core.LockProvider; +import org.springframework.beans.factory.ListableBeanFactory; + +/** + * Not public now. If you think you need your LockProviderSupplier please create an issue and explain your use-case. + */ +@FunctionalInterface +interface LockProviderSupplier { + LockProvider supply(Object target, Method method, Object[] parameterValues); + + static LockProviderSupplier create(ListableBeanFactory beanFactory) { + // Only fetching beanNames as the beans might not have been initialized yet. + String[] beanNamesForType = beanFactory.getBeanNamesForType(LockProvider.class); + // If there are no beans of LockProvider type, we can't fail here as in older version we + // did not fail, and it's quite common in the tests. To maintain backward compatibility + // the failure will happen in runtime. + if (beanNamesForType.length <= 1) { + return (target, method, arguments) -> beanFactory.getBean(LockProvider.class); + } + return new BeanNameSelectingLockProviderSupplier(beanFactory); + } +} diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockingNotSupportedException.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockingNotSupportedException.java index f12f7f16b..f13e6b5b3 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockingNotSupportedException.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/LockingNotSupportedException.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyLockConfiguration.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyLockConfiguration.java index df188f2f3..efe948751 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyLockConfiguration.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyLockConfiguration.java @@ -1,23 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; -import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; -import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,15 +27,11 @@ class MethodProxyLockConfiguration extends AbstractLockConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) MethodProxyScheduledLockAdvisor proxyScheduledLockAopBeanPostProcessor( - @Lazy LockProvider lockProvider, - @Lazy ExtendedLockConfigurationExtractor lockConfigurationExtractor - ) { - MethodProxyScheduledLockAdvisor advisor = new MethodProxyScheduledLockAdvisor( - lockConfigurationExtractor, - new DefaultLockingTaskExecutor(lockProvider) - ); + ListableBeanFactory beanFactory, @Lazy ExtendedLockConfigurationExtractor lockConfigurationExtractor) { + LockProviderSupplier lockProviderSupplier = LockProviderSupplier.create(beanFactory); + MethodProxyScheduledLockAdvisor advisor = + new MethodProxyScheduledLockAdvisor(lockConfigurationExtractor, lockProviderSupplier); advisor.setOrder(getOrder()); return advisor; - } } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyScheduledLockAdvisor.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyScheduledLockAdvisor.java index 5f9574430..ec4e83754 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyScheduledLockAdvisor.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/MethodProxyScheduledLockAdvisor.java @@ -1,22 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import java.lang.annotation.Annotation; +import java.util.Optional; +import net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor; import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.core.LockingTaskExecutor; +import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.LockingTaskExecutor.TaskResult; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; @@ -29,29 +30,21 @@ import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import java.lang.annotation.Annotation; -import java.util.Optional; - class MethodProxyScheduledLockAdvisor extends AbstractPointcutAdvisor { private final Pointcut pointcut = new ComposablePointcut(methodPointcutFor(SchedulerLock.class)); private final Advice advice; - MethodProxyScheduledLockAdvisor(ExtendedLockConfigurationExtractor lockConfigurationExtractor, LockingTaskExecutor lockingTaskExecutor) { - this.advice = new LockingInterceptor(lockConfigurationExtractor, lockingTaskExecutor); + MethodProxyScheduledLockAdvisor( + ExtendedLockConfigurationExtractor lockConfigurationExtractor, LockProviderSupplier lockProviderSupplier) { + this.advice = new LockingInterceptor(lockConfigurationExtractor, lockProviderSupplier); } - private static AnnotationMatchingPointcut methodPointcutFor(Class methodAnnotationType) { - return new AnnotationMatchingPointcut( - null, - methodAnnotationType, - true - ); - } + private static AnnotationMatchingPointcut methodPointcutFor(Class methodAnnotationType) { + return new AnnotationMatchingPointcut(null, methodAnnotationType, true); + } - /** - * Get the Pointcut that drives this advisor. - */ + /** Get the Pointcut that drives this advisor. */ @Override public Pointcut getPointcut() { return pointcut; @@ -64,11 +57,13 @@ public Advice getAdvice() { private static class LockingInterceptor implements MethodInterceptor { private final ExtendedLockConfigurationExtractor lockConfigurationExtractor; - private final LockingTaskExecutor lockingTaskExecutor; + private final LockProviderSupplier lockProviderSupplier; - LockingInterceptor(ExtendedLockConfigurationExtractor lockConfigurationExtractor, LockingTaskExecutor lockingTaskExecutor) { + LockingInterceptor( + ExtendedLockConfigurationExtractor lockConfigurationExtractor, + LockProviderSupplier lockProviderSupplier) { this.lockConfigurationExtractor = lockConfigurationExtractor; - this.lockingTaskExecutor = lockingTaskExecutor; + this.lockProviderSupplier = lockProviderSupplier; } @Override @@ -79,7 +74,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable { throw new LockingNotSupportedException("Can not lock method returning primitive value"); } - LockConfiguration lockConfiguration = lockConfigurationExtractor.getLockConfiguration(invocation.getThis(), invocation.getMethod()).get(); + LockConfiguration lockConfiguration = lockConfigurationExtractor + .getLockConfiguration(invocation.getThis(), invocation.getMethod(), invocation.getArguments()) + .get(); + + LockProvider lockProvider = lockProviderSupplier.supply( + invocation.getThis(), invocation.getMethod(), invocation.getArguments()); + DefaultLockingTaskExecutor lockingTaskExecutor = new DefaultLockingTaskExecutor(lockProvider); TaskResult result = lockingTaskExecutor.executeWithLock(invocation::proceed, lockConfiguration); if (Optional.class.equals(returnType)) { diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/RegisterDefaultTaskSchedulerPostProcessor.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/RegisterDefaultTaskSchedulerPostProcessor.java index 4f6304218..22781720f 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/RegisterDefaultTaskSchedulerPostProcessor.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/RegisterDefaultTaskSchedulerPostProcessor.java @@ -1,20 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; +import static org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME; + +import java.util.concurrent.ScheduledExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -29,15 +31,9 @@ import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; -import java.util.concurrent.ScheduledExecutorService; - -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; -import static org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME; - -/** - * Registers default TaskScheduler if none found. - */ -class RegisterDefaultTaskSchedulerPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { +/** Registers default TaskScheduler if none found. */ +class RegisterDefaultTaskSchedulerPostProcessor + implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { private BeanFactory beanFactory; private static final Logger logger = LoggerFactory.getLogger(RegisterDefaultTaskSchedulerPostProcessor.class); @@ -46,28 +42,31 @@ class RegisterDefaultTaskSchedulerPostProcessor implements BeanDefinitionRegistr public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory; if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(listableBeanFactory, TaskScheduler.class).length == 0) { - String[] scheduledExecutorsBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(listableBeanFactory, ScheduledExecutorService.class); + String[] scheduledExecutorsBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + listableBeanFactory, ScheduledExecutorService.class); if (scheduledExecutorsBeanNames.length != 1) { logger.debug("Registering default TaskScheduler"); - registry.registerBeanDefinition(DEFAULT_TASK_SCHEDULER_BEAN_NAME, rootBeanDefinition(ConcurrentTaskScheduler.class).getBeanDefinition()); + registry.registerBeanDefinition( + DEFAULT_TASK_SCHEDULER_BEAN_NAME, + rootBeanDefinition(ConcurrentTaskScheduler.class).getBeanDefinition()); if (scheduledExecutorsBeanNames.length != 0) { logger.warn("Multiple ScheduledExecutorService found, do not know which one to use."); } } else { - logger.debug("Registering default TaskScheduler with existing ScheduledExecutorService {}", scheduledExecutorsBeanNames[0]); - registry.registerBeanDefinition(DEFAULT_TASK_SCHEDULER_BEAN_NAME, - rootBeanDefinition(ConcurrentTaskScheduler.class) - .addPropertyReference("scheduledExecutor", scheduledExecutorsBeanNames[0]) - .getBeanDefinition() - ); + logger.debug( + "Registering default TaskScheduler with existing ScheduledExecutorService {}", + scheduledExecutorsBeanNames[0]); + registry.registerBeanDefinition( + DEFAULT_TASK_SCHEDULER_BEAN_NAME, + rootBeanDefinition(ConcurrentTaskScheduler.class) + .addPropertyReference("scheduledExecutor", scheduledExecutorsBeanNames[0]) + .getBeanDefinition()); } } } @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - - } + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} @Override public int getOrder() { diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerLockConfigurationSelector.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerLockConfigurationSelector.java index 3de1be817..9e46b33e7 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerLockConfigurationSelector.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerLockConfigurationSelector.java @@ -1,43 +1,47 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode; + import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.context.annotation.AutoProxyRegistrar; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode; -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_METHOD; -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; - public class SchedulerLockConfigurationSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata metadata) { - AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableSchedulerLock.class.getName(), false)); + AnnotationAttributes attributes = AnnotationAttributes.fromMap( + metadata.getAnnotationAttributes(EnableSchedulerLock.class.getName(), false)); InterceptMode mode = attributes.getEnum("interceptMode"); - if (mode == PROXY_METHOD) { - return new String[]{AutoProxyRegistrar.class.getName(), LockConfigurationExtractorConfiguration.class.getName(), MethodProxyLockConfiguration.class.getName()}; - } else if (mode == PROXY_SCHEDULER) { - return new String[]{AutoProxyRegistrar.class.getName(), LockConfigurationExtractorConfiguration.class.getName(), SchedulerProxyLockConfiguration.class.getName(), RegisterDefaultTaskSchedulerPostProcessor.class.getName()}; - } else { - throw new UnsupportedOperationException("Unknown mode " + mode); - } - + return switch (mode) { + case PROXY_METHOD -> + new String[] { + AutoProxyRegistrar.class.getName(), + LockConfigurationExtractorConfiguration.class.getName(), + MethodProxyLockConfiguration.class.getName() + }; + case PROXY_SCHEDULER -> + new String[] { + AutoProxyRegistrar.class.getName(), + LockConfigurationExtractorConfiguration.class.getName(), + SchedulerProxyLockConfiguration.class.getName(), + RegisterDefaultTaskSchedulerPostProcessor.class.getName() + }; + }; } } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyLockConfiguration.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyLockConfiguration.java index c0c425d75..505575309 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyLockConfiguration.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyLockConfiguration.java @@ -1,23 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; -import net.javacrumbs.shedlock.core.DefaultLockManager; -import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,10 +27,10 @@ class SchedulerProxyLockConfiguration extends AbstractLockConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) SchedulerProxyScheduledLockAdvisor proxyScheduledLockAopBeanPostProcessor( - @Lazy LockProvider lockProvider, - @Lazy ExtendedLockConfigurationExtractor lockConfigurationExtractor - ) { - SchedulerProxyScheduledLockAdvisor advisor = new SchedulerProxyScheduledLockAdvisor(new DefaultLockManager(lockProvider, lockConfigurationExtractor)); + ListableBeanFactory listableBeanFactory, + @Lazy ExtendedLockConfigurationExtractor lockConfigurationExtractor) { + SchedulerProxyScheduledLockAdvisor advisor = new SchedulerProxyScheduledLockAdvisor( + LockProviderSupplier.create(listableBeanFactory), lockConfigurationExtractor); advisor.setOrder(getOrder()); return advisor; } diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledLockAdvisor.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledLockAdvisor.java index 35a63bbbf..1e70c69e1 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledLockAdvisor.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledLockAdvisor.java @@ -1,22 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import java.lang.reflect.Field; +import net.javacrumbs.shedlock.core.DefaultLockManager; import net.javacrumbs.shedlock.core.LockManager; +import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.LockableRunnable; +import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; +import net.javacrumbs.shedlock.support.annotation.Nullable; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -29,19 +32,19 @@ import org.springframework.aop.support.NameMatchMethodPointcut; import org.springframework.aop.support.RootClassFilter; import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.support.ScheduledMethodRunnable; class SchedulerProxyScheduledLockAdvisor extends AbstractPointcutAdvisor { private final Pointcut pointcut = new TaskSchedulerPointcut(); private final Advice advice; private static final Logger logger = LoggerFactory.getLogger(SchedulerProxyScheduledLockAdvisor.class); - SchedulerProxyScheduledLockAdvisor(LockManager lockManager) { - this.advice = new LockingInterceptor(lockManager); + SchedulerProxyScheduledLockAdvisor( + LockProviderSupplier lockProviderSupplier, ExtendedLockConfigurationExtractor lockConfigurationExtractor) { + this.advice = new LockingInterceptor(lockProviderSupplier, lockConfigurationExtractor); } - /** - * Get the Pointcut that drives this advisor. - */ + /** Get the Pointcut that drives this advisor. */ @Override public Pointcut getPointcut() { return pointcut; @@ -53,23 +56,51 @@ public Advice getAdvice() { } private static class LockingInterceptor implements MethodInterceptor { - private final LockManager lockManager; + private final LockProviderSupplier lockProviderSupplier; - private LockingInterceptor(LockManager lockManager) { - this.lockManager = lockManager; - } + private final ExtendedLockConfigurationExtractor lockConfigurationExtractor; + private LockingInterceptor( + LockProviderSupplier lockProviderSupplier, + ExtendedLockConfigurationExtractor lockConfigurationExtractor) { + this.lockProviderSupplier = lockProviderSupplier; + this.lockConfigurationExtractor = lockConfigurationExtractor; + } @Override + @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { Object[] arguments = invocation.getArguments(); - if (arguments.length >= 1 && arguments[0] instanceof Runnable) { - arguments[0] = new LockableRunnable((Runnable) arguments[0], lockManager); + if (arguments.length >= 1) { + arguments[0] = wrapTask(arguments[0]); } else { - logger.warn("Task scheduler first argument should be Runnable"); + throw new IllegalStateException("Task scheduler method does not have any arguments"); } return invocation.proceed(); } + + private Object wrapTask(Object firstArgument) throws NoSuchFieldException, IllegalAccessException { + if (firstArgument instanceof ScheduledMethodRunnable task) { + return wrapTask(task); + } else if (firstArgument.getClass().getSimpleName().equals("OutcomeTrackingRunnable")) { + // We need to access the wrapped runnable. This is the only way + Field runnable = firstArgument.getClass().getDeclaredField("runnable"); + runnable.setAccessible(true); + Object wrappedRunnable = runnable.get(firstArgument); + if (wrappedRunnable instanceof ScheduledMethodRunnable task) { + return wrapTask(task); + } + } + logger.warn("Task scheduler first argument should be ScheduledMethodRunnable or OutcomeTrackingRunnable"); + return firstArgument; + } + + private LockableRunnable wrapTask(ScheduledMethodRunnable task) { + LockProvider lockProvider = + lockProviderSupplier.supply(task.getTarget(), task.getMethod(), new Object[] {}); + LockManager lockManager = new DefaultLockManager(lockProvider, lockConfigurationExtractor); + return new LockableRunnable(task, lockManager); + } } private static class TaskSchedulerPointcut implements Pointcut { diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractor.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractor.java index 4dbd6bbba..1ee9ce94c 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractor.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractor.java @@ -1,20 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.util.Objects.requireNonNull; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.time.Duration; +import java.util.Optional; import net.javacrumbs.shedlock.core.ClockProvider; import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; @@ -23,45 +30,79 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryAccessor; +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.KotlinDetector; +import org.springframework.core.KotlinReflectionParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.PrioritizedParameterNameDiscoverer; +import org.springframework.core.StandardReflectionParameterNameDiscoverer; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.convert.converter.Converter; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; -import java.lang.reflect.Method; -import java.time.Duration; -import java.util.Optional; - -import static java.time.temporal.ChronoUnit.MILLIS; -import static java.util.Objects.requireNonNull; - class SpringLockConfigurationExtractor implements ExtendedLockConfigurationExtractor { + private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + private static final ParserContext PARSER_CONTEXT = new TemplateParserContext(); + public static final PrioritizedParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = + new PrioritizedParameterNameDiscoverer(); private final Duration defaultLockAtMostFor; private final Duration defaultLockAtLeastFor; + @Nullable private final StringValueResolver embeddedValueResolver; + + @Nullable + private final BeanFactory beanFactory; + + private final StandardEvaluationContext originalEvaluationContext = new StandardEvaluationContext(); + private final Converter durationConverter; private final Logger logger = LoggerFactory.getLogger(SpringLockConfigurationExtractor.class); + static { + if (KotlinDetector.isKotlinReflectPresent()) { + PARAMETER_NAME_DISCOVERER.addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); + } + PARAMETER_NAME_DISCOVERER.addDiscoverer(new SimpleParameterNameDiscoverer()); + } + public SpringLockConfigurationExtractor( - Duration defaultLockAtMostFor, - Duration defaultLockAtLeastFor, - @Nullable StringValueResolver embeddedValueResolver, - Converter durationConverter - ) { + Duration defaultLockAtMostFor, + Duration defaultLockAtLeastFor, + @Nullable StringValueResolver embeddedValueResolver, + Converter durationConverter) { + this(defaultLockAtMostFor, defaultLockAtLeastFor, embeddedValueResolver, durationConverter, null); + } + + public SpringLockConfigurationExtractor( + Duration defaultLockAtMostFor, + Duration defaultLockAtLeastFor, + @Nullable StringValueResolver embeddedValueResolver, + Converter durationConverter, + @Nullable BeanFactory beanFactory) { this.defaultLockAtMostFor = requireNonNull(defaultLockAtMostFor); this.defaultLockAtLeastFor = requireNonNull(defaultLockAtLeastFor); this.durationConverter = requireNonNull(durationConverter); this.embeddedValueResolver = embeddedValueResolver; + this.beanFactory = beanFactory; + originalEvaluationContext.addPropertyAccessor(new BeanFactoryAccessor()); } - @Override public Optional getLockConfiguration(Runnable task) { - if (task instanceof ScheduledMethodRunnable) { - ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) task; - return getLockConfiguration(scheduledMethodRunnable.getTarget(), scheduledMethodRunnable.getMethod()); + if (task instanceof ScheduledMethodRunnable scheduledMethodRunnable) { + return getLockConfiguration( + scheduledMethodRunnable.getTarget(), scheduledMethodRunnable.getMethod(), new Object[] {}); } else { logger.debug("Unknown task type " + task); } @@ -69,50 +110,72 @@ public Optional getLockConfiguration(Runnable task) { } @Override - public Optional getLockConfiguration(Object target, Method method) { + public Optional getLockConfiguration(Object target, Method method, Object[] parameterValues) { AnnotationData annotation = findAnnotation(target, method); if (shouldLock(annotation)) { - return Optional.of(getLockConfiguration(annotation)); + return Optional.of(getLockConfiguration(annotation, method, parameterValues)); } else { return Optional.empty(); } } - private LockConfiguration getLockConfiguration(AnnotationData annotation) { + private LockConfiguration getLockConfiguration(AnnotationData annotation, Method method, Object[] parameterValues) { return new LockConfiguration( - ClockProvider.now(), - getName(annotation), - getLockAtMostFor(annotation), - getLockAtLeastFor(annotation)); + ClockProvider.now(), + getName(annotation, method, parameterValues), + getLockAtMostFor(annotation), + getLockAtLeastFor(annotation)); } - private String getName(AnnotationData annotation) { + private String getName(AnnotationData annotation, Method method, Object[] parameterValues) { + String name = parseSpEL(annotation.name(), method, parameterValues); if (embeddedValueResolver != null) { - return embeddedValueResolver.resolveStringValue(annotation.getName()); + return embeddedValueResolver.resolveStringValue(name); + } else { + return name; + } + } + + private String parseSpEL(String name, Method method, Object[] parameterValues) { + return getEvaluationContext(method, parameterValues) + .map(evaluationContext -> EXPRESSION_PARSER + .parseExpression(name, PARSER_CONTEXT) + .getValue(evaluationContext, String.class)) + .orElse(name); + } + + private Optional getEvaluationContext(Method method, Object[] parameterValues) { + // Only applying it when the method has parameters. The while code is pretty fragile, let's hope that + // most of the users do not parametrize their scheduled methods. + // We need this as embeddedValueResolver does not support parameters. Inspired by CacheEvaluationContextFactory. + if (method.getParameters().length > 0 && method.getParameters().length == parameterValues.length) { + StandardEvaluationContext evaluationContext = + new MethodBasedEvaluationContext(beanFactory, method, parameterValues, PARAMETER_NAME_DISCOVERER); + originalEvaluationContext.applyDelegatesTo(evaluationContext); + return Optional.of(evaluationContext); } else { - return annotation.getName(); + return Optional.empty(); } } Duration getLockAtMostFor(AnnotationData annotation) { return getValue( - annotation.getLockAtMostFor(), - annotation.getLockAtMostForString(), - this.defaultLockAtMostFor, - "lockAtMostForString" - ); + annotation.lockAtMostFor(), + annotation.lockAtMostForString(), + this.defaultLockAtMostFor, + "lockAtMostForString"); } Duration getLockAtLeastFor(AnnotationData annotation) { return getValue( - annotation.getLockAtLeastFor(), - annotation.getLockAtLeastForString(), - this.defaultLockAtLeastFor, - "lockAtLeastForString" - ); + annotation.lockAtLeastFor(), + annotation.lockAtLeastForString(), + this.defaultLockAtLeastFor, + "lockAtLeastForString"); } - private Duration getValue(long valueFromAnnotation, String stringValueFromAnnotation, Duration defaultValue, final String paramName) { + private Duration getValue( + long valueFromAnnotation, String stringValueFromAnnotation, Duration defaultValue, final String paramName) { if (valueFromAnnotation >= 0) { return Duration.of(valueFromAnnotation, MILLIS); } else if (StringUtils.hasText(stringValueFromAnnotation)) { @@ -122,11 +185,13 @@ private Duration getValue(long valueFromAnnotation, String stringValueFromAnnota try { Duration result = durationConverter.convert(stringValueFromAnnotation); if (result.isNegative()) { - throw new IllegalArgumentException("Invalid " + paramName + " value \"" + stringValueFromAnnotation + "\" - cannot set negative duration"); + throw new IllegalArgumentException("Invalid " + paramName + " value \"" + stringValueFromAnnotation + + "\" - cannot set negative duration"); } return result; } catch (IllegalStateException nfe) { - throw new IllegalArgumentException("Invalid " + paramName + " value \"" + stringValueFromAnnotation + "\" - cannot parse into long nor duration"); + throw new IllegalArgumentException("Invalid " + paramName + " value \"" + stringValueFromAnnotation + + "\" - cannot parse into long nor duration"); } } else { return defaultValue; @@ -134,71 +199,72 @@ private Duration getValue(long valueFromAnnotation, String stringValueFromAnnota } @Nullable - AnnotationData findAnnotation(Object target, Method method) { - AnnotationData annotation = findAnnotation(method); + static AnnotationData findAnnotation(Object target, Method method) { + SchedulerLock annotation = findAnnotation(target, method, SchedulerLock.class); + if (annotation != null) { + return new AnnotationData( + annotation.name(), -1, annotation.lockAtMostFor(), -1, annotation.lockAtLeastFor()); + } + return null; + } + + @Nullable + static A findAnnotation(Object target, Method method, Class annotationType) { + A annotation = AnnotatedElementUtils.getMergedAnnotation(method, annotationType); if (annotation != null) { return annotation; } else { // Try to find annotation on proxied class Class targetClass = AopUtils.getTargetClass(target); try { - Method methodOnTarget = targetClass - .getMethod(method.getName(), method.getParameterTypes()); - return findAnnotation(methodOnTarget); + Method methodOnTarget = targetClass.getMethod(method.getName(), method.getParameterTypes()); + return AnnotatedElementUtils.getMergedAnnotation(methodOnTarget, annotationType); } catch (NoSuchMethodException e) { return null; } } } - @Nullable - private AnnotationData findAnnotation(Method method) { - SchedulerLock annotation = AnnotatedElementUtils.getMergedAnnotation(method, SchedulerLock.class); - if (annotation != null) { - return new AnnotationData(annotation.name(), -1, annotation.lockAtMostFor(), -1, annotation.lockAtLeastFor()); - } - return null; - } - private boolean shouldLock(@Nullable AnnotationData annotation) { return annotation != null; } - static class AnnotationData { - private final String name; - private final long lockAtMostFor; - private final String lockAtMostForString; - private final long lockAtLeastFor; - private final String lockAtLeastForString; - - private AnnotationData(String name, long lockAtMostFor, String lockAtMostForString, long lockAtLeastFor, String lockAtLeastForString) { - this.name = name; - this.lockAtMostFor = lockAtMostFor; - this.lockAtMostForString = lockAtMostForString; - this.lockAtLeastFor = lockAtLeastFor; - this.lockAtLeastForString = lockAtLeastForString; - } - - public String getName() { - return name; - } - - public long getLockAtMostFor() { - return lockAtMostFor; - } + record AnnotationData( + String name, + long lockAtMostFor, + String lockAtMostForString, + long lockAtLeastFor, + String lockAtLeastForString) {} - public String getLockAtMostForString() { - return lockAtMostForString; + /** + * Not using {@link StandardReflectionParameterNameDiscoverer} as it is calling executable.hasRealParameterData() + * and it causes a test to fail. + */ + private static class SimpleParameterNameDiscoverer implements ParameterNameDiscoverer { + @Override + @Nullable + public String[] getParameterNames(Method method) { + return getParameterNames(method.getParameters()); } - public long getLockAtLeastFor() { - return lockAtLeastFor; + @Override + @Nullable + public String[] getParameterNames(Constructor ctor) { + return getParameterNames(ctor.getParameters()); } - public String getLockAtLeastForString() { - return lockAtLeastForString; + @Nullable + private String[] getParameterNames(Parameter[] parameters) { + String[] parameterNames = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + // Here it differs from StandardReflectionParameterNameDiscoverer + if (param.getName() == null) { + return null; + } + parameterNames[i] = param.getName(); + } + return parameterNames; } } } - - diff --git a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverter.java b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverter.java index 2e0376164..7d4559176 100644 --- a/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverter.java +++ b/spring/shedlock-spring/src/main/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverter.java @@ -1,21 +1,18 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; - /* * Copyright 2012-2017 the original author or authors. * @@ -32,9 +29,6 @@ * limitations under the License. */ -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.Assert; - import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Collections; @@ -42,14 +36,17 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.springframework.core.convert.converter.Converter; +import org.springframework.util.Assert; /** * {@link Converter} for {@link String} to {@link Duration}. Support - * {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form. + * {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} + * form. * * @author Phillip Webb - *

- * Copied from org.springframework.boot.context.properties.bind.convert + *

+ * Copied from org.springframework.boot.context.properties.bind.convert */ class StringToDurationConverter implements Converter { @@ -96,4 +93,3 @@ private ChronoUnit getUnit(String value) { return unit; } } - diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/TestUtils.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/TestUtils.java index 572ef4fa4..67395d6d5 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/TestUtils.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/TestUtils.java @@ -1,30 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.mockito.ArgumentMatchers.argThat; + +import java.time.Instant; import net.javacrumbs.shedlock.core.LockConfiguration; import org.mockito.ArgumentMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.mockito.ArgumentMatchers.argThat; - public class TestUtils { private static final int GAP = 1000; @@ -36,14 +33,15 @@ public static LockConfiguration hasParams(String name, long lockAtMostFor, long @Override public boolean matches(LockConfiguration c) { return name.equals(c.getName()) - && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) - && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); + && isNearTo(lockAtMostFor, c.getLockAtMostUntil()) + && isNearTo(lockAtLeastFor, c.getLockAtLeastUntil()); } @Override public String toString() { Instant now = now(); - return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + now.plusMillis(lockAtLeastFor) + ")"; + return "hasParams(\"" + name + "\", " + now.plusMillis(lockAtMostFor) + ", " + + now.plusMillis(lockAtLeastFor) + ")"; } }); } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSchedulerProxyTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSchedulerProxyTest.java index 00f4cc74c..f97b8b529 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSchedulerProxyTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSchedulerProxyTest.java @@ -1,20 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; @@ -30,20 +40,6 @@ import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - - @ExtendWith(SpringExtension.class) public abstract class AbstractSchedulerProxyTest { @Autowired @@ -64,13 +60,13 @@ public abstract class AbstractSchedulerProxyTest { public void prepareMocks() { Mockito.reset(lockProvider, simpleLock); when(lockProvider.lock(any())).thenReturn(Optional.of(simpleLock)); - } protected abstract void assertRightSchedulerUsed(); @Test - public void shouldCallLockProviderOnSchedulerCall() throws NoSuchMethodException, ExecutionException, InterruptedException { + public void shouldCallLockProviderOnSchedulerCall() + throws NoSuchMethodException, ExecutionException, InterruptedException { Runnable task = task("annotatedMethod"); taskScheduler.schedule(task, now()).get(); verify(lockProvider).lock(hasParams("lockName", 30_000, getDefaultLockAtLeastFor())); @@ -85,7 +81,6 @@ public void shouldUseCustomAnnotation() throws NoSuchMethodException, ExecutionE verify(simpleLock).unlock(); } - @Test public void shouldUserPropertyName() throws NoSuchMethodException, ExecutionException, InterruptedException { Runnable task = task("spelMethod"); @@ -106,7 +101,7 @@ public void shouldRethrowRuntimeException() throws NoSuchMethodException { public void shouldNotLockTaskExecutorMethods() { assertThat(taskScheduler).isInstanceOf(TaskExecutor.class); - ((TaskExecutor)taskScheduler).execute(() -> {}); + ((TaskExecutor) taskScheduler).execute(() -> {}); verifyNoInteractions(lockProvider); } @@ -114,7 +109,6 @@ private long getDefaultLockAtLeastFor() { return StringToDurationConverter.INSTANCE.convert(defaultLockAtLeastFor).toMillis(); } - private void schedule(Runnable task) throws InterruptedException, ExecutionException { taskScheduler.schedule(task, now()).get(); } @@ -125,7 +119,7 @@ private ScheduledMethodRunnable task(String methodName) throws NoSuchMethodExcep @Test public void shouldNotLockProviderOnPureRunnable() throws ExecutionException, InterruptedException { - taskScheduler.schedule(() -> { }, now()).get(); + taskScheduler.schedule(() -> {}, now()).get(); verifyNoInteractions(lockProvider); } @@ -145,13 +139,10 @@ public void custom() { } @SchedulerLock(name = "${property.value}", lockAtMostFor = "1000", lockAtLeastFor = "500") - public void spelMethod() { - - } + public void spelMethod() {} @SchedulerLock(name = "exception", lockAtMostFor = "1500") public void throwsException() { throw new NullPointerException("Just for test"); } - } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSpringLockConfigurationExtractorTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSpringLockConfigurationExtractorTest.java deleted file mode 100644 index d0110d9dc..000000000 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AbstractSpringLockConfigurationExtractorTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright 2009 the original author or authors. - * - * 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 net.javacrumbs.shedlock.spring.aop; - -import net.javacrumbs.shedlock.core.LockConfiguration; -import net.javacrumbs.shedlock.spring.aop.SpringLockConfigurationExtractor.AnnotationData; -import net.javacrumbs.shedlock.spring.proxytest.BeanInterface; -import net.javacrumbs.shedlock.spring.proxytest.DynamicProxyConfig; -import net.javacrumbs.shedlock.spring.proxytest.SubclassProxyConfig; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.scheduling.support.ScheduledMethodRunnable; -import org.springframework.util.StringValueResolver; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalAmount; -import java.util.Optional; - -import static java.time.temporal.ChronoUnit.MILLIS; -import static java.time.temporal.ChronoUnit.SECONDS; -import static net.javacrumbs.shedlock.core.ClockProvider.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public abstract class AbstractSpringLockConfigurationExtractorTest { - private static final Duration DEFAULT_LOCK_TIME = Duration.of(30, ChronoUnit.MINUTES); - private static final Duration DEFAULT_LOCK_AT_LEAST_FOR = Duration.of(5, ChronoUnit.MILLIS); - private final StringValueResolver embeddedValueResolver = mock(StringValueResolver.class); - private final SpringLockConfigurationExtractor extractor = new SpringLockConfigurationExtractor(DEFAULT_LOCK_TIME, DEFAULT_LOCK_AT_LEAST_FOR, embeddedValueResolver, new StringToDurationConverter()); - - - @Test - public void shouldLockForDefaultTimeIfNoAnnotation() throws NoSuchMethodException { - AnnotationData annotation = getAnnotation("annotatedMethodWithoutLockAtMostFor"); - TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); - assertThat(lockAtMostFor).isEqualTo(DEFAULT_LOCK_TIME); - } - - @Test - public void shouldLockTimeFromAnnotation() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethod"); - TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); - assertThat(lockAtMostFor).isEqualTo(Duration.of(100, MILLIS)); - } - - @Test - public void shouldLockTimeFromAnnotationWithString() throws NoSuchMethodException { - mockResolvedValue("${placeholder}", "5"); - AnnotationData annotation = getAnnotation("annotatedMethodWithString"); - TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); - assertThat(lockAtMostFor).isEqualTo(Duration.of(5, MILLIS)); - } - - @Test - public void shouldLockTimeFromAnnotationWithDurationString() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethodWithDurationString"); - TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); - assertThat(lockAtMostFor).isEqualTo(Duration.of(1, SECONDS)); - } - - @Test - public void shouldGetZeroGracePeriodFromAnnotation() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethodWithZeroGracePeriod"); - TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); - assertThat(gracePeriod).isEqualTo(Duration.ZERO); - } - - @Test - public void shouldGetPositiveGracePeriodFromAnnotation() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethodWithPositiveGracePeriod"); - TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); - assertThat(gracePeriod).isEqualTo(Duration.of(10, MILLIS)); - } - - @Test - public void shouldGetPositiveGracePeriodFromAnnotationWithString() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethodWithPositiveGracePeriodWithString"); - TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); - assertThat(gracePeriod).isEqualTo(Duration.of(10, MILLIS)); - } - - @Test - public void shoulFailOnNegativeLockAtMostFor() throws NoSuchMethodException { - noopResolver(); - AnnotationData annotation = getAnnotation("annotatedMethodWithNegativeGracePeriod"); - assertThatThrownBy(() -> extractor.getLockAtLeastFor(annotation)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void shouldExtractComposedAnnotation() throws NoSuchMethodException { - mockResolvedValue("20", "20"); - AnnotationData annotation = getAnnotation("composedAnnotation"); - TemporalAmount atMostFor = extractor.getLockAtMostFor(annotation); - assertThat(annotation.getName()).isEqualTo("lockName1"); - assertThat(atMostFor).isEqualTo(Duration.of(20, MILLIS)); - } - - @Test - public void shouldFindAnnotationOnDynamicProxy() throws NoSuchMethodException { - doTestFindAnnotationOnProxy(DynamicProxyConfig.class); - } - - @Test - public void shouldFindAnnotationOnSubclassProxy() throws NoSuchMethodException { - doTestFindAnnotationOnProxy(SubclassProxyConfig.class); - } - - @Test - public void shouldNotLockUnannotatedMethod() throws NoSuchMethodException { - ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "methodWithoutAnnotation"); - Optional lockConfiguration = extractor.getLockConfiguration(runnable); - assertThat(lockConfiguration).isEmpty(); - } - - @Test - public void shouldGetNameAndLockTimeFromAnnotation() throws NoSuchMethodException { - mockResolvedValue("lockName", "lockName"); - mockResolvedValue("100", "100"); - ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "annotatedMethod"); - LockConfiguration lockConfiguration = extractor.getLockConfiguration(runnable).get(); - assertThat(lockConfiguration.getName()).isEqualTo("lockName"); - assertThat(lockConfiguration.getLockAtMostUntil()).isBeforeOrEqualTo(now().plus(100, MILLIS)); - assertThat(lockConfiguration.getLockAtLeastUntil()).isAfter(now().plus(DEFAULT_LOCK_AT_LEAST_FOR).minus(1, SECONDS)); - } - - @Test - public void shouldGetNameFromSpringVariable() throws NoSuchMethodException { - mockResolvedValue("${name}", "lockNameX"); - ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "annotatedMethodWithNameVariable"); - LockConfiguration lockConfiguration = extractor.getLockConfiguration(runnable).get(); - assertThat(lockConfiguration.getName()).isEqualTo("lockNameX"); - } - - private void mockResolvedValue(String expression, String resolved) { - when(embeddedValueResolver.resolveStringValue(expression)).thenReturn(resolved); - } - - private void noopResolver() { - when(embeddedValueResolver.resolveStringValue(any())).thenAnswer(invocation -> invocation.getArgument(0)); - } - - private void doTestFindAnnotationOnProxy(Class config) throws NoSuchMethodException { - try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config)) { - BeanInterface bean = context.getBean(BeanInterface.class); - assertThat(extractor.findAnnotation(bean, bean.getClass().getMethod("method"))).isNotNull(); - } - } - - private AnnotationData getAnnotation(String method) throws NoSuchMethodException { - return extractor.findAnnotation(this, this.getClass().getMethod(method)); - } -} diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopCleanupTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopCleanupTest.java index 08bd96417..0cdcce7f2 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopCleanupTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopCleanupTest.java @@ -1,33 +1,36 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.concurrent.Executors; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - +@Disabled public class AopCleanupTest { @Test public void shouldCloseTaskExecutor() { @@ -37,7 +40,6 @@ public void shouldCloseTaskExecutor() { assertThat(context.isActive()).isFalse(); } - @Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "PT30S") @@ -49,10 +51,13 @@ public LockProvider lockProvider() { return lockProvider; } + @Bean + public TaskScheduler taskScheduler() { + return new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(10)); + } + @Scheduled(fixedRate = 10_000) @SchedulerLock(name = "task") - public void task() { - - } + public void task() {} } } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopOrderingTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopOrderingTest.java index 4ffe18cb7..2c68a0167 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopOrderingTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/AopOrderingTest.java @@ -1,20 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import net.javacrumbs.shedlock.core.LockAssert; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; @@ -35,19 +44,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE; - - @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {AopOrderingTest.AopOrderConfig.class, AopOrderingTest.Aspects.class}) public class AopOrderingTest { @@ -88,7 +84,6 @@ public LockProvider lockProvider() { public TestBean testBean() { return new TestBean(); } - } @Configuration @@ -96,14 +91,12 @@ static class Aspects { @Bean @Role(ROLE_INFRASTRUCTURE) public Advisor firstAspect() { - DefaultPointcutAdvisor aspect = new DefaultPointcutAdvisor( - shedlockPointcut(), - (MethodInterceptor) invocation -> { - aspectsCalled.add("first"); - assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); - return invocation.proceed(); - } - ); + DefaultPointcutAdvisor aspect = + new DefaultPointcutAdvisor(shedlockPointcut(), (MethodInterceptor) invocation -> { + aspectsCalled.add("first"); + assertThatThrownBy(LockAssert::assertLocked).isInstanceOf(IllegalStateException.class); + return invocation.proceed(); + }); aspect.setOrder(0); return aspect; } @@ -111,26 +104,19 @@ public Advisor firstAspect() { @Bean @Role(ROLE_INFRASTRUCTURE) public Advisor lastAspect() { - DefaultPointcutAdvisor aspect = new DefaultPointcutAdvisor( - shedlockPointcut(), - (MethodInterceptor) invocation -> { - aspectsCalled.add("last"); - LockAssert.assertLocked(); - return invocation.proceed(); - } - ); + DefaultPointcutAdvisor aspect = + new DefaultPointcutAdvisor(shedlockPointcut(), (MethodInterceptor) invocation -> { + aspectsCalled.add("last"); + LockAssert.assertLocked(); + return invocation.proceed(); + }); aspect.setOrder(200); return aspect; } private static AnnotationMatchingPointcut shedlockPointcut() { - return new AnnotationMatchingPointcut( - null, - SchedulerLock.class, - true - ); + return new AnnotationMatchingPointcut(null, SchedulerLock.class, true); } - } static class TestBean { diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopConfig.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopConfig.java index bb4117542..41500849e 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopConfig.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopConfig.java @@ -1,21 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; +import static org.mockito.Mockito.mock; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; @@ -24,16 +27,11 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableScheduling; -import java.io.IOException; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; -import static org.mockito.Mockito.mock; - @Configuration @EnableScheduling -@EnableSchedulerLock(defaultLockAtMostFor = "${default.lock_at_most_for}", defaultLockAtLeastFor = "${default.lock_at_least_for}") +@EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}") @PropertySource("test.properties") public class MethodProxyAopConfig { @@ -52,6 +50,11 @@ public AnotherTestBean anotherTestBean() { return new AnotherTestBeanImpl(); } + @Bean + public NameSource nameSource() { + return new NameSource(); + } + static class TestBean { private final AtomicBoolean called = new AtomicBoolean(false); @@ -117,6 +120,16 @@ public void spel() { called.set(true); } + @SchedulerLock(name = "#{nameSource.getLockName()}", lockAtLeastFor = "1s") + public void spelBean() { + called.set(true); + } + + @SchedulerLock(name = "#{nameSource.getLockName()}", lockAtLeastFor = "1s") + public void spelBean(String param) { + called.set(true); + } + @SchedulerLock(name = "${finalNotLocked}", lockAtLeastFor = "10ms") public final void finalNotLocked() { assertLocked(); @@ -131,8 +144,12 @@ static class AnotherTestBeanImpl implements AnotherTestBean { @Override @SchedulerLock(name = "classAnnotation") - public void runManually() { + public void runManually() {} + } + static class NameSource { + public String getLockName() { + return "myLockName"; } } } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopFailureTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopFailureTest.java new file mode 100644 index 000000000..5166ffb60 --- /dev/null +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopFailureTest.java @@ -0,0 +1,21 @@ +package net.javacrumbs.shedlock.spring.aop; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +public class MethodProxyAopFailureTest { + @Test + public void shouldFailOnStartupWithoutLockProvider() { + assertThatThrownBy(() -> new AnnotationConfigApplicationContext(BeanCreationException.class)) + .isInstanceOf(BeanCreationException.class); + } + + @Configuration + @EnableSchedulerLock(defaultLockAtMostFor = "60s") + public static class NoLockProviderConfig {} +} diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopTest.java index 96e6b7217..4b05ca589 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MethodProxyAopTest.java @@ -1,20 +1,29 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.spring.ExtendedLockConfigurationExtractor; @@ -28,19 +37,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.io.IOException; -import java.util.Optional; - -import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - - @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = MethodProxyAopConfig.class) public class MethodProxyAopTest { @@ -103,7 +99,6 @@ public void shouldRethrowDeclaredException() { assertThat(testBean.wasMethodCalled()).isTrue(); } - @Test public void shouldFailOnPrimitiveReturnType() { assertThatThrownBy(() -> testBean.returnsValue()).isInstanceOf(LockingNotSupportedException.class); @@ -139,6 +134,22 @@ public void shouldReadSpringProperty() { assertThat(testBean.wasMethodCalled()).isTrue(); } + @Test + public void shouldGetNameFromABean() { + testBean.spelBean(); + verify(lockProvider).lock(hasParams("myLockName", 30_000, 1_000)); + verify(simpleLock).unlock(); + assertThat(testBean.wasMethodCalled()).isTrue(); + } + + @Test + public void shouldGetNameFromABeanParametrized() { + testBean.spelBean("test"); + verify(lockProvider).lock(hasParams("myLockName", 30_000, 1_000)); + verify(simpleLock).unlock(); + assertThat(testBean.wasMethodCalled()).isTrue(); + } + @Test public void shouldFailOnNotLockedMethod() { assertThatThrownBy(() -> testBean.finalNotLocked()).hasMessageStartingWith("The task is not locked."); diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MultipleLockProvidersSchedulerProxyTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MultipleLockProvidersSchedulerProxyTest.java new file mode 100644 index 000000000..cc6119589 --- /dev/null +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MultipleLockProvidersSchedulerProxyTest.java @@ -0,0 +1,57 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.spring.aop; + +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; +import static org.mockito.Mockito.mock; + +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = MultipleLockProvidersSchedulerProxyTest.SchedulerWrapperConfig.class) +@LockProviderToUse("lockProvider") +public class MultipleLockProvidersSchedulerProxyTest extends AbstractSchedulerProxyTest { + + @Override + protected void assertRightSchedulerUsed() {} + + @Configuration + @EnableScheduling + @EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}", + interceptMode = PROXY_SCHEDULER) + @PropertySource("test.properties") + static class SchedulerWrapperConfig { + + @Bean + public LockProvider lockProvider() { + return mock(LockProvider.class); + } + + @Bean + public LockProvider lockProvider2() { + return mock(LockProvider.class); + } + } +} diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MyScheduled.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MyScheduled.java index 967c1b052..736a77a24 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MyScheduled.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/MyScheduled.java @@ -1,29 +1,26 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; -import org.springframework.core.annotation.AliasFor; -import org.springframework.scheduling.annotation.Async; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.core.annotation.AliasFor; +import org.springframework.scheduling.annotation.Async; @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyCglibTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyCglibTest.java index ee1f7e4ee..083a6dc0f 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyCglibTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyCglibTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.concurrent.Executors; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.junit.jupiter.api.Test; @@ -29,13 +32,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.concurrent.Executors; - -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - - @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SchedulerProxyCglibTest.SchedulerWrapperConfig.class) public class SchedulerProxyCglibTest extends AbstractSchedulerProxyTest { @@ -52,7 +48,11 @@ protected void assertRightSchedulerUsed() { @Configuration @EnableScheduling - @EnableSchedulerLock(defaultLockAtMostFor = "${default.lock_at_most_for}", defaultLockAtLeastFor = "${default.lock_at_least_for}", proxyTargetClass = true, interceptMode = PROXY_SCHEDULER) + @EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}", + proxyTargetClass = true, + interceptMode = PROXY_SCHEDULER) @PropertySource("test.properties") static class SchedulerWrapperConfig { @@ -61,15 +61,13 @@ public LockProvider lockProvider() { return mock(LockProvider.class); } - @Bean public TaskScheduler taskScheduler() { return new MyTaskScheduler(); } } - interface MyInterface { - } + interface MyInterface {} private static class MyTaskScheduler extends ConcurrentTaskScheduler implements MyInterface { MyTaskScheduler() { diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyDefaultSchedulerTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyDefaultSchedulerTest.java index 5af4680cd..19aff6778 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyDefaultSchedulerTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyDefaultSchedulerTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; +import static org.mockito.Mockito.mock; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,28 +29,20 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; - -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; -import static org.mockito.Mockito.mock; - - -/** - * Test creation of default task scheduler - */ +/** Test creation of default task scheduler */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SchedulerProxyDefaultSchedulerTest.SchedulerWrapperConfig.class) public class SchedulerProxyDefaultSchedulerTest extends AbstractSchedulerProxyTest { @Override - protected void assertRightSchedulerUsed() { - - } + protected void assertRightSchedulerUsed() {} @Configuration @EnableScheduling - @EnableSchedulerLock(defaultLockAtMostFor = "${default.lock_at_most_for}", defaultLockAtLeastFor = "${default.lock_at_least_for}", interceptMode = PROXY_SCHEDULER) + @EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}", + interceptMode = PROXY_SCHEDULER) @PropertySource("test.properties") static class SchedulerWrapperConfig { diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledExecutorServiceSchedulerTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledExecutorServiceSchedulerTest.java index 0ce5444ca..01e5c1b52 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledExecutorServiceSchedulerTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyScheduledExecutorServiceSchedulerTest.java @@ -1,20 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,17 +30,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - - -/** - * Test creation of default task scheduler - */ +/** Test creation of default task scheduler */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SchedulerProxyScheduledExecutorServiceSchedulerTest.SchedulerWrapperConfig.class) public class SchedulerProxyScheduledExecutorServiceSchedulerTest extends AbstractSchedulerProxyTest { @@ -47,7 +41,10 @@ protected void assertRightSchedulerUsed() { @Configuration @EnableScheduling - @EnableSchedulerLock(defaultLockAtMostFor = "${default.lock_at_most_for}", defaultLockAtLeastFor = "${default.lock_at_least_for}", interceptMode = PROXY_SCHEDULER) + @EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}", + interceptMode = PROXY_SCHEDULER) @PropertySource("test.properties") static class SchedulerWrapperConfig { diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyTest.java index 9502e5d01..c3ef3cfb3 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SchedulerProxyTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.concurrent.Executors; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.junit.jupiter.api.Test; @@ -29,13 +32,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.concurrent.Executors; - -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - - @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SchedulerProxyTest.SchedulerWrapperConfig.class) public class SchedulerProxyTest extends AbstractSchedulerProxyTest { @@ -53,7 +49,10 @@ protected void assertRightSchedulerUsed() { @Configuration @EnableScheduling - @EnableSchedulerLock(defaultLockAtMostFor = "${default.lock_at_most_for}", defaultLockAtLeastFor = "${default.lock_at_least_for}", interceptMode = PROXY_SCHEDULER) + @EnableSchedulerLock( + defaultLockAtMostFor = "${default.lock_at_most_for}", + defaultLockAtLeastFor = "${default.lock_at_least_for}", + interceptMode = PROXY_SCHEDULER) @PropertySource("test.properties") static class SchedulerWrapperConfig { @@ -62,7 +61,6 @@ public LockProvider lockProvider() { return mock(LockProvider.class); } - @Bean public TaskScheduler taskScheduler() { return new MyTaskScheduler(); diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractorTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractorTest.java index 44d6c3c79..0a9285abd 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractorTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/SpringLockConfigurationExtractorTest.java @@ -1,85 +1,249 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; -import org.springframework.core.annotation.AliasFor; -import org.springframework.scheduling.annotation.Scheduled; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static net.javacrumbs.shedlock.core.ClockProvider.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockConfiguration; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import net.javacrumbs.shedlock.spring.proxytest.BeanInterface; +import net.javacrumbs.shedlock.spring.proxytest.DynamicProxyConfig; +import net.javacrumbs.shedlock.spring.proxytest.SubclassProxyConfig; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.annotation.AliasFor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.support.ScheduledMethodRunnable; +import org.springframework.util.StringValueResolver; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +public class SpringLockConfigurationExtractorTest { -public class SpringLockConfigurationExtractorTest extends AbstractSpringLockConfigurationExtractorTest { + private static final Duration DEFAULT_LOCK_TIME = Duration.of(30, ChronoUnit.MINUTES); + private static final Duration DEFAULT_LOCK_AT_LEAST_FOR = Duration.of(5, ChronoUnit.MILLIS); + private final StringValueResolver embeddedValueResolver = mock(StringValueResolver.class); + private final SpringLockConfigurationExtractor extractor = new SpringLockConfigurationExtractor( + DEFAULT_LOCK_TIME, DEFAULT_LOCK_AT_LEAST_FOR, embeddedValueResolver, new StringToDurationConverter()); @SchedulerLock(name = "lockName", lockAtMostFor = "100") - public void annotatedMethod() { - - } + public void annotatedMethod() {} @SchedulerLock(name = "lockName", lockAtMostFor = "${placeholder}") - public void annotatedMethodWithString() { + public void annotatedMethodWithString() {} + + @SchedulerLock(name = "lockName", lockAtMostFor = "PT1S") + public void annotatedMethodWithDurationString() {} + + @SchedulerLock(name = "${name}") + public void annotatedMethodWithNameVariable() {} + + @SchedulerLock(name = "lockName-#{#arg0 + '-' + #arg1 + '-' + (1 + 2) + '-' + 'abcde'.length()}") + public void annotatedMethodWithNameSpringExpression(String arg0, Integer arg1) {} + + @SchedulerLock(name = "${name}-#{#arg0}") + public void annotatedMethodWithNameSpringExpressionAndVariable(String arg0) {} + + @SchedulerLock(name = "lockName") + public void annotatedMethodWithoutLockAtMostFor() {} + + @SchedulerLock(name = "lockName", lockAtLeastFor = "0") + public void annotatedMethodWithZeroGracePeriod() {} + + @SchedulerLock(name = "lockName", lockAtLeastFor = "10") + public void annotatedMethodWithPositiveGracePeriod() {} + + @SchedulerLock(name = "lockName", lockAtLeastFor = "10ms") + public void annotatedMethodWithPositiveGracePeriodWithString() {} + + @SchedulerLock(name = "lockName", lockAtLeastFor = "-1s") + public void annotatedMethodWithNegativeGracePeriod() {} + @ScheduledLocked(name = "lockName1") + public void composedAnnotation() {} + + public void methodWithoutAnnotation() {} + + @Test + public void shouldLockForDefaultTimeIfNoAnnotation() throws NoSuchMethodException { + SpringLockConfigurationExtractor.AnnotationData annotation = + getAnnotation("annotatedMethodWithoutLockAtMostFor"); + TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); + assertThat(lockAtMostFor).isEqualTo(DEFAULT_LOCK_TIME); } - @SchedulerLock(name = "lockName", lockAtMostFor = "PT1S") - public void annotatedMethodWithDurationString() { + @Test + public void shouldLockTimeFromAnnotation() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = getAnnotation("annotatedMethod"); + TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); + assertThat(lockAtMostFor).isEqualTo(Duration.of(100, MILLIS)); + } + @Test + public void shouldLockTimeFromAnnotationWithString() throws NoSuchMethodException { + mockResolvedValue("${placeholder}", "5"); + SpringLockConfigurationExtractor.AnnotationData annotation = getAnnotation("annotatedMethodWithString"); + TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); + assertThat(lockAtMostFor).isEqualTo(Duration.of(5, MILLIS)); } - @SchedulerLock(name = "${name}") - public void annotatedMethodWithNameVariable() { + @Test + public void shouldLockTimeFromAnnotationWithDurationString() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = getAnnotation("annotatedMethodWithDurationString"); + TemporalAmount lockAtMostFor = extractor.getLockAtMostFor(annotation); + assertThat(lockAtMostFor).isEqualTo(Duration.of(1, SECONDS)); + } + @Test + public void shouldGetZeroGracePeriodFromAnnotation() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = + getAnnotation("annotatedMethodWithZeroGracePeriod"); + TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); + assertThat(gracePeriod).isEqualTo(Duration.ZERO); } - @SchedulerLock(name = "lockName") - public void annotatedMethodWithoutLockAtMostFor() { + @Test + public void shouldGetPositiveGracePeriodFromAnnotation() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = + getAnnotation("annotatedMethodWithPositiveGracePeriod"); + TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); + assertThat(gracePeriod).isEqualTo(Duration.of(10, MILLIS)); + } + @Test + public void shouldGetPositiveGracePeriodFromAnnotationWithString() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = + getAnnotation("annotatedMethodWithPositiveGracePeriodWithString"); + TemporalAmount gracePeriod = extractor.getLockAtLeastFor(annotation); + assertThat(gracePeriod).isEqualTo(Duration.of(10, MILLIS)); } - @SchedulerLock(name = "lockName", lockAtLeastFor = "0") - public void annotatedMethodWithZeroGracePeriod() { + @Test + public void shoulFailOnNegativeLockAtMostFor() throws NoSuchMethodException { + noopResolver(); + SpringLockConfigurationExtractor.AnnotationData annotation = + getAnnotation("annotatedMethodWithNegativeGracePeriod"); + assertThatThrownBy(() -> extractor.getLockAtLeastFor(annotation)).isInstanceOf(IllegalArgumentException.class); + } + @Test + public void shouldExtractComposedAnnotation() throws NoSuchMethodException { + mockResolvedValue("20", "20"); + SpringLockConfigurationExtractor.AnnotationData annotation = getAnnotation("composedAnnotation"); + TemporalAmount atMostFor = extractor.getLockAtMostFor(annotation); + assertThat(annotation.name()).isEqualTo("lockName1"); + assertThat(atMostFor).isEqualTo(Duration.of(20, MILLIS)); } - @SchedulerLock(name = "lockName", lockAtLeastFor = "10") - public void annotatedMethodWithPositiveGracePeriod() { + @Test + public void shouldFindAnnotationOnDynamicProxy() throws NoSuchMethodException { + doTestFindAnnotationOnProxy(DynamicProxyConfig.class); + } + @Test + public void shouldFindAnnotationOnSubclassProxy() throws NoSuchMethodException { + doTestFindAnnotationOnProxy(SubclassProxyConfig.class); } - @SchedulerLock(name = "lockName", lockAtLeastFor = "10ms") - public void annotatedMethodWithPositiveGracePeriodWithString() { + @Test + public void shouldNotLockUnannotatedMethod() throws NoSuchMethodException { + ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "methodWithoutAnnotation"); + Optional lockConfiguration = extractor.getLockConfiguration(runnable); + assertThat(lockConfiguration).isEmpty(); + } + @Test + public void shouldGetNameAndLockTimeFromAnnotation() throws NoSuchMethodException { + mockResolvedValue("lockName", "lockName"); + mockResolvedValue("100", "100"); + ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "annotatedMethod"); + LockConfiguration lockConfiguration = + extractor.getLockConfiguration(runnable).get(); + assertThat(lockConfiguration.getName()).isEqualTo("lockName"); + assertThat(lockConfiguration.getLockAtMostUntil()).isBeforeOrEqualTo(now().plus(100, MILLIS)); + assertThat(lockConfiguration.getLockAtLeastUntil()) + .isAfter(now().plus(DEFAULT_LOCK_AT_LEAST_FOR).minus(1, SECONDS)); } - @SchedulerLock(name = "lockName", lockAtLeastFor = "-1s") - public void annotatedMethodWithNegativeGracePeriod() { + @Test + public void shouldGetNameFromSpringVariable() throws NoSuchMethodException { + mockResolvedValue("${name}", "lockNameX"); + ScheduledMethodRunnable runnable = new ScheduledMethodRunnable(this, "annotatedMethodWithNameVariable"); + LockConfiguration lockConfiguration = + extractor.getLockConfiguration(runnable).get(); + assertThat(lockConfiguration.getName()).isEqualTo("lockNameX"); + } + @Test + public void shouldGetNameFromSpringExpression() throws NoSuchMethodException { + mockResolvedValue("lockName-value-1-3-5", "lockName-value-1-3-5"); + Method method = + this.getClass().getMethod("annotatedMethodWithNameSpringExpression", String.class, Integer.class); + LockConfiguration lockConfiguration = extractor + .getLockConfiguration(this, method, new Object[] {"value", 1}) + .get(); + assertThat(lockConfiguration.getName()).isEqualTo("lockName-value-1-3-5"); } - @ScheduledLocked(name = "lockName1") - public void composedAnnotation() { + @Test + public void shouldGetNameFromSpringExpressionAndSpringVariable() throws NoSuchMethodException { + mockResolvedValue("${name}-value", "lockName-value"); + Method method = this.getClass().getMethod("annotatedMethodWithNameSpringExpressionAndVariable", String.class); + LockConfiguration lockConfiguration = extractor + .getLockConfiguration(this, method, new Object[] {"value"}) + .get(); + assertThat(lockConfiguration.getName()).isEqualTo("lockName-value"); + } + private void mockResolvedValue(String expression, String resolved) { + when(embeddedValueResolver.resolveStringValue(expression)).thenReturn(resolved); } - public void methodWithoutAnnotation() { + private void noopResolver() { + when(embeddedValueResolver.resolveStringValue(any())).thenAnswer(invocation -> invocation.getArgument(0)); + } + + private void doTestFindAnnotationOnProxy(Class config) throws NoSuchMethodException { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config)) { + BeanInterface bean = context.getBean(BeanInterface.class); + assertThat(extractor.findAnnotation(bean, bean.getClass().getMethod("method"))) + .isNotNull(); + } + } + private SpringLockConfigurationExtractor.AnnotationData getAnnotation(String method) throws NoSuchMethodException { + return extractor.findAnnotation(this, this.getClass().getMethod(method)); } @Target(METHOD) diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverterTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverterTest.java index 8e0462c34..d22365c41 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverterTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/StringToDurationConverterTest.java @@ -1,27 +1,24 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.aop; -import org.junit.jupiter.api.Test; - -import java.time.Duration; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.time.Duration; +import org.junit.jupiter.api.Test; + /** * Tests for {@link StringToDurationConverter}. * @@ -106,12 +103,12 @@ public void convertWhenSimpleWithoutSuffixShouldReturnDuration() { @Test public void convertWhenBadFormatShouldThrowException() { - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> convert("10foo")) - .withMessageContaining("'10foo' is not a valid duration"); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> convert("10foo")) + .withMessageContaining("'10foo' is not a valid duration"); } private Duration convert(String source) { return new StringToDurationConverter().convert(source); } - } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProviderMethodProxyAopTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProviderMethodProxyAopTest.java new file mode 100644 index 000000000..3116f78c0 --- /dev/null +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProviderMethodProxyAopTest.java @@ -0,0 +1,89 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.spring.aop.multiplelockproviders; + +import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.core.SimpleLock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = MultipleLockProvidersMethodProxyAopConfig.class) +public class MultipleLockProviderMethodProxyAopTest { + @Autowired + private LockProvider lockProvider1; + + @Autowired + private LockProvider lockProvider2; + + @Autowired + private LockProvider lockProvider3; + + @Autowired + private MultipleLockProvidersMethodProxyAopConfig.TestBean1 testBean1; + + @Autowired + private MultipleLockProvidersMethodProxyAopConfig.TestBean2 testBean2; + + private final SimpleLock simpleLock1 = mock(); + private final SimpleLock simpleLock2 = mock(); + private final SimpleLock simpleLock3 = mock(); + + @BeforeEach + public void prepareMocks() { + Mockito.reset(lockProvider1, lockProvider2, lockProvider3, simpleLock1, simpleLock2, simpleLock3); + when(lockProvider1.lock(any())).thenReturn(Optional.of(simpleLock1)); + when(lockProvider2.lock(any())).thenReturn(Optional.of(simpleLock2)); + when(lockProvider3.lock(any())).thenReturn(Optional.of(simpleLock3)); + testBean1.reset(); + testBean2.reset(); + } + + @Test + public void shouldCallLockProviderDefinedOnPackage() { + testBean1.method1(); + verify(lockProvider1).lock(hasParams("method1", 60_000, 0)); + verify(simpleLock1).unlock(); + assertThat(testBean1.wasMethodCalled()).isTrue(); + } + + @Test + public void shouldCallLockProviderDefinedOnClass() { + testBean2.method2(); + verify(lockProvider2).lock(hasParams("method2", 60_000, 0)); + verify(simpleLock2).unlock(); + assertThat(testBean2.wasMethodCalled()).isTrue(); + } + + @Test + public void shouldCallLockProviderDefinedOnMethod() { + testBean2.method3(); + verify(lockProvider3).lock(hasParams("method3", 60_000, 0)); + verify(simpleLock3).unlock(); + assertThat(testBean2.wasMethodCalled()).isTrue(); + } +} diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProvidersMethodProxyAopConfig.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProvidersMethodProxyAopConfig.java new file mode 100644 index 000000000..07af93795 --- /dev/null +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/MultipleLockProvidersMethodProxyAopConfig.java @@ -0,0 +1,98 @@ +/** + * Copyright 2009 the original author or authors. + * + *

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 net.javacrumbs.shedlock.spring.aop.multiplelockproviders; + +import static org.mockito.Mockito.mock; + +import java.util.concurrent.atomic.AtomicBoolean; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "60s") +public class MultipleLockProvidersMethodProxyAopConfig { + + @Bean + public LockProvider lockProvider1() { + return mock(); + } + + @Bean + public LockProvider lockProvider2() { + return mock(); + } + + @Bean + public LockProvider lockProvider3() { + return mock(); + } + + @Bean + TestBean1 testBean1() { + return new TestBean1(); + } + + @Bean + TestBean2 testBean2() { + return new TestBean2(); + } + + // LockProvider name taken from package-info.java + static class TestBean1 { + private final AtomicBoolean called = new AtomicBoolean(false); + + void reset() { + called.set(false); + } + + boolean wasMethodCalled() { + return called.get(); + } + + @SchedulerLock(name = "method1") + public void method1() { + called.set(true); + } + } + + @LockProviderToUse("lockProvider2") // Default for the class + static class TestBean2 { + private final AtomicBoolean called = new AtomicBoolean(false); + + void reset() { + called.set(false); + } + + boolean wasMethodCalled() { + return called.get(); + } + + @SchedulerLock(name = "method2") + public void method2() { + called.set(true); + } + + @SchedulerLock(name = "method3") + @LockProviderToUse("lockProvider3") // Override the default + public void method3() { + called.set(true); + } + } +} diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/package-info.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/package-info.java new file mode 100644 index 000000000..71c08704f --- /dev/null +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/aop/multiplelockproviders/package-info.java @@ -0,0 +1,4 @@ +@LockProviderToUse("lockProvider1") +package net.javacrumbs.shedlock.spring.aop.multiplelockproviders; + +import net.javacrumbs.shedlock.spring.annotation.LockProviderToUse; diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerConfig.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerConfig.java index d9c0d7b4e..25182d6fd 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerConfig.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerConfig.java @@ -1,21 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.it; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.util.Optional; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.core.SimpleLock; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; @@ -24,12 +26,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.Scheduled; -import java.util.Optional; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public abstract class AbstractSchedulerConfig { private static final Logger logger = LoggerFactory.getLogger(ProxyIntegrationTest.class); diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerTest.java index 7d43977b4..f43e36d05 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/AbstractSchedulerTest.java @@ -1,31 +1,28 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.it; -import net.javacrumbs.shedlock.core.LockProvider; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit.jupiter.SpringExtension; - import static net.javacrumbs.shedlock.spring.TestUtils.hasParams; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; +import net.javacrumbs.shedlock.core.LockProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) public abstract class AbstractSchedulerTest { diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/ProxyIntegrationTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/ProxyIntegrationTest.java index 3fa5c8ba8..c8d687a45 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/ProxyIntegrationTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/ProxyIntegrationTest.java @@ -1,34 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.it; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_METHOD; + import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.test.context.ContextConfiguration; -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_METHOD; - - @ContextConfiguration(classes = ProxyIntegrationTest.AopSchedulerConfig.class) public class ProxyIntegrationTest extends AbstractSchedulerTest { @Configuration @EnableScheduling @EnableSchedulerLock(interceptMode = PROXY_METHOD, defaultLockAtMostFor = "PT30S") - public static class AopSchedulerConfig extends AbstractSchedulerConfig { - } + public static class AopSchedulerConfig extends AbstractSchedulerConfig {} } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/SchedulerIntegrationTest.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/SchedulerIntegrationTest.java index 111db23b0..30fd9e606 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/SchedulerIntegrationTest.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/it/SchedulerIntegrationTest.java @@ -1,34 +1,30 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.it; +import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; + import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.test.context.ContextConfiguration; -import static net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock.InterceptMode.PROXY_SCHEDULER; - - @ContextConfiguration(classes = SchedulerIntegrationTest.AopSchedulerConfig.class) public class SchedulerIntegrationTest extends AbstractSchedulerTest { @Configuration @EnableScheduling @EnableSchedulerLock(interceptMode = PROXY_SCHEDULER, defaultLockAtMostFor = "PT30S") - public static class AopSchedulerConfig extends AbstractSchedulerConfig { - } + public static class AopSchedulerConfig extends AbstractSchedulerConfig {} } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanImpl.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanImpl.java index 67d69dfd7..b7828db2e 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanImpl.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanImpl.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.proxytest; @@ -20,11 +18,8 @@ public class BeanImpl implements BeanInterface { - @Override @SchedulerLock(name = "test") @Async // to generate proxy - public void method() { - - } + public void method() {} } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanInterface.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanInterface.java index fb0c5e9d6..873b48ef5 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanInterface.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/BeanInterface.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.proxytest; diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/DynamicProxyConfig.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/DynamicProxyConfig.java index 11b4bba92..63eb1ab82 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/DynamicProxyConfig.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/DynamicProxyConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.proxytest; @@ -27,5 +25,4 @@ public class DynamicProxyConfig { public BeanInterface bean() { return new BeanImpl(); } - } diff --git a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/SubclassProxyConfig.java b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/SubclassProxyConfig.java index 752f39073..c6af7b9f7 100644 --- a/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/SubclassProxyConfig.java +++ b/spring/shedlock-spring/src/test/java/net/javacrumbs/shedlock/spring/proxytest/SubclassProxyConfig.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.spring.proxytest; @@ -27,5 +25,4 @@ public class SubclassProxyConfig { public BeanInterface bean() { return new BeanImpl(); } - } diff --git a/spring/test/shedlock-springboot-future-test/pom.xml b/spring/test/shedlock-springboot-future-test/pom.xml index 6d3f94424..ec9b1b4e9 100644 --- a/spring/test/shedlock-springboot-future-test/pom.xml +++ b/spring/test/shedlock-springboot-future-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-springboot-future-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -22,6 +22,11 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-jdbc + + org.springframework.boot spring-boot-starter-test @@ -42,12 +47,7 @@ org.hsqldb hsqldb - 2.7.1 - - - - org.liquibase - liquibase-core + 2.7.4 @@ -63,7 +63,7 @@ org.springframework.boot spring-boot-dependencies - 3.0.0 + 4.0.0-M2 pom import diff --git a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java index 5079037a0..f012d6f77 100644 --- a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java +++ b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java @@ -1,23 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @@ -28,4 +30,9 @@ public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } + + @Bean + public LockProvider lockProvider(DataSource dataSource) { + return new JdbcTemplateLockProvider(dataSource, "shedlock"); + } } diff --git a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java index ee947b04d..390611cc7 100644 --- a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java +++ b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; @@ -25,5 +23,4 @@ public class HelloController { public String index() { return "Greetings from Spring Boot!"; } - } diff --git a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java index c4d3b5498..c20467679 100644 --- a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java +++ b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java @@ -1,31 +1,28 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.Duration; -import java.util.Date; - import static java.time.Duration.ZERO; import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; import static net.javacrumbs.shedlock.core.LockExtender.extendActiveLock; +import java.time.Duration; +import java.util.Date; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + @Component public class ScheduledTasks { @Scheduled(fixedRate = 100) diff --git a/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java new file mode 100644 index 000000000..3beb4c4ea --- /dev/null +++ b/spring/test/shedlock-springboot-future-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.boot; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/test/shedlock-springboot-future-test/src/main/resources/db/changelog/db.changelog-master.yaml b/spring/test/shedlock-springboot-future-test/src/main/resources/db/changelog/db.changelog-master.yaml deleted file mode 100644 index f7b753b5f..000000000 --- a/spring/test/shedlock-springboot-future-test/src/main/resources/db/changelog/db.changelog-master.yaml +++ /dev/null @@ -1,40 +0,0 @@ -databaseChangeLog: - - changeSet: - id: 1 - author: marceloverdijk - changes: - - createTable: - tableName: person - schemaName: myapp - columns: - - column: - name: id - type: int - autoIncrement: true - constraints: - primaryKey: true - nullable: false - - column: - name: first_name - type: varchar(255) - constraints: - nullable: false - - column: - name: last_name - type: varchar(255) - constraints: - nullable: false - - changeSet: - id: 2 - author: marceloverdijk - changes: - - insert: - tableName: person - schemaName: myapp - columns: - - column: - name: first_name - value: Marcel - - column: - name: last_name - value: Overdijk diff --git a/spring/test/shedlock-springboot-future-test/src/main/resources/schema.sql b/spring/test/shedlock-springboot-future-test/src/main/resources/schema.sql index f3546a268..5eca832d6 100644 --- a/spring/test/shedlock-springboot-future-test/src/main/resources/schema.sql +++ b/spring/test/shedlock-springboot-future-test/src/main/resources/schema.sql @@ -1,9 +1,7 @@ -CREATE SCHEMA myapp; - -CREATE TABLE myapp.shedlock( +CREATE TABLE shedlock( name VARCHAR(64), lock_until TIMESTAMP(3) NULL, locked_at TIMESTAMP(3) NULL, locked_by VARCHAR(255), PRIMARY KEY (name) -); \ No newline at end of file +); diff --git a/spring/test/shedlock-springboot-future-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java b/spring/test/shedlock-springboot-future-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java index 32cc80060..ed442adc5 100644 --- a/spring/test/shedlock-springboot-future-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java +++ b/spring/test/shedlock-springboot-future-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java @@ -1,38 +1,36 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.mockito.ArgumentCaptor; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; +import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest @EnabledForJreRange(min = JAVA_17) public class IntegrationTest { - @MockBean + @MockitoBean private LockProvider lockProvider; @Test diff --git a/spring/test/shedlock-springboot-kotlin-test/pom.xml b/spring/test/shedlock-springboot-kotlin-test/pom.xml index 702419e8f..ad4486b54 100644 --- a/spring/test/shedlock-springboot-kotlin-test/pom.xml +++ b/spring/test/shedlock-springboot-kotlin-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-springboot-kotlin-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -58,7 +58,7 @@ org.hsqldb hsqldb - 2.7.1 + 2.7.4 diff --git a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/Application.kt b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/Application.kt index cd98478e6..9a5a8aba9 100644 --- a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/Application.kt +++ b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/Application.kt @@ -1,20 +1,18 @@ /** * Copyright 2009 the original author or authors. * - * 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 + * 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. + * 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 net.javacrumbs.shedlock.test.boot +import javax.sql.DataSource import net.javacrumbs.shedlock.core.LockProvider import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock @@ -23,8 +21,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean import org.springframework.scheduling.annotation.EnableScheduling -import javax.sql.DataSource - @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "PT10M") diff --git a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/HelloController.kt b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/HelloController.kt index b29f2e17c..c05e5d585 100644 --- a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/HelloController.kt +++ b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/HelloController.kt @@ -1,17 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + * 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. + * 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 net.javacrumbs.shedlock.test.boot diff --git a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/ScheduledTasks.kt b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/ScheduledTasks.kt index 0826df63c..993e6ab05 100644 --- a/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/ScheduledTasks.kt +++ b/spring/test/shedlock-springboot-kotlin-test/src/main/kotlin/net/javacrumbs/shedlock/test/boot/ScheduledTasks.kt @@ -1,25 +1,22 @@ /** * Copyright 2009 the original author or authors. * - * 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 + * 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. + * 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 net.javacrumbs.shedlock.test.boot +import java.util.Date import net.javacrumbs.shedlock.core.LockAssert.assertLocked import net.javacrumbs.shedlock.spring.annotation.SchedulerLock import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component -import java.util.Date @Component class ScheduledTasks { diff --git a/spring/test/shedlock-springboot-kotlin-test/src/test/kotlin/net/javacrumbs/shedlock/test/boot/IntegrationTest.kt b/spring/test/shedlock-springboot-kotlin-test/src/test/kotlin/net/javacrumbs/shedlock/test/boot/IntegrationTest.kt index c924ca48f..0c87a1311 100644 --- a/spring/test/shedlock-springboot-kotlin-test/src/test/kotlin/net/javacrumbs/shedlock/test/boot/IntegrationTest.kt +++ b/spring/test/shedlock-springboot-kotlin-test/src/test/kotlin/net/javacrumbs/shedlock/test/boot/IntegrationTest.kt @@ -1,17 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + * 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. + * 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 net.javacrumbs.shedlock.test.boot @@ -28,8 +25,7 @@ import org.springframework.boot.test.mock.mockito.MockBean @SpringBootTest class IntegrationTest { - @MockBean - private lateinit var lockProvider: LockProvider + @MockBean private lateinit var lockProvider: LockProvider @Test fun testScheduler() { diff --git a/spring/test/shedlock-springboot-old-test/pom.xml b/spring/test/shedlock-springboot-old-test/pom.xml index 5da731240..07515afd5 100644 --- a/spring/test/shedlock-springboot-old-test/pom.xml +++ b/spring/test/shedlock-springboot-old-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-springboot-old-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -42,7 +42,7 @@ org.hsqldb hsqldb - 2.7.1 + 2.7.4 @@ -63,7 +63,7 @@ org.springframework.boot spring-boot-dependencies - 2.7.5 + 3.4.8 pom import diff --git a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java index 5079037a0..bb42ca267 100644 --- a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java +++ b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; diff --git a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java index ee947b04d..390611cc7 100644 --- a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java +++ b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; @@ -25,5 +23,4 @@ public class HelloController { public String index() { return "Greetings from Spring Boot!"; } - } diff --git a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java index 71f0437d4..5aa040ce6 100644 --- a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java +++ b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; + +import java.util.Date; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.util.Date; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; - @Component public class ScheduledTasks { @Scheduled(fixedRate = 100) diff --git a/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java new file mode 100644 index 000000000..3beb4c4ea --- /dev/null +++ b/spring/test/shedlock-springboot-old-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.boot; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java b/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java index b9864e4ca..23f984977 100644 --- a/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java +++ b/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import org.junit.jupiter.api.Test; @@ -22,11 +25,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - @SpringBootTest public class IntegrationTest { @MockBean diff --git a/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java b/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java index 0380aa5a2..de7196871 100644 --- a/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java +++ b/spring/test/shedlock-springboot-old-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; diff --git a/spring/test/shedlock-springboot-sleuth-test/pom.xml b/spring/test/shedlock-springboot-sleuth-test/pom.xml index 6cf6d3a34..53247a0ed 100644 --- a/spring/test/shedlock-springboot-sleuth-test/pom.xml +++ b/spring/test/shedlock-springboot-sleuth-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-springboot-sleuth-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -47,7 +47,7 @@ org.hsqldb hsqldb - 2.7.1 + 2.7.4 @@ -70,7 +70,7 @@ org.springframework.cloud spring-cloud-sleuth - 3.1.5 + 3.1.11 pom import diff --git a/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java index 1982c5974..8b8d72eab 100644 --- a/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java +++ b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java @@ -1,20 +1,19 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import javax.sql.DataSource; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; @@ -23,8 +22,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; -import javax.sql.DataSource; - @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "PT10M") diff --git a/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java index 71f0437d4..5aa040ce6 100644 --- a/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java +++ b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; + +import java.util.Date; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.util.Date; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; - @Component public class ScheduledTasks { @Scheduled(fixedRate = 100) diff --git a/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java new file mode 100644 index 000000000..3beb4c4ea --- /dev/null +++ b/spring/test/shedlock-springboot-sleuth-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.boot; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/test/shedlock-springboot-sleuth-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java b/spring/test/shedlock-springboot-sleuth-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java index 55a147d73..c186b3348 100644 --- a/spring/test/shedlock-springboot-sleuth-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java +++ b/spring/test/shedlock-springboot-sleuth-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import org.junit.jupiter.api.Test; @@ -22,11 +25,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - @SpringBootTest class IntegrationTest { diff --git a/spring/test/shedlock-springboot-test/pom.xml b/spring/test/shedlock-springboot-test/pom.xml index dcf11cf5f..34572eec4 100644 --- a/spring/test/shedlock-springboot-test/pom.xml +++ b/spring/test/shedlock-springboot-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-springboot-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -47,7 +47,7 @@ org.hsqldb hsqldb - 2.7.1 + 2.7.4 diff --git a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java index 95fa4f9e9..26af57d17 100644 --- a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java +++ b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java @@ -1,30 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; -import javax.sql.DataSource; - @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") @@ -33,9 +26,4 @@ public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } - - @Bean - public LockProvider lockProvider(DataSource dataSource) { - return new JdbcTemplateLockProvider(dataSource, "shedlock"); - } } diff --git a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java index ee947b04d..390611cc7 100644 --- a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java +++ b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/HelloController.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; @@ -25,5 +23,4 @@ public class HelloController { public String index() { return "Greetings from Spring Boot!"; } - } diff --git a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java index 71f0437d4..5aa040ce6 100644 --- a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java +++ b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java @@ -1,28 +1,25 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; + +import java.util.Date; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.util.Date; - -import static net.javacrumbs.shedlock.core.LockAssert.assertLocked; - @Component public class ScheduledTasks { @Scheduled(fixedRate = 100) diff --git a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ShedlockConfig.java b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ShedlockConfig.java new file mode 100644 index 000000000..bd0ca31b4 --- /dev/null +++ b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/ShedlockConfig.java @@ -0,0 +1,15 @@ +package net.javacrumbs.shedlock.test.boot; + +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ShedlockConfig { + @Bean + public LockProvider lockProvider(DataSource dataSource) { + return new JdbcTemplateLockProvider(dataSource, "shedlock"); + } +} diff --git a/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java new file mode 100644 index 000000000..3beb4c4ea --- /dev/null +++ b/spring/test/shedlock-springboot-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.boot; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/HelloControllerTest.java b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/HelloControllerTest.java new file mode 100644 index 000000000..62473e7c6 --- /dev/null +++ b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/HelloControllerTest.java @@ -0,0 +1,21 @@ +package net.javacrumbs.shedlock.test.boot; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(HelloController.class) +class HelloControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldCallController() throws Exception { + mockMvc.perform(get("/")).andExpect(status().isOk()); + } +} diff --git a/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java index b9864e4ca..23f984977 100644 --- a/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java +++ b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java @@ -1,20 +1,23 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + import net.javacrumbs.shedlock.core.LockConfiguration; import net.javacrumbs.shedlock.core.LockProvider; import org.junit.jupiter.api.Test; @@ -22,11 +25,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - @SpringBootTest public class IntegrationTest { @MockBean diff --git a/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java index 0380aa5a2..de7196871 100644 --- a/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java +++ b/spring/test/shedlock-springboot-test/src/test/java/net/javacrumbs/shedlock/test/boot/LockUnitTest.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; diff --git a/spring/test/shedlock-testng-test/pom.xml b/spring/test/shedlock-testng-test/pom.xml index 8aae03cbe..4c20aa8f9 100644 --- a/spring/test/shedlock-testng-test/pom.xml +++ b/spring/test/shedlock-testng-test/pom.xml @@ -3,13 +3,13 @@ shedlock-parent net.javacrumbs.shedlock - 5.0.0-SNAPSHOT + 6.10.1-SNAPSHOT ../../../pom.xml 4.0.0 shedlock-testng-test - 5.0.0-SNAPSHOT + ${project.groupId}:${project.artifactId} @@ -35,7 +35,7 @@ com.zaxxer HikariCP - 5.0.1 + 7.0.2 @@ -48,14 +48,14 @@ org.testng testng - 7.6.1 + 7.11.0 test org.hsqldb hsqldb - 2.7.1 + 2.7.4 diff --git a/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java index 5a1e82594..369936ee1 100644 --- a/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java +++ b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/Application.java @@ -1,21 +1,20 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; @@ -25,8 +24,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.EnableScheduling; -import javax.sql.DataSource; - @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "PT10M") @@ -48,13 +45,16 @@ public DataSource dataSource() { datasource.setUsername("SA"); datasource.setPassword(""); - new JdbcTemplate(datasource).execute("CREATE TABLE shedlock(\n" + - " name VARCHAR(64), \n" + - " lock_until TIMESTAMP(3) NULL, \n" + - " locked_at TIMESTAMP(3) NULL, \n" + - " locked_by VARCHAR(255), \n" + - " PRIMARY KEY (name)\n" + - ")"); + new JdbcTemplate(datasource) + .execute( + """ + CREATE TABLE shedlock( + name VARCHAR(64),\s + lock_until TIMESTAMP(3) NULL,\s + locked_at TIMESTAMP(3) NULL,\s + locked_by VARCHAR(255),\s + PRIMARY KEY (name) + )"""); return datasource; } } diff --git a/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java index 3e674304b..cd54737f2 100644 --- a/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java +++ b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/ScheduledTasks.java @@ -1,16 +1,14 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; @@ -23,7 +21,5 @@ public class ScheduledTasks { @Scheduled(fixedRate = 1) @SchedulerLock(name = "reportCurrentTime", lockAtLeastFor = "${lock.at.most.for}") - public void reportCurrentTime() { - - } + public void reportCurrentTime() {} } diff --git a/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java new file mode 100644 index 000000000..3beb4c4ea --- /dev/null +++ b/spring/test/shedlock-testng-test/src/main/java/net/javacrumbs/shedlock/test/boot/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package net.javacrumbs.shedlock.test.boot; + +import net.javacrumbs.shedlock.support.annotation.NonNullApi; +import net.javacrumbs.shedlock.support.annotation.NonNullFields; diff --git a/spring/test/shedlock-testng-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java b/spring/test/shedlock-testng-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java index 9902f0d82..78a20d5d5 100644 --- a/spring/test/shedlock-testng-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java +++ b/spring/test/shedlock-testng-test/src/test/java/net/javacrumbs/shedlock/test/boot/IntegrationTest.java @@ -1,30 +1,27 @@ /** * Copyright 2009 the original author or authors. * - * 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 + *

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 + *

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 + *

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 net.javacrumbs.shedlock.test.boot; +import static org.assertj.core.api.Assertions.assertThat; + +import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import javax.sql.DataSource; - -import static org.assertj.core.api.Assertions.assertThat; - @SpringBootTest public class IntegrationTest extends AbstractTestNGSpringContextTests { @Autowired