Can I control mapping order with MapStruct? - mapstruct

I have a use case where I want to map objects to a ByteBuffer for transmission....
#Mapper
public static interface ByteBufferMapper {
public static ByteBufferMapper INSTANCE = Mappers.getMapper(ByteBufferMapper.class);
default byte toByte(ByteBuffer buffer) {
byte b = buffer.get();
return b;
}
}
public static class Dto {
public byte b;
public byte bb;
...
}
#Mapper(uses = ByteBufferMapper.class)
public static interface DtoMapper {
public static DtoMapper INSTANCE = Mappers.getMapper(DtoMapper.class);
#Mapping(source = "buffer", target = "bb")
#Mapping(source = "buffer", target = "b")
Dto byteBufferToDto(ByteBuffer buffer);
}
public static void main( String[] args ) {
ByteBuffer buffer = ByteBuffer.allocate(2).put((byte) 0xFF).put((byte) 0x00).flip();
System.out.println(DtoMapper.INSTANCE.byteBufferToDto(buffer));
}
Is there a way I can control MapStructs mapping order so the b variable gets populated with the 0xFF and bb gets populated with the 0x00 value?

Yes you can by means of #Mapping.dependsOn.
Like this:
#Mappings({
#Mapping(target = "surName", source = "lastName", dependsOn = "middleName"),
#Mapping(target = "middleName", dependsOn = "givenName"),
#Mapping(target = "givenName", source = "firstName")
})
AddressDto addressToDto(Address address);

Related

How to use #ConfigProperty in a MapStruct mapper?

I need a #ConfigProperty in my Mapper.
I cannot inject it, since the Mapper is an interface.
How can I solve this?
#ConfigProperty(name = "limit") // <- Does not work here
int limit;
#Mapping(target = "myTarget", source = "mySource", qualifiedByName = "myLimitMapper")
MyDto toDTO(Entity entity);
#Named(value = "myLimitMapper")
default int mapLimit(int number) {
if (number >= limit) return limit;
else return number;
}
I'm assuming you're using Quarkus, seeing the #ConfigProperty. But you can define an abstract mapper and use the cdi componentModel to let MapStruct create an #ApplicationScoped CDI bean. This is described in the dependency injection section of the MapStruct docs.
E.g. you have 2 classes to map:
public record UserModel(String name, int number) {}
public record UserDto(String name, int number) {}
In which limit is configured in the application.properties as 100.
Your mapper will look like:
#Mapper(componentModel = "cdi")
public abstract class UserMapper {
#ConfigProperty(name = "limit")
int limit;
#Mapping(target = "number", source = "number", qualifiedByName = "myLimitMapper")
abstract UserDto mapToDto(UserModel userModel);
#Named(value = "myLimitMapper")
int mapLimit(int number) {
if (number >= limit) return limit;
else return number;
}
}
You could run this as a #QuarkusTest to verify:
#QuarkusTest
public class LimitTest {
#Inject
UserMapper userMapper;
#Test
public void testMapping() {
UserModel userModel = new UserModel("John", 150);
UserDto userDto = userMapper.mapToDto(userModel);
assertEquals("John", userDto.name());
assertEquals(100, userDto.number());
}
}

Select by ResultHandler will put a empty list objcet in localCache

MyBatis version
3.5.4
Database vendor and version
MySQL 5.7.14
Test case or example project
public static final String URL = "jdbc:mysql://localhost:3306/myTest?useUnicode=true&characterEncoding=UTF-8&useSSL=false";
public static final String Driver = "com.mysql.cj.jdbc.Driver";
public static final String User_Name = "root";
public static final String Password = "";
public static void main(String[] args) {
DataSource dataSource = new PooledDataSource(Driver,URL, User_Name,Password);
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.select("selectCount",resultContext -> {
System.out.println(resultContext.getResultCount());
System.out.println(resultContext.getResultObject());
});
Object c = sqlSession.selectOne("selectCount");
System.out.println(c);
}
public interface BlogMapper {
#Select("select count(*) from datas")
public int selectCount();
}
Steps to reproduce
Expected result
Object c = sqlSession.selectOne("selectCount");
selectOne function should retuen result ,but return null.
Actual result
sqlSession.selectOne("selectCount"); return null

