/*
* Copyright (c) 2003, KNOPFLERFISH project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* - Neither the name of the KNOPFLERFISH project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.knopflerfish.bundle.device;
import java.io.InputStream;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
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;
public class Activator extends Thread implements BundleActivator,
ServiceListener, BundleListener, FrameworkListener {
// NB: These constants should be configurable
private static final long LONG_LIFE = 15 * 60 * 1000;
private static final long SHORT_LIFE = 5 * 60 * 1000;
private static final long REAP_INTERVAL = 1 * 60 * 1000;
static final String DYNAMIC_DRIVER_TAG = "__DD_";
private static final String LOG_FILTER = "(objectClass="
+ LogService.class.getName() + ")";
private static final String DEVICE_FILTER = "(|(objectClass="
+ Device.class.getName() + ")(DEVICE_CATEGORY=*))";
private static final String DRIVER_FILTER = "(objectClass="
+ Driver.class.getName() + ")";
private static final String SELECTOR_FILTER = "(objectClass="
+ DriverSelector.class.getName() + ")";
private static final String LOCATOR_FILTER = "(objectClass="
+ DriverLocator.class.getName() + ")";
private static final String GLOBAL_FILTER = "(|" + LOG_FILTER
+ DEVICE_FILTER + DRIVER_FILTER + SELECTOR_FILTER + LOCATOR_FILTER
+ ")";
BundleContext bc;
private boolean active;
private boolean quit;
private Filter isLog;
private Filter isDevice;
private Filter isDriver;
private Filter isSelector;
private Filter isLocator;
private Vector /* DriverRef */drivers = new Vector();
private ServiceReference[] locatorRefs;
DriverLocator[] locators;
private ServiceReference selectorRef;
private DriverSelector selector;
private ServiceReference logRef;
private LogService log;
private Hashtable /* Integer->MatchValue */cache = new Hashtable();
private Hashtable /* ServiceReference->ServiceReference */newDevices = new Hashtable(
20);
private Hashtable /* Bundle->Long */tempDrivers = new Hashtable(10);
private long reapTime;
public Activator() {
super("DeviceManager");
}
public void start(BundleContext bc) throws Exception {
this.bc = bc;
isLog = bc.createFilter(LOG_FILTER);
isDevice = bc.createFilter(DEVICE_FILTER);
isDriver = bc.createFilter(DRIVER_FILTER);
isSelector = bc.createFilter(SELECTOR_FILTER);
isLocator = bc.createFilter(LOCATOR_FILTER);
startService(LOG_FILTER);
start();
bc.addFrameworkListener(this);
Bundle b = bc.getBundle(0);
if (b.getState() == Bundle.ACTIVE)
activate();
else
info("Passive start");
}
public void stop(BundleContext bc) {
info("Stopping");
quit = true;
synchronized (this) {
notifyAll();
}
}
private synchronized void activate() throws Exception {
if (active)
return;
active = true;
info("Activating");
bc.addBundleListener(this);
// Thanks to TID for combining the (buggy)
// separate listeners into one
startService(GLOBAL_FILTER);
}
public void frameworkEvent(FrameworkEvent e) {
try {
if (e.getType() == FrameworkEvent.STARTED)
activate();
} catch (Exception e1) {
}
}
private void startService(String filter) throws Exception {
bc.addServiceListener(this, filter);
ServiceReference[] sra = bc.getServiceReferences(null, filter);
if (sra != null) {
for (int i = 0; i < sra.length; i++) {
try {
serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED,
sra[i]));
} catch (Exception e) {
}
}
}
}
public void serviceChanged(ServiceEvent e) {
ServiceReference sr = e.getServiceReference();
if (isDevice.match(sr)) {
switch (e.getType()) {
case ServiceEvent.REGISTERED:
info("device found, " + showDevice(sr));
touchDevice(sr);
break;
case ServiceEvent.MODIFIED:
// We should most likely not do anything here
// removeSRCachedMatch(sr);
// touchDevice(sr);
break;
case ServiceEvent.UNREGISTERING:
info("device lost, " + showDevice(sr));
removeSRCachedMatch(sr);
break;
}
}
if (isDriver.match(sr)) {
try {
switch (e.getType()) {
case ServiceEvent.REGISTERED:
info("driver found, " + showDriver(sr));
driverAppeared(sr);
touchAllDevices();
break;
case ServiceEvent.MODIFIED:
// We should probably not do anything here
// driverGone(sr);
// driverAppeared(sr);
break;
case ServiceEvent.UNREGISTERING:
info("driver lost, " + showDriver(sr));
driverGone(sr);
touchAllDevices();
break;
}
} catch (Exception e1) {
}
}
if (isSelector.match(sr)) {
try {
bc.ungetService(selectorRef);
} catch (Exception e1) {
}
selector = null;
try {
selectorRef = bc.getServiceReference(DriverSelector.class
.getName());
selector = (DriverSelector) bc.getService(selectorRef);
} catch (Exception e1) {
}
}
if (isLocator.match(sr)) {
try {
for (int i = 0; i < locatorRefs.length; i++) {
bc.ungetService(locatorRefs[i]);
}
} catch (Exception e1) {
}
locatorRefs = null;
locators = null;
try {
locatorRefs = bc.getServiceReferences(DriverLocator.class
.getName(), null);
locators = new DriverLocator[locatorRefs.length];
for (int i = 0; i < locatorRefs.length; i++) {
locators[i] = (DriverLocator) bc.getService(locatorRefs[i]);
}
} catch (Exception e1) {
}
}
if (isLog.match(sr)) {
try {
bc.ungetService(logRef);
} catch (Exception e1) {
}
try {
logRef = bc.getServiceReference(LogService.class.getName());
log = (LogService) bc.getService(logRef);
} catch (Exception e1) {
}
}
}
private void driverAppeared(ServiceReference sr) {
try {
for (int i = 0; i < drivers.size(); i++) {
DriverRef dr = (DriverRef) drivers.elementAt(i);
if (dr.sr == sr)
return;
}
DriverRef dr = new DriverRef();
try {
dr.ranking = ((Integer) sr
.getProperty(org.osgi.framework.Constants.SERVICE_RANKING))
.intValue();
} catch (Exception e) {
}
dr.servid = ((Long) sr
.getProperty(org.osgi.framework.Constants.SERVICE_ID))
.longValue();
dr.id = (String) sr
.getProperty(org.osgi.service.device.Constants.DRIVER_ID);
dr.sr = sr;
if (dr.id != null)
dr.drv = (Driver) bc.getService(sr);
else
error("ignoring driver without id " + showDriver(sr));
if (dr.drv != null)
drivers.addElement(dr);
} catch (Exception e) {
}
}
private void driverGone(ServiceReference sr) {
try {
for (int i = 0; i < drivers.size(); i++) {
DriverRef dr = (DriverRef) drivers.elementAt(i);
if (dr.sr == sr) {
drivers.removeElementAt(i);
return;
}
}
} catch (Exception e) {
}
}
public void bundleChanged(BundleEvent e) {
if (e.getType() == BundleEvent.UNINSTALLED) {
tempDrivers.remove(e.getBundle());
}
}
private void info(String msg) {
try {
log.log(LogService.LOG_INFO, msg);
} catch (Exception e) {
System.err.println("[Device Manager] info: " + msg);
}
}
private void error(String msg) {
try {
log.log(LogService.LOG_ERROR, msg);
} catch (Exception e) {
System.err.println("[Device Manager] ERROR: " + msg);
}
}
public void run() {
while (!quit) {
boolean sleep = true;
try {
ServiceReference dev = (ServiceReference) newDevices.keys()
.nextElement();
newDevices.remove(dev);
sleep = false;
handleDevice(dev);
} catch (Exception e) {
}
if (!sleep)
continue;
long now = System.currentTimeMillis();
if (now >= reapTime) {
reapDrivers();
reapTime = now + REAP_INTERVAL;
// NB: Should also clean out old matches every now
// and then based on some kind of LRU scheme
}
synchronized (this) {
if (!quit)
try {
wait(reapTime - now);
} catch (Exception e) {
}
}
}
}
private void handleDevice(ServiceReference dev) {
if (isUsed(dev))
return;
Dictionary props = collectProperties(dev);
Vector /* MatchImpl */matches = new Vector();
// Populate matches with driver locator recommendations
DriverLocator[] dla = locators;
if (dla != null) {
for (int i = 0; i < dla.length; i++) {
try {
DriverLocator dl = dla[i];
String[] dria = dl.findDrivers(props);
for (int j = 0; j < dria.length; j++) {
String dri = dria[j];
MatchImpl m = null;
for (int k = 0; k < matches.size(); k++) {
m = (MatchImpl) matches.elementAt(k);
if (m.equals(dri))
break;
m = null;
}
if (m == null) {
m = new MatchImpl(this, dev, dri);
matches.addElement(m);
}
m.addDriverLocator(dl);
}
} catch (Exception e) {
}
}
}
for (;;) {
// Add current drivers to matches
for (int i = 0; i < drivers.size(); i++) {
DriverRef dr = (DriverRef) drivers.elementAt(i);
MatchImpl m = null;
for (int k = 0; k < matches.size(); k++) {
m = (MatchImpl) matches.elementAt(k);
if (m.connect(dr))
break;
m = null;
}
if (m == null) {
m = new MatchImpl(this, dev, dr);
matches.addElement(m);
}
}
int n = 0;
boolean loading = false;
// Count good matches and trigger loading
for (int i = 0; i < matches.size(); i++) {
MatchImpl m = (MatchImpl) matches.elementAt(i);
int match = m.getMatchValue();
if (match == MatchImpl.UNKNOWN)
loading = true;
else if (match > Device.MATCH_NONE)
n++;
}
// If not finished loading continue
if (loading)
continue;
// If nothing matches we're done
if (n == 0) {
tellNotFound(dev);
return;
}
// Filter out good matches and select default best match
MatchImpl best = null;
Match[] sel = new Match[n];
n = 0;
for (int i = 0; i < matches.size(); i++) {
MatchImpl m = (MatchImpl) matches.elementAt(i);
if (m.getMatchValue() > Device.MATCH_NONE) {
sel[n++] = m;
if (best == null || best.compare(m) < 0)
best = m;
}
}
// Maybe use driver selector instead
DriverSelector ds = selector;
if (ds != null) {
int ix = ds.select(dev, sel);
if (ix == DriverSelector.SELECT_NONE) {
tellNotFound(dev);
return;
}
try {
best = (MatchImpl) sel[ix];
} catch (Exception e) {
}
}
// If attach succeeds we're done
String ref = null;
try {
ref = best.attach();
} catch (Exception e) {
error("failed attach " + showDriver(best.getDriver()) + " -> "
+ showDevice(dev));
continue;
}
if (best.getMatchValue() > Device.MATCH_NONE) {
// Just loaded, go around and pick up the driver ref
continue;
}
if (ref == null) {
info("attached " + showDriver(best.getDriver()) + " -> "
+ showDevice(dev));
Bundle b = best.getBundle();
if (b != null)
updateLife(b, LONG_LIFE);
return;
}
info(showDriver(best.getDriver()) + " refers to " + ref);
// Append the referred match
MatchImpl m = null;
for (int i = 0; i < matches.size(); i++) {
m = (MatchImpl) matches.elementAt(i);
if (m.equals(ref))
break;
m = null;
}
if (m == null) {
m = new MatchImpl(this, dev, ref);
matches.addElement(m);
}
}
}
private void updateLife(Bundle b, long t) {
tempDrivers.put(b, new Long(System.currentTimeMillis() + t));
}
private boolean isUsed(ServiceReference sr) {
Bundle[] ba = sr.getUsingBundles();
if (ba != null) {
for (int i = 0; i < ba.length; i++) {
Bundle b = ba[i];
try {
for (int j = 0; j < drivers.size(); j++) {
DriverRef dr = (DriverRef) drivers.elementAt(j);
if (dr.sr.getBundle() == b)
return true;
}
} catch (Exception e) {
return true;
}
}
}
return false;
}
private void reapDrivers() {
long now = System.currentTimeMillis();
Bundle[] ba = bc.getBundles();
if (ba != null) {
for (int i = 0; i < ba.length; i++) {
try {
Bundle b = ba[i];
if (b.getLocation().startsWith(DYNAMIC_DRIVER_TAG)) {
Long expire = (Long) tempDrivers.get(b);
boolean inUse = false;
ServiceReference[] sra = b.getServicesInUse();
if (sra != null) {
for (int j = 0; j < sra.length; j++) {
if (isDevice.match(sra[j])) {
inUse = true;
break;
}
}
}
if (inUse) {
updateLife(b, LONG_LIFE);
} else if (expire == null) {
updateLife(b, SHORT_LIFE);
} else if (expire.longValue() < now) {
info("uninstalling " + b.getLocation());
b.uninstall();
}
}
} catch (Exception e) {
}
}
}
}
private void touchDevice(ServiceReference dev) {
if (newDevices.put(dev, dev) == null) {
synchronized (this) {
notifyAll();
}
}
}
private void touchAllDevices() {
boolean added = false;
try {
ServiceReference[] sra = bc.getServiceReferences(null,
DEVICE_FILTER);
if (sra != null) {
for (int i = 0; i < sra.length; i++) {
ServiceReference dev = sra[i];
if (newDevices.put(dev, dev) == null)
added = true;
}
}
} catch (Exception e) {
}
if (added) {
synchronized (this) {
notifyAll();
}
}
}
private void tellNotFound(ServiceReference dev) {
// NB: Should we avoid repeating the call to the same device?
info("no driver for " + showDevice(dev));
Object d = null;
try {
d = bc.getService(dev);
((Device) d).noDriverFound();
} catch (Exception e) {
} finally {
try {
bc.ungetService(dev);
} catch (Exception e1) {
}
}
}
private Dictionary /* String->Object */collectProperties(
ServiceReference sr) {
Dictionary props = new Hashtable();
String[] keys = sr.getPropertyKeys();
if (keys != null) {
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
props.put(key, sr.getProperty(key));
}
}
return props;
}
Bundle installBundle(String name, InputStream is) {
Bundle b = null;
try {
info("installing " + name);
b = bc.installBundle(name, is);
b.start();
updateLife(b, SHORT_LIFE);
return b;
} catch (Exception e) {
error("failed to install " + name);
try {
b.uninstall();
} catch (Exception e1) {
}
return null;
} finally {
try {
is.close();
} catch (Exception e1) {
}
}
}
void removeSRCachedMatch(ServiceReference sr) {
// NB: Index to speed up this process?
String pid = null;
try {
pid = (String) sr
.getProperty(org.osgi.framework.Constants.SERVICE_PID);
} catch (Exception e) {
}
if (pid != null)
return;
for (Enumeration e = cache.keys(); e.hasMoreElements();) {
Integer k = (Integer) e.nextElement();
MatchValue mv0 = (MatchValue) cache.get(k);
MatchValue mv = mv0;
while (mv != null) {
if (mv.dev != sr) {
// Keep it
mv0 = mv;
mv = mv.next;
} else if (mv == mv0) {
// Update hash table entry
mv0 = mv = mv.next;
if (mv != null)
cache.put(k, mv);
else
cache.remove(k);
} else {
// Link past this one
mv = mv.next;
mv0.next = mv;
}
}
}
}
int getCachedMatch(String drvid, ServiceReference dev) {
MatchValue mv = findMatch(drvid, dev, false);
return mv != null ? mv.match : MatchImpl.UNKNOWN;
}
void putCachedMatch(String drvid, ServiceReference dev, int match) {
MatchValue mv = findMatch(drvid, dev, true);
mv.match = match;
}
private MatchValue findMatch(String drvid, ServiceReference dev,
boolean create) {
String pid = null;
try {
pid = (String) dev
.getProperty(org.osgi.framework.Constants.SERVICE_PID);
} catch (Exception e) {
}
int k1 = pid != null ? pid.hashCode() : dev.hashCode();
int k2 = drvid.hashCode();
Integer key = new Integer(k1 + k2);
MatchValue mv0 = (MatchValue) cache.get(key);
MatchValue mv = mv0;
while (mv != null) {
if (drvid.equals(mv.drvid) && pid != null ? pid.equals(mv.pid)
: dev == mv.dev)
return mv;
mv = mv.next;
}
if (!create)
return null;
mv = new MatchValue();
mv.next = mv0;
mv.key = key;
mv.drvid = drvid;
mv.pid = pid;
if (pid == null)
mv.dev = dev;
cache.put(key, mv);
return mv;
}
private String showDevice(ServiceReference sr) {
StringBuffer sb = new StringBuffer();
Object o = sr
.getProperty(org.osgi.service.device.Constants.DEVICE_CATEGORY);
if (o instanceof String) {
sb.append(o);
} else if (o instanceof String[]) {
String[] dca = (String[]) o;
for (int i = 0; i < dca.length; i++) {
if (i > 0)
sb.append('_');
sb.append(dca[i]);
}
}
o = sr.getProperty(org.osgi.framework.Constants.SERVICE_ID);
if (o != null)
sb.append(o);
Bundle b = sr.getBundle();
if (b != null) {
sb.append('/');
sb.append(b.getBundleId());
}
return sb.toString();
}
private String showDriver(ServiceReference sr) {
StringBuffer sb = new StringBuffer();
String s = (String) sr
.getProperty(org.osgi.service.device.Constants.DRIVER_ID);
sb.append(s != null ? s : "driver");
Bundle b = sr.getBundle();
if (b != null) {
sb.append('/');
sb.append(b.getBundleId());
}
return sb.toString();
}
}