package de.otto.edison.mongo.jobs; import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.StreamSupport.stream; import static com.mongodb.client.model.Filters.and; import static com.mongodb.client.model.Filters.eq; import static com.mongodb.client.model.Filters.exists; import static com.mongodb.client.model.Updates.set; import static com.mongodb.client.model.Updates.unset; import java.util.Map; import java.util.Set; import org.bson.Document; import org.bson.conversions.Bson; import com.mongodb.BasicDBObject; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.FindOneAndUpdateOptions; import de.otto.edison.jobs.domain.JobMeta; import de.otto.edison.jobs.repository.JobMetaRepository; /** * {@inheritDoc} * <p> * MongoDB implementation of the JobMetaRepository. * * </p> */ public class MongoJobMetaRepository implements JobMetaRepository { private static final FindOneAndUpdateOptions UPSERT = new FindOneAndUpdateOptions().upsert(true); private static final String ID = "_id"; private static final String KEY_DISABLED = "_e_disabled"; private static final String KEY_RUNNING = "_e_running"; private final MongoCollection<Document> collection; public MongoJobMetaRepository(final MongoDatabase mongoDatabase, final String jobMetaCollectionName) { this.collection = mongoDatabase.getCollection(jobMetaCollectionName); } @Override public JobMeta getJobMeta(final String jobType) { final Document document = collection.find(eq(ID, jobType)).first(); if (document != null) { final Map<String, String> meta = document.keySet() .stream() .filter(key -> !key.startsWith("_e_") && !key.equals(ID)) .collect(toMap( key -> key, document::getString )); final boolean isRunning = document.containsKey(KEY_RUNNING); final boolean isDisabled = document.containsKey(KEY_DISABLED); final String comment = document.getString(KEY_DISABLED); return new JobMeta(jobType, isRunning, isDisabled, comment, meta); } else { return new JobMeta(jobType, false, false, "", emptyMap()); } } @Override public boolean setRunningJob(final String jobType, final String jobId) { return createValue(jobType, KEY_RUNNING, jobId); } @Override public String getRunningJob(final String jobType) { return getValue(jobType, KEY_RUNNING); } /** * Clears the job running mark of the jobType. Does nothing if not mark exists. * * @param jobType the job type */ @Override public void clearRunningJob(final String jobType) { setValue(jobType, KEY_RUNNING, null); } /** * Disables a job type, i.e. prevents it from being started * * @param jobType the disabled job type * @param comment an optional comment */ @Override public void disable(final String jobType, final String comment) { setValue(jobType, KEY_DISABLED, comment != null ? comment : ""); } /** * Reenables a job type that was disabled * * @param jobType the enabled job type */ @Override public void enable(final String jobType) { setValue(jobType, KEY_DISABLED, null); } @Override public String setValue(final String jobType, final String key, final String value) { final Document previous; if (value != null) { previous = collection.findOneAndUpdate(eq(ID, jobType), set(key, value), UPSERT); } else { previous = collection.findOneAndUpdate(eq(ID, jobType), unset(key), UPSERT); } return previous != null ? previous.getString("key") : null; } @Override public String getValue(final String jobType, final String key) { final Document first = collection.find(eq(ID, jobType)).first(); return first != null ? first.getString(key) : null; } @Override public boolean createValue(final String jobType, final String key, final String value) { final Bson filter = and( eq(ID, jobType), exists(key, false)); final Bson update = set(key, value); try { final Document previous = collection.findOneAndUpdate(filter, update, new FindOneAndUpdateOptions().upsert(true)); return previous == null || previous.getString(key) == null; } catch (final Exception e) { return false; } } /** * Returns all job types having state information. * * @return set containing job types. */ @Override public Set<String> findAllJobTypes() { return stream(collection.find().spliterator(), false) .map(doc -> doc.getString(ID)) .collect(toSet()); } @Override public void deleteAll() { collection.deleteMany(new BasicDBObject()); } @Override public String toString() { return "MongoJobMetaRepository"; } }