All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.javacrumbs.shedlock.provider.elasticsearch8.ElasticsearchLockProvider Maven / Gradle / Ivy

The newest version!
/**
 * 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.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; 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; import net.javacrumbs.shedlock.support.annotation.NonNull; import org.elasticsearch.client.ResponseException; /** * 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(@NonNull ElasticsearchClient client, @NonNull String index) { this.client = client; this.hostname = getHostname(); this.index = index; } public ElasticsearchLockProvider(@NonNull ElasticsearchClient client) { this(client, SCHEDLOCK_DEFAULT_INDEX); } @Override @NonNull public Optional lock(@NonNull 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(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) || (e instanceof ResponseException && ((ResponseException) e) .getResponse() .getStatusLine() .getStatusCode() == 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("ctx._source.lockUntil = params.unlockTime") .params(lockObject))); client.update(updateRequest, Lock.class); } catch (IOException | ElasticsearchException e) { throw new LockException("Unexpected exception occurred", e); } } } private static final class Lock { private final String name; private final String lockedBy; private final long lockedAt; private final long lockUntil; public Lock(String name, String lockedBy, Instant lockedAt, Instant lockUntil) { this.name = name; this.lockedBy = lockedBy; this.lockedAt = lockedAt.toEpochMilli(); this.lockUntil = lockUntil.toEpochMilli(); } public String getName() { return name; } public String getLockedBy() { return lockedBy; } public long getLockedAt() { return lockedAt; } public long getLockUntil() { return lockUntil; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy