/* * Copyright 2011-16 Fraunhofer ISE * * This file is part of OpenMUC. * For more information visit http://www.openmuc.org * * OpenMUC is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenMUC 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenMUC. If not, see <http://www.gnu.org/licenses/>. * */ package org.openmuc.framework.core.datamanager; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.locks.ReentrantLock; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactoryConfigurationError; import org.apache.felix.service.command.CommandProcessor; import org.openmuc.framework.config.ArgumentSyntaxException; import org.openmuc.framework.config.ChannelConfig; import org.openmuc.framework.config.ChannelScanInfo; import org.openmuc.framework.config.ConfigChangeListener; import org.openmuc.framework.config.ConfigService; import org.openmuc.framework.config.ConfigWriteException; import org.openmuc.framework.config.DeviceConfig; import org.openmuc.framework.config.DeviceScanInfo; import org.openmuc.framework.config.DeviceScanListener; import org.openmuc.framework.config.DriverConfig; import org.openmuc.framework.config.DriverInfo; import org.openmuc.framework.config.DriverNotAvailableException; import org.openmuc.framework.config.ParseException; import org.openmuc.framework.config.RootConfig; import org.openmuc.framework.config.ScanException; import org.openmuc.framework.config.ScanInterruptedException; import org.openmuc.framework.config.ServerMapping; import org.openmuc.framework.data.Flag; import org.openmuc.framework.dataaccess.Channel; import org.openmuc.framework.dataaccess.ChannelChangeListener; import org.openmuc.framework.dataaccess.ChannelState; import org.openmuc.framework.dataaccess.DataAccessService; import org.openmuc.framework.dataaccess.DataLoggerNotAvailableException; import org.openmuc.framework.dataaccess.DeviceState; import org.openmuc.framework.dataaccess.LogicalDevice; import org.openmuc.framework.dataaccess.LogicalDeviceChangeListener; import org.openmuc.framework.dataaccess.ReadRecordContainer; import org.openmuc.framework.dataaccess.WriteValueContainer; import org.openmuc.framework.datalogger.spi.DataLoggerService; import org.openmuc.framework.datalogger.spi.LogChannel; import org.openmuc.framework.datalogger.spi.LogRecordContainer; import org.openmuc.framework.driver.spi.ChannelRecordContainer; import org.openmuc.framework.driver.spi.Connection; import org.openmuc.framework.driver.spi.ConnectionException; import org.openmuc.framework.driver.spi.DriverDeviceScanListener; import org.openmuc.framework.driver.spi.DriverService; import org.openmuc.framework.driver.spi.RecordsReceivedListener; import org.openmuc.framework.server.spi.ServerMappingContainer; import org.openmuc.framework.server.spi.ServerService; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import aQute.bnd.annotation.component.Component; @Component(properties = { CommandProcessor.COMMAND_SCOPE + ":String=openmuc", CommandProcessor.COMMAND_FUNCTION + ":String=reload" }, provide = Object.class) public final class DataManager extends Thread implements DataAccessService, ConfigService, RecordsReceivedListener { private final static Logger logger = LoggerFactory.getLogger(DataManager.class); private volatile boolean stopFlag = false; private final HashMap<String, DriverService> newDrivers = new LinkedHashMap<>(); private final HashMap<String, ServerService> serverServices = new HashMap<>(); // does not need to be a list because RemovedService() for driver services // are never called in parallel: private volatile String driverToBeRemovedId = null; private volatile DataLoggerService dataLoggerToBeRemoved = null; final List<Device> connectedDevices = new LinkedList<>(); final List<Device> disconnectedDevices = new LinkedList<>(); final List<Device> connectionFailures = new LinkedList<>(); final List<SamplingTask> samplingTaskFinished = new LinkedList<>(); final List<WriteTask> newWriteTasks = new LinkedList<>(); final List<ReadTask> newReadTasks = new LinkedList<>(); final List<DeviceTask> tasksFinished = new LinkedList<>(); private volatile RootConfigImpl newRootConfigWithoutDefaults = null; private final HashMap<String, DriverService> activeDrivers = new LinkedHashMap<>(); private final List<Action> actions = new LinkedList<>(); private final List<ConfigChangeListener> configChangeListeners = new LinkedList<>(); CountDownLatch dataLoggerRemovedSignal; private final List<DataLoggerService> newDataLoggers = new LinkedList<>(); private final Deque<DataLoggerService> activeDataLoggers = new LinkedBlockingDeque<>(); private final List<List<ChannelRecordContainer>> receivedRecordContainers = new LinkedList<>(); volatile int activeDeviceCountDown; private volatile RootConfigImpl rootConfig; private volatile RootConfigImpl rootConfigWithoutDefaults; private File configFile; ThreadPoolExecutor executor = null; private volatile Boolean dataManagerActivated = false; CountDownLatch driverRemovedSignal; private CountDownLatch newConfigSignal; private final ReentrantLock configLock = new ReentrantLock(); protected void activate(ComponentContext context) throws TransformerFactoryConfigurationError, IOException, ParserConfigurationException, TransformerException, ParseException { logger.info("Activating Data Manager"); NamedThreadFactory namedThreadFactory = new NamedThreadFactory("OpenMUC Data Manager Pool - thread-"); executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(namedThreadFactory); String configFileName = System.getProperty("org.openmuc.framework.channelconfig"); if (configFileName == null) { configFileName = "conf/channels.xml"; } configFile = new File(configFileName); try { rootConfigWithoutDefaults = RootConfigImpl.createFromFile(configFile); } catch (FileNotFoundException e) { // create an empty configuration and store it in a file rootConfigWithoutDefaults = new RootConfigImpl(); rootConfigWithoutDefaults.writeToFile(configFile); logger.info("No configuration file found. Created an empty config file at: {}", configFile.getAbsolutePath()); } catch (ParseException e) { throw new ParseException("Error parsing openMUC config file: " + e.getMessage()); } rootConfig = new RootConfigImpl(); applyConfiguration(rootConfigWithoutDefaults, System.currentTimeMillis()); start(); dataManagerActivated = true; } public void reload() { logger.info("Reload config from file."); try { reloadConfigFromFile(); } catch (FileNotFoundException | ParseException e) { logger.error(e.getMessage()); } } protected void deactivate(ComponentContext context) { logger.info("Deactivating Data Manager"); stopFlag = true; interrupt(); try { this.join(); executor.shutdown(); } catch (InterruptedException e) { } dataManagerActivated = false; } @Override public void run() { setName("OpenMUC Data Manager"); handleInterruptEvent(); while (!stopFlag) { if (interrupted()) { handleInterruptEvent(); continue; } if (actions.isEmpty()) { try { while (true) { Thread.sleep(Long.MAX_VALUE); } } catch (InterruptedException e) { handleInterruptEvent(); continue; } } Action currentAction = actions.get(0); long currentTime = System.currentTimeMillis(); if ((currentTime - currentAction.startTime) > 1000l) { actions.remove(0); logger.error("Action was scheduled for UNIX time " + currentAction.startTime + ". But current time is already " + currentTime + ". Will calculate new action time because the action has timed out. Has the system clock jumped?"); if (currentAction.timeouts != null && !currentAction.timeouts.isEmpty()) { for (SamplingTask samplingTask : currentAction.timeouts) { samplingTask.timeout(); } } if (currentAction.loggingCollections != null) { for (ChannelCollection loggingCollection : currentAction.loggingCollections) { addLoggingCollectionToActions(loggingCollection, loggingCollection.calculateNextActionTime(currentTime)); } } if (currentAction.samplingCollections != null) { for (ChannelCollection samplingCollection : currentAction.samplingCollections) { addSamplingCollectionToActions(samplingCollection, samplingCollection.calculateNextActionTime(currentTime)); } } if (currentAction.connectionRetryDevices != null) { for (Device device : currentAction.connectionRetryDevices) { addReconnectDeviceToActions(device, currentTime + device.deviceConfig.getConnectRetryInterval()); } } continue; } long sleepTime = currentAction.startTime - currentTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException e1) { handleInterruptEvent(); continue; } } actions.remove(0); if (currentAction.timeouts != null && !currentAction.timeouts.isEmpty()) { for (SamplingTask samplingTask : currentAction.timeouts) { samplingTask.timeout(); } } if (currentAction.loggingCollections != null && !currentAction.loggingCollections.isEmpty()) { List<LogRecordContainer> logContainers = new LinkedList<>(); List<ChannelImpl> toRemove = new LinkedList<>(); for (ChannelCollection loggingCollection : currentAction.loggingCollections) { toRemove.clear(); for (ChannelImpl channel : loggingCollection.channels) { if (channel.config.state == ChannelState.DELETED) { toRemove.add(channel); } else if (!channel.config.isDisabled()) { logContainers.add(new LogRecordContainerImpl(channel.config.id, channel.latestRecord)); } } for (ChannelImpl channel : toRemove) { loggingCollection.channels.remove(channel); } if (!loggingCollection.channels.isEmpty()) { addLoggingCollectionToActions(loggingCollection, currentAction.startTime + loggingCollection.interval); } } for (DataLoggerService dataLogger : activeDataLoggers) { dataLogger.log(logContainers, currentAction.startTime); } } if (currentAction.connectionRetryDevices != null && !currentAction.connectionRetryDevices.isEmpty()) { for (Device device : currentAction.connectionRetryDevices) { device.connectRetrySignal(); } } if (currentAction.samplingCollections != null && !currentAction.samplingCollections.isEmpty()) { for (ChannelCollection samplingCollection : currentAction.samplingCollections) { List<ChannelRecordContainerImpl> selectedChannels = new ArrayList<>( samplingCollection.channels.size()); for (ChannelImpl channel : samplingCollection.channels) { selectedChannels.add(channel.createChannelRecordContainer()); } SamplingTask samplingTask = new SamplingTask(this, samplingCollection.device, selectedChannels, samplingCollection.samplingGroup); int timeout = samplingCollection.device.deviceConfig.samplingTimeout; if (samplingCollection.device.addSamplingTask(samplingTask, samplingCollection.interval) && timeout > 0) { addSamplingWorkerTimeoutToActions(samplingTask, currentAction.startTime + samplingCollection.device.deviceConfig.samplingTimeout); } addSamplingCollectionToActions(samplingCollection, currentAction.startTime + samplingCollection.interval); } } } } private void addSamplingCollectionToActions(ChannelCollection channelCollection, long startTimestamp) { Action fittingAction = null; ListIterator<Action> actionIterator = actions.listIterator(); while (actionIterator.hasNext()) { Action currentAction = actionIterator.next(); if (currentAction.startTime == startTimestamp) { fittingAction = currentAction; if (fittingAction.samplingCollections == null) { fittingAction.samplingCollections = new LinkedList<>(); } break; } else if (currentAction.startTime > startTimestamp) { fittingAction = new Action(startTimestamp); fittingAction.samplingCollections = new LinkedList<>(); actionIterator.previous(); actionIterator.add(fittingAction); break; } } if (fittingAction == null) { fittingAction = new Action(startTimestamp); fittingAction.samplingCollections = new LinkedList<>(); actions.add(fittingAction); } fittingAction.samplingCollections.add(channelCollection); channelCollection.action = fittingAction; } private void addLoggingCollectionToActions(ChannelCollection channelCollection, long startTimestamp) { Action fittingAction = null; ListIterator<Action> actionIterator = actions.listIterator(); while (actionIterator.hasNext()) { Action currentAction = actionIterator.next(); if (currentAction.startTime == startTimestamp) { fittingAction = currentAction; if (fittingAction.loggingCollections == null) { fittingAction.loggingCollections = new LinkedList<>(); } break; } else if (currentAction.startTime > startTimestamp) { fittingAction = new Action(startTimestamp); fittingAction.loggingCollections = new LinkedList<>(); actionIterator.previous(); actionIterator.add(fittingAction); break; } } if (fittingAction == null) { fittingAction = new Action(startTimestamp); fittingAction.loggingCollections = new LinkedList<>(); actions.add(fittingAction); } fittingAction.loggingCollections.add(channelCollection); channelCollection.action = fittingAction; } void addReconnectDeviceToActions(Device device, long startTimestamp) { Action fittingAction = null; ListIterator<Action> actionIterator = actions.listIterator(); while (actionIterator.hasNext()) { Action currentAction = actionIterator.next(); if (currentAction.startTime == startTimestamp) { fittingAction = currentAction; if (fittingAction.connectionRetryDevices == null) { fittingAction.connectionRetryDevices = new LinkedList<>(); } break; } else if (currentAction.startTime > startTimestamp) { fittingAction = new Action(startTimestamp); fittingAction.connectionRetryDevices = new LinkedList<>(); actionIterator.previous(); actionIterator.add(fittingAction); break; } } if (fittingAction == null) { fittingAction = new Action(startTimestamp); fittingAction.connectionRetryDevices = new LinkedList<>(); actions.add(fittingAction); } fittingAction.connectionRetryDevices.add(device); } private void addSamplingWorkerTimeoutToActions(SamplingTask readWorker, long timeout) { Action fittingAction = null; ListIterator<Action> actionIterator = actions.listIterator(); while (actionIterator.hasNext()) { Action currentAction = actionIterator.next(); if (currentAction.startTime == timeout) { fittingAction = currentAction; if (fittingAction.timeouts == null) { fittingAction.timeouts = new LinkedList<>(); } break; } else if (currentAction.startTime > timeout) { fittingAction = new Action(timeout); fittingAction.timeouts = new LinkedList<>(); actionIterator.previous(); actionIterator.add(fittingAction); break; } } if (fittingAction == null) { fittingAction = new Action(timeout); fittingAction.timeouts = new LinkedList<>(); actions.add(fittingAction); } fittingAction.timeouts.add(readWorker); } private void handleInterruptEvent() { if (stopFlag) { prepareStop(); return; } long currentTime = 0; if (newRootConfigWithoutDefaults != null) { currentTime = System.currentTimeMillis(); applyConfiguration(newRootConfigWithoutDefaults, currentTime); newRootConfigWithoutDefaults = null; newConfigSignal.countDown(); } synchronized (receivedRecordContainers) { if (receivedRecordContainers.size() != 0) { for (List<ChannelRecordContainer> recordContainers : receivedRecordContainers) { for (ChannelRecordContainer container : recordContainers) { ChannelRecordContainerImpl containerImpl = (ChannelRecordContainerImpl) container; if (containerImpl.channel.config.state == ChannelState.LISTENING) { containerImpl.channel.setNewRecord(containerImpl.record); } } } } receivedRecordContainers.clear(); } synchronized (samplingTaskFinished) { if (!samplingTaskFinished.isEmpty()) { for (SamplingTask samplingTask : samplingTaskFinished) { samplingTask.storeValues(); samplingTask.device.taskFinished(); } samplingTaskFinished.clear(); } } synchronized (tasksFinished) { if (tasksFinished.size() != 0) { for (DeviceTask deviceTask : tasksFinished) { deviceTask.device.taskFinished(); } tasksFinished.clear(); } } synchronized (newDrivers) { if (newDrivers.size() != 0) { // needed to synchronize with getRunningDrivers synchronized (activeDrivers) { activeDrivers.putAll(newDrivers); } for (Entry<String, DriverService> newDriverEntry : newDrivers.entrySet()) { logger.info("Driver registered: " + newDriverEntry.getKey()); DriverConfigImpl driverConfig = rootConfig.driverConfigsById.get(newDriverEntry.getKey()); if (driverConfig != null) { driverConfig.activeDriver = newDriverEntry.getValue(); for (DeviceConfigImpl deviceConfig : driverConfig.deviceConfigsById.values()) { deviceConfig.device.driverRegisteredSignal(); } } } newDrivers.clear(); } } synchronized (newDataLoggers) { if (newDataLoggers.size() != 0) { activeDataLoggers.addAll(newDataLoggers); for (DataLoggerService dataLogger : newDataLoggers) { logger.info("Data logger registered: " + dataLogger.getId()); dataLogger.setChannelsToLog(rootConfig.logChannels); } newDataLoggers.clear(); } } if (driverToBeRemovedId != null) { DriverService removedDriverService; synchronized (activeDrivers) { removedDriverService = activeDrivers.remove(driverToBeRemovedId); } if (removedDriverService == null) { // drivers was removed before it was added to activeDrivers newDrivers.remove(driverToBeRemovedId); driverRemovedSignal.countDown(); } else { DriverConfigImpl driverConfig = rootConfig.driverConfigsById.get(driverToBeRemovedId); if (driverConfig != null) { activeDeviceCountDown = driverConfig.deviceConfigsById.size(); if (activeDeviceCountDown > 0) { // all devices have to be given a chance to finish their current task and disconnect: for (DeviceConfigImpl deviceConfig : driverConfig.deviceConfigsById.values()) { deviceConfig.device.driverDeregisteredSignal(); } synchronized (driverRemovedSignal) { if (activeDeviceCountDown == 0) { driverRemovedSignal.countDown(); } } } else { driverRemovedSignal.countDown(); } } else { driverRemovedSignal.countDown(); } } driverToBeRemovedId = null; } if (dataLoggerToBeRemoved != null) { if (activeDataLoggers.remove(dataLoggerToBeRemoved) == false) { newDataLoggers.remove(dataLoggerToBeRemoved); } dataLoggerToBeRemoved = null; dataLoggerRemovedSignal.countDown(); } synchronized (connectionFailures) { if (connectionFailures.size() != 0) { if (currentTime == 0) { currentTime = System.currentTimeMillis(); } for (Device connectionFailureDevice : connectionFailures) { connectionFailureDevice.connectFailureSignal(currentTime); } connectionFailures.clear(); } } synchronized (connectedDevices) { if (!connectedDevices.isEmpty()) { if (currentTime == 0) { currentTime = System.currentTimeMillis(); } for (Device connectedDevice : connectedDevices) { connectedDevice.connectedSignal(currentTime); } connectedDevices.clear(); } } synchronized (newWriteTasks) { Iterator<WriteTask> writeTaskIter = newWriteTasks.iterator(); while (writeTaskIter.hasNext()) { WriteTask writeTask = writeTaskIter.next(); writeTask.device.addWriteTask(writeTask); writeTaskIter.remove(); } } synchronized (newReadTasks) { if (newReadTasks.size() != 0) { for (ReadTask newReadTask : newReadTasks) { newReadTask.device.addReadTask(newReadTask); } newReadTasks.clear(); } } synchronized (disconnectedDevices) { if (!disconnectedDevices.isEmpty()) { for (Device connectedDevice : disconnectedDevices) { connectedDevice.disconnectedSignal(); } disconnectedDevices.clear(); } } } private void applyConfiguration(RootConfigImpl configWithoutDefaults, long currentTime) { RootConfigImpl newRootConfig = configWithoutDefaults.cloneWithDefaults(); List<LogChannel> logChannels = new LinkedList<>(); for (DriverConfigImpl oldDriverConfig : rootConfig.driverConfigsById.values()) { DriverConfigImpl newDriverConfig = newRootConfig.driverConfigsById.get(oldDriverConfig.id); if (newDriverConfig != null) { newDriverConfig.activeDriver = oldDriverConfig.activeDriver; } for (DeviceConfigImpl oldDeviceConfig : oldDriverConfig.deviceConfigsById.values()) { DeviceConfigImpl newDeviceConfig = null; if (newDriverConfig != null) { newDeviceConfig = newDriverConfig.deviceConfigsById.get(oldDeviceConfig.id); } if (newDeviceConfig == null) { // Device was deleted in new config oldDeviceConfig.device.deleteSignal(); } else { // Device exists in new and old config oldDeviceConfig.device.configChangedSignal(newDeviceConfig, currentTime, logChannels); } } } for (DriverConfigImpl newDriverConfig : newRootConfig.driverConfigsById.values()) { DriverConfigImpl oldDriverConfig = rootConfig.driverConfigsById.get(newDriverConfig.id); if (oldDriverConfig == null) { newDriverConfig.activeDriver = activeDrivers.get(newDriverConfig.id); } for (DeviceConfigImpl newDeviceConfig : newDriverConfig.deviceConfigsById.values()) { DeviceConfigImpl oldDeviceConfig = null; if (oldDriverConfig != null) { oldDeviceConfig = oldDriverConfig.deviceConfigsById.get(newDeviceConfig.id); } if (oldDeviceConfig == null) { // Device is new newDeviceConfig.device = new Device(this, newDeviceConfig, currentTime, logChannels); if (newDeviceConfig.device.getState() == DeviceState.CONNECTING) { newDeviceConfig.device.connectRetrySignal(); } } } } for (ChannelConfigImpl oldChannelConfig : rootConfig.channelConfigsById.values()) { ChannelConfigImpl newChannelConfig = newRootConfig.channelConfigsById.get(oldChannelConfig.getId()); if (newChannelConfig == null) { // oldChannelConfig does not exist in the new configuration if (oldChannelConfig.state == ChannelState.SAMPLING) { removeFromSamplingCollections(oldChannelConfig.channel); } oldChannelConfig.state = ChannelState.DELETED; oldChannelConfig.channel.setFlag(Flag.CHANNEL_DELETED); // note: disabling SampleTasks and such has to be done at the // Device level } } for (DataLoggerService dataLogger : activeDataLoggers) { dataLogger.setChannelsToLog(logChannels); } newRootConfig.logChannels = logChannels; synchronized (configChangeListeners) { rootConfig = newRootConfig; rootConfigWithoutDefaults = configWithoutDefaults; for (final ConfigChangeListener configChangeListener : configChangeListeners) { if (configChangeListener == null) { continue; } executor.execute(new Runnable() { @Override public void run() { configChangeListener.configurationChanged(); } }); } } notifyServers(); } void addToSamplingCollections(ChannelImpl channel, Long time) { ChannelCollection fittingSamplingCollection = null; for (Action action : actions) { if (action.samplingCollections != null) { for (ChannelCollection samplingCollection : action.samplingCollections) { if (samplingCollection.interval == channel.config.samplingInterval && samplingCollection.timeOffset == channel.config.samplingTimeOffset && samplingCollection.samplingGroup.equals(channel.config.samplingGroup) && samplingCollection.device == channel.config.deviceParent.device) { fittingSamplingCollection = samplingCollection; break; } } } } if (fittingSamplingCollection == null) { fittingSamplingCollection = new ChannelCollection(channel.config.samplingInterval, channel.config.samplingTimeOffset, channel.config.samplingGroup, channel.config.deviceParent.device); addSamplingCollectionToActions(fittingSamplingCollection, fittingSamplingCollection.calculateNextActionTime(time)); } if (channel.samplingCollection != null) { if (channel.samplingCollection != fittingSamplingCollection) { removeFromSamplingCollections(channel); } else { return; } } fittingSamplingCollection.channels.add(channel); channel.samplingCollection = fittingSamplingCollection; } void addToLoggingCollections(ChannelImpl channel, Long time) { ChannelCollection fittingLoggingCollection = null; for (Action action : actions) { if (action.loggingCollections != null) { for (ChannelCollection loggingCollection : action.loggingCollections) { if (loggingCollection.interval == channel.config.loggingInterval && loggingCollection.timeOffset == channel.config.loggingTimeOffset) { fittingLoggingCollection = loggingCollection; break; } } } } if (fittingLoggingCollection == null) { fittingLoggingCollection = new ChannelCollection(channel.config.loggingInterval, channel.config.loggingTimeOffset, null, null); addLoggingCollectionToActions(fittingLoggingCollection, fittingLoggingCollection.calculateNextActionTime(time)); } if (channel.loggingCollection != null) { if (channel.loggingCollection != fittingLoggingCollection) { removeFromLoggingCollections(channel); } else { return; } } fittingLoggingCollection.channels.add(channel); channel.loggingCollection = fittingLoggingCollection; } void removeFromLoggingCollections(ChannelImpl channel) { channel.loggingCollection.channels.remove(channel); if (channel.loggingCollection.channels.size() == 0) { channel.loggingCollection.action.loggingCollections.remove(channel.loggingCollection); } channel.loggingCollection = null; } void removeFromSamplingCollections(ChannelImpl channel) { channel.samplingCollection.channels.remove(channel); if (channel.samplingCollection.channels.size() == 0) { channel.samplingCollection.action.samplingCollections.remove(channel.samplingCollection); } channel.samplingCollection = null; } void removeFromConnectionRetry(Device device) { for (Action action : actions) { if (action.connectionRetryDevices != null && action.connectionRetryDevices.remove(device)) { break; } } } protected void setDriverService(DriverService driver) { String driverId = driver.getInfo().getId(); synchronized (newDrivers) { if (activeDrivers.get(driverId) != null || newDrivers.get(driverId) != null) { logger.error("Unable to register driver: a driver with the ID {} is already registered.", driverId); return; } newDrivers.put(driverId, driver); interrupt(); } } /** * Registeres a new ServerService. * * @param serverService * ServerService object to register */ protected void setServerService(ServerService serverService) { String serverId = serverService.getId(); serverServices.put(serverId, serverService); if (dataManagerActivated) { notifyServer(serverService); } logger.info("Registered Server: " + serverId); } /** * Removes a registered ServerService. * * @param serverService * ServerService object to unset */ protected void unsetServerService(ServerService serverService) { serverServices.remove(serverService.getId()); } /** * Updates all ServerServices with mapped channels. */ protected void notifyServers() { if (serverServices != null) { for (ServerService serverService : serverServices.values()) { notifyServer(serverService); } } } /** * Updates a specified ServerService with mapped channels. * * @param serverService * ServerService object to updating */ protected void notifyServer(ServerService serverService) { List<ServerMappingContainer> relatedServerMappings = new ArrayList<>(); for (ChannelConfig config : rootConfig.channelConfigsById.values()) { for (ServerMapping serverMapping : config.getServerMappings()) { if (serverMapping.getId().equals(serverService.getId())) { relatedServerMappings .add(new ServerMappingContainer(this.getChannel(config.getId()), serverMapping)); } } } serverService.serverMappings(relatedServerMappings); } protected void unsetDriverService(DriverService driver) { String driverId = driver.getInfo().getId(); logger.info("Deregistering driver: " + driverId); // note: no synchronization needed here because this function and the // deactivate function are always called sequentially: if (dataManagerActivated) { driverToBeRemovedId = driverId; driverRemovedSignal = new CountDownLatch(1); interrupt(); try { driverRemovedSignal.await(); } catch (InterruptedException e) { } } else { if (activeDrivers.remove(driverId) == null) { newDrivers.remove(driverId); } } logger.info("Driver deregistered: " + driverId); } protected void setDataLoggerService(DataLoggerService dataLogger) { synchronized (newDataLoggers) { newDataLoggers.add(dataLogger); interrupt(); } } protected void unsetDataLoggerService(DataLoggerService dataLogger) { String dataLoggerId = dataLogger.getId(); logger.info("Deregistering data logger: " + dataLoggerId); if (dataManagerActivated) { dataLoggerRemovedSignal = new CountDownLatch(1); dataLoggerToBeRemoved = dataLogger; interrupt(); try { dataLoggerRemovedSignal.await(); } catch (InterruptedException e) { } } else { if (activeDataLoggers.remove(dataLogger) == false) { newDataLoggers.remove(dataLogger); } } logger.info("Data logger deregistered: " + dataLoggerId); } private void prepareStop() { // TODO tell all drivers to stop listening // Do I have to wait for all threads (such as SamplingTasks) to finish? executor.shutdown(); } @Override public Channel getChannel(String id) { ChannelConfigImpl channelConfig = rootConfig.channelConfigsById.get(id); if (channelConfig == null) { return null; } return channelConfig.channel; } @Override public Channel getChannel(String id, ChannelChangeListener channelChangeListener) { // TODO Auto-generated method stub return null; } @Override public List<LogicalDevice> getLogicalDevices(String type) { // TODO Auto-generated method stub return null; } @Override public List<LogicalDevice> getLogicalDevices(String type, LogicalDeviceChangeListener logicalDeviceChangeListener) { // TODO Auto-generated method stub return null; } @Override public void newRecords(List<ChannelRecordContainer> recordContainers) { List<ChannelRecordContainer> recordContainersCopy = new ArrayList<>(recordContainers.size()); for (ChannelRecordContainer container : recordContainers) { recordContainersCopy.add(container.copy()); } synchronized (receivedRecordContainers) { receivedRecordContainers.add(recordContainersCopy); } interrupt(); } @Override public void connectionInterrupted(String driverId, Connection connection) { // TODO synchronize here DriverConfig driverConfig = rootConfig.getDriver(driverId); if (driverConfig == null) { return; } for (DeviceConfig deviceConfig : driverConfig.getDevices()) { if (((DeviceConfigImpl) deviceConfig).device.connection == connection) { Device device = ((DeviceConfigImpl) deviceConfig).device; logger.info("Connection to device " + device.deviceConfig.id + " was interrupted."); device.disconnectedSignal(); return; } } } @Override public void lock() { configLock.lock(); } @Override public boolean tryLock() { return configLock.tryLock(); } @Override public void unlock() { configLock.unlock(); } @Override public RootConfig getConfig() { return rootConfigWithoutDefaults.clone(); } @Override public RootConfig getConfig(ConfigChangeListener listener) { synchronized (configChangeListeners) { for (ConfigChangeListener configChangeListener : configChangeListeners) { if (configChangeListener == listener) { configChangeListeners.remove(configChangeListener); } } configChangeListeners.add(listener); return getConfig(); } } @Override public void stopListeningForConfigChange(ConfigChangeListener listener) { synchronized (configChangeListeners) { for (ConfigChangeListener configChangeListener : configChangeListeners) { if (configChangeListener == listener) { configChangeListeners.remove(configChangeListener); } } } } @Override public void setConfig(RootConfig config) { configLock.lock(); try { RootConfigImpl newConfigCopy = ((RootConfigImpl) config).clone(); setNewConfig(newConfigCopy); } finally { configLock.unlock(); } } @Override public void reloadConfigFromFile() throws FileNotFoundException, ParseException { configLock.lock(); try { RootConfigImpl newConfigCopy = RootConfigImpl.createFromFile(configFile); setNewConfig(newConfigCopy); } finally { configLock.unlock(); } } private void setNewConfig(RootConfigImpl newConfigCopy) { synchronized (this) { newConfigSignal = new CountDownLatch(1); newRootConfigWithoutDefaults = newConfigCopy; interrupt(); } while (true) { try { newConfigSignal.await(); break; } catch (InterruptedException e) { } } } @Override public RootConfig getEmptyConfig() { return new RootConfigImpl(); } @Override public void writeConfigToFile() throws ConfigWriteException { try { rootConfigWithoutDefaults.writeToFile(configFile); } catch (Exception e) { throw new ConfigWriteException(e); } } class BlockingScanListener implements DriverDeviceScanListener { List<DeviceScanInfo> scanInfos = new ArrayList<>(); @Override public void scanProgressUpdate(int progress) { } @Override public void deviceFound(DeviceScanInfo scanInfo) { if (!scanInfos.contains(scanInfo)) { scanInfos.add(scanInfo); } } } @Override public List<DeviceScanInfo> scanForDevices(String driverId, String settings) throws DriverNotAvailableException, UnsupportedOperationException, ArgumentSyntaxException, ScanException, ScanInterruptedException { DriverService driver = activeDrivers.get(driverId); if (driver == null) { throw new DriverNotAvailableException(); } BlockingScanListener blockingScanListener = new BlockingScanListener(); try { driver.scanForDevices(settings, blockingScanListener); } catch (RuntimeException e) { if (e instanceof UnsupportedOperationException) { throw e; } throw new ScanException(e); } return blockingScanListener.scanInfos; } @Override public void scanForDevices(String driverId, String settings, DeviceScanListener scanListener) throws DriverNotAvailableException { DriverService driver = activeDrivers.get(driverId); if (driver == null) { throw new DriverNotAvailableException(); } executor.execute(new ScanForDevicesTask(driver, settings, scanListener)); } @Override public void interruptDeviceScan(String driverId) throws DriverNotAvailableException, UnsupportedOperationException { DriverService driver = activeDrivers.get(driverId); if (driver == null) { throw new DriverNotAvailableException(); } driver.interruptDeviceScan(); } @Override public List<ChannelScanInfo> scanForChannels(String deviceId, String settings) throws DriverNotAvailableException, UnsupportedOperationException, ArgumentSyntaxException, ScanException { // TODO this function is probably not thread safe DeviceConfigImpl config = (DeviceConfigImpl) this.rootConfig.getDevice(deviceId); if (config == null) { throw new ScanException("No device with ID \"" + deviceId + "\" found."); } DriverService activeDriver = activeDrivers.get(config.driverParent.id); if (activeDriver == null) { throw new DriverNotAvailableException(); } waitTilDeviceIsConnected(config.device); try { return config.device.connection.scanForChannels(settings); } catch (ConnectionException e) { config.device.disconnectedSignal(); throw new ScanException(e.getMessage(), e); } } private void waitTilDeviceIsConnected(Device device) throws ScanException { for (int i = 0; i < 10 && device.getState() == DeviceState.CONNECTING; i++) { try { Thread.sleep(10L); } catch (InterruptedException e) { // ignore } } if (device.connection == null) { throw new ScanException("Connection of the device is not yet initialized."); } } @Override public List<String> getIdsOfRunningDrivers() { List<String> availableDrivers; synchronized (activeDrivers) { availableDrivers = new ArrayList<>(activeDrivers.size()); for (String activeDriverName : activeDrivers.keySet()) { availableDrivers.add(activeDriverName); } } return availableDrivers; } @Override public void write(List<WriteValueContainer> values) { HashMap<Device, List<WriteValueContainerImpl>> containersByDevice = new LinkedHashMap<>(); for (WriteValueContainer value : values) { if (value.getValue() == null) { ((WriteValueContainerImpl) value).setFlag(Flag.CANNOT_WRITE_NULL_VALUE); continue; } WriteValueContainerImpl valueContainerImpl = (WriteValueContainerImpl) value; Device device = valueContainerImpl.channel.config.deviceParent.device; List<WriteValueContainerImpl> writeValueContainers = containersByDevice.get(device); if (writeValueContainers == null) { writeValueContainers = new LinkedList<>(); containersByDevice.put(device, writeValueContainers); } writeValueContainers.add(valueContainerImpl); } CountDownLatch writeTasksFinishedSignal = new CountDownLatch(containersByDevice.size()); synchronized (newWriteTasks) { for (Entry<Device, List<WriteValueContainerImpl>> writeValueContainers : containersByDevice.entrySet()) { WriteTask writeTask = new WriteTask(this, writeValueContainers.getKey(), writeValueContainers.getValue(), writeTasksFinishedSignal); newWriteTasks.add(writeTask); } } interrupt(); try { writeTasksFinishedSignal.await(); } catch (InterruptedException e) { } } @Override public void read(List<ReadRecordContainer> readContainers) { Map<Device, List<ChannelRecordContainerImpl>> containersByDevice = new HashMap<>(); for (ReadRecordContainer container : readContainers) { if (container instanceof ChannelRecordContainerImpl == false) { throw new IllegalArgumentException( "Only use ReadRecordContainer created by Channel.getReadContainer()"); } ChannelImpl channel = (ChannelImpl) container.getChannel(); List<ChannelRecordContainerImpl> containersOfDevice = containersByDevice .get(channel.config.deviceParent.device); if (containersOfDevice == null) { containersOfDevice = new LinkedList<>(); containersByDevice.put(channel.config.deviceParent.device, containersOfDevice); } containersOfDevice.add((ChannelRecordContainerImpl) container); } CountDownLatch readTasksFinishedSignal = new CountDownLatch(containersByDevice.size()); synchronized (newReadTasks) { for (Entry<Device, List<ChannelRecordContainerImpl>> channelRecordContainers : containersByDevice .entrySet()) { ReadTask readTask = new ReadTask(this, channelRecordContainers.getKey(), channelRecordContainers.getValue(), readTasksFinishedSignal); newReadTasks.add(readTask); } } interrupt(); try { readTasksFinishedSignal.await(); } catch (InterruptedException e) { } } @Override public List<String> getAllIds() { return new ArrayList<>(rootConfig.channelConfigsById.keySet()); } DataLoggerService getDataLogger() throws DataLoggerNotAvailableException { DataLoggerService dataLogger = activeDataLoggers.peekFirst(); if (dataLogger == null) { throw new DataLoggerNotAvailableException(); } logger.debug("Accessing logged values using " + dataLogger.getId()); return dataLogger; } @Override public DriverInfo getDriverInfo(String driverId) throws DriverNotAvailableException { DriverService driver = activeDrivers.get(driverId); if (driver == null) { throw new DriverNotAvailableException(); } return driver.getInfo(); } @Override public DeviceState getDeviceState(String deviceId) { DeviceConfigImpl deviceConfig = (DeviceConfigImpl) rootConfig.getDevice(deviceId); if (deviceConfig == null) { return null; } return deviceConfig.device.getState(); } }