Reputation: 15
I'm developing an SOAP client and I'm looking for more sophisticated solution to marshalling objects into XML-string using Jaxb2 library.
The goal is to marshall an object, which acts as a wrapper for any-type element. Example:
<Action id="5">
<Employee id="10">
<Name>John</Name>
</Employee>
</Action>
or.
<Action id="5">
<Department id="ABC">
<Name>Economy Department</Name>
<ParentId>CDE</ParentId>
</Department>
</Action>
Note: The xml root (Action) contains either "Employee" or "Department" or anything else.
My current working solution is as follows:
@XmlRootElement(name = "Action")
abstract class Action {
@XmlAttribute(name = "id")
protected String id;
}
class EmployeeAction extends Action {
@XmlElement(name = "Employee")
protected Employee employee;
}
class DepartmentAction extends Action {
@XmlElement(name = "Department")
protected Department department;
}
This works fine, but I'm looking for more universal solution, without the need to create class for each type (*Action extends Action). Name of the element must always be the same as the className of the (dynamic) type. My idea is something like this:
public class Action<T> {
@XmlAttribute(name = "id")
protected String id;
@XmlElement(name = "getClass().getSimpleName()") //???
protected T element;
}
... and marshalling something like:
Action<?> action = ...;
JAXBContext context = JAXBContext.newInstance(Action.class, action.getElement().getClass());
Marshaller marshaller = context.createMarshaller();
try(ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
marshaller.marshal(action, outStream);
return outStream.toString();
}
Is something like this possible?
Thanks in advance.
Upvotes: 0
Views: 567
Reputation: 3530
You can do something like this for the above-provided XML:
METHOD-1
Action.class;
@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
@XmlAttribute(name = "id")
protected String id;
@XmlElements({
@XmlElement(name = "Employee", type = Employee.class),
@XmlElement(name = "Department", type = Department .class),
})
private ActionItem type;
}
ActionItem.class;
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class ActionItem {
@XmlElement(name = "Name")
protected String name;
}
Employee.class;
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee extends ActionItem {
@XmlAttribute(name = "id")
private String id;
}
Department.class;
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department extends ActionItem {
@XmlAttribute
private String id;
@XmlElement(name = "ParentId")
private String parentID;
}
Main.class:
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("action.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Action.class).createUnmarshaller();
final Action action = unmarshaller.unmarshal(xmlStreamReader, Action.class).getValue();
System.out.println(action.toString());
Marshaller marshaller = JAXBContext.newInstance(Action.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(action, System.out);
}
}
If you provided Employee
XML then it produces the following result:
Action(id=5, type=Employee(id=10))
<Action id="5">
<Employee id="10">
<Name>John</Name>
</Employee>
</Action>
If you provide the Department
XML then it produces the following result:
Action(id=5, type=Department(parentID=CDE))
<Action id="5">
<Department>
<Name>Economy Department</Name>
<ParentId>CDE</ParentId>
</Department>
</Action>
METHOD-2
Creating the interface and using it:
public interface ActionItem2 {
}
Action.class uses the created interface.
@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
@XmlAttribute(name = "id")
protected String id;
@XmlElements({
@XmlElement(name = "Employee", type = Employee.class),
@XmlElement(name = "Department", type = Department .class),
})
private ActionItem2 type;
}
Employee.class which implements the created interface
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee implements ActionItem2 {
@XmlAttribute(name = "id")
private String id;
@XmlElement(name = "Name")
protected String name;
}
Department.class which implements the created interface
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department implements ActionItem2 {
@XmlAttribute
private String id;
@XmlElement(name = "ParentId")
private String parentID;
@XmlElement(name = "Name")
protected String name;
}
Main.class(No changes)
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("action.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Action.class).createUnmarshaller();
final Action action = unmarshaller.unmarshal(xmlStreamReader, Action.class).getValue();
System.out.println(action.toString());
Marshaller marshaller = JAXBContext.newInstance(Action.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(action, System.out);
}
}
The result would be the same.
Method - 3
If you don't want to modify your POJO then you can do something like this:
@XmlRootElement(name = "Action")
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Action {
@XmlAttribute(name = "id")
protected String id;
@XmlElements({
@XmlElement(name = "Employee", type = Employee.class),
@XmlElement(name = "Department", type = Department .class),
})
private Object type;
}
Employee.class:
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Employee {
@XmlAttribute(name = "id")
private String id;
@XmlElement(name = "Name")
protected String name;
}
Department.class:
@Data
@XmlAccessorType(XmlAccessType.NONE)
public class Department {
@XmlAttribute
private String id;
@XmlElement(name = "ParentId")
private String parentID;
@XmlElement(name = "Name")
protected String name;
}
This will provide the same output.
Upvotes: 1