JAXB multiple #XmlRootElement - java

I have a class annotated as follows:
#XmlRootElement(name="response")
#XmlType(propOrder={"paymentid",
"result",
"responsecode",
"authorizationcode",
"merchantorderid",
"rrn",
"cardcountry",
"cardtype"})
public class MOTOResponseIn {
...
}
The root element of the mapped XML could be also be error beside
response.
How can I annotate the class so that both root elements are allowed?

In this case #XmlRootElement can not be used.
You have to use ObjectFactory.
The #XmlElementDecl annotation is used to represent root elements that correspond to named complex types. It is placed on a factory method in a class annotated with #XmlRegistry (when generated from an XML schema this class is always called ObjectFactory). The factory method returns the domain object wrapped in an instance of JAXBElement
Hope this url will help.
https://dzone.com/articles/jaxb-and-root-elements

With a single class and #XmlRootElement it is not possible.
However, in case you don't want to mess with ObjectFactory, for a quick workaround you can use abstract and concrete classes. (Assuming the only difference is the root element)
Example:
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class MOTOabstract{
#XmlAttribute
private String paymentid;
#XmlAttribute
private String result
#XmlAttribute
private String responsecode;
...
}
#XmlRootElement(name="response")
#XmlAccessorType(XmlAccessType.FIELD)
public class MOTOresponse extends MOTOabstract{}
#XmlRootElement(name="error")
#XmlAccessorType(XmlAccessType.FIELD)
public class MOTOerror extends MOTOabstract{}

#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"id",
"name",
"serviceAttrs"
})
#XmlSeeAlso({ AddGroup.class, AddGroupRequest.class })
public class AddGroupAbstract {
#XmlElement(required = true)
protected String id;
#XmlElement(required = true)
protected String name;
#XmlElement(required = true)
protected ServiceAttrs serviceAttrs;
...
}
#XmlRootElement(name = "addGroup")
public class AddGroup extends AddGroupAbstract {}
#XmlRootElement(name = "addGroupRequest")
public class AddGroupRequest extends AddGroupAbstract {}
#Endpoint
public class GroupEndpoint {
private final GroupService groupService;
private final ServiceService serviceService;
private final RestTemplate restTemplate;
public GroupEndpoint(GroupService groupService, ServiceService serviceService, RestTemplate restTemplate) {
this.groupService = groupService;
this.serviceService = serviceService;
this.restTemplate = restTemplate;
}
#PayloadRoots({
#PayloadRoot(namespace = SoapConstants.NAMESPACE_ACCOUNT_URI, localPart = "addGroup"),
#PayloadRoot(namespace = SoapConstants.NAMESPACE_ACCOUNT_URI, localPart = "addGroupRequest")
})
#ResponsePayload
public AddGroupResponse addGroup(#RequestPayload AddGroupAbstract request) {
AddGroupResponse response = new AddGroupResponse();
...
}
}

Related

Spring boot validation for interfaces and extended ArrayList

I am using spring-boot with hibernate validator
I have a controller annotated with #Validated and I want to validate all the params in my APIs.
One of the annotation is not working(I debugged and it just never being checked as if it is not annotated at all).
public MyResponse getPolicyCounters(#RequestBody #Valid MyRequest request) throws Exception {
The MyRequest class looks like this:
#Valid
public class MyRequest{
boolean shouldSumSubRules;
private #Valid RulesSelector rules;
The RuleSelector class is an interface and looks like this:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = RulesStaticList.class, name = "list"),
#JsonSubTypes.Type(value = RulesDynamicList.class, name = "criteria")
})
#Valid
public interface RulesSelector {
}
The RuleStaticList class looks like this:
#Valid
public class RulesStaticList extends ArrayList<#Valid RuleReference> implements RulesSelector {
}
The RuleRefrence class looks like this:
#JsonDeserialize(using = RuleReferenceDeserializer.class)
#Valid
public interface RuleReference {
RuleReferenceKind getKind();
}
final class RuleReferenceDeserializer extends StdDeserializer<RuleReference> {
public RuleReferenceDeserializer() {
super(RuleReference.class);
}
#Override
public RuleReference deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String fieldName = p.nextFieldName();
switch (fieldName) {
case "tag":
return p.readValueAs(RuleReferenceByTag.class);
case "special":
return p.readValueAs(RuleReferenceBySpecial.class);
default:
throw ctxt.wrongTokenException(p, RuleReference.class, JsonToken.FIELD_NAME, "");
}
}
}
I have 2 implementation for RuleReference one of them is RuleReferenceByTag and it looks like this:
#Valid
public class RuleReferenceByTag implements RuleReference {
#Size(max = 128)
private String tag;
public RuleReferenceKind getKind() { return RuleReferenceKind.Tag; }
}
I added #Valid annotation everywhere I could but it still does not work.
What am I missing?
Other annotation in with different classes it does work but I could not solve this problem :/