How to Map to Generic Type?

PageInfoDto
public class PageInfoDto<T> implements Serializable {
private int currentPageNum;
private int totalPageNum;
private int perPageNum;
private int totalItemNum;
private List<T> list;
}
Page
public class Page<T> implements Serializable {
private int current;
private int total;
private int size;
private int items;
private List<T> list;
}
say, i have a school list and a student list.
i want to map Page to PageInfoDto and map Page to PageInfoDto。
this is how my mapper looks like;
#Mapper( config = CentralConfig.class, uses = {StudentMapper.class, SchoolMapper.class}, componentModel = "spring")
public interface PageInfoMapper {
#Mappings({
#Mapping(target = "list", source = "pageInfo.records"),
#Mapping(target = "currentPageNum", source = "pageInfo.current"),
#Mapping(target = "totalPageNum", source = "pageInfo.pages"),
#Mapping(target = "perPageNum", source = "pageInfo.size"),
#Mapping(target = "totalItemNum", source = "pageInfo.total"),
})
PageInfoDto<SchoolDto> toPageInfoDto1(Page<School> pageInfo);
#Mappings({
#Mapping(target = "list", source = "pageInfo.records"),
#Mapping(target = "currentPageNum", source = "pageInfo.current"),
#Mapping(target = "totalPageNum", source = "pageInfo.pages"),
#Mapping(target = "perPageNum", source = "pageInfo.size"),
#Mapping(target = "totalItemNum", source = "pageInfo.total"),
})
PageInfoDto<StudentDto> toPageInfoDto2(Page<Student> pageInfo);
}
this obviously not a clever way to do . is there a better way to do this?
Mapstruct is a code generator. So it needs to know which types to construct in order to generate a method implementation. Having said that, you could do this smarter by using a base mapping method on which you define all the #Mapping annotations and ignore the generic type mapping. You still have the methods above but you just specify #InheritConfiguration
Alternatively you could consider playing around with an objectfactory using #TargetType to construct the proper generic type. I'm not sure whether that works with a generic mapping method signature. I'm not in the position to check it, but let me know if that works
E.g.
public interface BasePageMapper<S, DTO> {
#Mapping(target = "list", source = "records"),
#Mapping(target = "currentPageNum", source = "current"),
#Mapping(target = "totalPageNum", source = "pages"),
#Mapping(target = "perPageNum", source = "size"),
#Mapping(target = "totalItemNum", source = "total"),
PageInfoDto<DTO> toPageInfoDto(Page<S> pageInfo);
DTO toDto(S source);
}
#Mapper( config = CentralConfig.class, uses = StudentMapper.class, componentModel = "spring")
public interface StudentMapper extends BasePageMapper<Student, StudentDto> {
}
#Mapper( config = CentralConfig.class, uses = SchoolMapper.class, componentModel = "spring")
public interface SchoolMapper extends BasePageMapper<School, SchoolDto> {
}

parsing issue in apache beam (beamSql)

