/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.util.impl; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.LoggerFactory; import org.slf4j.ext.XLogger; import ddf.catalog.source.Source; /** * The poller to check the availability of all configured sources. This class is instantiated by the * CatalogFramework's blueprint and is scheduled by the {@link SourcePoller} to execute at a fixed * rate, i.e., the polling interval. * * This class maintains a list of all of the sources to be polled for their availability. Sources * are added to this list when they come online and when they are deleted. A cached map is * maintained of all the sources and their last availability states. * */ public class SourcePollerRunner implements Runnable { private static final XLogger LOGGER = new XLogger( LoggerFactory.getLogger(SourcePollerRunner.class)); private List<Source> sources; private Map<Source, CachedSource> cachedSources = new ConcurrentHashMap<Source, CachedSource>(); private ExecutorService pool; private Map<Source, Lock> sourceStatusThreadLocks = new ConcurrentHashMap<Source, Lock>(); /** * Creates an empty list of {@link Source} sources to be polled for availability. This * constructor is invoked by the CatalogFramework's blueprint. */ public SourcePollerRunner() { LOGGER.info("Creating source poller runner."); sources = new CopyOnWriteArrayList<Source>(); } /** * Checks the availability of each source in the list of sources to be polled. */ @Override public void run() { LOGGER.trace("RUNNER checking source statuses"); for (Source source : sources) { if (source != null) { checkStatus(source); } } } /** * Checks if the specified source is available, updating the internally maintained map of * sources and their status. Lock ensures only one status thread is running per source. * * @param source * the source to check if it is available */ private void checkStatus(final Source source) { if (pool == null) { pool = Executors.newCachedThreadPool(); } final Runnable statusRunner = new Runnable() { public void run() { final CachedSource cachedSource = cachedSources.get(source); if (cachedSource != null) { Lock sourceStatusThreadLock = sourceStatusThreadLocks.get(source); if (sourceStatusThreadLock.tryLock()) { LOGGER.debug("Acquired lock for Source [{}] with id [{}]", source, source.getId()); try { cachedSource.checkStatus(); } finally { // release the lock acquired initially sourceStatusThreadLock.unlock(); LOGGER.debug("Released lock for Source [{}] with id [{}]", source, source.getId()); } } else { LOGGER.debug("Unable to get lock for Source [{}] with id [{}]." + " A status thread is already running.", source, source.getId()); } } } }; pool.execute(statusRunner); } /** * Adds the {@link Source} instance to the list and sets its current status to UNCHECKED, * indicating it will checked at the next polling interval. * * @param source * the source to add to the list */ public void bind(Source source) { LOGGER.info("Binding source: {}", source); if (source != null) { LOGGER.debug("Marking new source {} as UNCHECKED.", source); sources.add(source); sourceStatusThreadLocks.put(source, new ReentrantLock()); cachedSources.put(source, new CachedSource(source)); checkStatus(source); } } /** * Removes the {@link Source} instance from the list of references so that its availability is * no longer polled. * * @param source * the source to remove from the list */ public void unbind(Source source) { LOGGER.info("Unbinding source [{}]", source); if (source != null) { cachedSources.remove(source); sources.remove(source); sourceStatusThreadLocks.remove(source); } } /** * Retrieves a {@link CachedSource} which contains cached values from the * specified {@link Source}. Returns a {@link Source} with values from the * last polling interval. If the {@link Source} is not known, null is returned. * * @param source * the source to get the {@link CachedSource} for * * @return a {@link CachedSource} which contains cached values */ public CachedSource getCachedSource(Source source) { return cachedSources.get(source); } /** * * Calls the @link ExecutorService to shutdown immediately */ public void shutdown() { LOGGER.trace("Shutting down status threads"); if (pool != null) { pool.shutdownNow(); } LOGGER.trace("Status threads shut down"); } }