Jaxb: Dealing with an optional element in rest webservice response - java

I have an Object Ticket which has an element totalNoOfTickets associated with it. I use this object Ticket in many methods of my restful service. However, i only need the element 'totalNoOfTickets' for one method only and not others....
For example, in one of my method, i say...
Ticket.setTotalNoOfTickets(7);
I do not use this totalNoOfTickets in other methods. Hence,i do not set it in other methods.
Now in my Ticket.java...I have
public class Ticket
extends ActionType {
#XmlElementWrapper(name = "Tickets")
#XmlElement(name = "Ticket")
protected List<Ticket> tickets;
#XmlElement(name = "TotalNumOfTickets")
protected int totalNumofTickets;
public int getTotalNumofTickets() {
return totalNumofTickets;
}
public void setTotalNumofTickets(int totalNumoftokens) {
this.totalNumofTickets = totalNumoftickets;
}
In method responses, where i set the totalNumoftickets, response contains:
<TotalNumOfTickets>7</TotalNumOfTickets>
But, even in responses, where i do not need totalNumoftickets, reponse contains
<TotalNumOfTickets>0</TotalNumOfTickets>
Is there any way in Jax-B where it would not have
<TotalNumOfTickets>0</TotalNumOfTickets>
in the response of methods, where i do not set totalNumoftickets?...can i use that element optionally

By default a JAXB (JSR-222) implementation will not marshal null values. You could change totalNumofTickets field/property to an Integer and have the value null when you don't want to marshal it.
public class Ticket
extends ActionType {
#XmlElementWrapper(name = "Tickets")
#XmlElement(name = "Ticket")
protected List<Ticket> tickets;
#XmlElement(name = "TotalNumOfTickets")
protected Integer totalNumofTickets;
public Integer getTotalNumofTickets() {
return totalNumofTickets;
}
public void setTotalNumofTickets(Integer totalNumoftokens) {
this.totalNumofTickets = totalNumoftickets;
}
}
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html

Related

Is it possible to map a JPA/Hibernate Entity field to a field in another class without #Embeddable?

I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString

JAXB unmarshalling based on element's value

I would like to unmarshall an object using JAXB based on the enum value/string present in the xml. I have several classes inheriting from one abstract class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ InheritingClassOne.class, InheritingClassTwo.class })
public abstract class BasicClass {
protected String type; //determines class type
protected String description;
//getters & setters
}
two examplary subclasses:
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassOne extends BasicClass {
private String message;
private Float mark;
//getters & setters
}
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassTwo extends BasicClass {
private Integer value;
//getters & setters
}
I know that JAXB can unmarshall objects based on xsi:type attribute, which I cannot use, since the input xml is stripped from all atributes. I have tried using Moxy #XmlDiscriminatorNode & #XmlDiscriminatorValue, but these seem to work only with attributes and not the element values.
I have also seen #XmlElementRef which lets determine the type based on the name of the element, but again, due to some restrictions all elements have to have the same name in input and output xml.
My input xml is:
<someRoot>
<type>chooseOne</type>
<message>Message for InheritingClassOne</message>
<mark>12.3</mark>
</someRoot>
I did not find solution for this problem other than using #XmlJavaAdapter with defined adapter:
public class CustomAdapter extends XmlAdapter<Object, BasicClass> {
#Override
public BasicClass unmarshal(Object v) throws Exception {
//TODO: cast v to Element interface, get to type element value and handle accordingly
return null;
}
#Override
public Object marshal(BasicClass v) throws Exception {
//TODO: marshal
return null;
}
}
The adapter solution with reading ElementNsImpl child values to get the category seems awful and takes a lot of effort for such task. Are there any solutions that I am missing? Can I somehow change my models (without using xml attributes), so this task is doable?

Provide link instead of serializing collection

I have two classes:
public class A{
private String property;
private Set<B> bs = new HashSet<B>();
}
public class B{
private String property;
private A owner;
}
I created a basic JAX-RS + Spring boot application, and I want to return A.
Problem is that A contains a Set<B>, so I get an infinite nested level problem.
I found a solution: provide a link instead of the resource itself, this way I can have this:
{ "property" : "value", "bs" : "http://mywebsite/api/a/2/bs" }
And i don't get any nested level problem, since each level is serialized seperately.
How can I implement such a thing in my JAX-RS application? I found nothing about it but I know it's possible since Spring Data Neo4j REST is using it, and it works well.
I can think of the following possible ways how to achieve that.
Convert the set to URI during serialization by using a XmlAdapter
#XmlRootElement
public class A
{
private int id;
private String property;
#XmlJavaTypeAdapter(BsAdapter.class)
private Set<B> bs = new HashSet<B>();
}
public class BsAdapter extends XmlAdapter<URI, Set<B>>
{
#Override
public Set<B> unmarshal(URI v) throws Exception
{
return new HashSet<>();
}
#Override
public URI marshal(Set<B> v) throws Exception
{
return URI.create(
Optional.ofNullable(v)
.filter(b -> !b.isEmpty())
.map(b -> "/" + b.stream().findFirst().get().getOwner().getId() + "/bs")
.orElse(""));
}
}
If the id of A is part of the URI then it can only be retrieved if the set is not empty. The result is a relative URI, since there are no further information available.
As an alternative set the URI manually.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class A
{
private int id;
private String property;
#XmlTransient
private Set<B> bs = new HashSet<B>();
#XmlElement(name = "bs")
private URI bsUri;
}
And set the URL like this:
#Context
UriInfo uriInfo;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getA()
{
// ...
a.setBsUri(UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path("a")
.path("{id}")
.path("bs")
.build(2));
return Response.ok().entity(a).build();
}
Or if a link in HTTP Header is also fine, simply do it this way so you do not have to extend class A with the URI.
return Response.ok().entity(a).link(UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path("a")
.path("{id}")
.path("bs")
.build(2), "bs").build();

