/* * Dog - Core * * Copyright (c) 2013 Dario Bonino and Luigi De Russis * * This software is based on a bundle of the Apache Felix project. * See the NOTICE file distributed with this work for additional information. * * 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 it.polito.elite.dog.core.devicemanager; import it.polito.elite.dog.core.devicemanager.util.DriverLoader; import it.polito.elite.dog.core.devicemanager.util.DriverMatcher; import it.polito.elite.dog.core.devicemanager.util.Util; import it.polito.elite.dog.core.library.util.LogHelper; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.device.Constants; import org.osgi.service.device.Device; import org.osgi.service.device.Driver; import org.osgi.service.device.DriverLocator; import org.osgi.service.device.DriverSelector; import org.osgi.service.device.Match; import org.osgi.service.log.LogService; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; /** * This class represents the implementation of the device access specification. * It is based on version 1.1 of the spec. * * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> * (original version) * @author <a href="mailto:dario.bonino@polito.it">Dario Bonino</a> (successive * modifications) * @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a> * (successive modifications) * @see <a href="http://elite.polito.it">http://elite.polito.it</a> * */ public class DeviceManager implements Log, ServiceTrackerCustomizer<Object, Object> { private final long DEFAULT_TIMEOUT_SEC = 1; // the logger private volatile LogHelper logger; // the bundle context private BundleContext bundleContext; // the driver selector private volatile DriverSelector driverSelector; // the driver locators private List<DriverLocator> driverLocators; // the devices private Map<ServiceReference<Object>, Object> devices; // the drivers private Map<ServiceReference<Object>, DriverAttributes> drivers; // performs all the background actions private ExecutorService worker; // used to add delayed actions private ScheduledExecutorService delayed; // the devices filter private Filter deviceImplFilter; // the drivers filter private Filter driverImplFilter; /** * Public constructor. Used by the Activator in this <code>Bundle</code> to * instantiate one instance. * * @param context * the <code>BundleContext</code> */ public DeviceManager() { try { this.init(); } catch (InvalidSyntaxException e) { // the logger is still null at this point... System.err .println("Exception during the service tracker creation: " + e); } } public void activate(BundleContext context) { this.bundleContext = context; this.logger = new LogHelper(context); this.start(); ServiceTracker<Object, Object> devTracker = new ServiceTracker<Object, Object>( context, this.deviceImplFilter, this); devTracker.open(); ServiceTracker<Object, Object> driverTracker = new ServiceTracker<Object, Object>( context, this.driverImplFilter, this); driverTracker.open(); } public void debug(String message) { if (this.logger != null) this.logger.log(LogService.LOG_DEBUG, message); } public void info(String message) { if (this.logger != null) this.logger.log(LogService.LOG_INFO, message); } public void warning(String message) { if (this.logger != null) this.logger.log(LogService.LOG_WARNING, message); } public void error(String message, Throwable e) { System.err.println(message); if (e != null) { e.printStackTrace(); } if (this.logger != null) this.logger.log(LogService.LOG_ERROR, message, e); } private void init() throws InvalidSyntaxException { this.driverLocators = Collections .synchronizedList(new ArrayList<DriverLocator>()); this.worker = Executors.newSingleThreadExecutor(new NamedThreadFactory( "Device Manager")); this.delayed = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Device Manager - delayed")); this.driverImplFilter = Util.createFilter("(%s=%s)", new String[] { Constants.DRIVER_ID, "*" }); this.deviceImplFilter = Util.createFilter( "(|(%s=%s)(&(%s=%s)(%s=%s)))", new String[] { org.osgi.framework.Constants.OBJECTCLASS, Device.class.getName(), org.osgi.framework.Constants.OBJECTCLASS, "*", Constants.DEVICE_CATEGORY, "*" }); } private void start() { this.drivers = new ConcurrentHashMap<ServiceReference<Object>, DriverAttributes>(); this.devices = new ConcurrentHashMap<ServiceReference<Object>, Object>(); this.submit(new WaitForStartFramework()); } public void stop() { // nothing to do ? } public void destroy() { this.worker.shutdownNow(); this.delayed.shutdownNow(); } // callback methods public void selectorAdded(DriverSelector selector) { this.driverSelector = selector; this.debug("driver selector appeared"); } public void selectorRemoved(DriverSelector selector) { this.driverSelector = null; this.debug("driver selector lost"); } public void locatorAdded(DriverLocator locator) { this.driverLocators.add(locator); this.debug("driver locator appeared"); } public void locatorRemoved(DriverLocator locator) { this.driverLocators.remove(locator); this.debug("driver locator lost"); } public void driverAdded(ServiceReference<Object> ref, Object obj) { final Driver driver = Driver.class.cast(obj); this.drivers.put(ref, new DriverAttributes(ref, driver)); this.debug("driver appeared: " + Util.showDriver(ref)); // immediately check for idle devices this.submit(new CheckForIdleDevices()); } public void driverModified(ServiceReference<Object> ref, Object obj) { final Driver driver = Driver.class.cast(obj); this.debug("driver modified: " + Util.showDriver(ref)); this.drivers.remove(ref); this.drivers.put(ref, new DriverAttributes(ref, driver)); // check if devices have become idle after some time this.schedule(new CheckForIdleDevices()); } public void driverRemoved(ServiceReference<Object> ref) { this.debug("driver lost: " + Util.showDriver(ref)); this.drivers.remove(ref); // check if devices have become idle // after some time this.schedule(new CheckForIdleDevices()); } public void deviceAdded(ServiceReference<Object> ref, Object device) { this.devices.put(ref, device); this.debug("device appeared: " + Util.showDevice(ref)); this.submit(new DriverAttachAlgorithm(ref, device)); } public void deviceModified(ServiceReference<Object> ref, Object device) { this.debug("device modified: " + Util.showDevice(ref)); // nothing further to do ? // DeviceAttributes da = this.devices.get(ref); // submit(new DriverAttachAlgorithm(da)); } public void deviceRemoved(ServiceReference<Object> ref) { this.debug("device removed: " + Util.showDevice(ref)); this.devices.remove(ref); // nothing further to do ? // the services that use this // device should track it. } /** * perform this task as soon as possible. * * @param task * the task */ private void submit(Callable<Object> task) { this.worker.submit(new LoggedCall(task)); } /** * perform this task after the default delay. * * @param task * the task */ private void schedule(Callable<Object> task) { this.delayed.schedule(new DelayedCall(task), DEFAULT_TIMEOUT_SEC, TimeUnit.SECONDS); } // worker callables /** * Callable used to start the DeviceManager. It either waits (blocking the * worker thread) for the framework to start, or if it has already started, * returns immediately, freeing up the worker thread. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ private class WaitForStartFramework implements Callable<Object>, FrameworkListener { private final CountDownLatch latch = new CountDownLatch(1); public Object call() throws Exception { boolean addedAsListener = false; if (bundleContext.getBundle(0).getState() == Bundle.ACTIVE) { this.latch.countDown(); debug("Starting Device Manager immediately"); } else { bundleContext.addFrameworkListener(this); addedAsListener = true; debug("Waiting for framework to start"); } this.latch.await(); for (Map.Entry<ServiceReference<Object>, Object> entry : devices .entrySet()) { submit(new DriverAttachAlgorithm(entry.getKey(), entry.getValue())); } // cleanup if (addedAsListener) { bundleContext.removeFrameworkListener(this); } return null; } // FrameworkListener method public void frameworkEvent(FrameworkEvent event) { switch (event.getType()) { case FrameworkEvent.STARTED: debug("Framework has started"); this.latch.countDown(); break; } } @Override public String toString() { return getClass().getSimpleName(); } } private class LoggedCall implements Callable<Object> { private final Callable<Object> call; public LoggedCall(Callable<Object> call) { this.call = call; } private String getName() { return this.call.getClass().getSimpleName(); } public Object call() throws Exception { try { return this.call.call(); } catch (Exception e) { error("call failed: " + getName(), e); throw e; } catch (Throwable e) { error("call failed: " + getName(), e); throw new RuntimeException(e); } } } private class DelayedCall implements Callable<Object> { private final Callable<Object> call; public DelayedCall(Callable<Object> call) { this.call = call; } private String getName() { return this.call.getClass().getSimpleName(); } public Object call() throws Exception { info("Delayed call: " + getName()); return worker.submit(this.call); } } /** * Checks for Idle devices, and attaches them * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ private class CheckForIdleDevices implements Callable<Object> { public Object call() throws Exception { debug("START - check for idle devices"); for (ServiceReference<?> ref : getIdleDevices()) { info("IDLE: " + devices.get(ref).toString()); submit(new DriverAttachAlgorithm(ref, devices.get(ref))); } submit(new IdleDriverUninstallAlgorithm()); debug("STOP - check for idle devices"); return null; } /** * get a list of all idle devices. * * @return */ private List<ServiceReference<?>> getIdleDevices() { List<ServiceReference<?>> list = new ArrayList<ServiceReference<?>>(); for (ServiceReference<?> ref : devices.keySet()) { info("checking if idle: " + ref.getProperty(Constants.DEVICE_SERIAL)); // first patch boolean driverFound = false; // get all the bundles using the device... final Bundle[] usingBundles = ref.getUsingBundles(); // iterate over the using bundles for (int i = 0; ((i < usingBundles.length) && (!driverFound)); i++) { if (isDriverBundle(usingBundles[i])) { info("used by driver: " + usingBundles[i].getSymbolicName()); debug("not idle: " + ref.getBundle().getSymbolicName()); driverFound = true; } } // if no driver is using the device, then the device is actually // idle if (!driverFound) list.add(ref); } return list; } } private boolean isDriverBundle(Bundle bundle) { ServiceReference<?>[] refs = bundle.getRegisteredServices(); if (refs == null) { return false; } for (ServiceReference<?> ref : refs) { if (driverImplFilter.match(ref)) { return true; } } return false; } /** * * Used to uninstall unused drivers * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ private class IdleDriverUninstallAlgorithm implements Callable<Object> { public Object call() throws Exception { info("cleaning driver cache"); for (DriverAttributes da : drivers.values()) { // just call the tryUninstall; the da itself // will know if it should really uninstall the driver. try { da.tryUninstall(); } catch (Exception e) { debug(da.getDriverId() + " uninstall failed"); } } return null; } } private class DriverAttachAlgorithm implements Callable<Object> { private final ServiceReference<?> ref; private final Device device; private List<DriverAttributes> included; private List<DriverAttributes> excluded; private final DriverLoader driverLoader; private DriverAttributes finalDriver; public DriverAttachAlgorithm(ServiceReference<?> ref, Object obj) { this.ref = ref; if (deviceImplFilter.match(ref)) { this.device = Device.class.cast(obj); } else { this.device = null; } this.driverLoader = new DriverLoader(DeviceManager.this, bundleContext); } private Dictionary<?, ?> createDictionary(ServiceReference<?> ref) { final Properties p = new Properties(); for (String key : ref.getPropertyKeys()) { p.put(key, ref.getProperty(key)); } return p; } public Object call() throws Exception { info("finding suitable driver for: " + Util.showDevice(this.ref)); final Dictionary<?, ?> dict = createDictionary(this.ref); // first create a copy of all the drivers that are already there. // during the process, drivers will be added, but also excluded. this.included = new ArrayList<DriverAttributes>(drivers.values()); this.excluded = new ArrayList<DriverAttributes>(); // first find matching driver bundles // if there are no driver locators // we'll have to do with the drivers that were // added 'manually' Set<String> driverIds = this.driverLoader.findDrivers( driverLocators, dict); // remove the driverIds that are already available for (DriverAttributes da : drivers.values()) { driverIds.remove(da.getDriverId()); } driverIds.removeAll(drivers.keySet()); try { debug("entering attach phase for " + Util.showDevice(this.ref)); return driverAttachment(dict, driverIds.toArray(new String[0])); } finally { // unload loaded drivers // that were unnecessarily loaded this.driverLoader.unload(this.finalDriver); } } private Object driverAttachment(Dictionary<?, ?> dict, String[] driverIds) throws Exception { this.finalDriver = null; // remove the excluded drivers this.included.removeAll(this.excluded); // now load the drivers List<ServiceReference<?>> driverRefs = this.driverLoader .loadDrivers(driverLocators, driverIds); // these are the possible driver references that have been added // add them to the list of included drivers for (ServiceReference<?> serviceReference : driverRefs) { DriverAttributes da = drivers.get(serviceReference); if (da != null) { this.included.add(da); } } // now start matching all drivers final DriverMatcher mi = new DriverMatcher(DeviceManager.this); for (DriverAttributes driver : this.included) { try { int match = driver.match(this.ref); if (match <= Device.MATCH_NONE) { continue; } mi.add(match, driver); } catch (Throwable t) { error("match threw an exception", new Exception(t)); } } // get the best match Match bestMatch = null; // local copy final DriverSelector selector = driverSelector; if (selector != null) { bestMatch = mi.selectBestMatch(this.ref, selector); if (bestMatch != null) { debug(String.format( "DriverSelector (%s) found best match: %s", selector.getClass().getName(), Util.showDriver(bestMatch.getDriver()))); } } if (bestMatch == null) { bestMatch = mi.getBestMatch(); } if (bestMatch == null) { noDriverFound(); // really return return null; } String driverId = (String) bestMatch.getDriver().getProperty( Constants.DRIVER_ID); debug("best match: " + driverId); this.finalDriver = drivers.get(bestMatch.getDriver()); if (this.finalDriver == null) { error("we found a driverId, but not the corresponding driver: " + driverId, null); noDriverFound(); return null; } // here we get serious... try { debug("attaching to: " + driverId); String newDriverId = this.finalDriver.attach(this.ref); if (newDriverId == null) { // successful attach return null; } // its a referral info("attach led to a referral to: " + newDriverId); this.excluded.add(this.finalDriver); return driverAttachment(dict, new String[] { newDriverId }); } catch (Throwable t) { error("attach failed due to an exception", t); } this.excluded.add(this.finalDriver); return driverAttachment(dict, driverIds); } private void noDriverFound() { debug("no suitable driver found for: " + Util.showDevice(this.ref)); if (this.device != null) { this.device.noDriverFound(); } } @Override public String toString() { return getClass().getSimpleName();// + ": " + // Util.showDevice(this.ref); } } @Override public Object addingService(ServiceReference<Object> reference) { Object service = this.bundleContext.getService(reference); if (service instanceof Device) this.deviceAdded(reference, service); else if (service instanceof Driver) this.driverAdded(reference, service); return reference; } @Override public void modifiedService(ServiceReference<Object> reference, Object service) { Object modifiedService = this.bundleContext.getService(reference); if (modifiedService instanceof Device) this.deviceModified(reference, modifiedService); else if (modifiedService instanceof Driver) this.driverModified(reference, modifiedService); } @Override public void removedService(ServiceReference<Object> reference, Object service) { if (service instanceof Device) this.deviceRemoved(reference); else if (service instanceof Driver) this.driverRemoved(reference); } }