Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variant of Map #74

Closed
srehlig opened this issue Oct 5, 2019 · 19 comments
Closed

Variant of Map #74

srehlig opened this issue Oct 5, 2019 · 19 comments

Comments

@srehlig
Copy link

srehlig commented Oct 5, 2019

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?

@hypfvieh
Copy link
Owner

hypfvieh commented Oct 5, 2019

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

@srehlig
Copy link
Author

srehlig commented Oct 6, 2019

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?

@hypfvieh
Copy link
Owner

hypfvieh commented Oct 6, 2019

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).

@sim4ik
Copy link

sim4ik commented Mar 27, 2022

@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.

@hypfvieh
Copy link
Owner

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.

@josephguinet
Copy link

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);

@hypfvieh
Copy link
Owner

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.

@josephguinet
Copy link

Thank you for this clarification.

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

@hypfvieh
Copy link
Owner

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).

@josephguinet
Copy link

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

@Killbrum
Copy link

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!

@hypfvieh
Copy link
Owner

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.

@mattdibi
Copy link
Contributor

mattdibi commented Oct 17, 2022

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
Copy link
Contributor

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?

@hypfvieh
Copy link
Owner

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.

@mattdibi
Copy link
Contributor

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 :)

@JamesOlvertone
Copy link

JamesOlvertone commented Jul 23, 2023

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?

@hypfvieh
Copy link
Owner

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.

@JamesOlvertone
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants