package org.dcache.srm.request.sql; import com.google.common.base.Throwables; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.springframework.dao.DataAccessException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.dcache.srm.SRMUserPersistenceManager; import org.dcache.srm.request.BringOnlineFileRequest; import org.dcache.srm.request.BringOnlineRequest; import org.dcache.srm.request.CopyFileRequest; import org.dcache.srm.request.CopyRequest; import org.dcache.srm.request.GetFileRequest; import org.dcache.srm.request.GetRequest; import org.dcache.srm.request.Job; import org.dcache.srm.request.LsFileRequest; import org.dcache.srm.request.LsRequest; import org.dcache.srm.request.PutFileRequest; import org.dcache.srm.request.PutRequest; import org.dcache.srm.request.ReserveSpaceRequest; import org.dcache.srm.scheduler.AsynchronousSaveJobStorage; import org.dcache.srm.scheduler.CanonicalizingJobStorage; import org.dcache.srm.scheduler.ForceOnlyJobStorageDecorator; import org.dcache.srm.scheduler.JobStorage; import org.dcache.srm.scheduler.JobStorageFactory; import org.dcache.srm.scheduler.NoopJobStorage; import org.dcache.srm.scheduler.SchedulerContainer; import org.dcache.srm.scheduler.SharedMemoryCacheJobStorage; import org.dcache.srm.util.Configuration; import org.dcache.srm.util.Configuration.DatabaseParameters; import static com.google.common.base.Preconditions.checkNotNull; public class DatabaseJobStorageFactory extends JobStorageFactory { private final Map<Class<? extends Job>, JobStorage<?>> jobStorageMap = new LinkedHashMap<>(); // JobStorage initialization order is significant to ensure file // requests are cached before container requests are loaded private final Map<Class<? extends Job>, JobStorage<?>> unmodifiableJobStorageMap = Collections.unmodifiableMap(jobStorageMap); private final Map<Class<? extends Job>, DatabaseParameters> configurations = new HashMap<>(); private final ExecutorService executor; private final ScheduledExecutorService scheduledExecutor; private <J extends Job> void add(DatabaseParameters config, Class<J> entityClass, Supplier<JobStorage<J>> storageFactory) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, DataAccessException { JobStorage<J> js; if (config.isDatabaseEnabled()) { js = storageFactory.get(); js = new AsynchronousSaveJobStorage<>(js, executor); if (config.getStoreCompletedRequestsOnly()) { js = new ForceOnlyJobStorageDecorator<>(js); } } else { js = new NoopJobStorage<>(); } jobStorageMap.put(entityClass, new CanonicalizingJobStorage<>(new SharedMemoryCacheJobStorage<>(js, entityClass), entityClass)); configurations.put(entityClass, config); } public DatabaseJobStorageFactory(Configuration config, SRMUserPersistenceManager manager) throws DataAccessException, IOException { checkNotNull(manager); executor = new ThreadPoolExecutor( config.getJdbcExecutionThreadNum(), config.getJdbcExecutionThreadNum(), 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(config.getMaxQueuedJdbcTasksNum()), new ThreadFactoryBuilder().setNameFormat("srm-db-save-%d").build()); scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setNameFormat("srm-db-gc-%d").build()); try { add(config.getDatabaseParametersForBringOnline(), BringOnlineFileRequest.class, () -> new BringOnlineFileRequestStorage(config.getDatabaseParametersForBringOnline(), scheduledExecutor)); add(config.getDatabaseParametersForBringOnline(), BringOnlineRequest.class, () -> new BringOnlineRequestStorage(config.getDatabaseParametersForBringOnline(), scheduledExecutor, manager)); add(config.getDatabaseParametersForCopy(), CopyFileRequest.class, () -> new CopyFileRequestStorage(config.getDatabaseParametersForCopy(), scheduledExecutor)); add(config.getDatabaseParametersForCopy(), CopyRequest.class, () -> new CopyRequestStorage(config.getDatabaseParametersForCopy(), scheduledExecutor, manager)); add(config.getDatabaseParametersForPut(), PutFileRequest.class, () -> new PutFileRequestStorage(config.getDatabaseParametersForPut(), scheduledExecutor)); add(config.getDatabaseParametersForPut(), PutRequest.class, () -> new PutRequestStorage(config.getDatabaseParametersForPut(), scheduledExecutor, manager)); add(config.getDatabaseParametersForGet(), GetFileRequest.class, () -> new GetFileRequestStorage(config.getDatabaseParametersForGet(), scheduledExecutor)); add(config.getDatabaseParametersForGet(), GetRequest.class, () -> new GetRequestStorage(config.getDatabaseParametersForGet(), scheduledExecutor, manager)); add(config.getDatabaseParametersForList(), LsFileRequest.class, () -> new LsFileRequestStorage(config.getDatabaseParametersForList(), scheduledExecutor)); add(config.getDatabaseParametersForList(), LsRequest.class, () -> new LsRequestStorage(config.getDatabaseParametersForList(), scheduledExecutor, manager)); add(config.getDatabaseParametersForReserve(), ReserveSpaceRequest.class, () -> new ReserveSpaceRequestStorage(config.getDatabaseParametersForReserve(), scheduledExecutor, manager)); } catch (InstantiationException e) { Throwables.propagateIfPossible(e.getCause(), IOException.class); throw new RuntimeException("Request persistence initialization failed: " + e.toString(), e); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException("Request persistence initialization failed: " + e.toString(), e); } } public void init() throws DataAccessException { for (JobStorage<?> jobStorage : jobStorageMap.values()) { jobStorage.init(); } } public void restoreJobsOnSrmStart(SchedulerContainer schedulers) { for (Map.Entry<Class<? extends Job>, JobStorage<?>> entry : jobStorageMap.entrySet()) { DatabaseParameters config = configurations.get(entry.getKey()); Set<? extends Job> jobs = entry.getValue().getActiveJobs(); schedulers.restoreJobsOnSrmStart(jobs, config.isCleanPendingRequestsOnRestart()); } } public void shutdown() { scheduledExecutor.shutdown(); executor.shutdown(); try { if (scheduledExecutor.awaitTermination(3, TimeUnit.SECONDS)) { executor.awaitTermination(3, TimeUnit.SECONDS); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override @SuppressWarnings("unchecked") public <J extends Job> JobStorage<J> getJobStorage(J job) { return getJobStorage((Class<J>) job.getClass()); } @Override @SuppressWarnings("unchecked") public <J extends Job> JobStorage<J> getJobStorage(Class<? extends J> jobClass) { JobStorage<J> js = (JobStorage<J>) jobStorageMap.get(jobClass); if (js == null) { throw new UnsupportedOperationException( "JobStorage for class " + jobClass + " is not supported"); } return js; } @Override public Map<Class<? extends Job>, JobStorage<?>> getJobStorages() { return unmodifiableJobStorageMap; } }