dbus-java icon indicating copy to clipboard operation
dbus-java copied to clipboard

Variant of Map

Open srehlig opened this issue 4 years ago • 19 comments

First off: thanks for this great piece of work and the improvements coming in during the last couples of month. We're now using version 3.2.0.

I'm currently working on a java facade for connman (and ofono) and I'm facing the following issue:

The API for DBus interface net.connman.Service is described here https://github.com/aldebaran/connman/blob/master/doc/service-api.txt

The interface has a method SetProperty(string name, variant value). I want to pass a new IPv4.Configuration. The internal type of that value is dict, that should be defined in a Map<String, Variant<?>. By contract I need to define a Variant like new Variant(map) to pass it to the call of SetProperty. Doing this results in an exception with the following message:

Exception in thread "main" java.lang.IllegalArgumentException: Can't wrap class java.util.HashMap in an unqualified Variant (Exporting non-exportable type: class java.util.HashMap).
        at org.freedesktop.dbus.types.Variant.<init>(Variant.java:54)

How would you solve this?

srehlig avatar Oct 05 '19 09:10 srehlig

Have you also tried to use DBusMap (org.freedesktop.dbus.DBusMap<K, V>)?

hypfvieh avatar Oct 05 '19 15:10 hypfvieh

When I try to wrap a DBusMap in a Variant the following exceptions is thrown:

Exception in thread "main" java.lang.IllegalArgumentException: Can't wrap class org.freedesktop.dbus.DBusMap in an unqualified Variant (Exporting non-exportable type: class org.freedesktop.dbus.DBusMap).
        at org.freedesktop.dbus.types.Variant.<init>(Variant.java:54)

Another approach could be to use the DBusMap in the method call as SetProperty(String, DBusMap) (which is not the declared notation). This gives the following exception stack:

 Exception in thread "main" org.freedesktop.dbus.exceptions.DBusExecutionException: Failed to construct D-Bus type: Exporting non-exportable parameterized type org.freedesktop.dbus.DBusMap<java.lang.String, org.freedesktop.dbus.types.Variant<?>>
        at org.freedesktop.dbus.RemoteInvocationHandler.executeRemoteMethod(RemoteInvocationHandler.java:102)
        at org.freedesktop.dbus.RemoteInvocationHandler.invoke(RemoteInvocationHandler.java:228)
        at com.sun.proxy.$Proxy21.setProperty(Unknown Source)

Are there other options? Should we prepare a test case for this problem?

srehlig avatar Oct 06 '19 11:10 srehlig

A test case would be great. I would also try to investigate the structure using d-feet. Maybe it is required to create a class which represents a IPv4 configuration (if the structure is more like a struct than a map).

hypfvieh avatar Oct 06 '19 13:10 hypfvieh

@hypfvieh , could you please tell me if there are any updates on this issue? I am experiencing the same problem. Seems that there is a problem to set map argument. An example of the code can be as simple as getting config map and setting it back:

Properties p2PDeviceProperties = connection.getRemoteObject("fi.w1.wpa_supplicant1", "/fi/w1/wpa_supplicant1/Interfaces/0", Properties.class);
final Map<String, Variant<?>> p2pDeviceConfig = p2PDeviceProperties.Get("fi.w1.wpa_supplicant1.Interface.P2PDevice", "P2PDeviceConfig");
p2PDeviceProperties.Set("fi.w1.wpa_supplicant1.Interface.P2PDevice", "P2PDeviceConfig", p2pDeviceConfig);

Do you have any idea of what could go wrong? Thanks! Best regards.

sim4ik avatar Mar 27 '22 21:03 sim4ik

Please provide full sample code which does not rely on any system service which might not be available on any system (or is access restricted like in my case).

I added a unit test (MapOfVariantTest) where a Map of String/Variant is exported, read, overwritten and read again and I can't see any issue in there.

hypfvieh avatar Mar 28 '22 07:03 hypfvieh

I think I just found a sample code which correspond to this issue:

HashMap<String, String> map = new HashMap<>();
Map<String, Variant<?>> variantMap = new HashMap<>();
variantMap.put("key", new Variant<Map<String, String>>(map));

Doing this results following exception (with version 3.3.1):

java.lang.IllegalArgumentException: Can't wrap class java.util.HashMap in an unqualified Variant (Exporting non-exportable type: class java.util.HashMap).
    at org.freedesktop.dbus.types.Variant.<init>(Variant.java:42)

I also tried a DBusMap with the same result:

DBusMap<String, String> map = new DBusMap<String, String>(null);

josephguinet avatar Mar 29 '22 17:03 josephguinet

This issue cannot be fixed, as it is caused by type erasure in Java. When passing the HashMap Object into the Variant, the types used inside of the Map are no longer known at runtime (due to type erasure). Variant can only handle 'simple' cases where type erasure does not apply, which means if you wrap a proper class (e.g. String, Integer, custom class without generics) it will work. As soon as the information parametrized by generics is removed due to type erasure it is impossible to serialize the object properly.

hypfvieh avatar Mar 30 '22 08:03 hypfvieh

Thank you for this clarification.

Do you have any workaround suggestions in order to manage properties with dictionary values (like maps in Java) ?

josephguinet avatar Mar 30 '22 21:03 josephguinet

I don't know any application which uses Maps as value in properties when it comes to DBus. There is the DBus.Properties interface, but it will probably also don't work when you wrap HashMap as value for a property (due to same limitations mentioned in my previous post).

If you need to provide data organized in Maps you should create a method in you DBusInterface extending interface (like MapOfVariantTest shows). Another option would be to transform the map to e.g. a JSON blob and use this to get transmitted using DBus. This would require additional steps for serializing/deserializing from/to JSON using another library (like jackson or gson).