Unmarshal element attribute and text to separate fields with jaxb

How do I annotate the externalValue and companyId fields in the Root class so that "abc" gets mapped to externalValue and "123" gets mapped to companyId?
Do I need the #XmlJavaTypeAdapter annotation? Where? I'm hoping that if I do, it can just handle those 2 fields and I can leave the annotations for title and countryCodes as-is.
XML:
<item>
<externalValue companyId="123">abc</externalValue>
<title>My Title</title>
<country>US</country>
<country>CA</country>
</item>
POJO:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private String externalValue;
private String companyId;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
// getters and setters...
}
I am afraid that this is not possible to achieve only with annotations (so without extra POJO and some adapter) in general case namely JAXB specs. However if your happen to use MOXy as your JAXB implementation it is easy as adding annotation #XmlPath like this:
#XmlPath("externalValue/#companyId")
private String companyId;
Related question: Unmarshalling an XML using Xpath expression and jaxb
You have to define the class in the following manner.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
private CompanyIdValue companyIdValue;
#XmlElement
private String title;
#XmlElement(name = "country")
public List<String> countryCodes;
//getter and setter
}
In case of both attribute in an XML element tag, you have to define a separate class. Define a separate class called CompanyIdValue, for XML element, you have to define #XmlValue and for attribute you have to annotate with #XmlAttribute
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class CompanyIdValue {
#XmlElement(name = "externalValue")
private String externalValue;
private String companyId;
public String getExternalValue() {
return externalValue;
}
#XmlValue
public void setExternalValue(String externalValue) {
this.externalValue = externalValue;
}
public String getCompanyId() {
return companyId;
}
#XmlAttribute
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
}
I provide below a test program also for testing.
public class Test {
public static void main(String[] args) {
try {
Item item = new Item();
CompanyIdValue companyIdValue = new CompanyIdValue();
companyIdValue.setCompanyId("SomeId");
companyIdValue.setExternalValue("Some External value");
item.setCompanyIdValue(companyIdValue);
item.setCountryCodes(Arrays.asList("A", "B"));
item.setTitle("Some Title");
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(item, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}

Get Derived DTO From Base Class Request Body DTO

I try to get derived class fields from methods response body. Request body parameter is type of base class. Request comes with derived class fields but I can't cast it to derived class.
Here is my controller method and DTO classes:
Method:
#PostMapping("/{code}")
public ResponseEntity<PromotionDto> createPromotion(#PathVariable String code, #RequestBody PromotionDto promotion){
if(PromotionTypeEnum.ORDER_THRESHOLD_DISCOUNT.equals(promotion.getPromotionType())) {
promotionService.createPromotion(orderThresholdDiscountPromotionConverter.toEntity((OrderThresholdDiscountPromotionDto)promotion));
}
return ResponseEntity.ok(promotion);
}
Base class DTO:
import dto.base.BaseDto;
import promotionservice.PromotionTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class PromotionDto extends BaseDto {
private String code;
private String title;
private String description;
private PromotionTypeEnum promotionType;
}
Derived class DTO:
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}
Request JSON is:
{
"code":"qwewe",
"title":"qwewe",
"description":"qwewe",
"promotionType":"ORDER_THRESHOLD_DISCOUNT",
"thresholdTotal":1.3,
"discountPrice":"12.5",
"messageFired":"qwewe",
"messageCouldHaveFired":"qwewe"
}
as result, service returns error:
{
"type": "https://www.jhipster.tech/problem/problem-with-message",
"title": "Internal Server Error",
"status": 500,
"detail": "promotion.PromotionDto cannot be cast to promotion.OrderThresholdDiscountPromotionDto",
"path": "/api/promotionresults/qwewe",
"message": "error.http.500"
}
My question is: is there any way, library, annotation etc. to get the
derived class instance from request ?
Use Jackson inheritance feature. Annotate PromotionDto class as below:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "promotionType")
#JsonSubTypes({
#Type(value = OrderThresholdDiscountPromotionDto.class, name = "ORDER_THRESHOLD_DISCOUNT"),
})
class PromotionDto {
and remove:
private PromotionTypeEnum promotionType;
property. It will be handled automatically by Jackson. In controller you will be able to use instanceof.
What are you trying to do is you are trying typecast the Parent into a child which is known as Downcasting. This is only valid when you have the Parent as an instance of child. In your case, PromotionDto should be an instance of OrderThresholdDiscountPromotionDto.
Please refer below example:
public class PromotionDto {
private String code;
private String title;
private String description;
public static void main(String[] args) {
PromotionDto promotionDto = new OrderThresholdDiscountPromotionDto();
PromotionDto promotionDto_2 = new PromotionDto();
//Valid downcasting
OrderThresholdDiscountPromotionDto subClass1 = (OrderThresholdDiscountPromotionDto)promotionDto;
//Invalid down casting
OrderThresholdDiscountPromotionDto subClass2 = (OrderThresholdDiscountPromotionDto)promotionDto_2;
}
}
class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}

