Bidirectional field value conversion for JAX-RS - java

Class Foo:
#Entity
#Table(name = "FOO")
#XmlRootElement(name = "doo")
#XmlAccessorType(XmlAccessType.FIELD)
public class Fooimplements Serializable {
#Column(name = "SOME_FIELD")
private String someField;
If someField is "abc", the marshaled XML looks like
<foo>
<someField>abc</someField>
Is there any way to annotate a front and back conversion so that, just for the XML marshaling purposes, the Java value "abc" gets converted to "xyz.abc" (by concatenating "xyz." to the beginning of the string) and when the XML is unmarshaled, the value "xyz.abc" is converted to "abc" by removing the first 4 characters? I am using CXF if that matters.

It could be achieved with a XmlAdapter:
public class CustomAdapter extends XmlAdapter<String, String>{
#Override
public String marshal(String v) throws Exception {
return "xyz." + v;
}
#Override
public String unmarshal(String v) throws Exception {
return v.replaceFirst("^xyz.", "");
}
}
And use it as following:
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo implements Serializable {
#XmlJavaTypeAdapter(CustomAdapter.class)
private String someField;
...
}
Alternatively you could use getters and setters for this purpose.

Related

Spring Boot - Apply persistence Converter Annotation based on a condition

I have below repository model class
class Model {
#Column(name="id")
private static Integer id;
#Column(name="column-to-be-converted")
#Convert(converter=Converter.class)
private static String columnToBeConverted;
#Column(name="apply-converter")
private static boolean applyConverter;
// Getters and Setters
}
Below is the Converter class
#Component
#Converter
public class PasswordConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String rawData) {
// logic goes here
}
#Override
public String convertToEntityAttribute(String convertedData) {
// logic goes here
}
}
I want to apply #Convert annotation to the field columnToBeConverted only if the field applyConverter is set to true
I tried investigating if the model object can be passed to Converter Class as argument or with using #Conditional
Please suggest how can this be achieved
Thank you!

XML unmarshalling using JAXB, with field type based on XML attribute value?

My application receives, from another application, xml of the form:
<targetedMessage>
<sender>the sender</sender>
<payload class="other.app.classpath.Foo">
<id>1</id>
</payload>
</targetedMessage>
where Foo is any one of several classes which exist in both my module and the other application, and implements a common interface:
#XmlAccessorType(value = XmlAccessType.FIELD)
#XmlRootElement
public class Foo implements MyInterface {
private long id;
\\ getters and setters
}
and the TargetedMessage class is:
#XmlAccessorType(value = XmlAccessType.FIELD)
#XmlRootElement
public class TargetedMessage {
private String sender;
private MyInterface payload;
\\ getters and setters
}
I have an enum which maps the other apps class-paths to their classes in my module:
public enum MyClasses {
FOO(Foo.class, "other.app.classpath.Foo"),
BAR(Bar.class, "other.app.classpath.Bar"),
\\ ...
}
Using JAXB, is it possible to unmarshal the xml so that the payload is of the correct type (in the above case, the payload class would be Foo).
I don't know about JAXB but SimpleXml can do it:
#XmlName("targetedMessage")
public class TargetedMessage {
String sender;
#XmlAbstactClass(attribute="class", types={
#TypeMap(name="other.app.classpath.Foo", type=Foo.class),
#TypeMap(name="other.app.classpath.Bar", type=Bar.class)
})
Payload payload;
}
interface Payload {}
public class Foo implements Payload {
Integer id;
}
public class Bar implements Payload {
String name;
}
final String data = ...
final SimpleXml simple = new SimpleXml();
final TargetedMessage message = simple.fromXml(data, TargetedMessage.class);
System.out.println(message.payload.getClass().getSimpleName());
System.out.println(((Foo)message.payload).id);
Will output:
Foo
1
From maven central:
<dependency>
<groupId>com.github.codemonstur</groupId>
<artifactId>simplexml</artifactId>
<version>1.5.0</version>
</dependency>

How to do a custom EJB/JPA mapping type?

I want to store a property into the database as a Long, but use the object with helper methods in the code.
However the object type is a custom type I have that has an internal value (a long) that I want to store to the database.
public final class MyBean extends Number implements Serializable, Comparable<MyBean>
{
private long myValue;
public MyBean(Long value) { this.myValue = value; }
// Other helper methods and overrides
public MyBean valueOf(Long data)
{
return new MyBean(data);
}
#Override
public String toString()
{
return String.valueOf(myValue);
}
}
This is how I am using it:
#Entity
#Table(name = "mybeans")
public class MyBean implements Serializable
{
private static final long serialVersionUID = 1L;
MyBean myBean;
#Id
#Column(name = "mybean", nullable = false)
public MyBean getMyBean() { return myBean; }
public void setMyBean(MyBean value) { this.myBean = value; }
}
Deserializing this object calls toString and works fine (jax-rs/jersey). But when I try to pull it out of the database using my EJB, the error I get is:
The object [1,427,148,028,955], of class [class java.lang.Long], could
not be converted to [class com.MyBean]
Saving it produced the error
Can't infer the SQL type to use for an instance of com.MyBean. Use
setObject() with an explicit Types value to specify the type to use.
Which makes sense.
But what methods can I add in to male the EJB get the long as the value and use the long to set up a new object?
ANSWER:
Making the class #Embeddable and adding the following attributes worked.
#Embedded
#AttributeOverrides({
#AttributeOverride(name="value", column=#Column(name="mybean"))
})
(I didn't add EmbeddedId because I added a serial primary key id and just made this a column)
The one caveat is that it won't work with dynamic weaving. I had to add
<property name="eclipselink.weaving" value="static"/>
to my persistence.xml
You can try making MyBean an Embeddable to use that as an EmbeddedId, something like this:
#Embeddable
public final class MyBean extends Number implements Serializable, Comparable<MyBean> {
private Long myValue;
public MyBean(Long myValue) {
this.myValue = myValue;
}
// Other helper methods and overrides
public MyBean valueOf(Long data) {
return new MyBean(data);
}
#Override
public String toString() {
return String.valueOf(myValue);
}
}
In your entity, MyBean will be an EmbeddedId and will look like this:
#Entity
#Table(name = "mybeans")
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
private MyBean myBean;
#EmbeddedId
#AttributeOverride(name="myValue", #Column(name="mybean_id"))
public MyBean getMyBean() {
return myBean;
}
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}
Adjust MyBean as you need, such as making Transient some attributes.

How do I specify the name of a JAXB element using Java generics?

