diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMessageHandler.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMessageHandler.java index 361cd2a0..951efbd0 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMessageHandler.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMessageHandler.java @@ -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.
* Part of the {@link AbstractConnectionBase} → {@link ConnectionMethodInvocation} - * → {@link ConnectionMessageHandler} → {@link AbstractConnection} hierarchy. + * → {@link DBusBoundPropertyHandler} → {@link ConnectionMessageHandler} → {@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); } @@ -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.
- * It takes care of proper method calling (calling Get/Set stuff on DBus Properties interface)
- * 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:
- * - * {@link PropHandled#HANDLED} when property was defined by annotation and was handled by this method
- * {@link PropHandled#NOT_HANDLED} when object implements DBus Properties but the requested property was not defined by annotation
- * {@link PropHandled#NO_PROPERTY} when property is not defined by annotation and object does not implement DBus Properties
- * - * @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> 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 resultMap = new HashMap<>(); - for (Entry 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>) 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 - } } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMethodInvocation.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMethodInvocation.java index 8e630838..441b0d96 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMethodInvocation.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/ConnectionMethodInvocation.java @@ -23,12 +23,12 @@ /** * Abstract class containing most methods to invoke methods on a connection.
* Part of the {@link AbstractConnectionBase} → {@link ConnectionMethodInvocation} - * → {@link ConnectionMessageHandler} → {@link AbstractConnection} hierarchy. + * → {@link DBusBoundPropertyHandler} → {@link ConnectionMessageHandler} → {@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); diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/DBusBoundPropertyHandler.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/DBusBoundPropertyHandler.java new file mode 100644 index 00000000..4d3665ab --- /dev/null +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/connections/base/DBusBoundPropertyHandler.java @@ -0,0 +1,260 @@ +package org.freedesktop.dbus.connections.base; + +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.MethodTuple; +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.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.interfaces.Properties; +import org.freedesktop.dbus.messages.ExportedObject; +import org.freedesktop.dbus.messages.MethodCall; +import org.freedesktop.dbus.messages.constants.Flags; +import org.freedesktop.dbus.propertyref.PropRefRemoteHandler; +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.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Abstract class containing methods for handling DBus properties and {@link DBusBoundProperty} annotation.
+ * Part of the {@link AbstractConnectionBase} → {@link ConnectionMethodInvocation} + * → {@link DBusBoundPropertyHandler} → {@link ConnectionMessageHandler} → {@link AbstractConnection} hierarchy. + * + * @author hypfvieh + * @since 5.0.1 - 2024-03-18 + */ +public abstract sealed class DBusBoundPropertyHandler extends ConnectionMethodInvocation permits ConnectionMessageHandler { + + private static final Method PROP_GETALL_METHOD = PropRefRemoteHandler.getPropertiesMethod("GetAll", String.class); + + protected DBusBoundPropertyHandler(TransportConfig _transportConfig, ReceivingServiceConfig _rsCfg) throws DBusException { + super(_transportConfig, _rsCfg); + } + + /** + * Method which handles the magic related to {@link DBusBoundProperty} annotation.
+ * It takes care of proper method calling (calling Get/Set stuff on DBus Properties interface)
+ * and will also take care of converting wrapped Variant types. + * + * @param _conn connection + * @param _exportObject exported object + * @param _methodCall method to call + * @param _params parameter to pass to method + * + * @return Any of:
+ * + * {@link PropHandled#HANDLED} when property was defined by annotation and was handled by this method
+ * {@link PropHandled#NOT_HANDLED} when object implements DBus Properties but the requested property was not defined by annotation
+ * {@link PropHandled#NO_PROPERTY} when property is not defined by annotation and object does not implement DBus Properties
+ * + * @throws DBusException when something fails + */ + protected 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' + return handleGet(_exportObject, _methodCall, _params); + + } else if (_params.length == 3 + && _params[0] instanceof String + && _params[1] instanceof String + && _methodCall.getName().equals("Set")) { + // 'Set' + return handleSet(_exportObject, _methodCall, _params); + + } else if (_params.length == 1 && _params[0] instanceof String + && _methodCall.getName().equals("GetAll")) { + // 'GetAll' + return handleGetAll(_exportObject, _methodCall); + } + return PropHandled.NOT_HANDLED; + } + + /** + * Called when 'GetAll' method of DBus {@link Properties} interface is called. + * + * @param _exportObject exported object + * @param _methodCall method call + * + * @return {@link PropHandled#HANDLED} when call was handled {@link PropHandled#NOT_HANDLED} otherwise + * + * @throws DBusException when handling fails + */ + @SuppressWarnings("unchecked") + protected PropHandled handleGetAll(ExportedObject _exportObject, final MethodCall _methodCall) throws DBusException { + Set> 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 { + meth = PROP_GETALL_METHOD; + } + + Method originalMeth = meth; + + getReceivingService().execMethodCallHandler(() -> { + Map resultMap = new HashMap<>(); + for (Entry 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)); + } + } + } + + // 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>) 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; + } + + /** + * Called when 'Get' method of DBus {@link Properties} interface is called. + * + * @param _exportObject exported object + * @param _methodCall method call + * @param _params parameters for method call + * + * @return Any of:
+ * + * {@link PropHandled#HANDLED} when property was defined by annotation and was handled by this method
+ * {@link PropHandled#NOT_HANDLED} when object implements DBus Properties but the requested property was not defined by annotation
+ * {@link PropHandled#NO_PROPERTY} when property is not defined by annotation and object does not implement DBus Properties
+ */ + protected PropHandled handleGet(ExportedObject _exportObject, final MethodCall _methodCall, Object[] _params) { + 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; + } + } + + /** + * Called when 'Set' method of DBus {@link Properties} interface is called. + * + * @param _exportObject exported object + * @param _methodCall method call + * @param _params method call parameters + * + * @return {@link PropHandled#HANDLED} when property was definied by annotation, {@link PropHandled#NOT_HANDLED} otherwise + */ + protected PropHandled handleSet(ExportedObject _exportObject, final MethodCall _methodCall, Object[] _params) { + + 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; + } + return PropHandled.NOT_HANDLED; + + } + + 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 + } +} diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropRefRemoteHandler.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropRefRemoteHandler.java index a7b2698b..8dc169c8 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropRefRemoteHandler.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropRefRemoteHandler.java @@ -85,7 +85,7 @@ public static Object handleDBusBoundProperty(AbstractConnection _conn, RemoteObj return result; } - private static Method getPropertiesMethod(String _method, Class... _signature) { + public static Method getPropertiesMethod(String _method, Class... _signature) { try { return Properties.class.getMethod(_method, _signature); } catch (NoSuchMethodException | SecurityException _ex) {