package org.jvalue.ods.processor;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.base.Objects;
import com.google.inject.Inject;
import org.jvalue.commons.utils.Assert;
import org.jvalue.commons.utils.Cache;
import org.jvalue.commons.utils.ListValueMap;
import org.jvalue.commons.utils.Log;
import org.jvalue.ods.api.processors.ProcessorReferenceChain;
import org.jvalue.ods.api.sources.DataSource;
import org.jvalue.ods.data.AbstractDataSourcePropertyManager;
import org.jvalue.ods.db.DataRepository;
import org.jvalue.ods.db.ProcessorChainReferenceRepository;
import org.jvalue.ods.db.RepositoryFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public final class ProcessorChainManager extends AbstractDataSourcePropertyManager<ProcessorReferenceChain, ProcessorChainReferenceRepository> {
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final Map<ProcessorKey, ScheduledFuture<?>> scheduledTasks = new HashMap<>(); // reoccurring tasks to stop if necessary
private final ListValueMap<DataSource, ProcessorReferenceChain> runningTasks = new ListValueMap<>(); // all running tasks
private final Timer processorTimer;
private final ProcessorChainFactory processorChainFactory;
@Inject
ProcessorChainManager(
ProcessorChainFactory processorChainFactory,
Cache<ProcessorChainReferenceRepository> referenceRepositoryCache,
RepositoryFactory repositoryFactory,
MetricRegistry registry) {
super(referenceRepositoryCache, repositoryFactory);
this.processorChainFactory = processorChainFactory;
this.processorTimer = registry.timer(MetricRegistry.name(ProcessorChainManager.class, "processing-total"));
registry.register(MetricRegistry.name(ProcessorChainManager.class, "running-tasks"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return runningTasks.size();
}
});
}
public void executeOnce(DataSource source, DataRepository dataRepository, ProcessorReferenceChain reference) {
Assert.assertTrue(reference.getExecutionInterval() == null, "reference must not contain an execute interval");
startProcessorChain(source, dataRepository, reference);
}
@Override
protected void doAdd(DataSource source, DataRepository dataRepository, ProcessorReferenceChain reference) {
Assert.assertNotNull(reference.getExecutionInterval());
startProcessorChain(source, dataRepository, reference);
}
@Override
protected void doRemove(DataSource source, DataRepository dataRepository, ProcessorReferenceChain reference) {
stopProcessorChain(source, reference);
}
@Override
protected void doRemoveAll(DataSource source) {
for (ProcessorReferenceChain reference : getAll(source)) stopProcessorChain(source, reference);
}
@Override
protected ProcessorChainReferenceRepository createNewRepository(String sourceId, RepositoryFactory repositoryFactory) {
return repositoryFactory.createFilterChainReferenceRepository(sourceId);
}
/**
* Called once during lifecycle initialization.
* @param sources All sources including their data repositories to create the actual
* filter chain and start them.
*/
public void startAllProcessorChains(Map<DataSource, DataRepository> sources) {
for (Map.Entry<DataSource, DataRepository> sourceEntry : sources.entrySet()) {
// start chain
for (ProcessorReferenceChain reference : getAll(sourceEntry.getKey())) {
startProcessorChain(sourceEntry.getKey(), sourceEntry.getValue(), reference);
}
}
}
/**
* Called once at lifecycle end.
*/
public void stopAllProcessorChains() {
executorService.shutdown();
}
public ListValueMap<DataSource, ProcessorReferenceChain> getAllRunningTasks() {
return runningTasks;
}
private void startProcessorChain(DataSource source, DataRepository dataRepository, ProcessorReferenceChain reference) {
ProcessorKey key = new ProcessorKey(source.getId(), reference.getId());
Runnable runnable = new ProcessorRunnable(reference, source, dataRepository);
if (reference.getExecutionInterval() != null) {
System.out.println("inserting key (" + key.hashCode() + ")");
ScheduledFuture<?> task = executorService.scheduleAtFixedRate(
runnable,
0,
reference.getExecutionInterval().getPeriod(),
reference.getExecutionInterval().getUnit());
scheduledTasks.put(key, task);
} else {
executorService.execute(runnable);
}
}
private void stopProcessorChain(DataSource source, ProcessorReferenceChain reference) {
ProcessorKey key = new ProcessorKey(source.getId(), reference.getId());
System.out.println("removing key (" + key.hashCode() + ")");
ScheduledFuture<?> task = scheduledTasks.remove(key);
task.cancel(false);
runningTasks.remove(source, reference);
}
private final class ProcessorRunnable implements Runnable {
private final ProcessorReferenceChain reference;
private final DataSource source;
private final DataRepository dataRepository;
public ProcessorRunnable(ProcessorReferenceChain reference, DataSource source, DataRepository dataRepository) {
this.reference = reference;
this.source = source;
this.dataRepository = dataRepository;
}
@Override
public void run() {
Log.info("starting processor chain \"" + reference.getId() + "\" for source \"" + source.getId() + "\"");
runningTasks.add(source, reference);
Timer.Context timerContext = processorTimer.time();
try {
processorChainFactory.createProcessorChain(reference, source, dataRepository).startProcessing();
} catch (Throwable throwable) {
Log.error("error while running processor chain", throwable);
} finally {
timerContext.stop();
runningTasks.remove(source, reference);
Log.info("stopping processor chain \"" + reference.getId() + "\" for source \"" + source.getId() + "\"");
}
}
}
private static final class ProcessorKey {
private final String dataSourceId, processorChainId;
public ProcessorKey(String dataSourceId, String processorChainId) {
this.dataSourceId = dataSourceId;
this.processorChainId = processorChainId;
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof ProcessorKey)) return false;
if (other == this) return true;
ProcessorKey key = (ProcessorKey) other;
return Objects.equal(dataSourceId, key.dataSourceId)
&& Objects.equal(processorChainId, key.processorChainId);
}
@Override
public int hashCode() {
return Objects.hashCode(dataSourceId, processorChainId);
}
}
}