I have a situation in which a Java object contains a generic Payload<T> that needs to be marshalled into xml. So given the following classes:
AbstractBox
#XmlTransient
public abstract class AbstractBox {
String type = this.getClass().getSimpleName();
String name = this.getClass().getCanonicalName();
// setters/getters snipped
public abstract String saySomething();
}
SmallBox
#XmlRootElement(name="small-box")
#XmlType(name="small-box")
public class SmallBox extends AbstractBox {
#XmlElement
public String getMsg() {
return saySomething();
}
public void setMsg(String msg) {
// do nothing
}
#Override
public String saySomething() {
return "I'm a small box";
}
}
Payload
#XmlTransient
public class Payload<T> {
private T payload;
public T getPayload() {
return payload;
}
public void setPayload(T payload) {
this.payload = payload;
}
}
and some code like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class JaxbAnnotationsTest<P>
{
String name;
int age;
int id;
Payload<P> payload;
// setters and getters snipped
public static void main(String[] args) {
JaxbAnnotationsTest<AbstractBox> example = new JaxbAnnotationsTest<AbstractBox>();
example.setName("Brion");
example.setId(100);
example.setAge(34);
Payload<AbstractBox> object = new Payload<AbstractBox>();
object.setPayload(new SmallBox());
example.setPayloadContainer(object);
try {
XmlMapper xmapper = new XmlMapper();
xmapper.writeValue(System.out, example);
} catch (Exception ex) {
System.err.println("Damn..." + ex.getMessage());
}
}
I expect to see:
<JaxbAnnotationsTest>
<name>Brion</name>
<age>34</age>
<id>100</id>
<payloadContainer>
<small-box>
<type>SmallBox</type>
<name>sandbox.xml.jaxb.annotations.SmallBox</name>
<msg>I'm a small box</msg>
</small-box>
</payloadContainer>
</JaxbAnnotationsTest>
but instead I get:
<JaxbAnnotationsTest>
<name>Brion</name>
<age>34</age>
<id>100</id>
<payloadContainer>
<payload>
<type>SmallBox</type>
<name>sandbox.xml.jaxb.annotations.SmallBox</name>
<msg>I'm a small box</msg>
</payload>
</payloadContainer>
</JaxbAnnotationsTest>
I've tried using #XmlType on the concrete subclass to change payload to small-box but that didn't work either. If I remove the Payload<P> object and simply have a class member payload of generic type P then the paymentContainer tag goes away, but payload remains and does not use the small-box name I've specified.
Is there a way for me to force JAXB (any implementation) to set the tag to the name specified in the subclass instead of the generic type property?
Update:
The selected answer provides the solution but I wanted to follow up in the question as well. My problem was two-fold:
I was using #XmlTransient on the Payload<T> class and needed to instead use an #XmlAnyElement annotation on the setPayload(T payload) method (though I suspect it doesn't matter which method of the setter/getter pair is annotated as long as only one has the annotation).
I was using Jackson 2's JacksonJaxbXmlProvider which is an incomplete JAXB implementation that was ignoring the #XmlRootElement of the element used as the value of the #XmlAnyElement-annotated property.
Changing my JAXB provider to use the Java 6/7 built-in JAXB provider generated the output I expected.
Your Payload class expects any bean type as a property, so JAXB doesn't know how to marshall that particular object (in this case SmallBox). Since you need to keep the generic property in Payload the solution should be
Remove #XmlTransient annotation to make these types available for marshalling (I am wondering how it worked with this annotation as you mentioned)
Annotate setPayload in Payload class with #XmlAnyElement as follows
public class Payload {
private T payload;
public T getPayload() {
return payload;
}
#XmlAnyElement
public void setPayload(T payload) {
this.payload = payload;
}
}
#XmlAnyElement javadoc says
Maps a JavaBean property to XML infoset representation and/or JAXB
element.
This means any known bean type (annotated with #XmlRootElement) which is passed into setPayload() will be resolved by the JAXB to their corresponding type, here that bean is SmallBox, otherwise to a default element type ( i think it should be the default implementation of org.w3c.dom.Element). After this change it will marshall the JaxbAnnotationsTest nicely to following xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxbAnnotationsTest>
<age>34</age>
<id>100</id>
<name>Brion</name>
<payloadContainer>
<small-box>
<name>org.mycode.SmallBox</name>
<type>SmallBox</type>
<msg>I'm a small box</msg>
</small-box>
</payloadContainer>
</jaxbAnnotationsTest>
Hope that will help you.

How can JAXB XmlAdapter be used to marshall lists?

Instances of this class are part of a large object graph and are not at the root of the object graph:
public class Day
{
public Day(LocalDate date, List<LocalTime> times)
{
this.date = date;
this.times = times;
}
public Day()
{
this(null, null);
}
public LocalDate getDate()
{
return date;
}
public List<LocalTime> getTimes()
{
return times;
}
private final LocalDate date;
private final List<LocalTime> times;
}
The object graph is converted to JSON using Jersey and JAXB. I have XmlAdapters registered for LocalDate and LocalTime.
The problem is that it's only working for the date property and not the times property. I suspect this has something to do with the fact that times is a list rather than a single value. How, then, do I tell Jersey/JAXB to marshall each element in the times list using the registered XmlAdapter?
Update:
I confirmed that LocalTime marshalling is indeed working for scalar LocalTime properties by adding a scalar LocalTime property and observing the expected output in the JSON.
For completeness, here's package-info.java:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value = LocalDateAdapter.class, type = LocalDate.class),
#XmlJavaTypeAdapter(value = LocalTimeAdapter.class, type = LocalTime.class)
})
package same.package.as.everything.else;
LocalDateAdapter:
public class LocalDateAdapter extends XmlAdapter<String, LocalDate>
{
#Override
public LocalDate unmarshal(String v) throws Exception
{
return formatter.parseLocalDate(v);
}
#Override
public String marshal(LocalDate v) throws Exception
{
return formatter.print(v);
}
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMdd");
}
LocalTimeAdapter:
public class LocalTimeAdapter extends XmlAdapter<String, LocalTime>
{
#Override
public LocalTime unmarshal(String v) throws Exception
{
return formatter.parseLocalTime(v);
}
#Override
public String marshal(LocalTime v) throws Exception
{
return formatter.print(v);
}
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("HHmm");
An XmlAdapter for a class is applied to a mapped field/property of that type, and in the case of collections, for each item in the collection. The example below proves that this works. Have you tried running your example standalone to XML to verify the mappings that way. Is suspect the problem is something else other than the XmlAdapter specifically.
StringAdapter
The following XmlAdapter will convert a String to lower case on the unmarshal operation and convert it to upper case when marshalled.
package forum14569293;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String v) throws Exception {
return v.toLowerCase();
}
#Override
public String marshal(String v) throws Exception {
return v.toUpperCase();
}
}
package-info
Just as in your question a package level #XmlJavaTypeAdapters annotation will be used to register the XmlAdapter. This will register this XmlAdapter for all mapped String properties within this package (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum14569293;
import javax.xml.bind.annotation.adapters.*;
Root
Below is a sample domain model similar to your Day class with two mapped properties. The first is of type String and the second List<String>. One thing I notice about your Day class is that you only have get methods. This means that you will need to add an #XmlElement annotation for a JAXB impl to consider that a mapped property.
package forum14569293;
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
public Root(String foo, List<String> bar) {
this.foo = foo;
this.bar = bar;
}
public Root() {
this(null, null);
}
#XmlElement
public String getFoo() {
return foo;
}
#XmlElement
public List<String> getBar() {
return bar;
}
private final String foo;
private final List<String> bar;
}
Demo
package forum14569293;
import java.util.*;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
List<String> bar = new ArrayList<String>(3);
bar.add("a");
bar.add("b");
bar.add("c");
Root root = new Root("Hello World", bar);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code we see that all the strings were converted to upper case by the XmlAdapter.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bar>A</bar>
<bar>B</bar>
<bar>C</bar>
<foo>HELLO WORLD</foo>
</root>
UPDATE
Thanks. I tried it and the XML consisted of one empty tag only,
meaning there's something about the POJO model that JAXB doesn't like.
(Perhaps it should be Serializable?)
JAXB does not require that POJOs implement Serializable.
That's interesting because it seems to indicate that the only part
JAXB plays in this is to lend its annotations and some other
interfaces (e.g. XmlAdapter) to the JSON (de)serializer and that's
where the relationship ends.
It depends on what is being used as the JSON binding layer. The JAXB (JSR-222) specification does not cover JSON-binding so this type of support is beyond the spec. EclipseLink JAXB (MOXy) offers native JSON-binding (I'm the MOXy lead) with it you could do something like:
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
marshaller.marshal(root, System.out);
And get the following output that takes the XmlAdapter into account:
{
"bar" : [ "A", "B", "C" ],
"foo" : "HELLO WORLD"
}
Nevertheless, when I get an opportunity I will do what I can to get
JAXB to generate the XML and that may reveal something else.
This would be useful as I do not believe what you are seeing is a JAXB issue, but an issue in the JSON binding layer that you are using.

Categories

Resources