All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ogema.channels.ChannelManagerImpl Maven / Gradle / Ivy

/**
 * Copyright 2011-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
 *
 * 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 org.ogema.channels;

import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.servlet.http.HttpSession;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.ogema.accesscontrol.PermissionManager;
import org.ogema.applicationregistry.ApplicationListener;
import org.ogema.applicationregistry.ApplicationRegistry;
import org.ogema.core.application.AppID;
import org.ogema.core.application.Application;
import org.ogema.core.channelmanager.ChannelAccessException;
import org.ogema.core.channelmanager.ChannelConfiguration;
import org.ogema.core.channelmanager.ChannelConfiguration.Direction;
import org.ogema.core.channelmanager.ChannelEventListener;
import org.ogema.core.channelmanager.NoSuchDriverException;
import org.ogema.core.channelmanager.ChannelAccess;
import org.ogema.core.channelmanager.driverspi.ChannelDriver;
import org.ogema.core.channelmanager.driverspi.ChannelLocator;
import org.ogema.core.channelmanager.driverspi.ChannelScanListener;
import org.ogema.core.channelmanager.driverspi.DeviceListener;
import org.ogema.core.channelmanager.driverspi.DeviceLocator;
import org.ogema.core.channelmanager.driverspi.DeviceScanListener;
import org.ogema.core.channelmanager.driverspi.NoSuchChannelException;
import org.ogema.core.channelmanager.driverspi.SampledValueContainer;
import org.ogema.core.channelmanager.driverspi.ValueContainer;
import org.ogema.core.channelmanager.measurements.Quality;
import org.ogema.core.channelmanager.measurements.SampledValue;
import org.ogema.core.channelmanager.measurements.Value;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;

/**
 * 
 * The ChannelManager is a part of the OGEMA 2.0 Core Framework. 
 * The ChannelManager manages all Channels to external Devices. 
 * You can get, set, add or delete Channels.
 * 
 */
@Component(immediate = true)
@Service(ChannelAccess.class)
@Reference(policy = ReferencePolicy.DYNAMIC, name = "drivers", referenceInterface = ChannelDriver.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, bind = "addDriver", unbind = "removeDriver")
public class ChannelManagerImpl implements ChannelAccess {
 
	public final static String PROP_CHANNELMANAGER_READERTHREADFACTORY = "org.ogema.channels.readerthreadfactory";

	private static final String PROP_CHANNELMANAGER_LOG_TIMEOUT = "org.ogema.channels.loginterval";
	
	private final Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
	
	@Reference
	PermissionManager permMan;

	@Reference
	ApplicationRegistry appreg;

	/**	The map of known drivers */
	private final ConcurrentMap drivers = new ConcurrentHashMap();

	private ReaderThreadFactory readerThreadFactory = new ReaderThreadPerDeviceFactory(this);

	private final ApplicationListener appListener = new ApplicationListenerImpl();

	/**
	 * Add a driver to the driver list.
	 */
	synchronized protected void addDriver(ChannelDriver channelDriver) {
		
		if(channelDriver == null) {
			logger.warn("Rejected  driver.");
			return;
		}
		
		String driverId = channelDriver.getDriverId();

		if (driverId == null) {
			logger.warn("Rejected driver with  id string.");
			return;
		}


		ReaderThreadFactory factory = getReaderThreadFactory(driverId);
		
		if (drivers.putIfAbsent(driverId, new Driver(factory, channelDriver)) == null) {
			logger.info("Added driver {}", driverId);
		} else {
			logger.warn("Rejected already known driver {}", driverId);
		}
	}

	private ReaderThreadFactory getReaderThreadFactory(String driverId) {
		
		String factoryName;
		ReaderThreadFactory factory;
		
		// check if specific property for driver id is set
		factoryName = System.getProperty(PROP_CHANNELMANAGER_READERTHREADFACTORY + "." + driverId);
		
		// check if global property for factory is set
		if (factoryName == null) {
			factoryName = System.getProperty(PROP_CHANNELMANAGER_READERTHREADFACTORY);
		}
		
		try {
		if (factoryName != null) {
			// create factory
			// FIXME Class.forName is ugly... causes problems with OSGi class loaders, and should be generally avoided
			Class factoryClass = Class.forName(factoryName);
			factory = (ReaderThreadFactory)factoryClass.getDeclaredConstructor().newInstance();
			logger.info("Using ReaderThreadFactory {} for driver {} ", factoryName,  driverId);
		} else {
			factory = readerThreadFactory;
		}
		} catch (Exception e) {
			logger.warn("Use default ReaderThreadFactory for driver {} instead of {}", driverId, factoryName, e);
			factory = readerThreadFactory;
		}
		return factory;
	}

