1/ I have a bean with fields and getter()/setter() methods, and another method called getX() which IS NOT a getter() method (no X field behind). When i want to map my bean (wildcards enabled), i get the following error message :
Unable to determine write method for field: X
I have to explicitly add a <field-exclude> section in my dozerBeanMapping.xml to avoid it. No other way ?
2/ The bug where lower level objects were new()ed is fixed. Perfect. But the problem still remain for List (and probably Array) type fields. I have a class named Test1 with a field which is a List, and containing 2 objects. I map it on Test2 class with an empty List.
My field in Test2 now contains 2 objects. And now, i map Test2 on Test1. The field in Test1 now contains my 2 objects twice !! (actually 4 objects ;) ). You can't update objects in a List-type field.
I think it'll be safe to compare objects in List fields (with equals() method) before adding them.
3/ The last one ;)
I have to use Dozer with an application server (Websphere). I have many EARs running on it, each containing one or more WARs.
Castor jar is still used by the home-made framework, and so, added in ws.ext.dirs.
I want to define a dozerBeanMapping.xml file in each WAR (i don't want to share mappings).
At this time, i found only one way to solve my 'problem' :
- put dozer.jar in WEB-INF/lib
- put dozerBeanMapping.xml at the root of my Java Source folder
- alter the source code of Dozer :
* in the static{} block of net.sf.dozer.util.mapping.Mapper, change the line (108) :
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping();
with
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping(Thread.currentThread().getContextClassLoader());
* read() method of net.sf.dozer.util.mapping.util.Map, change the line (60) :
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping();
with
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping(Thread.currentThread().getContextClassLoader());
It works fine !
So, here's a suggestion :
add a new register() method, where you can specify :
- classLoader
- mapping file(s)
This method could be called before the Mapper.map(...) one.
In a idyllic vision, i can add Dozer.jar in ws.ext.dirs (no more jars in the WEB-INF/lib of my WARs), and each WAR can choose its own classloader and its mapping file(s).
Et voil !
Bye
Seb
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
1. I did find a bug with this. Dozer was adding 2 more objects which were ArrayLists. I fixed this. I still think the functionality should be to add more objects from prime. How do we know what should and shouldn't be added?
So A has an arraylist with 2 AFOO objects. We map this to prime..and the AFOO objects had a classmap with AFOOPRIME. So now APRIME has an arraylist with 2 AFOOPRIME objects. Mapping prime back to AFOO we should see 4 AFOO objects. It currently works this way.
Let me know if you have questions.
Franz
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I found the bug too while testing your example :)
I'll try to explain my point of view better than in my first message ...
Suppose i have A with a field aList (ArrayList) containing 2 AFoo objects. I also have APrime with a field aList containing AFoo-type objects.
I map A to APrime. (The bug is fixed ;-) ). I have APrime with 2 AFoo objects in its aList field.
I modify the first AFoo object in APrime.
Now, i want to map back APrime to A in order to UPDATE my first AFoo object in A.
In your example, i'll have 4 AFoo objects in A, true ?
So dozer doesn't use the setter() method on A for its aList field ??
If i understand well, dozer makes something like that (sorry for this very simplified vision ;-) ) :
Iterator it = aPrime.getAList().iterator();
while (it.hasNext()) {
a.getAList().add(it.next());
}
I think this will be better (this code is NOT optimized of course) :
Iterator it = aPrime.getAList().iterator();
while (it.hasNext()) {
if (a.getAList().contains(it.next())) {
a.getAList().set(a.getAList().indexOf(it.next()),
Mapper.map(it.next(), AFoo.class));
} else {
a.getAList().add(it.next());
}
}
As you can see, it's not optimized at all. You just have to code the equals() method on AFoo class in order to make contains() and indexOf() methods work. In this case, the behaviour for list-type fields will be 'add or update' like.
I wish i've been clear with my explanations ...
Bye
Seb
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I will put this fix in today. Tell me if it what you were expecting :) Our unit tests seems to reflect that it works. I am sure you will find another scenario that doesn't work :)
Franz
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I think we decided that determining whether or not something should be updated is not the direction we want to go in. When we are doing a mapping to FooPrime's List our test case has a conversion from one List of complex objects to an entirely different List of complex objects. When we map those back I guess you could say we are comparing apples and oranges. I feel much safer doing a generic 'add' for everything. Thoughts?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I can't agree with you with that :
"When we map those back I guess you could say we are comparing apples and oranges"
We don't compare apples and oranges, we map apples to oranges and then we compare those oranges with the previous ones.
Look at this example :
Tom : Person
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 10
...
...
Now I map that to a structure PersonDTO that only take some of these informations :
Tom : PersonDTO
- child Peter : PersonDTO
age : 12
- child Sarah : PersonDTO
age : 10
Then I need to modify Sarah age (age is probably not a good property choice ;-) :
Tom : PersonDTO
- child Peter : PersonDTO
age : 12
- child Sarah : PersonDTO
age : 11
Now I want to map back my DTOs to my Person objects and I actually obtain that :
Tom : Person
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 10
...
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 11
...
...
I think that a good way to solve that is to put a flag on 1-N relationships attributes (in the mapping file)to tell dozer if it's a "cumulative" mapping or a "non cumulative" one.
A default value for this property may be defined in the same way the wildcard property is defined.
Am I wrong ?
Richard.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thx for this awesome release !! Great work !
But it seems i've found a little bug easily fixeable ;-)
I was testing cumulative/non cumulative feature. At the end, some data was missing ...
Here's my 'example' :
Consider A class with a list field which contains AFoo objects (AFoo has a, b and c fields).
Consider APrime class with a lis field which contains AFooPrime objects (AFooPrime has only a and b fields).
equals() method for AFoo and AFooPrime just compares a fields.
I'm on non-cumulative mode.
I have a A instance with 1 AFoo object like that :
a = "a"
b = null
c = "c"
Let's map A on APrime. My AFooPrime object is :
a = "a"
b = null
OK. Let's modify AFooPrime loke that :
a = "a"
b = "b"
And now, let's map APrime on A. I should have my AFoo like that :
a = "a"
b = "b"
c = "c" ... but this one is null instead :(
Looking into the source code, i found your 'mistake'.
In MappingProcessor, line 488. set() method just replace the object, but doesn't update it. I fixed this by addind the following line before result.set(...) :
map(result.get(index), destValue, null);
Don't know if it's the better way, but it works for me :-) My AFoo object is not well updated 8-)
(... launching a new test unit ... ;-) )
Bye
Seb
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks for your diligence Seb. I put your patch in. Just let me know when you want to go full time on dozer :). You have definitely help the application become more bullet-proof.
Franz
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi guys,
Thx for the new release :)
Each day comes with a least one new suggestion :
1/ I have a bean with fields and getter()/setter() methods, and another method called getX() which IS NOT a getter() method (no X field behind). When i want to map my bean (wildcards enabled), i get the following error message :
Unable to determine write method for field: X
I have to explicitly add a <field-exclude> section in my dozerBeanMapping.xml to avoid it. No other way ?
2/ The bug where lower level objects were new()ed is fixed. Perfect. But the problem still remain for List (and probably Array) type fields. I have a class named Test1 with a field which is a List, and containing 2 objects. I map it on Test2 class with an empty List.
My field in Test2 now contains 2 objects. And now, i map Test2 on Test1. The field in Test1 now contains my 2 objects twice !! (actually 4 objects ;) ). You can't update objects in a List-type field.
I think it'll be safe to compare objects in List fields (with equals() method) before adding them.
3/ The last one ;)
I have to use Dozer with an application server (Websphere). I have many EARs running on it, each containing one or more WARs.
Castor jar is still used by the home-made framework, and so, added in ws.ext.dirs.
I want to define a dozerBeanMapping.xml file in each WAR (i don't want to share mappings).
At this time, i found only one way to solve my 'problem' :
- put dozer.jar in WEB-INF/lib
- put dozerBeanMapping.xml at the root of my Java Source folder
- alter the source code of Dozer :
* in the static{} block of net.sf.dozer.util.mapping.Mapper, change the line (108) :
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping();
with
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping(Thread.currentThread().getContextClassLoader());
* read() method of net.sf.dozer.util.mapping.util.Map, change the line (60) :
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping();
with
org.exolab.castor.mapping.Mapping castorMapping = new org.exolab.castor.mapping.Mapping(Thread.currentThread().getContextClassLoader());
It works fine !
So, here's a suggestion :
add a new register() method, where you can specify :
- classLoader
- mapping file(s)
This method could be called before the Mapper.map(...) one.
In a idyllic vision, i can add Dozer.jar in ws.ext.dirs (no more jars in the WEB-INF/lib of my WARs), and each WAR can choose its own classloader and its mapping file(s).
Et voil !
Bye
Seb
1. This has been added to the code and will be released today.
2. Need to research this one.
3. This should be finished by the end of today.
1. I did find a bug with this. Dozer was adding 2 more objects which were ArrayLists. I fixed this. I still think the functionality should be to add more objects from prime. How do we know what should and shouldn't be added?
So A has an arraylist with 2 AFOO objects. We map this to prime..and the AFOO objects had a classmap with AFOOPRIME. So now APRIME has an arraylist with 2 AFOOPRIME objects. Mapping prime back to AFOO we should see 4 AFOO objects. It currently works this way.
Let me know if you have questions.
Franz
Hi,
I found the bug too while testing your example :)
I'll try to explain my point of view better than in my first message ...
Suppose i have A with a field aList (ArrayList) containing 2 AFoo objects. I also have APrime with a field aList containing AFoo-type objects.
I map A to APrime. (The bug is fixed ;-) ). I have APrime with 2 AFoo objects in its aList field.
I modify the first AFoo object in APrime.
Now, i want to map back APrime to A in order to UPDATE my first AFoo object in A.
In your example, i'll have 4 AFoo objects in A, true ?
So dozer doesn't use the setter() method on A for its aList field ??
If i understand well, dozer makes something like that (sorry for this very simplified vision ;-) ) :
Iterator it = aPrime.getAList().iterator();
while (it.hasNext()) {
a.getAList().add(it.next());
}
I think this will be better (this code is NOT optimized of course) :
Iterator it = aPrime.getAList().iterator();
while (it.hasNext()) {
if (a.getAList().contains(it.next())) {
a.getAList().set(a.getAList().indexOf(it.next()),
Mapper.map(it.next(), AFoo.class));
} else {
a.getAList().add(it.next());
}
}
As you can see, it's not optimized at all. You just have to code the equals() method on AFoo class in order to make contains() and indexOf() methods work. In this case, the behaviour for list-type fields will be 'add or update' like.
I wish i've been clear with my explanations ...
Bye
Seb
Sorry, but i miss something on my previous post :
The new method should be :
Iterator it = aPrime.getAList().iterator();
while (it.hasNext()) {
AFoo afoo = (AFoo) Map(it.next(), AFoo.class);
if (a.getAList().contains(afoo)) {
int index = a.getAList().indexOf(afoo);
a.getAList().set(index, Mapper.map(afoo, a.getList().get(index));
} else {
a.getAList().add(afoo);
}
}
Hey Seb -
I will put this fix in today. Tell me if it what you were expecting :) Our unit tests seems to reflect that it works. I am sure you will find another scenario that doesn't work :)
Franz
I think we decided that determining whether or not something should be updated is not the direction we want to go in. When we are doing a mapping to FooPrime's List our test case has a conversion from one List of complex objects to an entirely different List of complex objects. When we map those back I guess you could say we are comparing apples and oranges. I feel much safer doing a generic 'add' for everything. Thoughts?
Hi,
I can't agree with you with that :
"When we map those back I guess you could say we are comparing apples and oranges"
We don't compare apples and oranges, we map apples to oranges and then we compare those oranges with the previous ones.
Look at this example :
Tom : Person
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 10
...
...
Now I map that to a structure PersonDTO that only take some of these informations :
Tom : PersonDTO
- child Peter : PersonDTO
age : 12
- child Sarah : PersonDTO
age : 10
Then I need to modify Sarah age (age is probably not a good property choice ;-) :
Tom : PersonDTO
- child Peter : PersonDTO
age : 12
- child Sarah : PersonDTO
age : 11
Now I want to map back my DTOs to my Person objects and I actually obtain that :
Tom : Person
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 10
...
- child Peter : Person
age : 12
...
- child Sarah : Person
age : 11
...
...
I think that a good way to solve that is to put a flag on 1-N relationships attributes (in the mapping file)to tell dozer if it's a "cumulative" mapping or a "non cumulative" one.
A default value for this property may be defined in the same way the wildcard property is defined.
Am I wrong ?
Richard.
Ok, this is a very good example. The Dozer team will talk it over and see what we can do.
Great point. I will put this in the next release.
Look under the simple property mapping listing for documentation. Basically -
<field relationshipType="cumulative"> default
<field relationshipType="non-cumulative">
Hi guys,
Thx for this awesome release !! Great work !
But it seems i've found a little bug easily fixeable ;-)
I was testing cumulative/non cumulative feature. At the end, some data was missing ...
Here's my 'example' :
Consider A class with a list field which contains AFoo objects (AFoo has a, b and c fields).
Consider APrime class with a lis field which contains AFooPrime objects (AFooPrime has only a and b fields).
equals() method for AFoo and AFooPrime just compares a fields.
I'm on non-cumulative mode.
I have a A instance with 1 AFoo object like that :
a = "a"
b = null
c = "c"
Let's map A on APrime. My AFooPrime object is :
a = "a"
b = null
OK. Let's modify AFooPrime loke that :
a = "a"
b = "b"
And now, let's map APrime on A. I should have my AFoo like that :
a = "a"
b = "b"
c = "c" ... but this one is null instead :(
Looking into the source code, i found your 'mistake'.
In MappingProcessor, line 488. set() method just replace the object, but doesn't update it. I fixed this by addind the following line before result.set(...) :
map(result.get(index), destValue, null);
Don't know if it's the better way, but it works for me :-) My AFoo object is not well updated 8-)
(... launching a new test unit ... ;-) )
Bye
Seb
Thanks for your diligence Seb. I put your patch in. Just let me know when you want to go full time on dozer :). You have definitely help the application become more bullet-proof.
Franz