hypfvieh avatar Mar 31 '22 04:03 hypfvieh

Thanks for your suggestions, I'll look into it.

josephguinet avatar Mar 31 '22 08:03 josephguinet

I don't know any application which uses Maps as value in properties when it comes to DBus.

Good day!

In my case there is the issue with setting P2P settings for WiFi interface. While your Wi-Fi chip supports P2P connections (and nowadays most of them support this functionality) you will have available P2PDevice under your wpa_supplicant DBus path.

Here is the example: fi.w1.wpa_supplicant1.Interface.P2PDevice

Under the hood it contains property called "P2PDeviceConfig - a{sv} - (read/write)" which I'm not able to set due to the same issue.

Thanks in advance!

Killbrum avatar Mar 31 '22 09:03 Killbrum

First of all, always provide sample code, otherwise it is not clear what you are trying to do.

As far as I can see in the wpa-supplicant documentation, the properties do not contain a map. You can retrieve the properties and set the properties like the NetworkManagerExample shows.

The properties are a{sv}, which will contain certain values, where the variant contents depends on the key. The DBus properties interface is some sort of map, but due to the fact that this is an interface not a class, the generic information will not be erased and can be obtained using TypeReference (which is done in dbus-java marshalling code).

None of the keys are defined to require a Map as value. These 3 keys expect more complex types, but not a map:

Key Dbus-Signature JavaType
PrimaryDeviceType ay byte[]
SecondaryDeviceTypes aay byte[][]
VendorExtension aay byte[][]

No generics used here.

hypfvieh avatar Mar 31 '22 10:03 hypfvieh

Hello there @hypfvieh 👋

First and foremost: thank you for building this project :)

I think I am in the same situation as the other guys in this thread: I'm currently trying to set the IP address through NetworkManager's DBus API.

To do so I'm following the examples provided in the NetworkManager repo in particular the Python one because the principle is the same even if the language is different.

The procedure requires to call the Update method of the org.freedesktop.NetworkManager.Settings.Connection object for the desired connection. In particular I need to update the address-data field of the settings which is an "array of vardict" as per the documentation (we can also see it from your example).

Therefore we incur in the situation described here.

Is this correct?

A little further above in this thread you propose the following alternatives to work around this limitation:

If you need to provide data organized in Maps you should create a method in you DBusInterface extending interface (like MapOfVariantTest shows). Another option would be to transform the map to e.g. a JSON blob and use this to get transmitted using DBus. This would require additional steps for serializing/deserializing from/to JSON using another library (like jackson or gson).

Can you elaborate a little bit further on these two options?

@josephguinet can you share some details on your solution?

mattdibi avatar Oct 17 '22 06:10 mattdibi

Hello again everyone,

in the end I was able to solve the issue on my own by leveraging the Variant(T _value, String _sig) constructor.

In my case this meant:

Map<String, Variant<?>> address = new HashMap<>();
address.put("address", new Variant<String>("192.168.1.224"));
address.put("prefix", new Variant<UInt32>(new UInt32(24)));

List<Map<String, Variant<?>>> addressData = Arrays.asList(address);
Variant<?> addressDataVariant = new Variant<>(addressData, "aa{sv}");
ipv4Map.put("address-data", addressDataVariant);

Similarly for @josephguinet the code should become:

HashMap<String, String> map = new HashMap<>();
Map<String, Variant<?>> variantMap = new HashMap<>();
variantMap.put("key", new Variant<Map<String, String>>(map, "a{ss}"));

@hypfvieh is this correct? Do you find any issue in this approach?

mattdibi avatar Oct 17 '22 12:10 mattdibi

I think this is the way it can be done.

As stated previously, you are pretty out of luck trying to create a proper DBus signature when generics come into play. As soon as you have multiple generics contained in each other you have the problem to determine the proper type at some point. This is usually the case when some sort of List/Map structure is used, or like in this case Variant.

It would be great if you could provide a simple example of this so I can add it to the examples project.

hypfvieh avatar Oct 17 '22 14:10 hypfvieh

I think this is the way it can be done.

Great to hear. Thanks for the feedback.

It would be great if you could provide a simple example of this so I can add it to the examples project.

I'm really busy these days but I'll try my best :)

mattdibi avatar Oct 18 '22 08:10 mattdibi

How can I express this ( a{sv} as parameter to a Method) with the spring expression language that is used in the FX-GUI?

Example: UDisks2 Bus: systembus, Busname: org.freedesktop.UDisks2 Objectpath: /org/freedesktop/UDisks2/drives/choose any drive Interface: org.freedesktop.UDisks2.Drive.Ata Methodname: SmartGetAttributes( Dict of {String, Variant} options) -> returns Array of foo...

In d-feet (the python programm) you enter something like this, nowakeuo is a standardoption that can be set on most Methods: {'nowakeup': GLib.Variant("b",1)}

or an empty Set here for SmartGetAttributes() works too: {}

In both cases you get a return value.

How do you express this with the spring expression language?

JamesOlvertone avatar Jul 23 '23 17:07 JamesOlvertone

Sorry no idea. I don't use spring in any project so I'm not familiar it (and I don't like to have black magic in my projects). In Java terms a{sv} is Map<String, Variant<?>>, so however you have to express a Map in spring expressions, this should also work here.

hypfvieh avatar Jul 24 '23 06:07 hypfvieh

Oops wrong page. dj-feet belongs to another person.

JamesOlvertone avatar Jul 24 '23 07:07 JamesOlvertone