I have a below code in which I'm reading a file in string format, then converting it into class format then converting it to BeamRecord and at the end converting back it to string format and writing the output in google storage.
DataflowPipelineOptions options = PipelineOptionsFactory.as(DataflowPipelineOptions.class);
options.setProject("beta-194409");
options.setStagingLocation("gs://clrtegbucket/staging");
options.setRunner(DataflowRunner.class);
DataflowRunner.fromOptions(options);
Pipeline p = Pipeline.create(options);
PCollection<String> weekly = p.apply(TextIO.read().from("gs://gcp/input/WeeklyDueto.csv"));
PCollection<ClassWeeklyDueto> pojos = weekly.apply(ParDo.of(new DoFn<String, ClassWeeklyDueto>() { // converting String into class
// typ
private static final long serialVersionUID = 1L;
#ProcessElement
public void processElement(ProcessContext c) {
String[] strArr = c.element().split(",");
ClassWeeklyDueto clr = new ClassWeeklyDueto();
clr.setCatLib(strArr[1]);
clr.setCausalValue(strArr[7]);
clr.setDuetoValue(strArr[5]);
clr.setModelIteration(strArr[8]);
clr.setOutlet(strArr[0]);
clr.setPrimaryCausalKey(strArr[6]);
clr.setProdKey(strArr[2]);
clr.setPublished(strArr[9]);
clr.setSalesComponent(strArr[4]);
clr.setWeek(strArr[3]);
global_Weekly.add(clr);
c.output(clr);
}
}));
BeamRecordSqlType appType = BeamRecordSqlType.create(
Arrays.asList("Outlet", "CatLib", "ProdKey", "Week", "SalesComponent", "DuetoValue","PrimaryCausalKey", "CausalValue", "ModelIteration", "Published"),
Arrays.asList(Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.FLOAT, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR));
PCollection<BeamRecord> apps = pojos.apply(ParDo.of(new DoFn<ClassWeeklyDueto, BeamRecord>() {
private static final long serialVersionUID = 1L;
#ProcessElement
public void processElement(ProcessContext c) {
BeamRecord br = new BeamRecord(appType, {
BeamRecord br = new BeamRecord(appType, c.element().Outlet, c.element().CatLib, c.element().ProdKey,
c.element().Week, c.element().SalesComponent, c.element().DuetoValue,
c.element().PrimaryCausalKey, c.element().CausalValue, c.element().ModelIteration,
c.element().Published);
c.output(br); }
})).setCoder(appType.getRecordCoder());
PCollection<String> gs_output_final = apps.apply(ParDo.of(new DoFn<BeamRecord, String>() {
private static final long serialVersionUID = 1L;
#ProcessElement
public void processElement(ProcessContext c) {
c.output(c.element().toString());
System.out.println(c.element().toString());
}
}));
gs_output_final.apply(TextIO.write().to("gs://gcp/output/Q"));
I have created class ClassWeeklyDueto below :
package com.pojo;
import java.io.Serializable;
public class ClassWeeklyDueto implements Serializable {
private static final long serialVersionUID = 1L;
public String Outlet;
public String CatLib;
public String ProdKey;
public String Week;
public String SalesComponent;
public float DuetoValue;
public String PrimaryCausalKey;
public String CausalValue;
public String ModelIteration;
public String Published;
public String getOutlet() {
return Outlet;
}
public void setOutlet(String outlet) {
Outlet = outlet;
}
public String getCatLib() {
return CatLib;
}
public void setCatLib(String catLib) {
CatLib = catLib;
}
public String getProdKey() {
return ProdKey;
}
public void setProdKey(String prodKey) {
ProdKey = prodKey;
}
public String getWeek() {
return Week;
}
public void setWeek(String week) {
Week = week;
}
public String getSalesComponent() {
return SalesComponent;
}
public void setSalesComponent(String salesComponent) {
SalesComponent = salesComponent;
}
public float getDuetoValue() {
return DuetoValue;
}
public void setDuetoValue(float duetoValue) {
DuetoValue = duetoValue;
}
public String getPrimaryCausalKey() {
return PrimaryCausalKey;
}
public void setPrimaryCausalKey(String primaryCausalKey) {
PrimaryCausalKey = primaryCausalKey;
}
public String getCausalValue() {
return CausalValue;
}
public void setCausalValue(String causalValue) {
CausalValue = causalValue;
}
public String getModelIteration() {
return ModelIteration;
}
public void setModelIteration(String modelIteration) {
ModelIteration = modelIteration;
}
public String getPublished() {
return Published;
}
public void setPublished(String published) {
Published = published;
}
public float setDuetoValue(String string) {
// TODO Auto-generated method stub
float f = Float.valueOf(string.trim()).floatValue();
return f;
}
}
The DueToValue field is declared float type, Only the field declared as varchar is getting parsed rest none of the datatypes are getting parsed.
So how shall I parse field declared as Int or float or even Date ?
When you manually split a string line from CSV, you get an array of strings. Then you have to manually parse the values from strings. Java doesn't handle it automatically.
In your case to handle floats you need to change clr.setDueToValue(strArr[5]) to clr.setDueToValue(Float.parseFloat(strArr[5])), see the doc.
Similarly you can use Integer.parseInt() to parse integers.
For parsing dates you will likely need to use a SimpleDateFormat.

JAXB Moxy- Question on how to annotate field that is xsd complex type

I am getting started with JaxB and am using the Moxy implementation. I have an industry standard xsd that I converted to Java Object Model using Jaxb. I have gotten as far as annotating simple fields like string,integer and date.
I have been searching and need to be pointed in the right direction to annotate the following field which is a xsd complex type which has 4 attributes and an optional string element. A subset of the generated code is as follows:
Conditions.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"condition"
})
#XmlRootElement(name = "conditions")
public class Conditions {
protected List<Conditions.Condition> condition;
public List<Conditions.Condition> getCondition() {
if (condition == null) {
condition = new ArrayList<Conditions.Condition>();
}
return this.condition;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"problemDate",
"problemType",
"problemCode",
"problemStatus",
})
public static class Condition {
protected IvlTs problemDate;
//This is the field I need to annotate (problemType)
protected Cd problemType;
//The 2 below fields (problemCode, problemStatus) will also have to be annotated but I am just focusing on problemType for now
protected Cd problemCode;
protected Ce problemStatus
public void setProblemDate(IvlTs value) {
this.problemDate = value;
}
public void setProblemType(Cd value) {
this.problemType = value;
}
public void setProblemCode(Cd value) {
this.problemCode = value;
}
public void setProblemStatus(Ce value) {
this.problemStatus = value;
}
//omitted getters
}
Cd.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "cd", propOrder = {
"originalText",
})
public class Cd {
protected Object originalText;
#XmlAttribute(name = "code")
#XmlSchemaType(name = "anySimpleType")
protected String code;
#XmlAttribute(name = "displayName")
#XmlSchemaType(name = "anySimpleType")
protected String displayName;
#XmlAttribute(name = "codeSystem")
#XmlSchemaType(name = "anySimpleType")
protected String codeSystem;
#XmlAttribute(name = "codeSystemName")
#XmlSchemaType(name = "anySimpleType")
protected String codeSystemName;
#XmlAttribute(name = "nullFlavor")
protected NullFlavorType nullFlavor;
//ommitted getters and setters
The Cd.java class will be used for a number of other classes, not only in the Conditions.java class.
My question in particular is how would I annotate my fields for problemType in Conditions.java, where problemType has 4 attributes and one optional element.
I will not be able to directly annotate Cd.java as the xml input will differ depending on what class I am implementing (choice of 8 other classes that use Cd.java class). The existing annotations above were auto-generated by Jaxb The xml input for the Conditions.java problemType is as follows:
<PROBLEM_MODULE>
<code>24434</code> //Maps to protected String code in Cd.java;
<codeName>ICD-9</codeName> //Maps to protected String codeSystem in Cd.java;
<display>Asthma</display> //Maps to protected String displayName in Cd.java;
<codeSystem>2.564.34343.222</codeSystem> // Maps to protected String codeSystemName in Cd.java;
</PROBLEM_MODULE>
Please advise where I need to clarify my question. Ultimately I am requesting resources or tutorial to help me through this.
******UPDATE*******
Blaise's solution worked perfectly as I tested it on another project that is not as complex. Thus, the method is right, but there is something that I am getting wrong with the metadata file. I updated the Conditions.java file above, as I left out details that may effect the way I need to implement the metadata file.
My oxm.xml file
<?xml version="1.0" encoding="UTF-8"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="conditions.exec"
xml-mapping-metadata-complete="true">
<java-types>
<java-type name="Conditions" xml-accessor-type="FIELD">
<xml-root-element name="PROBLEM_MODULE"/>
</java-type>
<java-type name="Cd" xml-accessor-type="FIELD">
<java-attributes>
<xml-type prop-order="code codeSystem displayName codeSystemName"/>
<xml-element java-attribute="codeSystem" name="codeName"/>
<xml-element java-attribute="displayName" name="display"/>
<xml-element java-attribute="codeSystemName" name="codeSystem"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
*Main Class*
public static void main(String[] args) {
try {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, new File("src/conditions/exec/oxm.xml"));
JAXBContext jc = JAXBContext.newInstance(new Class[] {Conditions.class,Cd.class}, properties);
// create an Unmarshaller
Unmarshaller u = jc.createUnmarshaller();
conditions.exec.Conditions InventoryInput = (conditions.exec.Conditions) u.unmarshal(
new File("src/conditions/exec/problems.xml")); //input file
// create a Marshaller and marshal to a file
Marshaller resultMarshaller = jc.createMarshaller();
resultMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
resultMarshaller.marshal(InventoryInput, System.out);
} catch (JAXBException je) {
je.printStackTrace();
}
You can leverage EclipseLink JAXB (MOXy)'s external binding file to apply a second mapping to your class:
oxm.xml
One thing that I have set in this file is xml-mapping-metadata-complete="true", this setting tells MOXy to ignore the annotations completely and just use this file. By default the OXM file is used to supplement the annotations.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum7043389"
xml-mapping-metadata-complete="true">
<java-types>
<java-type name="Root2">
<xml-root-element/>
</java-type>
<java-type name="Cd">
<xml-type prop-order="code codeSystem displayName codeSystemName"/>
<java-attributes>
<xml-element java-attribute="codeSystem" name="codeName"/>
<xml-element java-attribute="displayName" name="display"/>
<xml-element java-attribute="codeSystemName" name="codeSystem"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Demo
The oxm.xml file is passed in as a property to create the JAXBContext. In the example below jc1 is created on the classes and jc2 is created on the classes and oxm.xml
package forum7043389;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Cd cd = new Cd();
cd.setCode("24434");
cd.setCodeSystem("ICD-9");
cd.setDisplayName("Asthma");
cd.setCodeSystemName("2.564.34343.222");
JAXBContext jc1 = JAXBContext.newInstance(Root1.class);
Marshaller marshaller1 = jc1.createMarshaller();
marshaller1.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Root1 root1 = new Root1();
root1.setCd(cd);
marshaller1.marshal(root1, System.out);
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum7043389/oxm.xml");
JAXBContext jc2 = JAXBContext.newInstance(new Class[] {Root2.class}, properties);
Marshaller marshaller2 = jc2.createMarshaller();
marshaller2.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Root2 root2 = new Root2();
root2.setCd(cd);
marshaller2.marshal(root2, System.out);
}
}
Output
The following is the output from running the demo:
<?xml version="1.0" encoding="UTF-8"?>
<root1>
<cd code="24434" displayName="Asthma" codeSystem="ICD-9" codeSystemName="2.564.34343.222"/>
</root1>
<?xml version="1.0" encoding="UTF-8"?>
<root2>
<cd>
<code>24434</code>
<codeName>ICD-9</codeName>
<display>Asthma</display>
<codeSystem>2.564.34343.222</codeSystem>
</cd>
</root2>
Cd
package forum7043389;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "cd", propOrder = {"originalText",})
public class Cd {
protected Object originalText;
#XmlAttribute(name = "code")
#XmlSchemaType(name = "anySimpleType")
protected String code;
#XmlAttribute(name = "displayName")
#XmlSchemaType(name = "anySimpleType")
protected String displayName;
#XmlAttribute(name = "codeSystem")
#XmlSchemaType(name = "anySimpleType")
protected String codeSystem;
#XmlAttribute(name = "codeSystemName")
#XmlSchemaType(name = "anySimpleType")
protected String codeSystemName;
#XmlAttribute(name = "nullFlavor")
protected NullFlavorType nullFlavor;
public Object getOriginalText() {
return originalText;
}
public void setOriginalText(Object originalText) {
this.originalText = originalText;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getCodeSystem() {
return codeSystem;
}
public void setCodeSystem(String codeSystem) {
this.codeSystem = codeSystem;
}
public String getCodeSystemName() {
return codeSystemName;
}
public void setCodeSystemName(String codeSystemName) {
this.codeSystemName = codeSystemName;
}
public NullFlavorType getNullFlavor() {
return nullFlavor;
}
public void setNullFlavor(NullFlavorType nullFlavor) {
this.nullFlavor = nullFlavor;
}
}
Root1
package forum7043389;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root1 {
private Cd cd;
public Cd getCd() {
return cd;
}
public void setCd(Cd cd) {
this.cd = cd;
}
}
Root2
package forum7043389;
public class Root2 {
private Cd cd;
public Cd getCd() {
return cd;
}
public void setCd(Cd cd) {
this.cd = cd;
}
}
For More Information
http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html