Parse XML to Object using XmlMapper - spring

How shoud I correctly declare object to parse it from XML? Problem with field ProblemField which is found several times. I try to parse it using xmlMapper.readValue(content, ParentObject.class) but List<DataField> problemField is null.
Input XML sample:
<ParentObject>
<ChildObject>
<PersonalId title="Personal ID" value="123456789"/>
<Name title="sample value"
value="sample value"/>
</ChildObject>
<ChildObject>
<Sum title="Chapter title 1" value="452.00"/>
<ProblemField id="100"
title="100 | sample title"
value="4524.00"/>
<ProblemField id="101"
title="101 | sample title"
value="145224.00"/>
<ProblemField id="102" title="102 | sample title"
value="-71857.00"/>
</ChildObject>
<ChildObject>
<Sum title="Chapter title 2" value="78578.00"/>
<ProblemField id="100"
title="100 | sample title"
value="4152452.00"/>
<ProblemField id="101"
title="101 | sample title"
value="785178.00"/>
</ChildObject>
</ParentObject>
Objects declaration
ParentObject:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ParentObject {
#JsonProperty(value = "PersonalId")
private DataField personalId;
#JsonProperty(value = "Name")
private DataField name;
#JsonProperty(value = "Sum")
private DataField sum;
#JsonProperty(value = "ProblemField")
private List<DataField> problemField;
}
DataField:
#JsonIgnoreProperties(ignoreUnknown = true)
public class PkbDataField {
#JsonProperty(value = "title")
private String title;
#JsonProperty(value = "value")
private String value;
}

Related

Mapstruct throw exception when using both Mapping with and without qualifiedByName option

