/* * 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.input; 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.model.Input; import io.qdb.server.queue.QueueManager; import io.qdb.server.repo.Repository; 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 input has a corresponding {@link InputJob} instance running to fetch messages. * Detects changes to inputs by listening to repository events. */ @Singleton public class InputManager implements Closeable, Thread.UncaughtExceptionHandler { private static final Logger log = LoggerFactory.getLogger(InputManager.class); private final Repository repo; private final QueueManager queueManager; private final InputHandlerFactory handlerFactory; private final JsonService jsonService; private final Map<String, InputJob> jobs = new ConcurrentHashMap<String, InputJob>(); // input id -> job private final ExecutorService pool; @Inject public InputManager(EventBus eventBus, Repository repo, QueueManager queueManager, InputHandlerFactory handlerFactory, JsonService jsonService) throws IOException { this.repo = repo; this.queueManager = queueManager; this.handlerFactory = handlerFactory; this.jsonService = jsonService; this.pool = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder().setNameFormat("input-%d").setUncaughtExceptionHandler(this).build()); eventBus.register(this); for (Input input : this.repo.findInputs(0, -1)) inputChanged(input); } @Override public void close() throws IOException { List<InputJob> busy = new ArrayList<InputJob>(jobs.values()); for (InputJob job : busy) job.stop(); pool.shutdownNow(); } @Subscribe public void handleRepoEvent(Repository.ObjectEvent ev) { if (ev.value instanceof Input) { Input input = (Input) ev.value; if (ev.type == Repository.ObjectEvent.Type.DELETED) { synchronized (this) { InputJob job = jobs.remove(input.getId()); if (job != null) job.stop(); } } else { inputChanged(input); } } } private synchronized void inputChanged(Input in) { String inputId = in.getId(); InputJob existing = jobs.get(inputId); if (existing != null) { existing.inputChanged(in); // this will cause job to restart if it didn't make the change return; } if (!in.isEnabled()) return; InputJob job = new InputJob(this, handlerFactory, queueManager, repo, jsonService, inputId); jobs.put(inputId, job); pool.execute(job); } void onInputJobExit(InputJob job) { jobs.remove(job.getInputId()); } public ExecutorService getPool() { return pool; } @Override public void uncaughtException(Thread t, Throwable e) { log.error(e.toString(), e); } }