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

lambda expression in API conversions #40

Closed
faroukelabady opened this issue Jun 18, 2016 · 13 comments
Closed

lambda expression in API conversions #40

faroukelabady opened this issue Jun 18, 2016 · 13 comments

Comments

@faroukelabady
Copy link

Hi Alessandro Vurro,
I love you framework and how fast it can get , ans would be great if you can add the following ideas

1 - mix between xml configuration and annotation
digging around the code I found that xml configuration takes higher precedence, which is normal ,so what I wish for is the possiblity to mix between the two for a single class
for example I have a class called currency that I applied some annotation to it , but I need to make dynamic custom conversion so I needed to do it in xml or api style, but when I do that
all the annotation is gone , and I am forced to move all the annotation into the xml , would be great if the two can be applied.

2- mapping between destination and source list , for example
JMapper<Dest, Source> mapper = new JMapper<>(Dest.class, Source.class);
mapper.getDestinationList(source); or mapper.getDestination(// list of source and return list of destination);
it will be also great if I can control the collection type , or maybe return a configured map of key value.

3- using functional API and lambda for custom conversion.
to see this through I will apply a simple example with the following code.

public class Currency {

    String code;
    String name;
    int value;
// getters and setters
}

public class Dest {

    @JMap
    String test;
    @JMap("code")
    Currency currency;
}

public class Source {

    @JMap
    String test;
    String code;
}


public class Service {


    public Currency getCurrency(String code) {
        Currency curr = new Currency();
        curr.setCode(code);
        curr.setName("egypt");
        curr.setValue(123);

        return curr;
    }

}


    public static void main(String[] args) {


        Source source = new Source();
        source.setCode("EGypt");
        source.setTest("test  here");
        ModelMapper map =  new DefaultModelMapper();
        JMapperAPI api = new JMapperAPI();
        Service service = new Service();
        api.add(JMapperAPI.mappedClass(Dest.class).add(new Attribute("currency").value("code"))
        .add(new Conversion("conversion").from("code").to("currency").body(""
                + "System.out.println(\"trying dynamic covnersion\");"
                + "org.test.jmapper2.Service service = new org.test.jmapper2.Service();"
    +"  org.test.jmapper2.Currency cur = service.getCurrency(${source});"
                + "return cur;"))
        );
        JMapper<Dest, Source> mapper = new JMapper<>(Dest.class, Source.class, api);
        Dest d = mapper.getDestination(source);


    }

as from the example what I want to do is to custom convert between a String code , into a currency object, if I use api or xml , it will be erro prone plus , javaassist complaint a lot tell I got it right , and what if I want the vice versa of the conversion to convert from a destination to a source, so my idea is maybe if we used functional api to supply the custom conversion code then that would be great for example

Conversion convert = new Conversion("name")
convert.from("code").to("currency").body( (source) -> service.getCurrency(source.code));

I will also attach a wrapper class I made to solve this kind of problems , would love your feedback.
kindly find the google groups link for the files
https://groups.google.com/forum/#!topic/jmapper-framework/m8Dx9uMXsxM

Best regards
Farouk Elabady

@avurro
Copy link
Member

avurro commented Jun 19, 2016

Hi Farouk,

Thank you for the support, this encourages me to do better.
In regard of your proposals:

  • 1 It should be a feature that already exists, if as you say it does not work, it's a bug.
  • 2 I think it is more correct to encapsulate the list into a class and pass these to JMapper
  • 3 You can do this with static conversion in annotation. In XML/API is mandatory write body in string format actually, a workaround can be call a method that have custom conversion inside, for example:
<conversion>
return package.Service.conversion(${source});
</conversion>

I need to study ByteBuddy to find another solution.

@faroukelabady
Copy link
Author

Hi Alessandro
thanks for your reply , and glad that I gave you encouragement :),
regarding the points
1- this will be a bug then , because I tried it and the xml or api overrides the annotation. also I followed the code and I found this behavior in source code for jmapper

2- that could be good too , I manager to pass the colelction class type , and return the corresponding object as a result , you can see a sample of that in the source code I attached in the google groups.

3-if I use static annotation then I will be forced to write some bussiness code in the model class, and using thee api or xml will be very limited , we are currently using the jmapper in a spring environment . and we want to achieve something like this

 class test {
    @Autowired
    LookupService service;

   public Destination  getDestinationBy(int id) {
    Source source = em.find(Source.class, id);
  Currency currency =   service.getCurrecnyByCode(source.getCurrencyCode());
Jmapper<Destination,Source> mapper = new Jmapper<>(Destination.class, Source.class);
Detination d = mapper.getDestinatoin(source);
// can't convert from a code string to currency object need a custom conversion
// but if we do so then the bussiness code will be inside the model class
// doing it manual for now.
d.setCurrency(currency);
return d;

}

}