I have an interface mapper for profile:
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))
public interface SellerProfileMapper {
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2);
#Named("buildBase64EncodingProfilePicture")
default String buildBase64EncodingProfilePicture(Image picture) {
return ofNullable(picture)
.map(image -> Base64.getEncoder().encodeToString(image.getContent()))
.orElse(null);
}
The problem is that the implementation for this mapper can only work if I remove either
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
or this
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
then the implementation is generated! The question is why? Did I miss anything?
This is the DTO:
#Data
#Builder
#AllArgsConstructor
public class SellerProfileResponseDTO {
private CompanyProfileDTO companyProfileDTO;
private SellerProfileDTO sellerProfileDTO;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#Schema(description = "Being used as a based class for Adviser, Buyer and Seller company profile.")
public class CompanyProfileDTO {
#Schema(description = "Company logo as a base64 img", type = "String")
private String companyPicture;
private String companyPictureFileName;
private String companyProfileId;
private String companyId;
}
this is the entity I want to map:
public class SellerProfileV2 {
private String id;
private String alias;
private Company company;
...
}
The error that I get is:
Internal error in the mapping processor: java.lang.RuntimeException: org.ma pstruct.ap.shaded.freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
public interface SellerProfileMapper {
^
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (Thes
e only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?
This is the generated class for the mapper:
#Component
public class SellerProfileMapperImpl implements SellerProfileMapper {
#Override
public SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2) {
if ( sellerProfileV2 == null ) {
return null;
}
CompanyProfileDTO companyProfileDTO = null;
CompanyProfileDTO companyProfileDTO = null;
if ( sellerProfileV2.getCompany() != null ) {
if ( FreeMarker template error:
The following has evaluated to null or missing:
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.

What is the ideal way to serialize and deserialize polymorphic entity attribute in spring boot?

I have an Entity class with a column attribute whose type is an abstract class. I want to serialize (object to JSON string) while saving it in the database column and deserialize it into an abstract class (which in turn converts the string to the appropriate concrete class) when it is retrieved from the database.
Here's how I accomplished it:
ProductEntity.java
#Entity
#Table(name="PRODUCT")
#Data
public class ProductEntity{
#Id
#Column(name = "ID", insertable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "NAME")
private String name;
#Column(name = "PRODUCT_TYPE")
private String productType;
#Column(name = "PRODUCT_SPECS")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
#Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
NOTE : "PRODUCT_SPECS" database column is of JSON type.
ProductSpecification.java
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
#JsonSubTypes({
#JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
#JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
#JsonCreator
public ComputerSpecification (#JsonProperty("memory") String memory,
#JsonProperty("displaysize") String displaySize,
#JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
#JsonCreator
public SpeakerSpecification (#JsonProperty("sensitivity") String sensitivity,
#JsonProperty("dimension") String dimension,
#JsonProperty("bassPrinciple") String bassPrinciple,
#JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
NOTE: I am using Jackson ObjectMapper for serialization and deserialization.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
#Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Request body 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
This request gets saved in the database column "PRODUCT_SPECS" as :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Now this solution works perfectly fine. The "SpeakerSpecification " key neither appears in the response of GET API call nor in the swagger doc. But having to store the type info in the database really bothers me.
Is there a better approach to this problem where I could avoid having the typeinfo (".SpeakerSpecification ") in the column value?

JAXB #XmlValue not able to get the text, Not generating empty XML element, and not able to read attribute

I have a Spring Rest Controller, that accepts request as Xml. This is the sample request format coming in.
<Message>
<Header id="101" desc="Header content description">
<title text="The quick brown fox" />
</Header>
<Content />
<Footer name="test">Footer content sample.</Footer>
</Message>
this is my controller:
#RestController
#RequestMapping("myservice")
public class MessageController {
#PostMapping(consumes = MediaType.APPLICATION_XML_VALUE)
public String handler(#RequestBody Message message) {
System.out.println(message);
System.out.println("\n\n\n");
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Message.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(message, System.out);
} catch(JAXBException ex) {
System.out.println(ex.toString());
}
return "Done!";
}
}
and I have the following classes, for the Message class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Message")
public class Message {
#XmlElement(name = "Header", required = true)
private Header header;
#XmlElement(name = "Content", required = true)
private Content content;
#XmlElement(name = "Footer", required = true)
private Footer footer;
// Getters and setters here...
#Override
public String toString() {
// In here, I outputted the values of the header and footer.
}
}
Header class:
#XmlRootElement(name = "Header")
#XmlAccessorType(XmlAccessType.FIELD)
public class Header {
#XmlAttribute(name = "id", required = true)
private String id;
#XmlAttribute(name = "desc", required = true)
private String description;
// Getters and setters here...
}
Content class:
#XmlRootElement(name = "Content")
#XmlAccessorType(XmlAccessType.FIELD)
public class Content {
}
and for the Footer class:
#XmlRootElement(name = "Footer")
#XmlAccessorType(XmlAccessType.FIELD)
public class Footer {
#XmlValue
private String value;
#XmlAttribute(name = "name")
private String name;
//Getter and setters here...
}
So there are three issues that I see here from the output:
The description attribute value from the Header is always null. Basically I wanted to have a different field name in the class but reading an attribute ("desc") from the XML. The attribute "id" is fine though, I can retrieve the value from it.
It can't generate an empty Content XML e.g. . If I put nillable=true, it will generate Content with extra attributes e.g. xmnls="..." />. Not sure how to remove those extra attributes so that it generates only empty content element.
Footer attribute "name" value can be read but not the text that says "Footer content sample".
Any thoughts?
This has been resolved. My bad that I imported the following from my gradle file.
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
So by removing this from build.gradle, everything works as expected!

JacksonXML List duplicating tags

I have this
#JacksonXmlRootElement(localName = "results")
public class GenericResults{
#JacksonXmlProperty
private String copyright;
#JacksonXmlProperty
private int count;
#JacksonXmlProperty(localName = "list")
private List<Result> list;
And on the Pojo I have this
public class Result {
#JacksonXmlProperty(isAttribute = true, localName = "symbol")
private String symbol;
#JacksonXmlProperty(isAttribute = true, localName = "datatype")
private String datatype;
#JacksonXmlProperty(isAttribute = true, localName = "entitlement")
private String entitlement;
#JacksonXmlProperty(isAttribute = true, localName = "datetime")
private Long datetime;
The result is
<results>
<copyright>Copyrights Bla Bla Bla </copyright>
<symbolCount>2</symbolCount>
<list>
<list symbolstring="x" datatype="a" entitlement="r" datetime="1499375390609"/>
<list symbolstring="y" datatype="a" entitlement="r" datetime="1499375390731"/>
</list>
</results>
And what i Want to produce is...
<results>
<copyright>Copyrights Bla Bla Bla </copyright>
<symbolCount>2</symbolCount>
<list symbolstring="x" datatype="a" entitlement="r" datetime="1499375390609"/>
<list symbolstring="y" datatype="a" entitlement="r" datetime="1499375390731"/>
</results>
Someone Can help me?? I tried some other stuff but I was not able to do it... i dont want to have the duplicate tag for list....
I found the solution just use #JacksonXmlElementWrapper(useWrapping = false)
on the list property.
Thanks!

Display Oracle date in a given format with JSTL using Hibernate

I have a date field in a table in an Oracle(10g) database which is mapped by TemporalType.TIMESTAMP in Hibernate like,
#Column(name = "DISCOUNT_START_DATE")
#Temporal(TemporalType.TIMESTAMP)
private Date discountStartDate;
//Setter and getter.
It fetches dates and displays like the following (which are already inserted into the database),
2012-08-29 01:53:10.0
2012-08-20 02:32:22.0
2012-08-01 14:00:21.0
2012-08-20 13:58:01.0
2012-08-30 04:14:13.0
2012-09-10 16:13:45.0
When I attempt to display them using JSTL like,
<fmt:formatDate pattern="dd-MMM-yyyy hh:mm:ss" value="${row.discountStartDate}"/>
it throws an exception,
javax.el.ELException: Cannot convert 2012-08-29 01:53:10.0 of type
class java.lang.String to class java.util.Date
I have tried to change TemporalType.TIMESTAMP to TemporalType.DATE but didn't work.
Earlier, it was working with XML mapping files (xxx.hbm.xml) like.
<property name="discountStartDate" type="date">
<column length="7" name="DISCOUNT_START_DATE"/>
</property>
but with annotations, it failed. How to apply this format dd-MMM-yyyy hh:mm:ss to display those dates in JSP using JSTL?
EDIT:
The following is the Discount entity class which is mapped with the DSCOUNT table in Oracle (the equals() and the toString() methods have been omitted).
#Entity
#Table(name = "DISCOUNT", catalog = "", schema = "WAGAFASHIONDB")
public class Discount implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "DISCOUNT_ID", nullable = false, precision = 35, scale = 0)
#SequenceGenerator(name = "discountIdSequence", sequenceName = "DISCOUNT_SEQ", allocationSize=1, initialValue=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "discountIdSequence")
private Long discountId;
#Column(name = "DISCOUNT_CODE", length = 100)
private String discountCode;
#Column(name = "DISCOUNT_PER", precision = 35, scale = 2)
private double discountPer;
#Column(name = "DISCOUNT_START_DATE")
#Temporal(TemporalType.TIMESTAMP)
private Date discountStartDate;
#Column(name = "DISCOUNT_END_DATE")
#Temporal(TemporalType.TIMESTAMP)
private Date discountEndDate;
#OneToMany(mappedBy = "discountId", fetch = FetchType.LAZY)
private Set<OrderTable> orderTableSet;
public Discount() {
}
public Discount(Long discountId) {
this.discountId = discountId;
}
public Long getDiscountId() {
return discountId;
}
public void setDiscountId(Long discountId) {
this.discountId = discountId;
}
public String getDiscountCode() {
return discountCode;
}
public void setDiscountCode(String discountCode) {
this.discountCode = discountCode;
}
public double getDiscountPer() {
return discountPer;
}
public void setDiscountPer(double discountPer) {
this.discountPer = discountPer;
}
public Date getDiscountStartDate() {
return discountStartDate;
}
public void setDiscountStartDate(Date discountStartDate) {
this.discountStartDate = discountStartDate;
}
public Date getDiscountEndDate() {
return discountEndDate;
}
public void setDiscountEndDate(Date discountEndDate) {
this.discountEndDate = discountEndDate;
}
public Set<OrderTable> getOrderTableSet() {
return orderTableSet;
}
public void setOrderTableSet(Set<OrderTable> orderTableSet) {
this.orderTableSet = orderTableSet;
}
}
In JSP, the following loop is used to display data (reducing the code complexity).
<c:forEach var="row" items="${list}" varStatus="loop">
<fmt:formatDate pattern="dd-MMM-yyyy hh:mm:ss" value="${row.discountStartDate}"/>
</c:forEach>
and in Spring DAO, the list which is iterated by the preceding loop is simply retrieved as follows.
#SuppressWarnings("unchecked")
public List<Discount>getList(int currentPage, int rowsPerPage)
{
List<Discount> list = sessionFactory.getCurrentSession()
.createQuery("from Discount order by discountId desc")
.setFirstResult(currentPage)
.setMaxResults(rowsPerPage).list();
for(Discount d:list)
{
System.out.println(d.getDiscountStartDate()+" : "+d.getDiscountEndDate());
}
return list;
}
The preceding loop is just for the sake of demonstration. It displays the dates from the table as follows.
2012-08-29 01:53:10.0 : 2012-08-31 01:53:16.0
2012-08-20 02:32:22.0 : 2012-08-24 02:34:36.0
2012-08-01 14:00:21.0 : 2012-08-31 14:01:30.0
2012-08-20 13:58:01.0 : 2012-08-21 13:58:20.0
2012-08-30 04:14:13.0 : 2012-11-23 16:21:57.0
2012-09-10 16:13:45.0 : 2012-10-26 16:13:39.0
2012-08-22 16:06:23.0 : 2012-08-15 16:06:17.0
2012-08-22 10:35:04.0 : 2012-08-17 10:34:56.0
2012-08-17 10:35:29.0 : 2012-08-10 10:35:35.0
2012-10-08 10:35:56.0 : 2013-03-08 10:35:49.0

Resources