Skip to content

Commit

Permalink
Refactorings to cleanup Dbus property related stuff from rest
Browse files Browse the repository at this point in the history
  • Loading branch information
hypfvieh committed Mar 18, 2024
1 parent e6e4e3f commit 0e8a16d
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 191 deletions.
Original file line number Diff line number Diff line change
@@ -1,43 +1,33 @@
package org.freedesktop.dbus.connections.base;

import org.freedesktop.dbus.*;
import org.freedesktop.dbus.annotations.DBusBoundProperty;
import org.freedesktop.dbus.annotations.DBusProperty;
import org.freedesktop.dbus.annotations.DBusProperty.Access;
import org.freedesktop.dbus.connections.AbstractConnection;
import org.freedesktop.dbus.connections.config.ReceivingServiceConfig;
import org.freedesktop.dbus.connections.config.TransportConfig;
import org.freedesktop.dbus.errors.InvalidMethodArgument;
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.interfaces.CallbackHandler;
import org.freedesktop.dbus.interfaces.DBusSigHandler;
import org.freedesktop.dbus.interfaces.Properties;
import org.freedesktop.dbus.messages.*;
import org.freedesktop.dbus.messages.Error;
import org.freedesktop.dbus.messages.constants.Flags;
import org.freedesktop.dbus.propertyref.PropertyRef;
import org.freedesktop.dbus.types.Variant;
import org.freedesktop.dbus.utils.Util;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Queue;

