org.freedesktop.dbus.connections.impl.DBusConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dbus-java Show documentation
Show all versions of dbus-java Show documentation
Improved version of the DBus-Java library provided by freedesktop.org (https://dbus.freedesktop.org/doc/dbus-java/).
/*
D-Bus Java Implementation
Copyright (c) 2005-2006 Matthew Johnson
This program is free software; you can redistribute it and/or modify it
under the terms of either the GNU Lesser General Public License Version 2 or the
Academic Free Licence Version 2.1.
Full licence texts are included in the COPYING file with this program.
*/
package org.freedesktop.dbus.connections.impl;
import java.io.File;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusMatchRule;
import org.freedesktop.dbus.RemoteInvocationHandler;
import org.freedesktop.dbus.RemoteObject;
import org.freedesktop.dbus.SignalTuple;
import org.freedesktop.dbus.connections.AbstractConnection;
import org.freedesktop.dbus.errors.Error;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.freedesktop.dbus.exceptions.NotConnected;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.DBusSigHandler;
import org.freedesktop.dbus.interfaces.Introspectable;
import org.freedesktop.dbus.messages.DBusSignal;
import org.freedesktop.dbus.messages.ExportedObject;
import org.freedesktop.dbus.messages.MethodCall;
import org.freedesktop.dbus.types.UInt32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.hypfvieh.util.FileIoUtil;
import com.github.hypfvieh.util.StringUtil;
import com.github.hypfvieh.util.SystemUtil;
/**
* Handles a connection to DBus.
*
* This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made. Repeated calls to
* getConnection will return the same reference.
*
*
* Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency
* issues.
*
*/
public final class DBusConnection extends AbstractConnection {
private final Logger logger = LoggerFactory.getLogger(getClass());
public static final String DEFAULT_SYSTEM_BUS_ADDRESS =
"unix:path=/var/run/dbus/system_bus_socket";
private List busnames;
private static final ConcurrentMap CONNECTIONS = new ConcurrentHashMap<>();
private DBus dbus;
private final String machineId;
/** Count how many 'connections' we manage internally.
* This is required because a {@link DBusConnection} to the same address will always return the same object and
* the 'real' disconnection should only occur when there is no second/third/whatever connection is left. */
private final AtomicInteger concurrentConnections = new AtomicInteger(1);
/**
* Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. Will
* always register our own session to Dbus.
*
* @param _address The address of the bus to connect to
* @throws DBusException If there is a problem connecting to the Bus.
* @return {@link DBusConnection}
*/
public static DBusConnection getConnection(String _address) throws DBusException {
return getConnection(_address, true, true);
}
/**
* Connect to the BUS. If a connection already exists to the specified Bus and the shared-flag is true, a reference is returned.
* Will register our own session to DBus if registerSelf is true (default).
* A new connection is created every time if shared-flag is false.
*
* @param _address The address of the bus to connect to
* @param _registerSelf register own session in dbus
* @param _shared use a shared connections
* @throws DBusException If there is a problem connecting to the Bus.
* @return {@link DBusConnection}
*/
public static DBusConnection getConnection(String _address, boolean _registerSelf, boolean _shared)
throws DBusException {
// CONNECTIONS.getOrDefault(address, defaultValue)
if (_shared) {
synchronized (CONNECTIONS) {
DBusConnection c = CONNECTIONS.get(_address);
if (c != null) {
c.concurrentConnections.incrementAndGet();
return c;
} else {
c = new DBusConnection(_address, _registerSelf, getDbusMachineId());
// do not increment connection counter here, it always starts at 1 on new objects!
// c.getConcurrentConnections().incrementAndGet();
CONNECTIONS.put(_address, c);
return c;
}
}
} else {
return new DBusConnection(_address, _registerSelf, getDbusMachineId());
}
}
private static DBusConnection getConnection(Supplier _addressGenerator, boolean _registerSelf, boolean _shared) throws DBusException {
if (_addressGenerator == null) {
throw new DBusException("Invalid address generator");
}
String address = _addressGenerator.get();
if (address == null) {
throw new DBusException("null is not a valid DBUS address");
}
return getConnection(address, _registerSelf, _shared);
}
/**
* Connect to DBus.
* If a connection already exists to the specified Bus, a reference to it is returned.
*
* @param _bustype The Bus to connect to.
* @return {@link DBusConnection}
*
* @throws DBusException If there is a problem connecting to the Bus.
*
*/
public static DBusConnection getConnection(DBusBusType _bustype) throws DBusException {
return getConnection(_bustype, true);
}
/**
* Connect to DBus using a new connection even if there is already a connection established.
*
* @param _bustype The Bus to connect to.
*
* @return {@link DBusConnection}
*
* @throws DBusException If there is a problem connecting to the Bus.
*
*/
public static DBusConnection newConnection(DBusBusType _bustype) throws DBusException {
return getConnection(_bustype, false);
}
/**
* Connect to the BUS.
* If a connection to the specified Bus already exists and shared-flag is true, a reference to it is returned.
* Otherwise a new connection will be created.
*
* @param _bustype The Bus to connect to.
* @param _shared use shared connection
*
* @return {@link DBusConnection}
*
* @throws DBusException If there is a problem connecting to the Bus.
*
*/
public static DBusConnection getConnection(DBusBusType _bustype, boolean _shared) throws DBusException {
switch (_bustype) {
case SYSTEM:
DBusConnection systemConnection = getConnection(() -> {
String bus = System.getenv("DBUS_SYSTEM_BUS_ADDRESS");
if (bus == null) {
bus = DEFAULT_SYSTEM_BUS_ADDRESS;
}
return bus;
}, true, _shared);
return systemConnection;
case SESSION:
DBusConnection sessionConnection = getConnection(() -> {
String s = null;
// MacOS support: e.g DBUS_LAUNCHD_SESSION_BUS_SOCKET=/private/tmp/com.apple.launchd.4ojrKe6laI/unix_domain_listener
if (SystemUtil.isMacOs()) {
s = "unix:path=" + System.getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET");
} else { // all others (linux)
s = System.getenv("DBUS_SESSION_BUS_ADDRESS");
}
if (s == null) {
// address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<<
// $DISPLAY`
String display = System.getenv("DISPLAY");
if (null == display) {
throw new RuntimeException("Cannot Resolve Session Bus Address");
}
if (!display.startsWith(":") && display.contains(":")) { // display seems to be a remote display
// (e.g. X forward through SSH)
display = display.substring(display.indexOf(':'));
}
try {
String uuid = getDbusMachineId();
String homedir = System.getProperty("user.home");
File addressfile = new File(homedir + "/.dbus/session-bus",
uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1"));
if (!addressfile.exists()) {
throw new RuntimeException("Cannot Resolve Session Bus Address");
}
Properties readProperties = FileIoUtil.readProperties(addressfile);
String sessionAddress = readProperties.getProperty("DBUS_SESSION_BUS_ADDRESS");
if (StringUtil.isEmpty(sessionAddress)) {
throw new RuntimeException("Cannot Resolve Session Bus Address");
}
return sessionAddress;
} catch (DBusException _ex) {
throw new RuntimeException("Cannot Resolve Session Bus Address", _ex);
}
}
return s;
}, true, _shared);
return sessionConnection;
default:
throw new DBusException("Invalid Bus Type: " + _bustype);
}
}
private AtomicInteger getConcurrentConnections() {
return concurrentConnections;
}
/**
* Extracts the machine-id usually found in /var/lib/dbus/machine-id.
*
* @return machine-id string, never null
* @throws DBusException if machine-id could not be found
*/
public static String getDbusMachineId() throws DBusException {
File uuidfile = new File("/var/lib/dbus/machine-id");
if (!uuidfile.exists()) {
uuidfile = new File("/usr/local/var/lib/dbus/machine-id");
}
if (!uuidfile.exists()) {
throw new DBusException("Cannot Resolve Session Bus Address");
}
String uuid = FileIoUtil.readFileToString(uuidfile);
if (StringUtil.isEmpty(uuid)) {
throw new DBusException("Cannot Resolve Session Bus Address: MachineId file is empty.");
}
return uuid;
}
private DBusConnection(String _address, boolean _registerSelf, String _machineId) throws DBusException {
super(_address);
busnames = new ArrayList<>();
machineId = _machineId;
// start listening for calls
listen();
// register disconnect handlers
DBusSigHandler> h = new SigHandler();
addSigHandlerWithoutMatch(org.freedesktop.dbus.interfaces.Local.Disconnected.class, h);
addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h);
// register ourselves if not disabled
if (_registerSelf) {
dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class);
try {
busnames.add(dbus.Hello());
} catch (DBusExecutionException dbee) {
logger.debug("", dbee);
throw new DBusException(dbee.getMessage());
}
}
}
protected DBusInterface dynamicProxy(String _source, String _path) throws DBusException {
logger.debug("Introspecting {} on {} for dynamic proxy creation", _path, _source);
try {
Introspectable intro = getRemoteObject(_source, _path, Introspectable.class);
String data = intro.Introspect();
logger.trace("Got introspection data: {}", data);
String[] tags = data.split("[<>]");
List ifaces = new ArrayList<>();
for (String tag : tags) {
if (tag.startsWith("interface")) {
ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1"));
}
}
List> ifcs = new ArrayList<>();
for (String iface : ifaces) {
// if this is a default DBus interface, look for it in our package structure
if (iface.startsWith("org.freedesktop.DBus.")) {
iface = iface.replaceAll("^.*\\.([^\\.]+)$", DBusInterface.class.getPackage().getName() + ".$1");
}
logger.debug("Trying interface {}", iface);
int j = 0;
while (j >= 0) {
try {
Class> ifclass = Class.forName(iface);
if (!ifcs.contains(ifclass)) {
ifcs.add(ifclass);
}
break;
} catch (Exception e) {
}
j = iface.lastIndexOf(".");
char[] cs = iface.toCharArray();
if (j >= 0) {
cs[j] = '$';
iface = String.valueOf(cs);
}
}
}
// interface could not be found, we guess that this exported object at least support DBusInterface
if (ifcs.isEmpty()) {
// throw new DBusException("Could not find an interface to cast to");
ifcs.add(DBusInterface.class);
}
RemoteObject ro = new RemoteObject(_source, _path, null, false);
DBusInterface newi = (DBusInterface) Proxy.newProxyInstance(ifcs.get(0).getClassLoader(),
ifcs.toArray(new Class[0]), new RemoteInvocationHandler(this, ro));
getImportedObjects().put(newi, ro);
return newi;
} catch (Exception e) {
logger.debug("", e);
throw new DBusException(
String.format("Failed to create proxy object for %s exported by %s. Reason: %s", _path,
_source, e.getMessage()));
}
}
@Override
public DBusInterface getExportedObject(String _source, String _path) throws DBusException {
ExportedObject o = null;
synchronized (getExportedObjects()) {
o = getExportedObjects().get(_path);
}
if (null != o && null == o.getObject().get()) {
unExportObject(_path);
o = null;
}
if (null != o) {
return o.getObject().get();
}
if (null == _source) {
throw new DBusException("Not an object exported by this connection and no remote specified");
}
return dynamicProxy(_source, _path);
}
/**
* Release a bus name. Releases the name so that other people can use it
*
* @param _busname
* The name to release. MUST be in dot-notation like "org.freedesktop.local"
* @throws DBusException
* If the busname is incorrectly formatted.
*/
public void releaseBusName(String _busname) throws DBusException {
if (!_busname.matches(BUSNAME_REGEX) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name");
}
synchronized (this.busnames) {
try {
dbus.ReleaseName(_busname);
} catch (DBusExecutionException dbee) {
logger.debug("", dbee);
throw new DBusException(dbee.getMessage());
}
this.busnames.remove(_busname);
}
}
/**
* Request a bus name. Request the well known name that this should respond to on the Bus.
*
* @param _busname
* The name to respond to. MUST be in dot-notation like "org.freedesktop.local"
* @throws DBusException
* If the register name failed, or our name already exists on the bus. or if busname is incorrectly
* formatted.
*/
public void requestBusName(String _busname) throws DBusException {
if (!_busname.matches(BUSNAME_REGEX) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name");
}
synchronized (this.busnames) {
UInt32 rv;
try {
rv = dbus.RequestName(_busname,
new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING | DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE));
} catch (DBusExecutionException dbee) {
logger.debug("", dbee);
throw new DBusException(dbee.getMessage());
}
switch (rv.intValue()) {
case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
break;
case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
throw new DBusException("Failed to register bus name");
case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS:
throw new DBusException("Failed to register bus name");
case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
break;
default:
break;
}
this.busnames.add(_busname);
}
}
/**
* Returns the unique name of this connection.
*
* @return unique name
*/
public String getUniqueName() {
return busnames.get(0);
}
/**
* Returns all the names owned by this connection.
*
* @return connection names
*/
public String[] getNames() {
Set names = new TreeSet();
names.addAll(busnames);
return names.toArray(new String[0]);
}
public I getPeerRemoteObject(String _busname, String _objectpath, Class _type)
throws DBusException {
return getPeerRemoteObject(_busname, _objectpath, _type, true);
}
/**
* Return a reference to a remote object. This method will resolve the well known name (if given) to a unique bus
* name when you call it. This means that if a well known name is released by one process and acquired by another
* calls to objects gained from this method will continue to operate on the original process.
*
* This method will use bus introspection to determine the interfaces on a remote object and so may block and
* may fail. The resulting proxy object will, however, be castable to any interface it implements. It will
* also autostart the process if applicable. Also note that the resulting proxy may fail to execute the correct
* method with overloaded methods and that complex types may fail in interesting ways. Basically, if something odd
* happens, try specifying the interface explicitly.
*
* @param _busname
* The bus name to connect to. Usually a well known bus name in dot-notation (such as
* "org.freedesktop.local") or may be a DBus address such as ":1-16".
* @param _objectpath
* The path on which the process is exporting the object.$
* @return A reference to a remote object.
* @throws ClassCastException
* If type is not a sub-type of DBusInterface
* @throws DBusException
* If busname or objectpath are incorrectly formatted.
*/
public DBusInterface getPeerRemoteObject(String _busname, String _objectpath) throws DBusException {
if (null == _busname) {
throw new DBusException("Invalid bus name: null");
}
if ((!_busname.matches(BUSNAME_REGEX) && !_busname.matches(CONNID_REGEX)) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _busname);
}
String unique = dbus.GetNameOwner(_busname);
return dynamicProxy(unique, _objectpath);
}
/**
* Return a reference to a remote object. This method will always refer to the well known name (if given) rather
* than resolving it to a unique bus name. In particular this means that if a process providing the well known name
* disappears and is taken over by another process proxy objects gained by this method will make calls on the new
* proccess.
*
* This method will use bus introspection to determine the interfaces on a remote object and so may block and
* may fail. The resulting proxy object will, however, be castable to any interface it implements. It will
* also autostart the process if applicable. Also note that the resulting proxy may fail to execute the correct
* method with overloaded methods and that complex types may fail in interesting ways. Basically, if something odd
* happens, try specifying the interface explicitly.
*
* @param _busname
* The bus name to connect to. Usually a well known bus name name in dot-notation (such as
* "org.freedesktop.local") or may be a DBus address such as ":1-16".
* @param _objectpath
* The path on which the process is exporting the object.
* @return A reference to a remote object.
* @throws ClassCastException
* If type is not a sub-type of DBusInterface
* @throws DBusException
* If busname or objectpath are incorrectly formatted.
*/
public DBusInterface getRemoteObject(String _busname, String _objectpath) throws DBusException {
if (null == _busname) {
throw new DBusException("Invalid bus name: null");
}
if (null == _objectpath) {
throw new DBusException("Invalid object path: null");
}
if ((!_busname.matches(BUSNAME_REGEX) && !_busname.matches(CONNID_REGEX)) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _busname);
}
if (!_objectpath.matches(OBJECT_REGEX) || _objectpath.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid object path: " + _objectpath);
}
return dynamicProxy(_busname, _objectpath);
}
/**
* Return a reference to a remote object. This method will resolve the well known name (if given) to a unique bus
* name when you call it. This means that if a well known name is released by one process and acquired by another
* calls to objects gained from this method will continue to operate on the original process.
*
* @param
* class extending {@link DBusInterface}
* @param _busname
* The bus name to connect to. Usually a well known bus name in dot-notation (such as
* "org.freedesktop.local") or may be a DBus address such as ":1-16".
* @param _objectpath
* The path on which the process is exporting the object.$
* @param _type
* The interface they are exporting it on. This type must have the same full class name and exposed
* method signatures as the interface the remote object is exporting.
* @param _autostart
* Disable/Enable auto-starting of services in response to calls on this object. Default is enabled; when
* calling a method with auto-start enabled, if the destination is a well-known name and is not owned the
* bus will attempt to start a process to take the name. When disabled an error is returned immediately.
* @return A reference to a remote object.
* @throws ClassCastException
* If type is not a sub-type of DBusInterface
* @throws DBusException
* If busname or objectpath are incorrectly formatted or type is not in a package.
*/
public I getPeerRemoteObject(String _busname, String _objectpath, Class _type,
boolean _autostart) throws DBusException {
if (null == _busname) {
throw new DBusException("Invalid bus name: null");
}
if ((!_busname.matches(BUSNAME_REGEX) && !_busname.matches(CONNID_REGEX)) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _busname);
}
String unique = dbus.GetNameOwner(_busname);
return getRemoteObject(unique, _objectpath, _type, _autostart);
}
/**
* Return a reference to a remote object. This method will always refer to the well known name (if given) rather
* than resolving it to a unique bus name. In particular this means that if a process providing the well known name
* disappears and is taken over by another process proxy objects gained by this method will make calls on the new
* proccess.
*
* @param
* class extending {@link DBusInterface}
* @param _busname
* The bus name to connect to. Usually a well known bus name name in dot-notation (such as
* "org.freedesktop.local") or may be a DBus address such as ":1-16".
* @param _objectpath
* The path on which the process is exporting the object.
* @param _type
* The interface they are exporting it on. This type must have the same full class name and exposed
* method signatures as the interface the remote object is exporting.
* @return A reference to a remote object.
* @throws ClassCastException
* If type is not a sub-type of DBusInterface
* @throws DBusException
* If busname or objectpath are incorrectly formatted or type is not in a package.
*/
public I getRemoteObject(String _busname, String _objectpath, Class _type)
throws DBusException {
return getRemoteObject(_busname, _objectpath, _type, true);
}
/**
* Return a reference to a remote object. This method will always refer to the well known name (if given) rather
* than resolving it to a unique bus name. In particular this means that if a process providing the well known name
* disappears and is taken over by another process proxy objects gained by this method will make calls on the new
* proccess.
*
* @param
* class extending {@link DBusInterface}
* @param _busname
* The bus name to connect to. Usually a well known bus name name in dot-notation (such as
* "org.freedesktop.local") or may be a DBus address such as ":1-16".
* @param _objectpath
* The path on which the process is exporting the object.
* @param _type
* The interface they are exporting it on. This type must have the same full class name and exposed
* method signatures as the interface the remote object is exporting.
* @param _autostart
* Disable/Enable auto-starting of services in response to calls on this object. Default is enabled; when
* calling a method with auto-start enabled, if the destination is a well-known name and is not owned the
* bus will attempt to start a process to take the name. When disabled an error is returned immediately.
* @return A reference to a remote object.
* @throws ClassCastException
* If type is not a sub-type of DBusInterface
* @throws DBusException
* If busname or objectpath are incorrectly formatted or type is not in a package.
*/
@SuppressWarnings("unchecked")
public I getRemoteObject(String _busname, String _objectpath, Class _type,
boolean _autostart) throws DBusException {
if (null == _busname) {
throw new DBusException("Invalid bus name: null");
}
if (null == _objectpath) {
throw new DBusException("Invalid object path: null");
}
if (null == _type) {
throw new ClassCastException("Not A DBus Interface");
}
if ((!_busname.matches(BUSNAME_REGEX) && !_busname.matches(CONNID_REGEX)) || _busname.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _busname);
}
if (!_objectpath.matches(OBJECT_REGEX) || _objectpath.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid object path: " + _objectpath);
}
if (!DBusInterface.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Interface");
}
// don't let people import things which don't have a
// valid D-Bus interface name
if (_type.getName().equals(_type.getSimpleName())) {
throw new DBusException("DBusInterfaces cannot be declared outside a package");
}
RemoteObject ro = new RemoteObject(_busname, _objectpath, _type, _autostart);
I i = (I) Proxy.newProxyInstance(_type.getClassLoader(), new Class[] {
_type
}, new RemoteInvocationHandler(this, ro));
getImportedObjects().put(i, ro);
return i;
}
/**
* Remove a Signal Handler. Stops listening for this signal.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @param _source
* The source of the signal.
* @param _handler
* the handler
* @throws DBusException
* If listening for the signal on the bus failed.
* @throws ClassCastException
* If type is not a sub-type of DBusSignal.
*/
public void removeSigHandler(Class _type, String _source, DBusSigHandler _handler)
throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
if (_source.matches(BUSNAME_REGEX)) {
throw new DBusException(
"Cannot watch for signals based on well known bus name as source, only unique names.");
}
if (!_source.matches(CONNID_REGEX) || _source.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _source);
}
removeSigHandler(new DBusMatchRule(_type, _source, null), _handler);
}
/**
* Remove a Signal Handler. Stops listening for this signal.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @param _source
* The source of the signal.
* @param _object
* The object emitting the signal.
* @param _handler
* the handler
* @throws DBusException
* If listening for the signal on the bus failed.
* @throws ClassCastException
* If type is not a sub-type of DBusSignal.
*/
public void removeSigHandler(Class _type, String _source, DBusInterface _object,
DBusSigHandler _handler) throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
if (_source.matches(BUSNAME_REGEX)) {
throw new DBusException(
"Cannot watch for signals based on well known bus name as source, only unique names.");
}
if (!_source.matches(CONNID_REGEX) || _source.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _source);
}
String objectpath = getImportedObjects().get(_object).getObjectPath();
if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid object path: " + objectpath);
}
removeSigHandler(new DBusMatchRule(_type, _source, objectpath), _handler);
}
@Override
protected void removeSigHandler(DBusMatchRule _rule, DBusSigHandler _handler)
throws DBusException {
SignalTuple key = new SignalTuple(_rule.getInterface(), _rule.getMember(), _rule.getObject(), _rule.getSource());
synchronized (getHandledSignals()) {
List> v = getHandledSignals().get(key);
if (null != v) {
v.remove(_handler);
if (0 == v.size()) {
getHandledSignals().remove(key);
try {
dbus.RemoveMatch(_rule.toString());
} catch (NotConnected exNc) {
logger.debug("No connection.", exNc);
} catch (DBusExecutionException dbee) {
logger.debug("", dbee);
throw new DBusException(dbee);
}
}
}
}
}
/**
* Add a Signal Handler. Adds a signal handler to call when a signal is received which matches the specified type,
* name and source.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @param _source
* The process which will send the signal. This MUST be a unique bus name and not a well known
* name.
* @param _handler
* The handler to call when a signal is received.
* @throws DBusException
* If listening for the signal on the bus failed.
* @throws ClassCastException
* If type is not a sub-type of DBusSignal.
*/
public void addSigHandler(Class _type, String _source, DBusSigHandler _handler)
throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
if (_source.matches(BUSNAME_REGEX)) {
throw new DBusException(
"Cannot watch for signals based on well known bus name as source, only unique names.");
}
if (!_source.matches(CONNID_REGEX) || _source.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _source);
}
addSigHandler(new DBusMatchRule(_type, _source, null), (DBusSigHandler extends DBusSignal>) _handler);
}
/**
* Add a Signal Handler. Adds a signal handler to call when a signal is received which matches the specified type,
* name, source and object.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @param _source
* The process which will send the signal. This MUST be a unique bus name and not a well known
* name.
* @param _object
* The object from which the signal will be emitted
* @param _handler
* The handler to call when a signal is received.
* @throws DBusException
* If listening for the signal on the bus failed.
* @throws ClassCastException
* If type is not a sub-type of DBusSignal.
*/
public void addSigHandler(Class _type, String _source, DBusInterface _object,
DBusSigHandler _handler) throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
if (_source.matches(BUSNAME_REGEX)) {
throw new DBusException(
"Cannot watch for signals based on well known bus name as source, only unique names.");
}
if (!_source.matches(CONNID_REGEX) || _source.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid bus name: " + _source);
}
String objectpath = getImportedObjects().get(_object).getObjectPath();
if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) {
throw new DBusException("Invalid object path: " + objectpath);
}
addSigHandler(new DBusMatchRule(_type, _source, objectpath), (DBusSigHandler extends DBusSignal>) _handler);
}
@Override
public void addSigHandler(DBusMatchRule _rule, DBusSigHandler _handler)
throws DBusException {
try {
dbus.AddMatch(_rule.toString());
} catch (DBusExecutionException dbee) {
logger.debug("", dbee);
throw new DBusException(dbee.getMessage());
}
SignalTuple key = new SignalTuple(_rule.getInterface(), _rule.getMember(), _rule.getObject(), _rule.getSource());
synchronized (getHandledSignals()) {
List> v = getHandledSignals().get(key);
if (null == v) {
v = new ArrayList<>();
v.add(_handler);
getHandledSignals().put(key, v);
} else {
v.add(_handler);
}
}
}
/**
* Disconnect from the Bus.
* This only disconnects when the last reference to the bus has disconnect called on it or
* has been destroyed.
*/
@Override
public void disconnect() {
synchronized (CONNECTIONS) {
DBusConnection connection = CONNECTIONS.get(getAddress().getRawAddress());
if (connection != null) {
if (connection.getConcurrentConnections().get() <= 1) { // one left, this should be ourselfs
logger.debug("Disconnecting last remaining DBusConnection");
// Set all pending messages to have an error.
try {
Error err = new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected",
0, "s", new Object[] {
"Disconnected"
});
cleanupPendingCalls(err, true);
synchronized (getPendingErrorQueue()) {
getPendingErrorQueue().add(err);
}
} catch (DBusException dbe) {
}
CONNECTIONS.remove(getAddress().getRawAddress());
super.disconnect();
} else {
logger.debug("Still {} connections left, decreasing connection counter", connection.getConcurrentConnections().get() -1);
connection.getConcurrentConnections().addAndGet(-1);
}
}
}
}
private void cleanupPendingCalls(Error _err, boolean _clearPendingCalls) throws DBusException {
synchronized (getPendingCalls()) {
Iterator> iter = getPendingCalls().entrySet().iterator();
while (iter.hasNext()) {
Entry entry = iter.next();
if (entry.getKey() != -1) {
MethodCall m = entry.getValue();
iter.remove();
if (m != null) {
m.setReply(_err);
}
}
}
if (_clearPendingCalls) {
getPendingCalls().clear();
}
}
}
private class SigHandler implements DBusSigHandler {
@Override
public void handle(DBusSignal _signal) {
if (_signal instanceof org.freedesktop.dbus.interfaces.Local.Disconnected) {
logger.debug("Handling Disconnected signal from bus");
try {
Error err = new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0,
"s", new Object[] {
"Disconnected"
});
cleanupPendingCalls(err, false);
synchronized (getPendingErrorQueue()) {
getPendingErrorQueue().add(err);
}
} catch (DBusException exDb) {
}
} else if (_signal instanceof org.freedesktop.DBus.NameAcquired) {
busnames.add(((org.freedesktop.DBus.NameAcquired) _signal).name);
}
}
}
@Override
public String getMachineId() {
return machineId;
}
public static enum DBusBusType {
/**
* System Bus
*/
SYSTEM,
/**
* Session Bus
*/
SESSION;
}
}