org.freedesktop.dbus.connections.AbstractConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dbus-java-core Show documentation
Show all versions of dbus-java-core Show documentation
Improved version of the DBus-Java library provided by freedesktop.org (https://dbus.freedesktop.org/doc/dbus-java/).
package org.freedesktop.dbus.connections;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import org.freedesktop.dbus.DBusAsyncReply;
import org.freedesktop.dbus.DBusCallInfo;
import org.freedesktop.dbus.DBusMatchRule;
import org.freedesktop.dbus.InternalSignal;
import org.freedesktop.dbus.Marshalling;
import org.freedesktop.dbus.MethodTuple;
import org.freedesktop.dbus.RemoteInvocationHandler;
import org.freedesktop.dbus.RemoteObject;
import org.freedesktop.dbus.SignalTuple;
import org.freedesktop.dbus.connections.transports.AbstractTransport;
import org.freedesktop.dbus.connections.transports.TransportBuilder;
import org.freedesktop.dbus.errors.Error;
import org.freedesktop.dbus.errors.UnknownMethod;
import org.freedesktop.dbus.errors.UnknownObject;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.freedesktop.dbus.exceptions.FatalDBusException;
import org.freedesktop.dbus.exceptions.NotConnected;
import org.freedesktop.dbus.interfaces.CallbackHandler;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.DBusSigHandler;
import org.freedesktop.dbus.messages.DBusSignal;
import org.freedesktop.dbus.messages.ExportedObject;
import org.freedesktop.dbus.messages.Message;
import org.freedesktop.dbus.messages.MethodCall;
import org.freedesktop.dbus.messages.MethodReturn;
import org.freedesktop.dbus.messages.ObjectTree;
import org.freedesktop.dbus.utils.LoggingHelper;
import org.freedesktop.dbus.utils.NameableThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles a connection to DBus.
*/
public abstract class AbstractConnection implements Closeable {
private static final Map INFOMAP = new ConcurrentHashMap<>();
/**
* Default thread pool size.
*/
private static final int THREADCOUNT = 4;
/**
* Connect timeout, used for TCP only.
*/
public static final int TCP_CONNECT_TIMEOUT = 100000;
/**
* System property name containing the DBUS TCP SESSION address used by dbus-java DBusDaemon in TCP mode.
*/
public static final String TCP_ADDRESS_PROPERTY = "DBUS_TCP_SESSION";
/** Lame method to setup endianness used on DBus messages */
private static byte endianness = getSystemEndianness();
public static final boolean FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS"));
public static final Pattern BUSNAME_REGEX = Pattern.compile("^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$");
public static final Pattern CONNID_REGEX = Pattern.compile("^:[0-9]*\\.[0-9]*$");
public static final Pattern OBJECT_REGEX_PATTERN = Pattern.compile("^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$");
public static final Pattern DOLLAR_PATTERN = Pattern.compile("[$]");
public static final int MAX_ARRAY_LENGTH = 67108864;
public static final int MAX_NAME_LENGTH = 255;
private final Logger logger;
private final ObjectTree objectTree;
private final Map exportedObjects;
private final Map importedObjects;
private final PendingCallbackManager callbackManager;
private final FallbackContainer fallbackContainer;
private final Queue pendingErrorQueue;
private final Map>> handledSignals;
private final Map>> genericHandledSignals;
private final Map pendingCalls;
private final IncomingMessageThread readerThread;
// private final SenderThread senderThread;
private final ExecutorService senderService;
private boolean weakreferences = false;
private volatile boolean connected = false;
private AbstractTransport transport;
private volatile ThreadPoolExecutor workerThreadPool;
private final ReadWriteLock workerThreadPoolLock =
new ReentrantReadWriteLock();
private final TransportBuilder transportBuilder;
protected AbstractConnection(String address, int timeout) throws DBusException {
logger = LoggerFactory.getLogger(getClass());
exportedObjects = new HashMap<>();
importedObjects = new ConcurrentHashMap<>();
exportedObjects.put(null, new ExportedObject(new GlobalHandler(this), weakreferences));
handledSignals = new ConcurrentHashMap<>();
genericHandledSignals = new ConcurrentHashMap<>();
pendingCalls = Collections.synchronizedMap(new LinkedHashMap<>());
callbackManager = new PendingCallbackManager();
pendingErrorQueue = new ConcurrentLinkedQueue<>();
workerThreadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREADCOUNT,
new NameableThreadFactory("DBus Worker Thread-", false));
senderService =
Executors.newFixedThreadPool(1, new NameableThreadFactory("DBus Sender Thread-", false));
objectTree = new ObjectTree();
fallbackContainer = new FallbackContainer();
readerThread = new IncomingMessageThread(this);
try {
transportBuilder = TransportBuilder.create(address);
transport = transportBuilder.withTimeout(timeout).build();
connected = true;
} catch (IOException | DBusException _ex) {
logger.debug("Error creating transport", _ex);
disconnect();
throw new DBusException("Failed to connect to bus: " + _ex.getMessage(), _ex);
}
}
public abstract DBusInterface getExportedObject(String source, String path) throws DBusException;
/**
* Remove a match rule with the given {@link DBusSigHandler}.
* The rule will only be removed from DBus if no other additional handlers are registered to the same rule.
*
* @param _rule rule to remove
* @param _handler handler to remove
*
* @param signal type
*
* @throws DBusException on error
*/
protected abstract void removeSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException;
/**
* Add a signal handler with the given {@link DBusMatchRule} to DBus.
* The rule will be added to DBus if it was not added before.
* If the rule was already added, the signal handler is added to the internal map receiving
* the same signal as the first (and additional) handlers for this rule.
*
* @param _rule rule to add
* @param _handler handler to use
*
* @param signal type
*
* @throws DBusException on error
*/
protected abstract void addSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException;
/**
* Remove a generic signal handler with the given {@link DBusMatchRule}.
* The rule will only be removed from DBus if no other additional handlers are registered to the same rule.
*
* @param _rule rule to remove
* @param _handler handler to remove
* @throws DBusException on error
*/
protected abstract void removeGenericSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException;
/**
* Adds a {@link DBusMatchRule} to with a generic signal handler.
* Generic signal handlers allow receiving different signals with the same handler.
* If the rule was already added, the signal handler is added to the internal map receiving
* the same signal as the first (and additional) handlers for this rule.
*
* @param _rule rule to add
* @param _handler handler to use
* @throws DBusException on error
*/
protected abstract void addGenericSigHandler(DBusMatchRule _rule, DBusSigHandler _handler) throws DBusException;
/**
* The generated UUID of this machine.
* @return String
*/
public abstract String getMachineId();
/**
* Start reading and sending messages.
*/
protected void listen() {
readerThread.start();
}
/**
* Change the number of worker threads to receive method calls and handle signals. Default is 4 threads
*
* @param _newPoolSize
* The new number of worker Threads to use.
*/
public void changeThreadCount(byte _newPoolSize) {
if (workerThreadPool.getMaximumPoolSize() != _newPoolSize) {
workerThreadPoolLock.writeLock().lock();
try {
List remainingTasks = workerThreadPool.shutdownNow(); // kill previous threadpool
workerThreadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(_newPoolSize,
new NameableThreadFactory("DbusWorkerThreads", false));
// re-schedule previously waiting tasks
for (Runnable runnable : remainingTasks) {
workerThreadPool.execute(runnable);
}
} finally {
workerThreadPoolLock.writeLock().unlock();
}
}
}
public String getExportedObject(DBusInterface _interface) throws DBusException {
Optional> foundInterface =
getExportedObjects().entrySet().stream()
.filter(e -> _interface.equals(e.getValue().getObject().get()))
.findFirst();
if (foundInterface.isPresent()) {
return foundInterface.get().getKey();
} else {
RemoteObject rObj = getImportedObjects().get(_interface);
if (rObj != null) {
String s = rObj.getObjectPath();
if (s != null) {
return s;
}
}
throw new DBusException("Not an object exported or imported by this connection");
}
}
/**
* If set to true the bus will not hold a strong reference to exported objects. If they go out of scope they will
* automatically be unexported from the bus. The default is to hold a strong reference, which means objects must be
* explicitly unexported before they will be garbage collected.
*
* @param _weakreferences
* reference
*/
public void setWeakReferences(boolean _weakreferences) {
this.weakreferences = _weakreferences;
}
/**
* Export an object so that its methods can be called on DBus.
*
* @param _objectPath
* The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local", and
* SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but
* an object may be exposed on several paths at once.
* @param _object
* The object to export.
* @throws DBusException
* If the objectpath is already exporting an object. or if objectpath is incorrectly formatted,
*/
public void exportObject(String _objectPath, DBusInterface _object) throws DBusException {
if (null == _objectPath || "".equals(_objectPath)) {
throw new DBusException("Must Specify an Object Path");
}
if (_objectPath.length() > MAX_NAME_LENGTH || !(OBJECT_REGEX_PATTERN.matcher(_objectPath).matches())) {
throw new DBusException("Invalid object path: " + _objectPath);
}
synchronized (getExportedObjects()) {
if (null != getExportedObjects().get(_objectPath)) {
throw new DBusException("Object already exported");
}
ExportedObject eo = new ExportedObject(_object, weakreferences);
getExportedObjects().put(_objectPath, eo);
synchronized (getObjectTree()) {
getObjectTree().add(_objectPath, eo, eo.getIntrospectiondata());
}
}
}
/**
* Export an object so that its methods can be called on DBus. The path to the object will be taken from the
* {@link DBusInterface#getObjectPath()} method, make sure it is implemented and returns immutable value.
* If you want export object with multiple paths, please use {@link AbstractConnection#exportObject(String, DBusInterface)}.
*
* @param _object
* The object to export.
* @throws DBusException
* If the object path is already exporting an object or if object path is incorrectly formatted.
*/
public void exportObject(DBusInterface _object) throws DBusException {
Objects.requireNonNull(_object, "object must not be null");
exportObject(_object.getObjectPath(), _object);
}
/**
* Export an object as a fallback object. This object will have it's methods invoked for all paths starting with
* this object path.
*
* @param _objectPrefix
* The path below which the fallback handles calls. MUST be in slash-notation, like
* "/org/freedesktop/Local",
* @param _object
* The object to export.
* @throws DBusException
* If the objectpath is incorrectly formatted,
*/
public void addFallback(String _objectPrefix, DBusInterface _object) throws DBusException {
if (null == _objectPrefix || "".equals(_objectPrefix)) {
throw new DBusException("Must Specify an Object Path");
}
if (_objectPrefix.length() > MAX_NAME_LENGTH || !OBJECT_REGEX_PATTERN.matcher(_objectPrefix).matches()) {
throw new DBusException("Invalid object path: " + _objectPrefix);
}
ExportedObject eo = new ExportedObject(_object, weakreferences);
fallbackContainer.add(_objectPrefix, eo);
}
/**
* Remove a fallback
*
* @param _objectprefix
* The prefix to remove the fallback for.
*/
public void removeFallback(String _objectprefix) {
fallbackContainer.remove(_objectprefix);
}
/**
* Stop Exporting an object
*
* @param _objectpath
* The objectpath to stop exporting.
*/
public void unExportObject(String _objectpath) {
synchronized (getExportedObjects()) {
getExportedObjects().remove(_objectpath);
getObjectTree().remove(_objectpath);
}
}
/**
* Send a message or signal to the DBus daemon.
* @param _message message to send
*/
public void sendMessage(Message _message) {
Runnable runnable = new Runnable() {
@Override
public void run() {
sendMessageInternally(_message);
}
};
senderService.execute(runnable);
}
/**
* Remove a Signal Handler. Stops listening for this signal.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @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, DBusSigHandler _handler) throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
removeSigHandler(new DBusMatchRule(_type), _handler);
}
/**
* Remove a Signal Handler. Stops listening for this signal.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @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, DBusInterface _object, DBusSigHandler _handler)
throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
String objectpath = getImportedObjects().get(_object).getObjectPath();
if (objectpath.length() > MAX_NAME_LENGTH || !OBJECT_REGEX_PATTERN.matcher(objectpath).matches()) {
throw new DBusException("Invalid object path: " + objectpath);
}
removeSigHandler(new DBusMatchRule(_type, null, objectpath), _handler);
}
/**
* Add a Signal Handler. Adds a signal handler to call when a signal is received which matches the specified type
* and name.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @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, DBusSigHandler _handler) throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
addSigHandler(new DBusMatchRule(_type), _handler);
}
/**
* Add a Signal Handler. Adds a signal handler to call when a signal is received which matches the specified type,
* name and object.
*
* @param
* class extending {@link DBusSignal}
* @param _type
* The signal to watch for.
* @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, DBusInterface _object, DBusSigHandler _handler)
throws DBusException {
if (!DBusSignal.class.isAssignableFrom(_type)) {
throw new ClassCastException("Not A DBus Signal");
}
RemoteObject rObj = getImportedObjects().get(_object);
if (rObj == null) {
throw new DBusException("Not an object exported or imported by this connection");
}
String objectpath = rObj.getObjectPath();
if (objectpath.length() > MAX_NAME_LENGTH || !OBJECT_REGEX_PATTERN.matcher(objectpath).matches()) {
throw new DBusException("Invalid object path: " + objectpath);
}
addSigHandler(new DBusMatchRule(_type, null, objectpath), _handler);
}
protected void addSigHandlerWithoutMatch(Class extends DBusSignal> signal,
DBusSigHandler handler) throws DBusException {
DBusMatchRule rule = new DBusMatchRule(signal);
SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
synchronized (getHandledSignals()) {
Queue> v = getHandledSignals().get(key);
if (null == v) {
v = new ConcurrentLinkedQueue<>();
v.add(handler);
getHandledSignals().put(key, v);
} else {
v.add(handler);
}
}
}
/**
* Special disconnect method which may be used whenever some cleanup before or after
* disconnection to DBus is required.
* @param _before action execute before actual disconnect, null if not needed
* @param _after action execute after disconnect, null if not needed
*/
protected synchronized void disconnect(IDisconnectAction _before, IDisconnectAction _after) {
if (_before != null) {
_before.perform();
}
internalDisconnect();
if (_after != null) {
_after.perform();
}
}
/**
* Disconnects the DBus session.
* This method is private as it should never be overwritten by subclasses,
* otherwise we have an endless recursion when using {@link #disconnect(IDisconnectAction, IDisconnectAction)}
* which then will cause a StackOverflowError.
*/
private synchronized void internalDisconnect() {
if (connected == false) { // already disconnected
logger.debug("Ignoring disconnect, already disconnected");
return;
}
// stop the main thread
connected = false;
logger.debug("Sending disconnected signal");
try {
handleMessage(new org.freedesktop.dbus.interfaces.Local.Disconnected("/"), false);
} catch (Exception ex) {
logger.debug("Exception while disconnecting", ex);
}
logger.debug("Disconnecting Abstract Connection");
// stop reading new messages
readerThread.terminate();
// terminate the signal handling pool
workerThreadPoolLock.writeLock().lock();
try {
// try to wait for all pending tasks.
workerThreadPool.shutdown();
workerThreadPool.awaitTermination(10, TimeUnit.SECONDS); // 10 seconds should be enough, otherwise fail
} catch (InterruptedException _ex) {
logger.debug("Interrupted while waiting for worker threads to be terminated.", _ex);
} finally {
workerThreadPoolLock.writeLock().unlock();
}
// shutdown sender executor service, send all remaining messages in main thread
for (Runnable runnable : senderService.shutdownNow()) {
runnable.run();
}
// disconnect from the transport layer
try {
if (transport != null) {
transport.close();
transport = null;
}
} catch (IOException exIo) {
logger.debug("Exception while disconnecting transport.", exIo);
}
// stop all the workers
workerThreadPoolLock.writeLock().lock();
try {
if (!workerThreadPool.isTerminated()) { // try forceful shutdown
workerThreadPool.shutdownNow();
}
} finally {
workerThreadPoolLock.writeLock().unlock();
}
}
/**
* Disconnect from the Bus.
*/
public synchronized void disconnect() {
internalDisconnect();
}
/**
* Disconnect this session (for use in try-with-resources).
*/
@Override
public void close() throws IOException {
disconnect();
}
/**
* Call a method asynchronously and set a callback. This handler will be called in a separate thread.
*
* @param
* whatever
* @param object
* The remote object on which to call the method.
* @param m
* The name of the method on the interface to call.
* @param callback
* The callback handler.
* @param parameters
* The parameters to call the method with.
*/
public void callWithCallback(DBusInterface object, String m, CallbackHandler callback,
Object... parameters) {
logger.trace("callWithCallback({}, {}, {})", object, m, callback);
Class>[] types = createTypesArray( parameters );
RemoteObject ro = getImportedObjects().get(object);
try {
Method me;
if (null == ro.getInterface()) {
me = object.getClass().getMethod(m, types);
} else {
me = ro.getInterface().getMethod(m, types);
}
RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK,
callback, parameters);
} catch (DBusExecutionException exEe) {
logger.debug("", exEe);
throw exEe;
} catch (Exception e) {
logger.debug("", e);
throw new DBusExecutionException(e.getMessage());
}
}
/**
* Call a method asynchronously and get a handle with which to get the reply.
*
* @param _object
* The remote object on which to call the method.
* @param _method
* The name of the method on the interface to call.
* @param _parameters
* The parameters to call the method with.
* @return A handle to the call.
*/
public DBusAsyncReply> callMethodAsync(DBusInterface _object, String _method, Object... _parameters) {
Class>[] types = createTypesArray( _parameters );
RemoteObject ro = getImportedObjects().get(_object);
try {
Method me;
if (null == ro.getInterface()) {
me = _object.getClass().getMethod(_method, types);
} else {
me = ro.getInterface().getMethod(_method, types);
}
return (DBusAsyncReply>) RemoteInvocationHandler.executeRemoteMethod(ro, me, this,
RemoteInvocationHandler.CALL_TYPE_ASYNC, null, _parameters);
} catch (DBusExecutionException exDee) {
logger.debug("", exDee);
throw exDee;
} catch (Exception e) {
logger.debug("", e);
throw new DBusExecutionException(e.getMessage());
}
}
private Class>[] createTypesArray(Object... parameters) {
if (parameters == null) {
return null;
}
return Arrays.stream(parameters)
.filter(p -> p != null) // do no try to convert null values to concrete class
.map(p -> {
if (List.class.isAssignableFrom(p.getClass())) { // turn possible List subclasses (e.g. ArrayList) to interface class List
return List.class;
} else if (Map.class.isAssignableFrom(p.getClass())) { // do the same for Map subclasses
return Map.class;
} else if (Set.class.isAssignableFrom(p.getClass())) { // and also for Set subclasses
return Set.class;
} else {
return p.getClass();
}
})
.toArray(Class[]::new);
}
protected void handleException(AbstractConnection _dbusConnection, Message _methodOrSignal, DBusExecutionException _exception) {
if (_dbusConnection == null) {
throw new NullPointerException("DBusConnection cannot be null");
}
try {
_dbusConnection.sendMessage(new Error(_methodOrSignal, _exception));
} catch (DBusException ex) {
logger.warn("Exception caught while processing previous error.", ex);
}
}
/**
* Handle received message from DBus.
* @param _message
* @throws DBusException
*/
void handleMessage(Message _message) throws DBusException {
if (_message instanceof DBusSignal) {
handleMessage((DBusSignal) _message, true);
} else if (_message instanceof MethodCall) {
handleMessage((MethodCall) _message);
} else if (_message instanceof MethodReturn) {
handleMessage((MethodReturn) _message);
} else if (_message instanceof Error) {
handleMessage((Error) _message);
}
}
private void handleMessage(final MethodCall _methodCall) throws DBusException {
logger.debug("Handling incoming method call: {}", _methodCall);
ExportedObject eo = null;
Method meth = null;
Object o = null;
if (null == _methodCall.getInterface() || _methodCall.getInterface().equals("org.freedesktop.DBus.Peer")
|| _methodCall.getInterface().equals("org.freedesktop.DBus.Introspectable")) {
eo = getExportedObjects().get(null);
if (null != eo && null == eo.getObject().get()) {
unExportObject(null);
eo = null;
}
if (null != eo) {
meth = eo.getMethods().get(new MethodTuple(_methodCall.getName(), _methodCall.getSig()));
}
if (null != meth) {
o = new GlobalHandler(this, _methodCall.getPath());
} else {
eo = null;
}
}
if (null == o) {
// now check for specific exported functions
eo = getExportedObjects().get(_methodCall.getPath());
if (null != eo && null == eo.getObject().get()) {
logger.info("Unexporting {} implicitly", _methodCall.getPath());
unExportObject(_methodCall.getPath());
eo = null;
}
if (null == eo) {
eo = fallbackContainer.get(_methodCall.getPath());
}
if (null == eo) {
sendMessage(new Error(_methodCall,
new UnknownObject(_methodCall.getPath() + " is not an object provided by this process.")));
return;
}
if (logger.isTraceEnabled()) {
logger.trace("Searching for method {} with signature {}", _methodCall.getName(), _methodCall.getSig());
logger.trace("List of methods on {}: ", eo);
for (MethodTuple mt : eo.getMethods().keySet()) {
logger.trace(" {} => {}", mt, eo.getMethods().get(mt));
}
}
meth = eo.getMethods().get(new MethodTuple(_methodCall.getName(), _methodCall.getSig()));
if (null == meth) {
sendMessage(new Error(_methodCall, new UnknownMethod(String.format(
"The method `%s.%s' does not exist on this object.", _methodCall.getInterface(), _methodCall.getName()))));
return;
}
o = eo.getObject().get();
}
// now execute it
final Method me = meth;
final Object ob = o;
final boolean noreply = (1 == (_methodCall.getFlags() & Message.Flags.NO_REPLY_EXPECTED));
final DBusCallInfo info = new DBusCallInfo(_methodCall);
final AbstractConnection conn = this;
logger.trace("Adding Runnable for method {}", meth);
Runnable r = new Runnable() {
@Override
public void run() {
logger.debug("Running method {} for remote call", me);
if (me == null) {
logger.debug("Cannot run method - method variable was null");
return;
}
try {
Type[] ts = me.getGenericParameterTypes();
_methodCall.setArgs(Marshalling.deSerializeParameters(_methodCall.getParameters(), ts, conn));
logger.trace("Deserialised {} to types {}", LoggingHelper.arraysDeepString(logger.isTraceEnabled(), _methodCall.getParameters()), LoggingHelper.arraysDeepString(logger.isTraceEnabled(),ts));
} catch (Exception e) {
logger.debug("", e);
handleException(conn, _methodCall, new UnknownMethod("Failure in de-serializing message: " + e));
return;
}
try {
INFOMAP.put(Thread.currentThread(), info);
Object result;
try {
logger.trace("Invoking Method: {} on {} with parameters {}", me, ob, Arrays.deepToString(_methodCall.getParameters()));
result = me.invoke(ob, _methodCall.getParameters());
} catch (InvocationTargetException ite) {
logger.debug(ite.getMessage(), ite);
throw ite.getCause();
}
INFOMAP.remove(Thread.currentThread());
if (!noreply) {
MethodReturn reply;
if (Void.TYPE.equals(me.getReturnType())) {
reply = new MethodReturn(_methodCall, null);
} else {
StringBuffer sb = new StringBuffer();
for (String s : Marshalling.getDBusType(me.getGenericReturnType())) {
sb.append(s);
}
Object[] nr = Marshalling.convertParameters(new Object[] {
result
}, new Type[] {
me.getGenericReturnType()
}, conn);
reply = new MethodReturn(_methodCall, sb.toString(), nr);
}
conn.sendMessage(reply);
}
} catch (DBusExecutionException exDee) {
logger.debug("", exDee);
handleException(conn, _methodCall, exDee);
} catch (Throwable e) {
logger.debug("", e);
handleException(conn, _methodCall,
new DBusExecutionException(String.format("Error Executing Method %s.%s: %s",
_methodCall.getInterface(), _methodCall.getName(), e.getMessage())));
}
}
};
executeInWorkerThreadPool(r);
}
/**
* Handle a signal received on DBus.
*
* @param _signal signal to handle
* @param _useThreadPool whether to handle this signal in another thread or handle it byself
*/
@SuppressWarnings({
"unchecked"
})
private void handleMessage(final DBusSignal _signal, boolean _useThreadPool) {
logger.debug("Handling incoming signal: {}", _signal);
List> handlers = new ArrayList<>();
List> genericHandlers = new ArrayList<>();
Queue> t;
t = getHandledSignals().get(new SignalTuple(_signal.getInterface(), _signal.getName(), null, null));
if (null != t) {
handlers.addAll(t);
}
t = getHandledSignals().get(new SignalTuple(_signal.getInterface(), _signal.getName(), _signal.getPath(), null));
if (null != t) {
handlers.addAll(t);
}
t = getHandledSignals().get(new SignalTuple(_signal.getInterface(), _signal.getName(), null, _signal.getSource()));
if (null != t) {
handlers.addAll(t);
}
t = getHandledSignals().get(new SignalTuple(_signal.getInterface(), _signal.getName(), _signal.getPath(), _signal.getSource()));
if (null != t) {
handlers.addAll(t);
}
Queue> gt;
Set allTuples = SignalTuple.getAllPossibleTuples(_signal.getInterface(), _signal.getName(), _signal.getPath(), _signal.getSource());
for( SignalTuple tuple : allTuples ){
gt = getGenericHandledSignals().get(tuple);
if (null != gt) {
genericHandlers.addAll(gt);
}
}
if (handlers.isEmpty() && genericHandlers.isEmpty()) {
return;
}
final AbstractConnection conn = this;
for (final DBusSigHandler extends DBusSignal> h : handlers) {
logger.trace("Adding Runnable for signal {} with handler {}", _signal, h);
Runnable command = new Runnable() {
@Override
public void run() {
try {
DBusSignal rs;
if (_signal instanceof InternalSignal || _signal.getClass().equals(DBusSignal.class)) {
rs = _signal.createReal(conn);
} else {
rs = _signal;
}
if (rs == null) {
return;
}
((DBusSigHandler) h).handle(rs);
} catch (DBusException _ex) {
logger.warn("Exception while running signal handler '{}' for signal '{}':", h, _signal, _ex);
handleException(conn, _signal, new DBusExecutionException("Error handling signal " + _signal.getInterface()
+ "." + _signal.getName() + ": " + _ex.getMessage()));
}
}
};
if (_useThreadPool) {
executeInWorkerThreadPool(command);
} else {
command.run();
}
}
for (final DBusSigHandler h : genericHandlers) {
logger.trace("Adding Runnable for signal {} with handler {}", _signal, h);
Runnable command = new Runnable() {
@Override
public void run() {
h.handle(_signal);
}
};
if (_useThreadPool) {
executeInWorkerThreadPool(command);
} else {
command.run();
}
}
}
private void executeInWorkerThreadPool(Runnable _task) {
workerThreadPoolLock.readLock().lock();
try {
workerThreadPool.execute(_task);
} finally {
workerThreadPoolLock.readLock().unlock();
}
}
private void handleMessage(final Error _err) {
logger.debug("Handling incoming error: {}", _err);
MethodCall m = null;
if (getPendingCalls() == null) {
return;
}
synchronized (getPendingCalls()) {
if (getPendingCalls().containsKey(_err.getReplySerial())) {
m = getPendingCalls().remove(_err.getReplySerial());
}
}
if (m != null) {
m.setReply(_err);
CallbackHandler> cbh = null;
cbh = callbackManager.removeCallback(m);
logger.trace("{} = pendingCallbacks.remove({})", cbh, m);
// queue callback for execution
if (null != cbh) {
final CallbackHandler> fcbh = cbh;
logger.trace("Adding Error Runnable with callback handler {}", fcbh);
Runnable command = new Runnable() {
@Override
public synchronized void run() {
try {
logger.trace("Running Error Callback for {}", _err);
DBusCallInfo info = new DBusCallInfo(_err);
INFOMAP.put(Thread.currentThread(), info);
fcbh.handleError(_err.getException());
INFOMAP.remove(Thread.currentThread());
} catch (Exception e) {
logger.debug("Exception while running error callback.", e);
}
}
};
executeInWorkerThreadPool(command);
}
} else {
getPendingErrorQueue().add(_err);
}
}
@SuppressWarnings("unchecked")
private void handleMessage(final MethodReturn _mr) {
logger.debug("Handling incoming method return: {}", _mr);
MethodCall m = null;
if (null == getPendingCalls()) {
return;
}
synchronized (getPendingCalls()) {
if (getPendingCalls().containsKey(_mr.getReplySerial())) {
m = getPendingCalls().remove(_mr.getReplySerial());
}
}
if (null != m) {
m.setReply(_mr);
_mr.setCall(m);
@SuppressWarnings("rawtypes")
CallbackHandler cbh = callbackManager.getCallback(m);
DBusAsyncReply> asr = callbackManager.getCallbackReply(m);
callbackManager.removeCallback(m);
// queue callback for execution
if (null != cbh) {
final CallbackHandler