REST: convert XML content passed with POST to a java object, attribute inside the element

I am working with REST services and i want to pass an XML-text with a POST request. My server is implemented in JAVA. Let's say that i am sending this XML:
<range>
<higher value="3"></higher>
<lower value="2"></lower>
</range>
As i understand (correct me if i am wrong), the easiest way to convert the XML in the request to a java object, is to define a class with the proper annotations. For example:
#XmlRootElement(name = "range")
public class RangeClass {
#XmlElement (name = "lower")
private int lower;
#XmlElement (name = "higher")
private int higher;
.
.
???
}
And then read it like this:
#POST
#PATH(<somePath>)
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.TEXT_PLAIN)
public String myFun(RangeClass range) {
.
.
.
}
The thing that i am missing (if the other parts are correct) is how to define that i have attributes inside the elements. If i put an '#XmlAttribute' annotation this will refer to an attribute of the root element ('range') and not an attribute of a specific element ('lower' or 'higher').
First and the easiest way is to create a Java mapping per each XML tag:
#XmlRootElement(name = "range")
public class RangeClass {
private Higher higher;
private Lower lower;
}
#XmlElement(name = "higher")
public class Higher {
#XmlAttribute
private int value;
}
#XmlElement(name = "lower")
public class Lower {
#XmlAttribute
private int value;
}
Second option is to change XML structure to:
<range>
<higher>3</higher>
<lower>2</lower>
</range>
Then you can use #XmlElement annotation:
#XmlRootElement(name = "range")
#XmlAccessorType(XmlAccessType.FIELD)
public class RangeClass {
#XmlElement
private int lower;
#XmlElement
private int higher;
}
Third option is to use Eclipse Link Moxy and its #XmlPath annotation:
#XmlRootElement(name = "range")
#XmlAccessorType(XmlAccessType.FIELD)
public class RangeClass {
#XmlPath("lower/#value")
private int lower;
#XmlPath("higher/#value")
private int higher;
}

REST API with Interfaces - JAXB

I am using an implementation of an interface in Java.
For eg: There can be many PaymentTypes like Credit Card, Mobile etc.
I am making a REST API which contains an interface- how do I map this in JAXB, currently it gives me JAXBException occurred : 2 counts of IllegalAnnotationExceptions.
Currently I am using Apache-CXF and JAXb
#XmlRootElement
public class Payment {
#XmlElement
private PaymentType paymentType;
#XmlElement
private long price;
public Payment() {
}
public Payment(final PaymentType paymentType, final long price) {
super();
this.paymentType = paymentType;
this.price = price;
}
}
#Path("/trial")
public class TrialService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Payment> getPayments() {
final List<Payment> payments = new LinkedList<Payment>();
final CreditCardDetails creditCard = new CreditCardDetails(
"8767798778", "123", 12, 2016);
final Payment payment = new Payment(creditCard, 10);
payments.add(payment);
return payments;
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public PaymentDetails startPayment(final PaymentDetails paymentDetails) {
return paymentDetails;
}
}
public class CreditCardDetails implements PaymentType {
#XmlElement
private String creditCardNumber;
#XmlElement
private String cvv;
#XmlElement
private int expirationMonth;
#XmlElement
private int expirationYear;
public CreditCardDetails() {
}
#SuppressWarnings("javadoc")
public CreditCardDetails(
// final BillingAddress billingAddress,
final String creditCardNumber, final String cvv,
final int expirationMonth, final int expirationYear) {
super();
this.creditCardNumber = creditCardNumber;
this.cvv = cvv;
setExpirationMonth(expirationMonth);
setExpirationYear(expirationYear);
}
}
How should I be mapping this or should I be using an entirely different approach?
Edits:
For the POST method I am receiving a payment. Payment could contain any object CreditCard, Wallet etc. What annotation should I provide so that it is desirialized correctly.
Currently it throws a JAXB exception.
The full error message which you've got is:
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions PaymentType is an interface, and JAXB can't handle interfaces.
You need to use concreate class for your elements or point it in type attribute of #XmlElement annotation:
#XmlElement(type = CreditCardDetails.class)
private PaymentType paymentType;
If you have more classes that uses PaymentType interface then you may use the following solution:
#XmlAnyElement
#XmlElementRefs({
#XmlElementRef(type=CreditCardDetails.class),
#XmlElementRef(type=Wallet.class)
})
PaymentType paymentType;
The list of #XmlElementRefs can have any number of elements but all possibilities must be listed. CreditCardDetails and Wallet must be annotated with #XmlRootElement.
You can skip #XmlElementRefs annotation:
#XmlAnyElement(lax=true)
PaymentType paymentType;
but in that case make sure you have any required class in JAXB context, if you do not use registry annotate your class with PaymentType field with #XmlSeeAlso({CreditCardDetails.class, Wallet.class}).

Categories