blob: ed7159af5483f8181c38512d617c1eb9c2736a11 [file] [log] [blame]
/*
* 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);
}
}
}