package org.dcache.srm.scheduler; import com.google.common.collect.MapMaker; import org.springframework.dao.DataAccessException; import java.sql.Connection; import java.sql.SQLException; import java.util.Set; import java.util.concurrent.ConcurrentMap; import org.dcache.srm.request.Job; import static java.util.stream.Collectors.toSet; /** * Canonicalizing job storage. * * Guarantees that for a given job ID, the same Job instance is returned as long as * that instance is not garbage collected. * * The canonicalizing map is shared among all instances of CanonicalizingJobStorage, * thus Job IDs must be unique over all instances. This has the benefit * that the map allows early detection of job IDs that don't belong to the * decorated job storage and avoid an expensive load from the storage. */ public class CanonicalizingJobStorage<J extends Job> implements JobStorage<J> { private static final ConcurrentMap<Long, Job> map = new MapMaker().weakValues().makeMap(); private final JobStorage<J> storage; private final Class<J> type; public CanonicalizingJobStorage(JobStorage<J> storage, Class<J> type) { this.storage = storage; this.type = type; } private J canonicalize(Long jobId, J job) { if (job != null) { Job other = map.putIfAbsent(jobId, job); if (other != null) { job = type.cast(other); } } return job; } private Set<J> canonicalize(Set<J> jobs) { return jobs.stream().map(job -> canonicalize(job.getId(), job)).collect(toSet()); } @Override public void init() throws DataAccessException { storage.init(); } @Override public J getJob(long jobId) throws DataAccessException { Job job = map.get(jobId); if (job == null) { return canonicalize(jobId, storage.getJob(jobId)); } else if (type.isInstance(job)) { return type.cast(job); } else { return null; } } @Override public J getJob(long jobId, Connection connection) throws SQLException { Job job = map.get(jobId); if (job == null) { return canonicalize(jobId, storage.getJob(jobId, connection)); } else if (type.isInstance(job)) { return type.cast(job); } else { return null; } } @Override public Set<J> getJobs(String scheduler) throws DataAccessException { return canonicalize(storage.getJobs(scheduler)); } @Override public Set<J> getJobs(String scheduler, State state) throws DataAccessException { return canonicalize(storage.getJobs(scheduler, state)); } @Override public void saveJob(J job, boolean force) throws DataAccessException { Job other = map.putIfAbsent(job.getId(), job); if (other != null && other != job) { throw new IllegalStateException("Duplicate job #" + job.getId()); } storage.saveJob(job, force); } @Override public Set<Long> getLatestCompletedJobIds(int maxNum) throws DataAccessException { return storage.getLatestCompletedJobIds(maxNum); } @Override public Set<Long> getLatestDoneJobIds(int maxNum) throws DataAccessException { return storage.getLatestDoneJobIds(maxNum); } @Override public Set<Long> getLatestFailedJobIds(int maxNum) throws DataAccessException { return storage.getLatestFailedJobIds(maxNum); } @Override public Set<Long> getLatestCanceledJobIds(int maxNum) throws DataAccessException { return storage.getLatestCanceledJobIds(maxNum); } @Override public Set<J> getActiveJobs() throws DataAccessException { return canonicalize(storage.getActiveJobs()); } }