so as you can see our problem here is the conversion from a currency code string to currency object that is retrieved from another autowired service, it will be good if jmapper can handle those kind of mapping in some ways maybe something like the following
// using java8 lambdas
mapper.getDestination(source, () -> service.getCurrencyByCode(source.code));

ofcourse that raises a question of how to map this kind of custom relation and how to make jmapper map the correct variable,
I have done some manual code for that also attached in the google groups :)

Best regards
Farouk Elabady

@avurro
Copy link
Member

avurro commented Jun 19, 2016

It is better if we work for single point

For the first point what i mean is that you can define configuration in annotation and conversion methods in XML/API, or viceversa. This mix is provided by the framework, if in your case it does not work, give me a test case on which to work.

let's start with this first

@faroukelabady
Copy link
Author

faroukelabady commented Jun 19, 2016

Okay let's revise the example then


public class Dest {
    @JMap
    String test;
    String currency;
}

public class Source {
    String test;
    String code;
}


    public static void main(String[] args) {


        Source source = new Source();
        source.setCode("USD");
        source.setTest("test  here");
        ModelMapper map =  new DefaultModelMapper();
        JMapperAPI api = new JMapperAPI();

        api.add(JMapperAPI.mappedClass(Dest.class).add(new Attribute("currency").value("code"))
        .add(new Conversion("conversion").from("code").to("currency").body(""         
                + "return ${source};"))
        );
        JMapper<Dest, Source> mapper = new JMapper<>(Dest.class, Source.class, api);
        Dest d = mapper.getDestination(source);


    }

okay in this example I have a destination class , with one variable annotated while the other is mapped using the API or xml , so my prediction here is that Jmapper will be able to read the xml and then combine it with the annotation for the same destination class , and have the final configured class, but what actually happens in code is as follows
if class is xml configured then
process xml configruation
else
look for the annotation inside the class

so basically if a class in xml configured it will ignore this class annotation , but if we have two classes one xml configured and one annotated , then it will load one class from the xml and read the other using the annotation.

@avurro
Copy link
Member

avurro commented Jun 20, 2016

I do not understand why you need this, why not set up everything in annotation or API / XML? the mix is allowed between configuration and conversion, or between classes (a class configured in the annotation and another in xml), but the same class configured with two formats I do not see the usefulness.

follow your example revisited, classes:

public class Dest {
    @JMap String test;
    @JMap String currency;
}

public class Source {
    String test;
    String code;
}

execution code:

...
import static com.googlecode.jmapper.api.JMapperAPI.*;
...
public static void main(String[] args) {

        JMapperAPI api = new JMapperAPI().add(
                  mappedClass(Dest.class).add(
                      conversion("conversion").from("code").to("currency")
                          .body("return ${source};")));

        Source source = new Source();
        source.setCode("USD");
        source.setTest("test  here");

        JMapper<Dest, Source> mapper = new JMapper<>(Dest.class, Source.class, api);
        Dest d = mapper.getDestination(source);

    }

Classes configured with annotation and conversion in XML, viceversa is permitted too.

What do you think about? could you do me a concrete case where instead there is the need to mix various configuration types for the same class?

@avurro
Copy link
Member

avurro commented Jun 20, 2016

For the third point, give the possibility to pass a lambda expression (or an inner class for old java versions) to conversions can be useful.
This is an enhancement that i will develop

@faroukelabady
Copy link
Author

including lamdba expression or inner classes in custom conversion will be very good , and will also solve the first point too ,
as for a concrete case, in my company we are using jmapper annotation and with a custom wrapper class around jmapper we are doing the covnersion between java entity and DTO model , so annotation was very convenient , and easily accessible for all the developers , but we came across an issue , which is now we want to to get a string value from an entity pass it to a rest service and get the required object , e.g ( pass currency code , and get a currency object ) , now the entity will hold a string and the model will hold an object , and we need a custom conversion for that, but if we write the code inside the DTO class then it won't be a pure data class anymore because it will talk to a rest service , and things won't be totally isolated , and trying the custom conversion using xml or api will force me to move all the annotation from this model to the api or xml version , and that won't be very pretty for some classes to be annotated while others are xml configured, it will somehow confuse the developers , on when to use what kind of configuration.