	/**
	 * Remove a driver from the driver list.
	 * Close and remove all channels for this driver.
	 *
	 */
	synchronized protected void removeDriver(ChannelDriver channelDriver) {
		
		if(channelDriver == null) {
			logger.warn("Tried to remove  driver.");
			return;
		}
		
		String driverId = channelDriver.getDriverId();

		logger.info("Removed driver {}", driverId);
		
		Driver removed = drivers.remove(driverId);
		
		if (removed != null)
			removed.close();
	}

	// this will be called at component activation
	// the appreg reference will be valid, as it is declared static
	protected void activate(ComponentContext context) {
		logger.info("Starting ChannelManager");
		appreg.registerAppListener(appListener);
		readLogTimeout();
	}
	
	// this will be called at component deactivation
	synchronized  protected void deactivate(ComponentContext context) {
		logger.info("Stopping ChannelManager");
		appreg.unregisterAppListener(appListener);
		
		for (Driver driver : drivers.values()) {
			driver.close();
		}
		
		// remove closed drivers
		drivers.clear();
	}
	
	private void readLogTimeout() {
		String logTimeoutString = System.getProperty(PROP_CHANNELMANAGER_LOG_TIMEOUT);
		long logTimeout;

		try {
			if (logTimeoutString != null) {
				logTimeout = Long.parseLong(logTimeoutString);
				LogLimiter.LOG_SUPPRESSION_INTERVAL = logTimeout;
				logger.info("Setting log timeout to {} ms.", logTimeout);
			}
		} catch (NumberFormatException e) {
			logger.info("Could not parse Property {}.", PROP_CHANNELMANAGER_LOG_TIMEOUT, e);
		}
	}
	
	Driver getDriver(String driverName) throws NoSuchDriverException {
		
		if (driverName == null)
			throw new NullPointerException();
		
		Driver result = drivers.get(driverName);
		
		if (result == null)
			throw new NoSuchDriverException(driverName);
		
		return result;
	}
	
	@Override
	public ChannelConfiguration addChannel(ChannelLocator channelLocator, Direction direction, long samplingTimeInMs) throws ChannelAccessException {
		AppID appID = appreg.getContextApp(getClass());
		if (appID == null)
			throw new NullPointerException("Could not determine app");
		return addChannel(channelLocator, direction, samplingTimeInMs, appID);
	}
	
	private ChannelConfiguration addChannel(ChannelLocator channelLocator, Direction direction, long samplingTimeInMs, AppID appID) throws ChannelAccessException {
		ChannelConfigurationImpl configuration = new ChannelConfigurationImpl(channelLocator, samplingTimeInMs, direction, appID);
		DeviceLocator deviceLocator = channelLocator.getDeviceLocator();
		Driver driver;
		
		// Check permission
		if (!permMan.checkAddChannel(configuration, deviceLocator)) {
			throw new SecurityException("Action not permitted.");
		}

		// get driver
		try {
			driver = getDriver(deviceLocator.getDriverName());
		} catch (NoSuchDriverException e) {
			throw new ChannelAccessException(e);
		}
		
		// get channel
		return driver.addConfiguration(configuration);
	}
	
	@Override
	public boolean deleteChannel(ChannelConfiguration configuration) {
		
		Driver driver;
		
		try {
			driver = getDriver(configuration.getDeviceLocator().getDriverName());
		} catch (NoSuchDriverException e1) {
			return false;
		}
		
		// No permission check necessary. 
		// The app had the right to open the channel, 
		// it is allowed to close it for itself.
		
		// remove the channel from the device
		try {
			return driver.removeConfiguration(configuration);
		} catch (NoSuchChannelException e) { 
			return false;
		}
	}

	@Override
	public List getDriverIds() {
		return new ArrayList(drivers.keySet());
	}

