/* * Copyright 2013 David Tinker * * 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 io.qdb.server.output; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.qdb.server.controller.JsonService; import io.qdb.server.filter.MessageFilterFactory; import io.qdb.server.model.Output; import io.qdb.server.repo.Repository; import io.qdb.server.queue.QueueManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.*; /** * Ensures that each enabled output has a corresponding {@link OutputJob} instance running to process its queue. * Detects changes to outputs by listening to repository events. */ @Singleton public class OutputManager implements Closeable, Thread.UncaughtExceptionHandler { private static final Logger log = LoggerFactory.getLogger(OutputManager.class); private final Repository repo; private final QueueManager queueManager; private final OutputHandlerFactory handlerFactory; private final MessageFilterFactory messageFilterFactory; private final JsonService jsonService; private final Map<String, OutputJob> jobs = new ConcurrentHashMap<String, OutputJob>(); // output id -> job private final ExecutorService pool; @Inject public OutputManager(EventBus eventBus, Repository repo, QueueManager queueManager, OutputHandlerFactory handlerFactory, MessageFilterFactory messageFilterFactory, JsonService jsonService) throws IOException { this.repo = repo; this.queueManager = queueManager; this.handlerFactory = handlerFactory; this.messageFilterFactory = messageFilterFactory; this.jsonService = jsonService; this.pool = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder().setNameFormat("output-%d").setUncaughtExceptionHandler(this).build()); eventBus.register(this); for (Output output : this.repo.findOutputs(0, -1)) outputChanged(output); } @Override public void close() throws IOException { List<OutputJob> busy = new ArrayList<OutputJob>(jobs.values()); for (OutputJob job : busy) job.stop(); pool.shutdownNow(); } @Subscribe public void handleRepoEvent(Repository.ObjectEvent ev) { if (ev.value instanceof Output) { Output output = (Output) ev.value; if (ev.type == Repository.ObjectEvent.Type.DELETED) { synchronized (this) { OutputJob job = jobs.remove(output.getId()); if (job != null) job.stop(); } } else { outputChanged(output); } } } private synchronized void outputChanged(Output o) { String oid = o.getId(); OutputJob existing = jobs.get(oid); if (existing != null) { existing.outputChanged(o); // this will cause job to restart if it didn't make the change return; } if (!o.isEnabled()) return; OutputJob job = new OutputJob(this, handlerFactory, messageFilterFactory, queueManager, repo, jsonService, oid); jobs.put(oid, job); pool.execute(job); } void onOutputJobExit(OutputJob job) { jobs.remove(job.getOid()); } @Override public void uncaughtException(Thread t, Throwable e) { log.error(e.toString(), e); } }