Hi!
After some experimentation, I think I may have found a way to make this work.
It would require one update to the code, though, as I describe further below.
The trick for me was to create a subclass of TypeReference at runtime using
known information like so:
TypeReference<AbstractDTO<E>> tr = new TypeReference<AbstractDTO<E>>() {
@Override
public Type getType() {
ParameterizedType t = (ParameterizedType)super.getType();
return new ParameterizedType() {
@Override
public String getTypeName() {
return type.getTypeName();
}
@Override
public Type getRawType() {
return type; // <— this is the type of the runtime
"envelope" object, i.e. object.getClass()
}
@Override
public Type getOwnerType() {
return t.getOwnerType();
}
@Override
public Type[] getActualTypeArguments() {
return new Type[]{ entityType }; // <— This is the
class of the contained generic Parameter
}
};
}
};
In the example above, the “AbstractDTO” is an abstract superclass of each
TransactionDTO (an object required by Prevayler to wrap a transaction). The
actual type of the entity to persist is passed as a Type argument from the
application (i.e. the backend does not know the static type).
The above seems to work… but for one problem.
In the method below, the type information is not used when retrieving the DTO
field.
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> T convertToDTO(Class<T> targetCls) {
Map m = mapView(object, converter);
try {
T dto = targetCls.newInstance();
for (Map.Entry entry : (Set<Map.Entry>) m.entrySet()) {
try {
Field f = targetCls.getField(entry.getKey().toString());
Object val = entry.getValue();
// ****** HERE!!! ****** Need to use the type parameter
somehow
f.set(dto, converter.convert(val).to(f.getType()));
} catch (NoSuchFieldException e) {
}
}
return dto;
} catch (Exception e) {
throw new ConversionException("Cannot create DTO " + targetCls, e);
}
}
The type for the field is f.getType(), which ignores the generic parameter type
provided with the TypeReference. We would somehow need to return the correct
type for this field, and the field conversion should work as expected and
return the correct type instead of returning a Map.
With a fix for the above, I am pretty sure this would work. Any idea how to
make that happen?
Anyway, please let me know what you think.
Cheers,
=David
> On Aug 25, 2016, at 5:19 AM, David Bosschaert <[email protected]>
> wrote:
>
> For DTOs, I think the following should be ok:
>
> class MyDTO {
> List<Long> foo;
> }
>
> In any case, I think the converter should try to honour generics where
> possible, so if you have a JavaBean with generics it should also work
> class FooBean<T> {
> T getFoo();
> }
>
> In this case you should be able to do:
> FooBean<Bar> foo = converter.convert(something).to(new
> TypeReference<FooBean<Bar>>(){});
>
> I don't think it's implemented for JavaBeans yet, but I'll try to add this
> soon.
>
> Cheers,
>
> David
>
> On 24 August 2016 at 20:06, Raymond Auge <[email protected]> wrote:
>
>> I'm not really sure that defining DTOs with generics is even acceptable!
>>
>> I think the primary point of a DTO is to be completely concrete.
>>
>> - Ray
>>
>> On Wed, Aug 24, 2016 at 1:39 PM, David Bosschaert <
>> [email protected]> wrote:
>>
>>> BTW I'm just noticing that the generic type arguments are not yet used
>> for
>>> conversions to DTOs, if you have a test case or even a patch that would
>> be
>>> great :)
>>>
>>> Cheers,
>>>
>>> David
>>>
>>> On 24 August 2016 at 18:38, David Bosschaert <[email protected]
>>>
>>> wrote:
>>>
>>>> Hi David,
>>>>
>>>> The current converter implementation actually has (some) support for
>> this
>>>> already via the TypeReference-based APIs. Basically this generic
>>>> information is preserved if you create a subclass for your type, and
>> the
>>>> way to do this is by creating an (anonymous) TypeReference subclass.
>>>>
>>>> You can find an example in the ConverterMapTest.
>>> testGenericMapConversion()
>>>> [1]
>>>>
>>>> Basically what that test does is convert this map:
>>>> Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
>>>> into a Map<String, Long>. So the number 42 needs to be converted into a
>>>> String and the string "987654321" needs to be converted into a long.
>>>>
>>>> An anonymous TypeReference subclass is used to tell the converter to do
>>>> this:
>>>> Map<String, Long> m2 = converter.convert(m1).to(new
>>>> TypeReference<Map<String, Long>>(){});
>>>>
>>>> Then at then end of the test you'll see that the correct converted
>> types
>>>> are being converted to are asserted.
>>>>
>>>> So in your case you should be able to specify the conversion as:
>>>> TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
>>>> TypeReference<TopDTO<BottomDTO>>(){});
>>>>
>>>> Hope this works for you :) The implementation of TypeReference-based
>> APIs
>>>> isn't completely finished, so if you find an issue, let us know!
>>>>
>>>> Cheers,
>>>>
>>>> David
>>>>
>>>> [1] https://svn.apache.org/viewvc/felix/trunk/converter/
>>>> src/test/java/org/apache/felix/converter/impl/
>>> ConverterMapTest.java?view=
>>>> markup#l54
>>>>
>>>> On 24 August 2016 at 18:21, David Leangen <[email protected]> wrote:
>>>>
>>>>>
>>>>> Hi!
>>>>>
>>>>> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
>>>>> simple answer.
>>>>>
>>>>> If I convert from Map—>DTO, and the DTO has in its tree a generic
>> field,
>>>>> how can I tell the converter the correct type so that it does not get
>>>>> converted as a Map?
>>>>>
>>>>> Example:
>>>>>
>>>>> BottomDTO {
>>>>> public String a;
>>>>> public String b;
>>>>>
>>>>> MiddleDTO<T> {
>>>>> public T bottom;
>>>>> }
>>>>>
>>>>> TopDTO<T> {
>>>>> public MiddleDTO<T> middle;
>>>>> }
>>>>>
>>>>> If I just do this, then Map is used for the value of bottom, which
>> will
>>>>> cause a ClassCastException sometime later during execution:
>>>>>
>>>>> // Convert from Map to TopDTO<BottomDTO>
>>>>> converter.convert(someMap).to(TopDTO.class);
>>>>>
>>>>>
>>>>> Thanks!
>>>>> =David
>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>>
>>
>> --
>> *Raymond Augé* <http://www.liferay.com/web/raymond.auge/profile>
>> (@rotty3000)
>> Senior Software Architect *Liferay, Inc.* <http://www.liferay.com>
>> (@Liferay)
>> Board Member & EEG Co-Chair, OSGi Alliance <http://osgi.org>
>> (@OSGiAlliance)
>>