	@Override
	public List getAllConfiguredChannels() {
		List channels = new LinkedList();

		// no need to protect from concurrent access
		// iterator is weakly-consistent
		// never throws an ConcurrentModificationException
		for (Driver driver: drivers.values())
		{
			driver.getChannels(channels);
		}
		
		return channels;
	}

	private Configuration getConfiguration(ChannelConfiguration configuration) throws ChannelAccessException {
		
		Configuration result = null;
		Driver driver = null;
		try {
			driver = getDriver(configuration.getDeviceLocator().getDriverName());
			result = driver.getConfiguration(configuration);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
		
		return result;
	}
	
	@Override
	public void setChannelValue(ChannelConfiguration configuration, Value value) throws ChannelAccessException {
		Configuration conf = getConfiguration(configuration);
		
		try {
			conf.setChannelValue(value);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}	
	}

	@Override
	public SampledValue getChannelValue(ChannelConfiguration configuration) throws ChannelAccessException {
		Configuration conf = getConfiguration(configuration);
		
		try {
			return conf.getChannelValue();
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
	}

	private class AppIDbyThread implements AppID {

		Thread thread;
		
		AppIDbyThread(Thread thread) {
			this.thread = thread;
		}
		
		@Override
		public String getIDString() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public String getLocation() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Bundle getBundle() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Application getApplication() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public String getOwnerUser() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public String getOwnerGroup() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public String getVersion() {
			// TODO Auto-generated method stub
			return null;
		}
		
		@Override
		public boolean isActive() {
			return false;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + ((thread == null) ? 0 : thread.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			AppIDbyThread other = (AppIDbyThread) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (thread == null) {
				if (other.thread != null)
					return false;
			} else if (!thread.equals(other.thread))
				return false;
			return true;
		}

		private ChannelManagerImpl getOuterType() {
			return ChannelManagerImpl.this;
		}

		@Override
		public URL getOneTimePasswordInjector(String path, HttpSession ses) {
			// TODO Auto-generated method stub
			return null;
		}
	}
	
	@Override
	public void readUnconfiguredChannels(List channelList) throws ChannelAccessException {

		// implement readUnconfigured by opening the channels internally and closing them at the end of the call.
		// to be able to distinguish normally opened channels from the same app a new AppID implentation is used. 
		// This AppID is based on the thread hash code. This is unique because the thread is used for the call.

		AppID appID = new AppIDbyThread(Thread.currentThread());
		Driver driver = null;
		List openChannels = new ArrayList();

		try {
			for (SampledValueContainer vCont : channelList) {
				Driver driverTmp;
				ChannelLocator channelLocator = vCont.getChannelLocator();

				driverTmp = drivers.get(channelLocator.getDeviceLocator().getDriverName());

				if (driver == null)
					driver = driverTmp;

				// We have to ensure that all Values are directed to the same
				// driver
				if (driverTmp == null || (driverTmp != driver))
					throw new UnsupportedOperationException("All requests must be directed to the same driver.");
				
				// A channel is temporarily created for each request. 
				// This is done to avoid special handling of ChannelDriver.channelAdded().
				openChannels.add(addChannel(channelLocator, Direction.DIRECTION_INPUT,
						ChannelConfiguration.NO_READ_NO_LISTEN, appID));
			}

			driver.readChannels(channelList);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		} finally {
			for (ChannelConfiguration conf : openChannels) {
				deleteChannel(conf);
			}
		}
	}

	@Override
	public void writeUnconfiguredChannels(List channelList) throws ChannelAccessException {

		// implement writeUnconfigured by opening the channels internally and closing them at the end of the call.
		// to be able to distinguish normally opened channels from the same app a new AppID implementation is used. 
		// This AppID is based on the thread hash code. This is unique because the thread is used for the call.

		AppID appID = new AppIDbyThread(Thread.currentThread());
		Driver driver = null;
		List openChannels = new ArrayList();

		try {
			for (ValueContainer vCont : channelList) {
				Driver driverTmp;
				ChannelLocator channelLocator = vCont.getChannelLocator();

				driverTmp = drivers.get(channelLocator.getDeviceLocator().getDriverName());

				if (driver == null)
					driver = driverTmp;

				// We have to ensure that all Values are directed to the same
				// driver
				if (driverTmp == null || (driverTmp != driver))
					throw new UnsupportedOperationException("All requests must be directed to the same driver.");
				
				openChannels.add(addChannel(channelLocator, Direction.DIRECTION_OUTPUT,
						ChannelConfiguration.NO_READ_NO_LISTEN, appID));
			}

			driver.writeChannels(channelList);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		} finally {
			for (ChannelConfiguration conf : openChannels) {
				deleteChannel(conf);
			}
		}
	}
	
	@Override
	public void setMultipleChannelValues(List configurations, List values)
			throws ChannelAccessException {

		if (configurations.size() != values.size()) {
			throw new IllegalArgumentException("Non-matching list sizes");
		}

		for (int i = 0; i < configurations.size(); i++) {
			setChannelValue(configurations.get(i), values.get(i));
		}
	}

	@Override
	public List getMultipleChannelValues(List configurations) {

		List values = new LinkedList();
		SampledValue value;

		for (ChannelConfiguration cl : configurations) {
			try {
				value = getChannelValue(cl);
			} catch (ChannelAccessException e) {
				value = new SampledValue(null, 0, Quality.BAD);
			}
			values.add(value);
		}

		return values;

	}

	@Override
	public void registerUpdateListener(
			List configurations,
			ChannelEventListener listener) {
		for (ChannelConfiguration configuration : configurations) {
			Configuration conf;
			try {
				conf = getConfiguration(configuration);
				conf.addUpdateListener(listener);
			} catch (ChannelAccessException e) {
				// this is an internal error
				e.printStackTrace();
			}
		}
	}

	@Override
	public void registerChangedListener(
			List configurations,
			ChannelEventListener listener) {
		for (ChannelConfiguration configuration : configurations) {
			Configuration conf;
			try {
				conf = getConfiguration(configuration);
				conf.addChangedListener(listener);
			} catch (ChannelAccessException e) {
				// this is an internal error
				e.printStackTrace();
			}
		}
	}

	@Override
	public void unregisterUpdateListener(
			List configurations,
			ChannelEventListener listener) {
		for (ChannelConfiguration configuration : configurations) {
			Configuration conf;
			try {
				conf = getConfiguration(configuration);
				conf.removeUpdateListener(listener);
			} catch (ChannelAccessException e) {
				// this is an internal error
				e.printStackTrace();
			}
		}
	}

	@Override
	public void unregisterChangedListener(
			List configurations,
			ChannelEventListener listener) {
		for (ChannelConfiguration configuration : configurations) {
			Configuration conf;
			try {
				conf = getConfiguration(configuration);
				conf.removeChangedListener(listener);
			} catch (ChannelAccessException e) {
				// this is an internal error
				e.printStackTrace();
			}
		}
	}

	@Override
	public List discoverChannels(DeviceLocator device) throws ChannelAccessException {

		final List channels = new LinkedList();
		final AtomicBoolean finished = new AtomicBoolean(false);

		try {
			Driver driver = getDriver(device.getDriverName());

			ChannelScanListener listener = new ChannelScanListener() {

				@Override
				public void progress(float ratio) {
					// ignored
				}

				@Override
				public synchronized void finished(boolean success) {
					finished.set(true);
					this.notify();
				}

				@Override
				public void channelFound(ChannelLocator channel) {
					channels.add(channel);
				}
			};

			driver.startChannelScan(device, listener);

			synchronized (listener) {
				while (finished.get() == false)
					try {
						listener.wait();
					} catch (InterruptedException e) {
						// don't care
					}
			}

			return channels;
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
	}

	@Override
	public void discoverChannels(DeviceLocator device, ChannelScanListener listener) throws ChannelAccessException {

		try {
			Driver driver = getDriver(device.getDriverName());

			final ChannelScanListener client = listener;
			listener = new ChannelScanListener() {

				@Override
				public void progress(float ratio) {
					try {
						client.progress(ratio);
					} catch (Throwable t) {
						logger.warn("caught application exception in ChannelScanListener callback", t);
					}
				}

				@Override
				public void finished(boolean success) {
					try {
						client.finished(success);
					} catch (Throwable t) {
						logger.warn("caught application exception in ChannelScanListener callback", t);
					}
				}

				@Override
				public void channelFound(ChannelLocator channel) {
					try {
						client.channelFound(channel);
					} catch (Throwable t) {
						logger.warn("caught application exception in ChannelScanListener callback", t);
					}
				}
			};

			driver.startChannelScan(device, listener);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
	}

	@Override
	public List discoverDevices(String driverId, String interfaceId, String filter)
			throws ChannelAccessException {
		// convert device scan to synchronous behavior

		AppID appID = appreg.getContextApp(getClass());
		
		try {
			Driver driver = getDriver(driverId);

			DeviceScanListenerSyncImpl listener = new DeviceScanListenerSyncImpl();

			DeviceScanner scanner = driver.startDeviceScan(interfaceId, filter, listener, appID);
			scanner.waitUntilFinished();

			if (listener.exceptionResult != null)
				throw listener.exceptionResult;
			
			return listener.deviceList;
			
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
	}

	/**
	 * DeviceScanListener implementation for the synchronous invocation of discoverDevices.
	 * 
	 * @author pau
	 *
	 */
	private class DeviceScanListenerSyncImpl implements DeviceScanListener {

		List deviceList = new LinkedList();
		Exception exceptionResult;
		
		@Override
		public void deviceFound(DeviceLocator device) {
			deviceList.add(device);
		}

		@Override
		public void finished(boolean success, Exception e) {
			// DeviceScanner wakes up by itself from waitUntilFinished
			exceptionResult = e;
		}

		@Override
		public void progress(float ratio) {
			// ignore it
		}
	}
	
	@Override
	public void discoverDevices(String driverId, String interfaceId,
			String filter, DeviceScanListener listener)
			throws ChannelAccessException {
		
		AppID appID = appreg.getContextApp(getClass());
		Driver driver = null;
		
		try {
			driver = getDriver(driverId);
			driver.startDeviceScan(interfaceId, filter, listener, appID);
		} catch (Exception e) {
			throw new ChannelAccessException(e);
		}
	}

	// cleanup functions should not throw an exception
	@Override
	public boolean abortDiscoverDevices(String driverId, String interfaceId, String filter) 
	{
		AppID appID = appreg.getContextApp(getClass());
		Driver driver;
		boolean success = false;
		
		try {
			driver = getDriver(driverId);
			success = driver.abortDeviceScan(interfaceId, filter, appID);
		} catch (NoSuchDriverException | NullPointerException e) {
			logger.warn("could not abort device scan.", e);
		}
		
		return success;
	}

	@Override
	public String getDriverDescription(String driverId) {
		String result;
		Driver driver;
		
		try {
			driver = getDriver(driverId);
			result = driver.getDescription();
		} catch (NoSuchDriverException | NullPointerException e) {
			result = null;
		}
		
		return result;
	}

	@Override
	public List getChannelList(DeviceLocator deviceLocator) throws ChannelAccessException {
		Driver driver;

		try {
			driver = getDriver(deviceLocator.getDriverName());
		} catch (NoSuchDriverException e) {
			throw new ChannelAccessException(e);
		}
		
		return driver.getChannelList(deviceLocator);
	}

	@Override
	public void addDeviceListener(String driverId, DeviceListener listener) throws ChannelAccessException {
		AppID appId = appreg.getContextApp(getClass());
		Driver driver = null;
		
		try {
			driver = getDriver(driverId);
		} catch (NoSuchDriverException e) {
			throw new ChannelAccessException(e);
		}
		
		driver.addDeviceListener(appId, listener);
	}

	// should not throw an exception. Application clean-up code might not be very robust.
	@Override
	public void removeDeviceListener(String driverId, DeviceListener listener) {
		AppID appId = appreg.getContextApp(getClass());
		Driver driver = null;
		
		try {
			driver = getDriver(driverId);
		} catch (NoSuchDriverException | NullPointerException e) {
			return;
		}
		
		try {
			driver.removeDeviceListener(appId, listener);
		} catch (NullPointerException e) {
			; // do nothing
		}
	}		

	ReaderThread getReaderThread(Channel channel) throws ChannelAccessException {
		return readerThreadFactory.getReaderThread(channel);
	}
	
	private class ApplicationListenerImpl implements ApplicationListener {

		@Override
		public void appInstalled(AppID app) {
			// don't care
		}

		@Override
		public void appRemoved(AppID app) {
			removeAppID(app);
		}
		
	}

	private void removeAppID(AppID app) {
		for (Driver driver : drivers.values()) {
			driver.removeAppID(app);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy