/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.felix.das; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; 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.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.felix.das.util.DriverLoader; import org.apache.felix.das.util.DriverMatcher; import org.apache.felix.das.util.Util; 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; /** * This class represents the Apache Felix 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> */ public class DeviceManager implements Log { private final long DEFAULT_TIMEOUT_SEC = 1; // the logger private volatile LogService m_log; // the bundle context private final BundleContext m_context; // the driver selector private volatile DriverSelector m_selector; // the driver locators private List<DriverLocator> m_locators; // the devices private Map<ServiceReference, Object> m_devices; // the drivers private Map<ServiceReference, DriverAttributes> m_drivers; // performs all the background actions private ExecutorService m_worker; // used to add delayed actions private ScheduledExecutorService m_delayed; //the devices filter private Filter m_deviceImplFilter; //the drivers filter private Filter m_driverImplFilter; /** * Public constructor. Used by the Activator in this <code>Bundle</code> * to instantiate one instance. * * @param context the <code>BundleContext</code> */ public DeviceManager( BundleContext context ) { m_context = context; } public void debug( String message ) { m_log.log( LogService.LOG_DEBUG, message ); } public void info( String message ) { m_log.log( LogService.LOG_INFO, message ); } public void warning( String message ) { m_log.log( LogService.LOG_WARNING, message ); } public void error( String message, Throwable e ) { System.err.println( message ); if ( e != null ) { e.printStackTrace(); } m_log.log( LogService.LOG_ERROR, message, e ); } // dependency manager methods @SuppressWarnings("unused") private void init() throws InvalidSyntaxException { m_locators = Collections.synchronizedList( new ArrayList<DriverLocator>() ); m_worker = Executors.newSingleThreadExecutor( new NamedThreadFactory( "Apache Felix Device Manager" ) ); m_delayed = Executors.newScheduledThreadPool( 1, new NamedThreadFactory( "Apache Felix Device Manager - delayed" ) ); m_deviceImplFilter = Util.createFilter( "(%s=%s)", new Object[] { org.osgi.framework.Constants.OBJECTCLASS, Device.class.getName() } ); m_driverImplFilter = Util.createFilter( "(%s=%s)", new Object[] { org.osgi.framework.Constants.OBJECTCLASS, Driver.class.getName() } ); } @SuppressWarnings("unused") private void start() { m_drivers = new HashMap<ServiceReference, DriverAttributes>(); m_devices = new HashMap<ServiceReference, Object>(); submit( new WaitForStartFramework() ); } public void stop() { // nothing to do ? } public void destroy() { m_worker.shutdownNow(); m_delayed.shutdownNow(); } // callback methods public void selectorAdded( DriverSelector selector ) { m_selector = selector; debug( "driver selector appeared" ); } public void selectorRemoved( DriverSelector selector ) { m_selector = null; debug( "driver selector lost" ); } public void locatorAdded( DriverLocator locator ) { m_locators.add( locator ); debug( "driver locator appeared" ); } public void locatorRemoved( DriverLocator locator ) { m_locators.remove( locator ); debug( "driver locator lost" ); } public void driverAdded( ServiceReference ref, Object obj ) { final Driver driver = Driver.class.cast( obj ); m_drivers.put( ref, new DriverAttributes( ref, driver ) ); debug( "driver appeared: " + Util.showDriver( ref ) ); //immediately check for idle devices submit( new CheckForIdleDevices() ); } public void driverModified( ServiceReference ref, Object obj ) { final Driver driver = Driver.class.cast( obj ); debug( "driver modified: " + Util.showDriver( ref ) ); m_drivers.remove( ref ); m_drivers.put( ref , new DriverAttributes( ref, driver ) ); // check if devices have become idle // after some time schedule( new CheckForIdleDevices() ); } public void driverRemoved( ServiceReference ref ) { debug( "driver lost: " + Util.showDriver( ref ) ); m_drivers.remove( ref ); // check if devices have become idle // after some time schedule( new CheckForIdleDevices() ); } public void deviceAdded( ServiceReference ref, Object device ) { m_devices.put( ref, device ); debug( "device appeared: " + Util.showDevice( ref ) ); submit( new DriverAttachAlgorithm( ref, device ) ); } public void deviceModified( ServiceReference ref, Object device ) { debug( "device modified: " + Util.showDevice( ref ) ); // nothing further to do ? // DeviceAttributes da = m_devices.get(ref); // submit(new DriverAttachAlgorithm(da)); } public void deviceRemoved( ServiceReference ref ) { debug( "device removed: " + Util.showDevice( ref ) ); m_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 ) { m_worker.submit( new LoggedCall( task ) ); } /** * perform this task after the default delay. * * @param task * the task */ private void schedule( Callable<Object> task ) { m_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 m_latch = new CountDownLatch( 1 ); public Object call() throws Exception { boolean addedAsListener = false; if ( m_context.getBundle( 0 ).getState() == Bundle.ACTIVE ) { m_latch.countDown(); debug( "Starting Device Manager immediately" ); } else { m_context.addFrameworkListener( this ); addedAsListener = true; debug( "Waiting for framework to start" ); } m_latch.await(); for ( Map.Entry<ServiceReference, Object> entry : m_devices.entrySet() ) { submit( new DriverAttachAlgorithm( entry.getKey(), entry.getValue() ) ); } // cleanup if ( addedAsListener ) { m_context.removeFrameworkListener( this ); } return null; } // FrameworkListener method public void frameworkEvent( FrameworkEvent event ) { switch ( event.getType() ) { case FrameworkEvent.STARTED: debug( "Framework has started" ); m_latch.countDown(); break; } } @Override public String toString() { return getClass().getSimpleName(); } } private class LoggedCall implements Callable<Object> { private final Callable<Object> m_call; public LoggedCall( Callable<Object> call ) { m_call = call; } private String getName() { return m_call.getClass().getSimpleName(); } public Object call() throws Exception { try { return m_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> m_call; public DelayedCall( Callable<Object> call ) { m_call = call; } private String getName() { return m_call.getClass().getSimpleName(); } public Object call() throws Exception { info( "Delayed call: " + getName() ); return m_worker.submit( m_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: " + ref.getBundle().getSymbolicName() ); submit( new DriverAttachAlgorithm( ref, m_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 : m_devices.keySet() ) { info( "checking if idle: " + ref.getBundle().getSymbolicName() ); final Bundle[] usingBundles = ref.getUsingBundles(); for ( Bundle bundle : usingBundles ) { if ( isDriverBundle( bundle ) ) { info( "used by driver: " + bundle.getSymbolicName() ); debug( "not idle: " + ref.getBundle().getSymbolicName() ); break; } list.add( ref ); } } return list; } } private boolean isDriverBundle( Bundle bundle ) { ServiceReference[] refs = bundle.getRegisteredServices(); if (refs == null) { return false; } for ( ServiceReference ref : refs ) { if ( m_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 : m_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 m_ref; private final Device m_device; private List<DriverAttributes> m_included; private List<DriverAttributes> m_excluded; private final DriverLoader m_driverLoader; private DriverAttributes m_finalDriver; public DriverAttachAlgorithm( ServiceReference ref, Object obj ) { m_ref = ref; if ( m_deviceImplFilter.match( ref ) ) { m_device = Device.class.cast( obj ); } else { m_device = null; } m_driverLoader = new DriverLoader( DeviceManager.this, m_context ); } @SuppressWarnings("all") private Dictionary createDictionary( ServiceReference ref ) { final Properties p = new Properties(); for ( String key : ref.getPropertyKeys() ) { p.put( key, ref.getProperty( key ) ); } return p; } @SuppressWarnings("all") public Object call() throws Exception { info( "finding suitable driver for: " + Util.showDevice( m_ref ) ); final Dictionary dict = createDictionary( m_ref ); // first create a copy of all the drivers that are already there. // during the process, drivers will be added, but also excluded. m_included = new ArrayList<DriverAttributes>( m_drivers.values() ); m_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 = m_driverLoader.findDrivers( m_locators, dict ); // remove the driverIds that are already available for ( DriverAttributes da : m_drivers.values() ) { driverIds.remove( da.getDriverId() ); } driverIds.removeAll( m_drivers.keySet() ); try { debug("entering attach phase for " + Util.showDevice( m_ref ) ); return driverAttachment( dict, driverIds.toArray( new String[0] ) ); } finally { // unload loaded drivers // that were unnecessarily loaded m_driverLoader.unload( m_finalDriver ); } } @SuppressWarnings("all") private Object driverAttachment( Dictionary dict, String[] driverIds ) throws Exception { m_finalDriver = null; // remove the excluded drivers m_included.removeAll( m_excluded ); // now load the drivers List<ServiceReference> driverRefs = m_driverLoader.loadDrivers( m_locators, 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 = m_drivers.get( serviceReference ); if ( da != null ) { m_included.add( da ); } } // now start matching all drivers final DriverMatcher mi = new DriverMatcher( DeviceManager.this ); for ( DriverAttributes driver : m_included ) { try { int match = driver.match( m_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 = m_selector; if ( selector != null ) { bestMatch = mi.selectBestMatch( m_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.class.cast( bestMatch.getDriver().getProperty( Constants.DRIVER_ID ) ); debug( "best match: " + driverId ); m_finalDriver = m_drivers.get( bestMatch.getDriver() ); if ( m_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 = m_finalDriver.attach( m_ref ); if ( newDriverId == null ) { // successful attach return null; } // its a referral info( "attach led to a referral to: " + newDriverId ); m_excluded.add( m_finalDriver ); return driverAttachment( dict, new String[]{ newDriverId } ); } catch ( Throwable t ) { error( "attach failed due to an exception", t ); } m_excluded.add( m_finalDriver ); return driverAttachment( dict, driverIds ); } private void noDriverFound() { debug( "no suitable driver found for: " + Util.showDevice( m_ref ) ); if ( m_device != null ) { m_device.noDriverFound(); } } @Override public String toString() { return getClass().getSimpleName();// + ": " + // Util.showDevice(m_ref); } } }