Is it possible to hide a value in XML such that it can be retrieved only when unmarshalling.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below are some options for implementing this use case if you are using MOXy as your JAXB provider. To use MOXy as your JAXB provider you need to include a file named jaxb.properties in the same package as your domain model with the following entry:
OPTION #1 - XmlAdapter
An XmlAdapter could be used to null out a property value during the marshal operation. While the XmlAdapter is a standard JAXB class, returning null from the marshal method causes an exception to occur when the JAXB reference implementation is used.
StringAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String string) throws Exception {
return null;
}
#Override
public String unmarshal(String string) throws Exception {
return string;
}
}
Person
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlJavaTypeAdapter(StringAdapter.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
OPTION #2 - Setter With No Getter
When MOXy is used as the JAXB provider if you have a property with an annotated setter and no getter then MOXy will treat it as a readonly property.
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlElement
public void setPassword(String password) {
this.password = password;
}
}
OPTION #3 - MOXy's #XmlReadOnly Extension
MOXy's #XmlReadOnly extension can also be used to mark a property as read only.
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlReadOnly;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlReadOnly
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
DEMO CODE
input.xml
<Person>
<password> some password </password>
</Person>
*Demo*
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14231799/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println(person.password);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(person, System.out);
}
}
Output
some password
<?xml version="1.0" encoding="UTF-8"?>
<Person/>
The following approach could be used with any JAXB (JSR-222) implementation.
Demo
A Marshaller.Listener could be used to null out a value before the object is marshalled and then restore it afterwards.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14231799/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println(person.password);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setListener(new Marshaller.Listener() {
private String password;
#Override
public void afterMarshal(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
person.setPassword(password);
password = null;
}
}
#Override
public void beforeMarshal(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
password = person.getPassword();
person.setPassword(null);
}
}
});
marshaller.marshal(person, System.out);
System.out.println(person.password);
}
}
input.xml
<Person>
<password> some password </password>
</Person>
Output
some password
<?xml version="1.0" encoding="UTF-8"?>
<Person/>
some password
Related
Is there a way for marshaller to generate a new xml file skipping any null attributes? So
something like someAttribute="" does not show up in the file.
Thanks
A JAXB (JSR-222) implementation will not marshal a field/property annotated with #XmlAttribute that contains a null value.
Java Model (Root)
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
private String foo;
private String bar;
#XmlAttribute
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
#XmlAttribute(required=true)
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo Code
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.setFoo(null);
root.setBar(null);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<root/>
I'm trying to inject JAXB annotation at runtime using Javassist. I have written following code:
public class AssistAnnotationInjector {
public static void addAnnotationRunTime(String className, String fieldName) throws NotFoundException, CannotCompileException, IOException, ClassNotFoundException{
CtClass ctClass = ClassPool.getDefault().get(className);
ClassFile ccFile = ctClass.getClassFile();
ConstPool constPool = ccFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation("javax.xml.bind.annotation.XmlTransient",constPool);
attr.addAnnotation(annot);
CtField field = ctClass.getDeclaredField(fieldName);
field.getFieldInfo().addAttribute(attr);
System.out.println(field.getAnnotation(XmlTransient.class));
ccFile.setVersionToJava5();
ctClass.writeFile();
}
public static void main (String args[]) throws CannotCompileException, NotFoundException, IOException, SecurityException, NoSuchMethodException, ClassNotFoundException, JAXBException, NoSuchFieldException{
Person<Student> p = new Person<Student>();
p.setName("XYZ");
Student s = new Student();
s.setName("ABC");
s.setId("239423");
p.setPayload(s);
addAnnotationRunTime("RuntimeAnnotation.Person", "name");
Field f = p.getClass().getDeclaredField("name");
System.out.println(f.getAnnotation(XmlTransient.class));
JAXBContext context = JAXBContext.newInstance(p.getClass());
Marshaller mr = context.createMarshaller();
mr.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
mr.marshal(p, System.out);
}
}
And Person.java class is:
#XmlRootElement(name="Person")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Student.class})
public class Person <T>{
private T payload;
private String name;
public void setPayload(T payload){
this.payload = payload;
}
public T getPayload(){
return payload;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
In AssistAnnotationInjector.java, I am trying to add XmlTransient annotation to 'name' field. But the name field is still coming in marshalling output. Why is it so?
PS: marshal output is :
#javax.xml.bind.annotation.XmlTransient
null
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person>
<payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="student">
<name>ABC</name>
<id>239423</id>
</payload>
**<name>XYZ</name>**
</Person>
name tag was not expected to present in output..
You basicaly have 2 options:
do the modification before you load the class. You can not use reflection in the normal way! One can try to use org.reflections with Maven plugin to pre-fetch classes. See here for more info.
use custom classloader to load the modified class. See here for more info.
After adding the attribute to the field you need to call ctClass.toClass() method,which freezes the class. After this you can check for the annotation.
Following are the classes I am using for create sub classes using MOXy JAXB conversion on WebLogic 10.3.2 version. I am using the EclipseLink 2.4.1 MOXy for generating the XML. I am unable to generate the type attribute in the following code. Let me know if I am doing anything wrong here.
I am using EclipseLink MOXy 2.4.1 and WebLogic 10.3.2 and MOXy 2.4.1 is configured in the WebLogic
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlDiscriminatorNode("#type")
public abstract class BaseEntity {
private String firstName;
private String lastName;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Subclass
package forum13831189;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
#XmlDiscriminatorValue("xyz")
public class XyzEntity extends BaseEntity {
public XyzEntity() {
super();
}
}
Another Sub Class
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
#XmlDiscriminatorValue("Abc")
public class AbcEntity extends BaseEntity {
}
RESTful Web Service Class:
#GET
#Path("/xyz")
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Representation getAccount() throws CPAException {
Representation rep = new Representation();
BaseEntity entity = new XyzEntity();
entity.setFirstName("first-name");
entity.setLastName("last-name");
rep.setEntity(entity);
return rep;
}
#XmlRootElement
static class Representation {
private BaseEntity entity;
public BaseEntity getEntity() {
return entity;
}
public void setEntity(BaseEntity entity) {
this.entity = entity;
}
}
The above is generating the following XML.
<representation>
<firstName>first-name</firstName>
<lastName>last-name</lastName>
</representation>
The attribute type is not generated in the above.
Thanks a lot. Yes, I missed jaxb.properties in the above.
Also, Yes when I use the PUT or POST, when XML is de-serialized, it is not able to create the subclasses if #XmlSeeAlso is not present.
There are a couple items that may be causing you problems.
BaseEntity
By default a JAX-RS implementation creates a JAXBContext on the return type or parameter of the service method, in this case Represenatation. When processing the domain model the JAXB impl will also pull in referred types such as BaseEntity. It can't automatically pull in subclasses so we can use the #XmlSeeAlso annotation to reference those.
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlDiscriminatorNode("#type")
#XmlSeeAlso({AbcEntity.class, XyzEntity.class})
public abstract class BaseEntity {
private String firstName;
private String lastName;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
jaxb.properties
Also since #XmlDescriminatorNode/#XmlDescriminatorValue are MOXy extensions you need to make sure you specify MOXy as your JAXB provider. This is done by adding a file named jaxb.properties in the same package as your domain model with the following entry.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
Below is a standalone example that mimics what your RESTful service does.
import javax.xml.bind.*;
import javax.xml.bind.annotation.XmlRootElement;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Representation.class);
Representation rep = new Representation();
BaseEntity entity = new XyzEntity();
entity.setFirstName("first-name");
entity.setLastName("last-name");
rep.setEntity(entity);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(rep, System.out);
}
#XmlRootElement
static class Representation {
private BaseEntity entity;
public BaseEntity getEntity() {
return entity;
}
public void setEntity(BaseEntity entity) {
this.entity = entity;
}
}
}
Output
Below is the output from running the demo code. See that the type attribute is now present.
<?xml version="1.0" encoding="UTF-8"?>
<representation>
<entity type="xyz">
<firstName>first-name</firstName>
<lastName>last-name</lastName>
</entity>
</representation>
For More Information
Specifying EclipseLink MOXy as Your JAXB Provider
JAXB and Inheritance - MOXy Extension #XmlDescriminatorNode/#XmlDescrimintatorValue
Updating EclipseLink in WebLogic
First of all a small example. The class ReferencingEntity holds a reference to the abstract class AbstractEntity. There are two implementations fo this class:
#XmlRootElement
public abstract class AbstractEntity {
#XmlID
private String id;
}
#XmlRootElement
public class EntityImpl1 extends AbstractEntity {
}
#XmlRootElement
public class EntityImpl2 extends AbstractEntity {
}
#XmlRootElement
public class ReferencingEntity {
#XmlIDREF
private AbstractEntity entity;
}
There is no problem marshalling an instance of ReferencingEntity (except that the concrete type is not present in xml), but when trying to unmarshal the xml representation, the descriptor is missing to determine the concrete implementation.
Currently I'm using an XmlAdapter to set all non-id fields null, but it would be better to use #XmlID if possible. Any ideas?
UPDATE:
I'm using RESTEasy in JBoss 6.1.0.Final and the provider creates the context as follows:
ContextResolver<JAXBContextFinder> resolver = providers.getContextResolver(JAXBContextFinder.class, mediaType);
JAXBContextFinder finder = resolver.getContext(type);
if (finder == null)
{
if (reader) throw new JAXBUnmarshalException("Could not find JAXBContextFinder for media type: " + mediaType);
else throw new JAXBMarshalException("Could not find JAXBContextFinder for media type: " + mediaType);
}
JAXBContext context = finder.findCachedContext(type, mediaType, annotations);
Below is my initial answer to your question. I imagine it will evolve as I better understand your use case.
ABOUT #XmlID/#XmlIDREF
Every instance referenced from a field/property annotated with #XmlIDREF also needs to be referenced via containment. I'll use the class below in this example.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root {
private AbstractEntity abstractEntity;
private ReferencingEntity referencingEntity;
public AbstractEntity getAbstractEntity() {
return abstractEntity;
}
public void setAbstractEntity(AbstractEntity abstractEntity) {
this.abstractEntity = abstractEntity;
}
public ReferencingEntity getReferencingEntity() {
return referencingEntity;
}
public void setReferencingEntity(ReferencingEntity referencingEntity) {
this.referencingEntity = referencingEntity;
}
}
REGARDING INHERITANCE
JAXB (JSR-222) implementations can't automatically discover subclasses, so you will need to be sure that the JAXBContext is aware of them. One way to accomplish this is to use the #XmlSeeAlso annotation on the parent class to point at the child classes.
import javax.xml.bind.annotation.*;
#XmlSeeAlso({EntityImpl1.class, EntityImpl2.class})
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractEntity {
#XmlID
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
DEMO CODE
Demo
package forum12111815;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum12111815/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
System.out.println(root.getAbstractEntity().getClass());
System.out.println(root.getAbstractEntity() == root.getReferencingEntity().getEntity());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abstractEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="entityImpl2">
<id>123</id>
</abstractEntity>
<referencingEntity>
<entity>123</entity>
</referencingEntity>
</root>
Output
class forum12111815.EntityImpl2
true
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abstractEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="entityImpl2">
<id>123</id>
</abstractEntity>
<referencingEntity>
<entity>123</entity>
</referencingEntity>
</root>
FOR MORE INFORMATION
http://blog.bdoughan.com/2010/10/jaxb-and-shared-references-xmlid-and.html
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
I am trying to unmarshall an XML file using MOXy JAXB. I have a set of classes, already generated, and I am using Xpath to map every XML element I need into my model.
I have an XML file like this:
<?xml version="1.0" encoding="UTF-8"?>
<fe:Facturae xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:fe="http://www.facturae.es/Facturae/2009/v3.2/Facturae">
<Parties>
<SellerParty>
<LegalEntity>
<CorporateName>Company Comp SA</CorporateName>
<TradeName>Comp</TradeName>
<ContactDetails>
<Telephone>917776665</Telephone>
<TeleFax>917776666</TeleFax>
<WebAddress>www.facturae.es</WebAddress>
<ElectronicMail>facturae#mityc.es</ElectronicMail>
<ContactPersons>Fernando</ContactPersons>
<CnoCnae>28000</CnoCnae>
<INETownCode>2134AAB</INETownCode>
<AdditionalContactDetails>Otros datos</AdditionalContactDetails>
</ContactDetails>
</LegalEntity>
</SellerParty>
<BuyerParty>
<Individual>
<Name>Juana</Name>
<FirstSurname>MauriƱo</FirstSurname>
<OverseasAddress>
<Address>Juncal 1315</Address>
<PostCodeAndTown>00000 Buenos Aires</PostCodeAndTown>
<Province>Capital Federal</Province>
<CountryCode>ARG</CountryCode>
</OverseasAddress>
<ContactDetails>
<Telephone>00547775554</Telephone>
<TeleFax>00547775555</TeleFax>
</ContactDetails>
</Individual>
</BuyerParty>
</Parties>
</fe:Facturae>
Then I have my model:
#XmlRootElement(namespace="http://www.facturae.es/Facturae/2009/v3.2/Facturae", name="Facturae")
public class Facturae implements BaseObject, SecuredObject, CreationDataAware {
#XmlPath("Parties/SellerParty")
private Party sellerParty;
#XmlPath("Parties/BuyerParty")
private Party buyerParty;
}
public class Party implements BaseObject, SecuredObject, CreationDataAware {
#XmlPath("LegalEntity/ContactDetails")
private ContactDetails contactDetails;
}
As you can see, <ContactDetails></ContactDetails> is present in <SellerParty></SellerParty> and <BuyerParty></BuyerParty> but this two tags share the same JAVA object (Party). With the previous mapping (#XmlPath("LegalEntity/ContactDetails")) I can pass correctly the ContactDetails info in SellerParty, but I want also to pass the ContactDetails in <BuyerParty> at the same time.
I was trying something like that:
#XmlPaths(value = { #XmlPath("LegalEntity/ContactDetails"),#XmlPath("Individual/ContactDetails") })
private ContactDetails contactDetails;
but it doesn't work.
Can you guys give me a hand?
Thank you very much.
You could use an XmlAdapter for this use case:
PartyAdapter
We will use an XmlAdapter to convert an instance of Party to another type of object AdaptedParty. AdaptedParty will have two properties corresponding to each property in Party (one for each mapping possibility). We will take advantage of the ability to pre-initialize an instance of XmlAdapter to set an instance of Facturae on it. We will use the instance of Facturae to determine if the instance of Party we are handling is a sellerParty or a buyerParty.
package forum9807536;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
public class PartyAdapter extends XmlAdapter<PartyAdapter.AdaptedParty, Party> {
private Facturae facturae;
public PartyAdapter() {
}
public PartyAdapter(Facturae facturae) {
this.facturae = facturae;
}
#Override
public Party unmarshal(AdaptedParty v) throws Exception {
Party party = new Party();
if(v.individualName != null) {
party.setName(v.individualName);
party.setContactDetails(v.individualContactDetails);
} else {
party.setName(v.sellPartyName);
party.setContactDetails(v.sellerPartyContactDetails);
}
return party;
}
#Override
public AdaptedParty marshal(Party v) throws Exception {
AdaptedParty adaptedParty = new AdaptedParty();
if(null == facturae || facturae.getSellerParty() == v) {
adaptedParty.sellPartyName = v.getName();
adaptedParty.sellerPartyContactDetails = v.getContactDetails();
} else {
adaptedParty.individualName = v.getName();
adaptedParty.individualContactDetails = v.getContactDetails();
}
return adaptedParty;
}
public static class AdaptedParty {
#XmlPath("Individual/Name/text()")
public String individualName;
#XmlPath("Individual/ContactDetails")
public ContactDetails individualContactDetails;
#XmlPath("LegalEntity/CorporateName/text()")
public String sellPartyName;
#XmlPath("LegalEntity/ContactDetails")
public ContactDetails sellerPartyContactDetails;
}
}
Party
We will use the #XmlJavaTypeAdapter to associate the PartyAdapter to the Party class:
package forum9807536;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlJavaTypeAdapter(PartyAdapter.class)
public class Party implements BaseObject, SecuredObject, CreationDataAware {
private String name;
private ContactDetails contactDetails;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ContactDetails getContactDetails() {
return contactDetails;
}
public void setContactDetails(ContactDetails contactDetails) {
this.contactDetails = contactDetails;
}
}
Demo
The following demo code demonstrates how to set a pre-initialized XmlAdapter on the Marshaller:
package forum9807536;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Facturae.class);
File xml = new File("src/forum9807536/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Facturae facturae = (Facturae) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(new PartyAdapter(facturae));
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(facturae, System.out);
}
}
Output
Below is the output from the demo code that corresponds to the portion of your model that I have mapped.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Facturae xmlns:ns0="http://www.facturae.es/Facturae/2009/v3.2/Facturae">
<Parties>
<BuyerParty>
<Individual>
<Name>Juana</Name>
<ContactDetails/>
</Individual>
</BuyerParty>
<SellerParty>
<LegalEntity>
<CorporateName>Company Comp SA</CorporateName>
<ContactDetails/>
</LegalEntity>
</SellerParty>
</Parties>
</ns0:Facturae>
For More Information
http://blog.bdoughan.com/search/label/XmlAdapter
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html