JAXB : annotation. Exception raised

I have a REST service which serialize into the response some objects.
My entities ares annotated with XML but JAXB raised an illegalAnnotationExceptions...
Here the entities :
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "icns")
public class IcnList {
#XmlElement(required = true)
private List<IcnElement> icns;
public List<IcnElement> getIcns() {
return icns;
}
public void setIcns(List<IcnElement> icns) {
this.icns = icns;
}
}
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "icn")
public class IcnElement {
private String status;
private String revision;
private String icnName;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
}
public String getIcnName() {
return icnName;
}
public void setIcnName(String icnName) {
this.icnName = icnName;
}
}
Here the exception :
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "icns"
this problem is related to the following location:
at public java.util.List com.xx.model.IcnList.getIcns()
at com.xx.model.IcnList
this problem is related to the following location:
at private java.util.List com.xx.model.IcnList.icns
at com.xx.model.IcnList
Can someone tell me what is the problem ? and why ?
I made some research but I'm totally lost...
Thank you.
By default JAXB will treat public properties and annotated fields as mapped. The conflict is occurring in your mapping because JAXB thinks you have the following mappings:
A field called icns that is mapped to the element icns.
A property called icns that is mapped to the element icns.
This is causing your name conflict. You can eliminate the conflict by annotating the property (get or set method):
#XmlRootElement(name = "icns")
public class IcnList {
private List<IcnElement> icns;
#XmlElement(required = true)
public List<IcnElement> getIcns() {
return icns;
}
public void setIcns(List<IcnElement> icns) {
this.icns = icns;
}
}
Or if you wish to annotate the field you can use #XmlAccessorType(XmlAccessType.FIELD) at the class level.
#XmlRootElement(name = "icns")
#XmlAccessorType(XmlAccessType.FIELD)
public class IcnList {
#XmlElement(required = true)
private List<IcnElement> icns;
public List<IcnElement> getIcns() {
return icns;
}
public void setIcns(List<IcnElement> icns) {
this.icns = icns;
}
}
For More Information
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
Change the name of the root element
#XmlRootElement(name = "icns")
or, this element:
#XmlElement(required = true)
private List<IcnElement> icns;
Use #XmlType(name = "icn" ....) instead

Hierarchy in jersey's EntityHolder type

#XmlRootElement(name = "request")
#XmlAccessorType(XmlAccessType.FIELD)
#JSONConfigurable
public class InteractionRequest {
#XmlElement(name = "skill")
protected String skillName;
}
#XmlRootElement(name = "request")
#XmlAccessorType(XmlAccessType.FIELD)
#JSONConfigurable
public class InteractionChatRequest extends InteractionRequest {
#XmlElement
#XmlJavaTypeAdapter(LPStringsXmlAdapter.class)
#XmlElementWrapper(name = "preChatLines")
protected List<String> line;
}
And 2 usages:
#PUT
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response postExitSurvey(EntityHolder<InteractionRequest> ent) {
InteractionRequest request = ent.getEntity();
return null;
}
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response interactionRequest(EntityHolder<InteractionChatRequest> ent) {
InteractionChatRequest params = ent.getEntity();
return null;
}
Now, in both cases, the entity holder holds InterationRequest object which results in a ClassCastException in the second usage.
Any idea why? Shouldn't Jersey cast the entity to the type I declare?
Is hierarchy even possible in this case?
Thanks,
Udi
You have a problem with the JAXB annotations: both InteractionRequest and InteractionChatRequest are annotated with #XmlRootElement(name = "request"). So they have the same root element, which makes it impossible for JAXB to distinguish between them.
Try to change the InteractionChatRequest to #XmlRootElement(name = "chat-request").

Categories