/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.onewire.internal.scheduler; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.openhab.binding.onewire.internal.listener.OneWireDevicePropertyWantsUpdateListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is the central class that takes care of the refreshing (cyclical reading) from the 1-Wire bus. * * @author Dennis Riegelbauer * @since 1.7.0 * */ public class OneWireUpdateScheduler { private static final Logger logger = LoggerFactory.getLogger(OneWireUpdateScheduler.class); /** * Number of threads executing in parallel for auto refresh feature. Default value is <code>5</code> */ private static int cvNumberOfThreads = 5; /** * Time in seconds to wait for an orderly shutdown of the auto refresher feature. Default value is <code>5</code> */ private static int cvScheduledExecutorServiceShutdownTimeout = 5; /** * list with items which wants to be updated */ private final BlockingQueue<String> ivUpdateQueue = new LinkedBlockingQueue<String>(); /** * map with scheduled items */ private static Map<Integer, List<String>> cvScheduleMap = new ConcurrentHashMap<Integer, List<String>>(); /** * Executor, which handles AutoRefreshTaks (Threads) */ private ScheduledExecutorService ivScheduledExecutorService; /** * is this scheduler running */ private boolean ivIsRunning = false; /** * this task runs in an separate Thread. It informs the Listeners, that an item wants to get updated */ private OneWireUpdateTask ivOneWireUpdateTask = null; /** * @param pvWantsUpdateListener */ public OneWireUpdateScheduler(OneWireDevicePropertyWantsUpdateListener pvWantsUpdateListener) { super(); ivOneWireUpdateTask = new OneWireUpdateTask(ivUpdateQueue, pvWantsUpdateListener); } /** * Starts the scheduler */ public void start() { logger.debug("Starting auto refresh scheduler"); logger.debug("Starting reader task."); ivOneWireUpdateTask.start(); logger.debug("Starting schedule executor."); ivScheduledExecutorService = Executors.newScheduledThreadPool(cvNumberOfThreads); ivIsRunning = true; } /** * Stop the scheduler */ public void stop() { logger.debug("Stopping auto refresh scheduler"); logger.debug("Clearing all items from the refresher queue"); ivUpdateQueue.clear(); logger.debug("Terminating schedule executor."); ivScheduledExecutorService.shutdown(); try { if (ivScheduledExecutorService.awaitTermination(cvScheduledExecutorServiceShutdownTimeout, TimeUnit.SECONDS)) { logger.debug("Auto refresh scheduler successfully terminated"); } else { logger.debug("Auto refresh scheduler couldn't be terminated and termination timed out."); } } catch (InterruptedException e) { logger.debug("Auto refresh scheduler: interrupted while waiting for termination."); } logger.debug("Stopping reader task"); ivOneWireUpdateTask.interrupt(); ivIsRunning = false; } /** * Clears all items from the scheduler */ public synchronized void clear() { logger.debug("Clearing all items from auto refresh scheduler"); ivUpdateQueue.clear(); // Restarting schedule executor if (ivScheduledExecutorService != null) { logger.debug("Schedule executor restart."); ivScheduledExecutorService.shutdown(); try { if (ivScheduledExecutorService.awaitTermination(cvScheduledExecutorServiceShutdownTimeout, TimeUnit.SECONDS)) { logger.debug("Schedule executor restart: successfully terminated old instance"); } else { logger.debug("Schedule executor restart failed: termination timed out."); } } catch (InterruptedException e) { logger.debug("Schedule executor restart failed: interrupted while waiting for termination."); } ivScheduledExecutorService = Executors.newScheduledThreadPool(cvNumberOfThreads); logger.debug("Schedule executor restart: started."); } for (Iterator<Integer> lvIterator = cvScheduleMap.keySet().iterator(); lvIterator.hasNext();) { int autoRefreshTimeInSecs = lvIterator.next(); List<String> lvItemListe = cvScheduleMap.get(autoRefreshTimeInSecs); synchronized (lvItemListe) { logger.debug("Clearing list {}", autoRefreshTimeInSecs); lvItemListe.clear(); } logger.debug("Removing list {} from scheduler", autoRefreshTimeInSecs); lvIterator.remove(); } } /** * Schedules immediate and one-time reading of a <code>item</code>. * * @param pvItemName * the <code>item</code> to read * @return false if the item is null. */ public synchronized boolean updateOnce(String pvItemName) { if (pvItemName == null) { logger.error("Argument itemName cannot be null"); return false; } logger.debug("Item '{}': one time reading scheduled.", pvItemName); return ivUpdateQueue.add(pvItemName); } /** * Schedules a <code>item</code> to be cyclicly read. When parameter <code>autoRefreshTimeInSecs</code> is 0 then * calling ths method is equal to calling <link>readOnce</link>. * * @param bindingConfig * the <code>OneWireBindingConfig</code> to be read * @param ovautoRefreshTimeInSecs * time in seconds specifying the reading cycle. 0 is equal to calling <link>readOnce</link> * @return true if the OneWireBindingProvider was scheduled for reading, false in all other cases */ public synchronized boolean scheduleUpdate(String pvItemName, int pvAutoRefreshTimeInSecs) { if (pvItemName == null) { logger.error("Argument itemName cannot be null"); return false; } if (pvAutoRefreshTimeInSecs < 0) { logger.debug("AutoRefreshTimeInSecs must be >= 0 for itemName '{}'", pvItemName); return false; } // Check if item is already present in another list and if so, remove it int lvOldListNumber = getAutoRefreshTimeInSecs(pvItemName); if (lvOldListNumber > 0) { if (lvOldListNumber == pvAutoRefreshTimeInSecs) { logger.debug("item '{}' was already in auto refresh list {}", pvItemName, pvAutoRefreshTimeInSecs); return true; } List<String> lvOldList = cvScheduleMap.get(lvOldListNumber); synchronized (lvOldList) { logger.debug("item '{}' already present in different list: {}, removing", pvItemName, lvOldListNumber); /* * The simple method to remove a <code>item</code> from a list would be * <code>itemListe.remove(item)</code> Unfortunately, this cannot be used as the * <code>item.equals()</code> method is comparing objects and sometimes new objects are being created * for example when a configuration file is reread. */ for (Iterator<String> lvIterator = lvOldList.iterator(); lvIterator.hasNext();) { String lvItemNameOldList = lvIterator.next(); if (lvItemNameOldList.toString().equals(pvItemName)) { lvIterator.remove(); } } } } // Check if we have a list for autoRefreshTimeInSecs. If not create it. if (!cvScheduleMap.containsKey(pvAutoRefreshTimeInSecs)) { logger.debug("Creating auto refresh list: {}.", pvAutoRefreshTimeInSecs); cvScheduleMap.put(pvAutoRefreshTimeInSecs, new LinkedList<String>()); if (ivIsRunning) { // Start scheduled task for the new time logger.debug("Starting auto refresh cycle {}", pvAutoRefreshTimeInSecs); ivScheduledExecutorService.scheduleAtFixedRate(new AutoRefreshTask(pvAutoRefreshTimeInSecs), pvAutoRefreshTimeInSecs, pvAutoRefreshTimeInSecs, TimeUnit.SECONDS); } } // Add the item to the list List<String> lvItemListe = cvScheduleMap.get(pvAutoRefreshTimeInSecs); synchronized (lvItemListe) { logger.debug("Adding item '{}' to auto refresh list {}.", pvItemName, pvAutoRefreshTimeInSecs); return lvItemListe.add(pvItemName); } } /** * Returns the auto refresh time in seconds for <code>item</code>. If the item is not present <code>0</code> is * returned. * * @param item * the item to check * @return the auto refresh time in seconds if item was added previously. <code>0</code> otherwise. */ private synchronized int getAutoRefreshTimeInSecs(String pvItemName) { for (int lvNumber : cvScheduleMap.keySet()) { List<String> lvItemListe = cvScheduleMap.get(lvNumber); synchronized (lvItemListe) { /* * The simple method to see if a <code>item</code> is already in the list would be * <code>itemListe.contains(item)</code> Unfortunately, this cannot be used as the item.equals() method * is comparing objects and sometimes new objects are being created for example when a configuration * file is reread. */ for (String lvTempItemName : lvItemListe) { if (lvTempItemName.toString().equals(pvItemName)) { return lvNumber; } } } } return 0; } /** * Removes scheduled item * * @param pvItemName */ public synchronized void removeItem(String pvItemName) { for (int lvNumber : cvScheduleMap.keySet()) { List<String> lvItemListe = cvScheduleMap.get(lvNumber); if (lvItemListe.contains(pvItemName)) { logger.debug("remove item={} from scheduler!", pvItemName); lvItemListe.remove(pvItemName); } } } /** * This Taks fills the ivUpdateQueue with items, which must be updated. * The ivUpdateQueue is used by OneWireUpdateTask to inform the Listeners about theses items * * @author Dennis Riegelbauer * @since 1.7.0 * */ private final class AutoRefreshTask implements Runnable { private int ivAutoRefreshTimeInSecs = 0; public AutoRefreshTask(int pvAutoRefreshTimeInSecs) { this.ivAutoRefreshTimeInSecs = pvAutoRefreshTimeInSecs; } @Override public void run() { List<String> lvItemNameList = null; synchronized (cvScheduleMap) { lvItemNameList = cvScheduleMap.get(ivAutoRefreshTimeInSecs); if (lvItemNameList == null) { logger.debug("Autorefresh: List {} was deleted. Terminating thread.", ivAutoRefreshTimeInSecs); } else { logger.debug("Autorefresh: Adding {} item(s) with refresh time {} to reader queue.", lvItemNameList.size(), ivAutoRefreshTimeInSecs); logger.debug("Update Task isAlive: {}", ivOneWireUpdateTask.isAlive()); if (!ivOneWireUpdateTask.isAlive()) { logger.debug("create and start a new Update Task again..."); OneWireUpdateTask lvNewOneWireUpdateTask = new OneWireUpdateTask(ivUpdateQueue, ivOneWireUpdateTask.getIvWantsUpdateListener()); ivOneWireUpdateTask = lvNewOneWireUpdateTask; ivOneWireUpdateTask.start(); } synchronized (lvItemNameList) { // increase performance one slower systems on startup // only add items to queue which aren't already in queue for (String lvItemName : lvItemNameList) { if (!ivUpdateQueue.contains(lvItemName)) { logger.debug("add item {} to updateQueue", lvItemName); ivUpdateQueue.add(lvItemName); } else { logger.debug("didn't add item {} to updateQueue; it is already there", lvItemName); } } } } } } } }