I want to modify JAXB loaded objects (from XML) so that i can marshall them back to the disk with updated XML.
Now lets take an example:
<Customers>
<Customer id="1" name="Jack">
<Address type="Residence">
<FirstLine>1 saxon Court</FirstLine>
<City>CY</City>
</Address>
</Customer>
<Customer id="2" name="Iain">
<Address type="Residence">
<FirstLine>104 Bank Road</FirstLine>
<City>NY</City>
</Address>
</Customer>
</Customers>
Now as I have multiple customers, I want to use XPATH functionality to get the handle of the Customer[#id=2] object where I can add/update an address.
If I will not use the XPATH like functionality then JAXB generated classes will have "List<Customer> customer" and I will have to iterate through the list of customer to match with my desirable Customer[#id=2].
Can anyone give me idea how to get the Object instance handle for the JAXB generated objects using XPATH, so I can marshall it back to update the actual XML on the disk.
If it cannot be done through JAXB, then what can be alternative solution to read and write XMLs using java Objects with XPATH flexibility.
Updated Question with Sample code:
Following is the sample code which shows what i want to achieve with Moxy/JAXB.
package org.soc.test.customers.moxy;
import java.io.File;
import java.util.List;
import javax.xml.bind.*;
public class UnmarshalDemo {
public static void main(String[] args) throws Exception {
org.eclipse.persistence.jaxb.JAXBContext jc = (org.eclipse.persistence.jaxb.JAXBContext) JAXBContext.newInstance(Customer.class);
File instanceDoc = new File("input.xml");
Customer customer = (Customer) jc.createUnmarshaller() .unmarshal(instanceDoc);
List<PhoneNumber> phones = jc.getValueByXPath(customer, "phone-number[#id=\"12\"]", null, List.class);
String customerId = jc.getValueByXPath(customer, "#id", null, String.class);
System.out.println("Customerid " + customerId + " , phone " + (phones==null?"0":phones.size()));
jc.setValueByXPath(customer, "phone-number[#id=\"12\"]/area-code/text()", null, "555");
jc.createMarshaller().marshal(customer, System.out);
}
}
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<customer id="1141">
<first-name>Jon</first-name>
<last-name>Smith</last-name>
<phone-number id="11">
<area-code>515</area-code>
<number>2726652</number>
</phone-number>
<phone-number id="12">
<area-code>515</area-code>
<number>2726652</number>
</phone-number>
</customer>
XSD:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<xs:element name="customer">
<xs:complexType>
<xs:sequence>
<xs:element name="first-name" type="stringMaxSize5"/>
<xs:element name="last-name" type="stringMaxSize5"/>
<xs:element ref="phone-number" maxOccurs="2"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="phone-number">
<xs:complexType>
<xs:sequence>
<xs:element name="area-code" type="stringMaxSize5"/>
<xs:element name="number" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:simpleType name="stringMaxSize5">
<xs:restriction base="xs:string">
<xs:maxLength value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
jaxb.properties:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Output:
Customerid 1141 , phone 0
I am expecting the list size of the phone number to be 1 but it is returning null.
Hope this helps in understanding about the problem that I facing.
Thanks
Venkat
It's not supported with Moxy. The org.eclipse.persistence.internal.oxm.Context only supports numeric indexes in square brackets. While the XPathFragment understands it and creates appropriate predicates, the Context ignores those as far as searching for a match goes. I'd either raise a bug, or look for another tool.
See the Context source code (Commit 7cedaac6cdf9384ae9a06129d6f9abd607f9e3c4, Line 371 onwards) for exactly what's happening.
Related
I'm using Spring Batch for reading XML files. And I want to validate records using XSD.
I'm able to run validations using setSchema but it will throw exception and kill whole job. My goal is to handle these invalid records, save them to log and skip them for final process.
My StaxEcentItemReader
#Bean
#JobScope
public StaxEventItemReader<?> reader() throws Exception {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(BookDto.class);
jaxb2Marshaller.setSchema(new ClassPathResource("book.xsd"));
jaxb2Marshaller.afterPropertiesSet();
return new StaxEventItemReaderBuilder<>()
.name("xmlReader")
.resource(new ClassPathResource("books.xml"))
.addFragmentRootElements("book")
.unmarshaller(jaxb2Marshaller)
.build();
}
XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.test.com/xsd"
xmlns="http://www.test.com/xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="book" type="bookDto"/>
<xs:simpleType name="simAuthor">
<xs:restriction base="xs:string">
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="bookDto">
<xs:sequence>
<xs:element name="author" type="simAuthor" minOccurs="0"/>
<xs:element type="xs:float" name="price"/>
</xs:sequence>
<xs:attribute type="xs:string" name="id" use="required"/>
</xs:complexType>
</xs:schema>
Items
<?xml version="1.0"?>
<catalog>
<book xmlns="http://www.test.com/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com/xsd" id="bk101">
<author>Gambardella, MatthewMatthewMatthewMatthewMatthewMatthewMatthewMatthew</author>
<price>44.95s</price>
</book>
<book xmlns="http://www.test.com/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com/xsd" id="bk102">
<author>Ralls, Kim</author>
<price>5.95</price>
</book>
</catalog>
I'm able to run validations using setSchema but it will throw exception and kill whole job.
You can use a fault-tolerant step and declare the exception as skippable. With this in place, invalid items will be skipped and the job will continue with next items instead of failing at the first invalid item. For more details, please refer to the Configuring Skip Logic section of the reference documentation.
My goal is to handle these invalid records, save them to log and skip them for final process.
For that, you need to register a SkipListener and log invalid items where needed.
I am using JAXB 2.0. I have various elements and types defined in an XSD file. Here's an example:
<xs:element name="Person" type="Person" />
<xs:complexType name="Person">
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:element name="Musician" type="Musician"/>
<xs:complexType name="Musician">
<xs:complexContent>
<xs:extension base="Person">
<xs:attribute name="instrument" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="People" type="People"/>
<xs:complexType name="People">
<xs:sequence>
<xs:element name="person" type="Person" minOccurs="0" maxOccurs="Unbounded/>
</xs:sequence>
</xs:complexType>
So as you can see from the above schema example, we have a Person, who has a name, and a Musician, who is also a Person (though this may be subject to some debate, but that's for another forum). There is also a People element, which is essentially a collection of Person types.
I have the following in a bindings file:
<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jaxb:version="2.0"
xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc">
<jaxb:globalBindings optionalProperty="wrapper">
<xjc:simple/>
</jaxb:globalBindings>
My intended use of these objects is:
A Person may appear as a solitary Marshalled element, or may be a part of a People object
If a Person is part of a People object, then the xsi:type should indicate whether it's just a normal Person or a Musician.
So I need the generated Java classes to contain both an #XmlRootElement annotation as well as an #XmlType annotation. The xjc:simple binding creates both annotations for a Musician, but only creates the #XmlType for Person. So when I Marshall a Person object, all I get is:
<?xml version="1.0" encoding="UTF-8"?>
Whereas what I would like to see is:
<?xml version="1.0" encoding="UTF-8"?>
<Person name="John Doe"/>
For a People object, I want to see:
<?xml version="1.0" encoding="UTF-8"?>
<People>
<person name="John Doe" xsi:type="Person"/>
<person name="Keith Richards" xsi:type="Musician"/>
</People>
I have read about the simple binding with xjc, and it works with all the lowest levels in an inheritance hierarchy. However, the base classes end up without an #XmlRootElement annotation. For the use case I'm working on, it's imperative that base classes can be Marshalled as both a top-level element and as a member of other elements. Any suggestions would be welcome.
https://github.com/highsource/jaxb2-annotate-plugin could be used
<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings xmlns:annox="http://annox.dev.java.net"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jaxb:version="2.1"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc annox">
<jaxb:bindings node="//xs:complexType[#name='Person']">
<annox:annotate>
<annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="person"/>
</annox:annotate>
</jaxb:bindings>
</jaxb:bindings>
I need to generate following schema from java class using JAXB.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xdb="http://xmlns.oracle.com/xdb">
<xs:element name="test" type="test"/>
<xs:complexType name="testName" xdb:SQLType="WEBY_TEST_NAME">
<xs:sequence>
<xs:element minOccurs="0" name="date" type="xs:dateTime"/>
<xs:element name="id" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
How to add xdb:SQLType="WEBY_TEST_NAME" into complexType element using jaxb annotations ?
i done same try for generating the schema for different tags, but the names which include reserve words or else need to declare as
#XmlElement(name="class")
public String getClasss() {
return classs;
}
in pojo, so at time of coding it uses name which we provide, and in java program it uses the declared variables.
may be your declaration become
#XmlElement(name="xdb:SQLType")
public String getxdbSQLType() {
return xdbSQLType;
}
Let's assume we defined a collection type in XSD as
<xs:complexType name="Foos">
<xs:sequence>
<xs:element name="foo" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:all>
<xs:element name="bar" type="xs:string"/>
<xs:element name="baz" type="xs:string"/>
</xs:all>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
When generating Java code from it using XJC the type roughly translates to
public class Foos {
public List<Foos.Foo> getFoos();
public static class Foo {
public String getBar();
public String getBaz();
}
}
As the collection type is part of some other type such as the root of the document, client code of the generated code looks somewhat like this
for(Foo foo : document.getFoos().getFoos())
{
//do something
}
Is there any way to make the client code less ugly without writing a wrapper manually?
It should look like this
for(Foo foo : document.getFoos())
{
//do something
}
Thanks
UPDATE
There are XJC plug-ins that people have written to generate the #XmlElementWrapper annotation instead of having the extra wrapper class.
https://github.com/dmak/jaxb-xew-plugin
ORIGINAL ANSWER
Alternative you could create the class with the #XmlElementWrapper yourself and have the generated classes reference it by doing the following:
Document
You could handcraft your own Document class to get the desired behaviour. You can get the behaviour you are looking for by leveraging the #XmlElementWrapper annotation to get a grouping element.
package forum18247182;
import java.util.*;
import javax.xml.bind.annotation.*;
public class Document {
private List<Foos.Foo> foos = new ArrayList<Foos.Foo>();
#XmlElementWrapper
#XmlElement(name="foo")
public List<Foos.Foo> getFoos() {
return foos;
}
}
XML Schema (schema.xsd)
Here is an expanded XML schema based on your fragment that I will use.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/schema"
xmlns="http://www.example.org/schema"
elementFormDefault="qualified">
<xs:element name="document" type="Document"/>
<xs:complexType name="Document">
<xs:sequence>
<xs:element name="foos" type="Foos"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Foos">
<xs:sequence>
<xs:element name="foo" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:all>
<xs:element name="bar" type="xs:string" />
<xs:element name="baz" type="xs:string" />
</xs:all>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
Leverage Existing Class When Generating Java Model from XML Schema (binding.xml)
We will use an external binding file to indicate that during class generation we wish to use our existing class for the complex type called Document.
<jxb:bindings
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
version="2.1">
<jxb:bindings schemaLocation="schema.xsd">
<jxb:bindings node="//xs:complexType[#name='Document']">
<jxb:class ref="forum18247182.Document"/>
</jxb:bindings>
</jxb:bindings>
</jxb:bindings>
XJC Call
We will use the -b option to specify our binding file. We will also use the -p option to force the package name of the generated classes to match that of our Document class. We could also have made the package name of our Document class match the package name that results from generating classes from the XML schema.
xjc -b binding.xml -p forum18247182 schema.xsd
Demo Code
package forum18247182;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import forum18247182.Foos.Foo;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance("forum18247182");
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource xml = new StreamSource("src/forum18247182/input.xml");
Document document = unmarshaller.unmarshal(xml, Document.class).getValue();
for(Foo foo : document.getFoos())
{
System.out.println(foo);
}
}
}
Output
Below is the output from running the demo code:
forum18247182.Foos$Foo#51f3336e
forum18247182.Foos$Foo#35b5a4ca
Question is , for some reason. the xsd doesn't/can't define all logic vars other than basic properties and setters and getters, so we've tried to 'inject code' by xsd definition which are actually discussed by other folks couple of times. I have no problem with 'simple injection' with 'simple java method' which won't need any 'import' statement on top of class def.
yet somehow if we want to use it. it looks to me there is no way we could take or import any packages other than setter or getters. , see below for details
xsd definition test.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://company.com/schema/response"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:test="http://company.com/schema/response"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.1"
xmlns:ci="http://jaxb.dev.java.net/plugin/code-injector"
jaxb:extensionBindingPrefixes="ci">
<xs:element name="client">
<xs:complexType>
<xs:annotation>
<xs:appinfo>
<ci:code>
<![CDATA[
private String str;
public String returnStr() {
Locations locationCls =this.getLocations();
List<String> locationids = new ArrayList<String>();
// get a list of locationid into locationids (list)
List<Location> locationList = locationCls.getLocation();
for (Location loc : locationList) {
locationids.add(String.valueOf(loc.getId()));
}
// return string like loc1,loc2,loc3
return StringUtils.join(locationids, ',');
}
]]>
</ci:code>
</xs:appinfo>
</xs:annotation>
<xs:sequence>
<xs:element name="name" type="xs:NCName" />
<xs:element name="pass" type="xs:NCName" />
<xs:element ref="test:locations" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="locations">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="test:location" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="location">
<xs:complexType>
<xs:attribute name="id" use="required" type="xs:string" />
<xs:attribute name="address" use="required" type="xs:string" />
<xs:attribute name="biz" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:schema>
run jaxb ri command :
xjc.bat test.xsd -Xinject-code -extension
observe below code snippet in the Client.java successfully
private String str;
public String returnStr() {
Locations locationCls =this.getLocations();
List<String> locationids = new ArrayList<String>();
// get a list of locationid into locationids (list)
List<Location> locationList = locationCls.getLocation();
for (Location loc : locationList) {
locationids.add(String.valueOf(loc.getId()));
}
// return string like loc1,loc2,loc3
return StringUtils.join(locationids, ',');
}
As a consequence, we know jdk complains the compilation error as StringUtils in Apache commons ( or other 3rd part util tools like google collections to help in other scenarios) are not imported in generated file. understand there are some google projects which use jaxb plugin to insert or invoke method into generated java files. just want to spend day or so to see if we could make it by xsd itself only without any plugin. any idea would be appreciated .
You could specify the fully classified class name within the code that you want to inject, e.g.:
return org.apache.commons.lang.StringUtils.join(locationids, ',');