@avurro
Copy link
Member

avurro commented Jun 20, 2016

trying the custom conversion using xml or api will force me to move all the annotation from this model to the api or xml version

No, you can define configuration in annotation and ONLY conversion method in API/XML format (see the example above, mappedClass has no attribute definition), you do not need to redefine the relationship in XML.

I have helped you in some way, or I did not understand?

@faroukelabady
Copy link
Author

that sounds very good, okay I will try it out once more and inform you if there is anything new,
thanks for your help so much :)

@avurro
Copy link
Member

avurro commented Jun 20, 2016

ok, i hope that everything works 😃

@faroukelabady
Copy link
Author

I tried your example but it gives me the following error

Caused by: com.googlecode.jmapper.exceptions.MappingErrorException: incorrect configuration of the currency field in Dest Class: the currency field doesn't exist in Source Class
at com.googlecode.jmapper.config.Error.mapping(Error.java:404)
at com.googlecode.jmapper.config.ConfigReader.getValue(ConfigReader.java:99)
at com.googlecode.jmapper.config.ConfigReader.retrieveTargetFieldName(ConfigReader.java:280)
at com.googlecode.jmapper.operations.OperationHandler.loadStructures(OperationHandler.java:123)
at com.googlecode.jmapper.generation.MapperConstructor.(MapperConstructor.java:257)
at com.googlecode.jmapper.generation.MapperBuilder.generate(MapperBuilder.java:84)
at com.googlecode.jmapper.JMapper.createMapper(JMapper.java:458)
at com.googlecode.jmapper.JMapper.(JMapper.java:440)
... 3 more

then I edited the Dest class and added the following


public class Dest {
    @JMap String test;
    @JMap("code") String currency;
}

but now I get the following error

Caused by: com.googlecode.jmapper.exceptions.ConversionBodyIllegalCodeException: the conversion method contains illegal code, check the conversion code belonging to the Dest class. Additional information: [source error] syntax error near "UTF-8"?>
<jmapper
"
at com.googlecode.jmapper.config.Error.bodyContainsIllegalCode(Error.java:107)
at com.googlecode.jmapper.generation.JavassistGenerator.generate(JavassistGenerator.java:75)
at com.googlecode.jmapper.generation.MapperGenerator.generateMapperClass(MapperGenerator.java:76)
at com.googlecode.jmapper.generation.MapperBuilder.generate(MapperBuilder.java:88)
at com.googlecode.jmapper.JMapper.createMapper(JMapper.java:458)
at com.googlecode.jmapper.JMapper.(JMapper.java:440)
... 3 more

very strange exception I was pretty sure it was working before

@avurro
Copy link
Member

avurro commented Jun 20, 2016

please attach the JMapperAPI configuration and the XML obtained from the method call api.toXstream().toString()

@avurro avurro changed the title possible enhancments lambda expression in API conversions Jun 20, 2016
@faroukelabady
Copy link
Author

sorry for the late reply here is the XML document


<?xml version="1.0" encoding="UTF-8"?>
<jmapper
   xmlns="http://jmapper-framework.github.io"
   xmlns:xsi="http://jmapper-framework.github.io/jmapper-core"
   xsi:noNamespaceSchemaLocation="http://jmapper-framework.github.io/jmapper-core/jmapper-1.6.0.xsd">
   <class name = "org.test.jmapper2.Dest">
      <conversion  name= "conversion" from ="code" to ="currency">return ${source};
      </conversion>
   </class>
</jmapper>

also here is the code I wrote, maybe I did something wrong


public class Dest {

    @JMap
    String test;

    @JMap("code")
    String currency;

    public String getTest() {
        return test;
    }


    public void setTest(String test) {
        this.test = test;
    }


    public String getCurrency() {
        return currency;
    }

    public void setCurrency(String currency) {
        this.currency = currency;
    }

}
public class Source {

    String test;
    String code;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}


public class Main {

    public static void main(String[] args) {

        JMapperAPI api = new JMapperAPI().add(
                mappedClass(Dest.class).add(
                    conversion("conversion").from("code").to("currency")
                        .body("return ${source};")));
System.out.println(api.toXStream().toString());
      Source source = new Source();
      source.setCode("USD");
      source.setTest("test  here");

      JMapper<Dest, Source> mapper = new JMapper<>(Dest.class, Source.class, api);
      Dest d = mapper.getDestination(source);   
    }
}

@avurro avurro closed this as completed Aug 10, 2016
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

2 participants