/**
* Abstract class containing most methods to handle/react to a message received on a connection. <br>
* Part of the {@link AbstractConnectionBase} &rarr; {@link ConnectionMethodInvocation}
* &rarr; {@link ConnectionMessageHandler} &rarr; {@link AbstractConnection} hierarchy.
* &rarr; {@link DBusBoundPropertyHandler} &rarr; {@link ConnectionMessageHandler} &rarr; {@link AbstractConnection} hierarchy.
*
* @author hypfvieh
* @since 5.0.0 - 2023-10-23
*/
public abstract sealed class ConnectionMessageHandler extends ConnectionMethodInvocation permits AbstractConnection {
public abstract sealed class ConnectionMessageHandler extends DBusBoundPropertyHandler permits AbstractConnection {
protected ConnectionMessageHandler(TransportConfig _transportConfig, ReceivingServiceConfig _rsCfg) throws DBusException {
super(_transportConfig, _rsCfg);
}
Expand Down Expand Up @@ -332,177 +322,4 @@ private void handleMessage(final MethodCall _methodCall) throws DBusException {
queueInvokeMethod(_methodCall, meth, o);
}

/**
* Method which handles the magic related to {@link DBusBoundProperty} annotation.<br>
* It takes care of proper method calling (calling Get/Set stuff on DBus Properties interface)<br>
* and will also take care of converting wrapped Variant types.
*
* @param _exportObject exported object
* @param _methodCall method to call
* @param _params parameter to pass to method
*
* @return Any of:<br>
*
* {@link PropHandled#HANDLED} when property was defined by annotation and was handled by this method<br>
* {@link PropHandled#NOT_HANDLED} when object implements DBus Properties but the requested property was not defined by annotation<br>
* {@link PropHandled#NO_PROPERTY} when property is not defined by annotation and object does not implement DBus Properties<br>
*
* @throws DBusException when something fails
*/
@SuppressWarnings("unchecked")
private PropHandled handleDBusBoundProperties(ExportedObject _exportObject, final MethodCall _methodCall, Object[] _params) throws DBusException {
if (_params.length == 2 && _params[0] instanceof String
&& _params[1] instanceof String
&& _methodCall.getName().equals("Get")) {

// 'Get' This MIGHT be a property reference
PropertyRef propertyRef = new PropertyRef((String) _params[1], null, DBusProperty.Access.READ);
Method propMeth = _exportObject.getPropertyMethods().get(propertyRef);
if (propMeth != null) {
// This IS a property reference
Object object = _exportObject.getObject().get();

getReceivingService().execMethodCallHandler(() -> {
_methodCall.setArgs(new Object[0]);
invokeMethodAndReply(_methodCall, propMeth, object, 1 == (_methodCall.getFlags() & Flags.NO_REPLY_EXPECTED));
});

return PropHandled.HANDLED;
} else if (_exportObject.getImplementedInterfaces().contains(Properties.class)) {
return PropHandled.NOT_HANDLED;
} else {
return PropHandled.NO_PROPERTY;
}
} else if (_params.length == 3
&& _params[0] instanceof String
&& _params[1] instanceof String
&& _methodCall.getName().equals("Set")) {
// 'Set' This MIGHT be a property reference

PropertyRef propertyRef = new PropertyRef((String) _params[1], null, Access.WRITE);
Method propMeth = _exportObject.getPropertyMethods().get(propertyRef);
if (propMeth != null) {
// This IS a property reference
Object object = _exportObject.getObject().get();
Class<?> type = PropertyRef.typeForMethod(propMeth);
AtomicBoolean isVariant = new AtomicBoolean(false);

Object val = Optional.ofNullable(_params[2])
.map(v -> {
if (v instanceof Variant<?> va) {
isVariant.set(true);
return va.getValue();
}
return v;
}).orElse(null);

getReceivingService().execMethodCallHandler(() -> {
try {
Object myVal = val;
Parameter[] parameters = propMeth.getParameters();
// the setter method can only be used if it has just 1 parameter
if (parameters.length != 1) {
throw new InvalidMethodArgument("Expected method with one argument, but found " + parameters.length);
}
// take care of arrays:
// DBus only knows arrays of types, not lists or other collections.
// if the method which should be called wants a Collection we have to
// convert the array to a proper type
if (Collection.class.isAssignableFrom(parameters[0].getType())
&& isVariant.get() && myVal != null && myVal.getClass().isArray()) {

if (Set.class.isAssignableFrom(parameters[0].getType())) {
myVal = new LinkedHashSet<>(Arrays.asList(Util.toObjectArray(myVal)));
} else { // assume list is fine for all other collection types
myVal = new ArrayList<>(Arrays.asList(Util.toObjectArray(myVal)));
}
}
_methodCall.setArgs(Marshalling.deSerializeParameters(new Object[] {myVal}, new Type[] {type}, this));
invokeMethodAndReply(_methodCall, propMeth, object, 1 == (_methodCall.getFlags() & Flags.NO_REPLY_EXPECTED));
} catch (Exception _ex) {
getLogger().debug("Failed to invoke method call on Properties", _ex);
handleException(_methodCall, new UnknownMethod("Failure in de-serializing message: " + _ex));
}
});

return PropHandled.HANDLED;
}
} else if (_params.length == 1 && _params[0] instanceof String
&& _methodCall.getName().equals("GetAll")) {
// 'GetAll'
Set<Entry<PropertyRef, Method>> allPropertyMethods = _exportObject.getPropertyMethods().entrySet();
/* If there are no property methods on this object, just process as normal */
if (!allPropertyMethods.isEmpty()) {
Object object = _exportObject.getObject().get();
Method meth = null;
if (object instanceof Properties) {
meth = _exportObject.getMethods().get(new MethodTuple(_methodCall.getName(), _methodCall.getSig()));
if (null == meth) {
sendMessage(getMessageFactory().createError(_methodCall, new UnknownMethod(String.format(
"The method `%s.%s' does not exist on this object.", _methodCall.getInterface(), _methodCall.getName()))));
return PropHandled.HANDLED;
}
} else {
try {
meth = Properties.class.getDeclaredMethod("GetAll", String.class);
} catch (NoSuchMethodException | SecurityException _ex) {
getLogger().debug("Properties GetAll failed", _ex);
handleException(_methodCall,
new DBusExecutionException(String.format("Error Executing Method %s.%s: %s",
_methodCall.getInterface(), _methodCall.getName(), _ex.getMessage())));
}
}

Method originalMeth = meth;

getReceivingService().execMethodCallHandler(() -> {
Map<String, Object> resultMap = new HashMap<>();
for (Entry<PropertyRef, Method> propEn : allPropertyMethods) {
Method propMeth = propEn.getValue();
if (propEn.getKey().getAccess() == Access.READ) {
try {
_methodCall.setArgs(new Object[0]);
Object val = invokeMethod(_methodCall, propMeth, object);
resultMap.put(propEn.getKey().getName(), val);
} catch (Throwable _ex) {
getLogger().debug("Error executing method {} on method call {}", propMeth, _methodCall, _ex);
handleException(_methodCall, new UnknownMethod("Failure in de-serializing message: " + _ex));
return;
}
}
}

// this object implements Properties, so we have to query for these properties as well as
// collecting the properties only available by annotations
if (object instanceof Properties) {
_methodCall.setArgs(new Object[] {_methodCall.getInterface()});
resultMap.putAll((Map<String, ? extends Variant<?>>) setupAndInvoke(_methodCall, originalMeth, object, true));
}

try {
invokedMethodReply(_methodCall, originalMeth, resultMap);
} catch (DBusExecutionException _ex) {
getLogger().debug("Error invoking method call", _ex);
handleException(_methodCall, _ex);
} catch (Throwable _ex) {
getLogger().debug("Failed to invoke method call", _ex);
handleException(_methodCall,
new DBusExecutionException(String.format("Error Executing Method %s.%s: %s",
_methodCall.getInterface(), _methodCall.getName(), _ex.getMessage())));
}
});
return PropHandled.HANDLED;
}
}
return PropHandled.NOT_HANDLED;
}

public enum PropHandled {
/** Property request was handled. */
HANDLED,
/** Property request was not handled. */
NOT_HANDLED,
/** Property was not handled and Properties interface was not defined on exported object. */
NO_PROPERTY
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
/**
* Abstract class containing most methods to invoke methods on a connection. <br>
* Part of the {@link AbstractConnectionBase} &rarr; {@link ConnectionMethodInvocation}
* &rarr; {@link ConnectionMessageHandler} &rarr; {@link AbstractConnection} hierarchy.
* &rarr; {@link DBusBoundPropertyHandler} &rarr; {@link ConnectionMessageHandler} &rarr; {@link AbstractConnection} hierarchy.
*
* @author hypfvieh
* @since 5.0.0 - 2023-10-23
*/
public abstract sealed class ConnectionMethodInvocation extends AbstractConnectionBase permits ConnectionMessageHandler {
public abstract sealed class ConnectionMethodInvocation extends AbstractConnectionBase permits DBusBoundPropertyHandler {

protected ConnectionMethodInvocation(TransportConfig _transportConfig, ReceivingServiceConfig _rsCfg) throws DBusException {
super(_transportConfig, _rsCfg);
Expand Down

0 comments on commit 0e8a16d

Please sign in to comment.