XJC plugin to restrict marshalled data according to the TolerantReader pattern.
Sometimes you have an extensive system of schema files with lots of classes and attributes, at times so large that developers struggle with the richness of the schema. Furthermore, the schema might evolve in incompatible ways for reasons which are out of your control.
The client on the other hand uses only a tiny fraction of the types from the schema. As a developer, I do not want to bother with an enormous tree of classes I don’t understand and I want no changes to affect my code base which are irrelevant to my client.
The goal of this plugin is to handle this situation by applying ideas of TolerantReader to JAXB. I want to generate beans from the schema, but I want to be able to depend only on the data I care about, and most importantly, I still want to avoid the serialization antipattern of enterprise integration by being able to decouple structurally.
I want to use the incoming xml as a business document defined in terms of a schema by creating a Java bean from it, but not by deserializing it as a remote binary object. I want to decouple my Java bean from the evolution of the schema as much as possible, thus at least partially avoiding the dangers of serialization.
Note that schema changes might still require regeneration and recompilation of the client code. The goal is that at least this should be all there is to do, and that I get immediate feedback when the schema change breaks my client, so I can fix the breaking change at the root.
To a certain extent it is possible to support multiple schema versions with a single JAXB bean, for details see the section Support Multiple Schema Versions.
The plugin configuration can also be handed to the service as an executable description of my client expectations. The service can use it as a test for ConsumerDrivenContracts to see which client will break upon any given change.
Finally, the plugin annotates the generated beans with the @Expose
annotation from hydra-jsonld, if hydra-jsonld is on the classpath.
To set up the plugin for a maven build, use the maven-jaxb2-plugin. In the maven plugin configuration add the jaxb2-tolerant-reader xjc plugin and make sure you enable xjc extension mode, as shown below. The plugin is activated via the -Xtolerant-reader switch.
Important
|
Currently the tolerant-reader-plugin requires a patch to xjc classinfo (xjc-classinfo-patch). The patch is based upon the xjc version 2.2.11 used by the current maven-jaxb2-plugin. A pull request has been submitted to eclipse-ee4j/jaxb-ri. In order to introduce the patch, add it as dependency to the maven-jaxb2-plugin as shown below. |
Since the tolerant-reader-plugin updates the Outline
built from the schema, it must run before other plugins such as the jaxb2-basics plugin, so they can pick up the changes introduced by tolerant-reader-plugin.
The pom.xml of the it/person test project uses the tolerant-reader-plugin with jaxb2-basics plugin.
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.1</version>
<dependencies>
<dependency>
<groupId>de.escalon.jaxb2</groupId>
<artifactId>xjc-classinfo-patch</artifactId>
<version>0.5.1</version>
</dependency>
</dependencies>
<configuration>
<extension>true</extension>
<verbose>true</verbose>
</configuration>
<executions>
<execution>
<id>person</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<args>
<arg>-Xtolerant-reader</arg>
</args>
<schemaDirectory>${basedir}/src/main/wsdl/example</schemaDirectory>
<produces>
<produce>com.example.person/**/*.java</produce>
</produces>
<episode>false</episode>
<specVersion>2.0</specVersion>
<plugins>
<plugin>
<groupId>de.escalon.jaxb2</groupId>
<artifactId>jaxb2-tolerant-reader</artifactId>
<version>0.5.1</version>
</plugin>
</plugins>
</configuration>
</execution>
</executions>
</plugin>
The idea is to require only the Java beans and bean attributes your client really needs and be tolerant about the rest.
For this, you define a binding file with an include
element on the schema level where you describe beans that should be generated.
If the service provider renames a property or introduces an otherwise incompatible structural change, I want to keep my representation of the data intact. To cope with situations like that, it is possible to rename properties and to use XmlAdapters or computed fields as a mechanism to handle a structural change.
Schemas which use version numbers in their targetNamespace
are a particular challenge. See the sections Support Multiple Schema Versions and packageRoot attribute (optional) for possibilities to handle versioned package names for your beans.
First we discuss some example bindings, followed by a reference of all binding elements supported by the tolerant-reader-plugin.
The configuration of the tolerant-reader-plugin uses the standard customization options of the xml-to-java compiler xjc. Below you see examples of external binding customization files, i.e bindings.xjb files which you put into your schemadirectory.
In the sample binding below we use the extension binding prefix tr
for the tolerant-reader plugin namespace. We rename beans and attributes, we apply an @XmlJavaTypeAdapter
and we compute a field.
<jxb:bindings version="2.1" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tr="http://jaxb2-commons.dev.java.net/tolerant-reader"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:person="http://example.com/person"
jxb:extensionBindingPrefixes="xjc tr">
<jxb:globalBindings>
<xjc:simple />
<xjc:serializable uid="1" />
</jxb:globalBindings>
<jxb:bindings schemaLocation="Person.xsd">
<tr:include packageRoot="com.example" prefix="cust">
<!-- bean with required properties only -->
<tr:bean name="USAddress" />
<!-- bean alias name (e.g. to translate bean names) -->
<tr:bean name="BaseAddress" alias="AddrBase" />
<!-- bean with required and some optional properties -->
<tr:bean name="Name" properties="firstName middleInitial lastName" />
<!-- bean with property alias -->
<tr:bean name="GlobalAddress" alias="Address">
<tr:alias property="postalCode">postCode</tr:alias>
</tr:bean>
<!-- bean with an adapted and a computed property -->
<tr:bean name="Person" alias="Individuum" properties="age name">
<tr:alias property="role" alias="function">
<tr:adapter class="com.example.ValueWrapperXmlAdapter"
to="java.lang.String" />
</tr:alias>
<tr:alias alias="displayName">
<tr:compute to="java.lang.String"
expr="T(org.apache.commons.lang3.StringUtils).trimToNull(
(name?.firstName?:'') + ' ' + (name?.lastName?:''))" />
</tr:alias>
<tr:add property="myProperty" class="java.lang.Integer"/>
</tr:bean>
</tr:include>
</jxb:bindings>
</jxb:bindings>
A second example in the it/xfinanz test project uses the tolerant-reader-plugin with the public schema XFinanz created by German governments. It demonstrates how you can support two schema versions with a single JAXB bean, looking at a contrived scenario where the older schema version "had" a single element for streetName/houseNumber/houseNumberAppendix, whereas the new version uses three distinct elements for the same information and drops the element for the complete street address.
The bindings.xjb file below creates a JAXB bean with the distinct street address fields of the new version and an artificial bean property strassekomplett
for the old version.
<?xml version="1.0"?>
<jxb:bindings version="2.1" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tr="http://jaxb2-commons.dev.java.net/tolerant-reader"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:person="http://example.com/person"
jxb:extensionBindingPrefixes="xjc tr">
<jxb:bindings schemaLocation="xoev-code.xsd">
<jxb:schemaBindings>
<jxb:package name="xoev.schemata.code"/>
</jxb:schemaBindings>
</jxb:bindings>
<jxb:bindings schemaLocation="xfinanz-nachricht-sepa.xsd">
<jxb:schemaBindings>
<jxb:package name="de.escalon.xfinanz"/>
</jxb:schemaBindings>
<tr:include>
<tr:bean name="SepaMandatAenderung0601"/>
<tr:bean name="SepaMandatNeu0602"/>
<tr:bean name="CodeMandatTyp" properties="listURI"/>
<tr:bean name="PersonKomplett" properties="personNats bankverbindungs adressStamms"/>
<tr:bean name="NameNatuerlichePerson" properties="vorname"/>
<tr:bean name="AllgemeinerName" properties="name"/>
<tr:bean name="Code" properties="listVersionID"/>
<tr:bean name="Bankkonto"
properties="arts nummer iban geldinstitut inhaberNatuerlichePersons inhaberJuristischePerson bankverbindungsNummern bankkontoGueltigkeit">
</tr:bean>
<tr:bean name="AnschriftErweitert" properties="postanschrift"/>
<tr:bean name="Postanschrift" properties="strasse hausnummer hausnummernzusatz postleitzahl ort staat adressierungszusatz">
<tr:alias alias="strassekomplett">
<tr:compute to="java.lang.String"
expr="T(org.apache.commons.lang3.StringUtils).trimToNull(strasse + ' ' + hausnummer + hausnummernzusatz?:'')" />
<tr:set>
<tr:regex>^([\S\s]+?)\s+([\d-\s]*?)\s*([a-zA-Z])?$</tr:regex>
<tr:assign>strasse = #matcher.group(1)</tr:assign>
<tr:assign>hausnummer = #matcher.group(2)</tr:assign>
<tr:assign>hausnummerzusatz = #matcher.group(3)</tr:assign>
</tr:set>
</tr:alias>
</tr:bean>
</tr:include>
</jxb:bindings>
</jxb:bindings>
The resulting bean will have all the setters and getters of the new version, and synthetic methods getStrassekomplett
and setStrassekomplett
for the old version.
I.e. the old field strasse
will become a bean property strassekomplett
consisting of a getter which computes the old field value from the new distinct fields, and a setter which parses an incoming full street address string into its components and assigns them to the new distinct fields.
Below you see the generated JAXB bean.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Postanschrift")
public class Postanschrift implements Serializable, Equals, HashCode, ToString
{
private final static long serialVersionUID = 1L;
protected String hausnummer;
protected String hausnummernzusatz;
protected String ort;
protected String postleitzahl;
protected String strasse;
protected Staat staat;
protected String adressierungszusatz;
...
@XmlTransient
public String getStrassekomplett() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(this);
Expression exp = parser.parseExpression("T(org.apache.commons.lang3.StringUtils).trimToNull(strasse + ' ' + hausnummer + hausnummernzusatz?:'')");
Object ret = exp.getValue(context);
return ((String) ret);
}
public void setStrassekomplett(String value) {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(this);
Pattern pattern = Pattern.compile("^([\\S\\s]+?)\\s+([\\d-\\s]*?)\\s*([a-zA-Z])?$");
Matcher matcher = pattern.matcher(value);
matcher.find();
context.setVariable("matcher", matcher);
context.setVariable("value", value);
List<String> assignmentExpressions = Arrays.asList(new String[] {"strasse = #matcher.group(1)", "hausnummer = #matcher.group(2)", "hausnummerzusatz = #matcher.group(3)"});
for (String assignmentExpression: assignmentExpressions) {
parser.parseExpression(assignmentExpression);
}
}
Note that getStrassekomplett
is annotated with @XMLTransient
, i.e. it will not be marshalled by the standard marshalling procedure.
However, it is possible to adjust marshalling so that it renders the result of getStrassekomplett
as an element strasse
in the xml output and produces the older namespace. The Eclipselink MOXy implementation of JAXB is able to do this via its custom xml-bindings. We use three customizations:
-
xml-schema
to adjust the namespace; here we set the namespace to the older version -
xml-named-object-graphs
to describe a desired partial output; here we select the elementstrassekomplett
only and drop the distinct elements, which are not supported by the older version. This also allows us to use an@XMLTransient
element during marshalling -
xml-element
to rename bean properties inside a java-type; here we renamestrassekomplett
in thePostanschrift
java-type to the old namestrasse
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="de.escalon.xfinanz">
<xml-schema element-form-default="QUALIFIED" namespace="xFinanz280" />
<java-types>
<java-type name="SepaMandatNeu0602">
<xml-named-object-graphs>
<xml-named-object-graph name="v1">
<xml-named-attribute-node name="nachrichtenkopf"/>
<xml-named-attribute-node name="sepaMandats"/>
<xml-named-attribute-node name="personKomplett" subgraph="v1"/>
</xml-named-object-graph>
</xml-named-object-graphs>
</java-type>
<java-type name="PersonKomplett">
<xml-named-object-graphs>
<xml-named-object-graph name="v1">
<xml-named-attribute-node name="personNats"/>
<xml-named-attribute-node name="adressStamms" subgraph="v1"/>
<xml-named-attribute-node name="bankverbindung"/>
</xml-named-object-graph>
</xml-named-object-graphs>
</java-type>
<java-type name="Adressstamm">
<xml-named-object-graphs>
<xml-named-object-graph name="v1">
<xml-named-attribute-node name="anschrift" subgraph="v1"/>
<xml-named-attribute-node name="personenNummern"/>
</xml-named-object-graph>
</xml-named-object-graphs>
</java-type>
<java-type name="AnschriftErweitert">
<xml-named-object-graphs>
<xml-named-object-graph name="v1">
<xml-named-attribute-node name="postanschrift" subgraph="v1"/>
<xml-named-attribute-node name="anschriftNummern"/>
</xml-named-object-graph>
</xml-named-object-graphs>
</java-type>
<java-type name="Postanschrift">
<xml-named-object-graphs>
<xml-named-object-graph name="v1">
<xml-named-attribute-node name="strassekomplett"/>
</xml-named-object-graph>
</xml-named-object-graphs>
<java-attributes>
<xml-element java-attribute="strassekomplett" name="strasse"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
MOXy named object graphs must describe the entire desired graph, therefore the graph needs to start with the root bean SepaMandatNeu602
, describe all elements of the desired output and pass down the version "v1"
to all subgraphs which have version-specific adjustments.
The only subgraph which really adjusts something is the subgraph for Postanschrift
. This makes the xml-bindings file quite verbose.
It is also possible to create named object graphs programmatically, which might allow to cut down the verbosity of the xml-bindings file.
Add at least one tr:include element as customization root. If you need to define beans from multiple packages, have one include element per package.
You may add a packageRoot
attribute to an include
element if you have to select beans from specific packages. The package root does not have to be the entire package name, it uses startsWith to match packages and falls back to regex matching. That way you can be tolerant about particular versions of a schema if the schema provider uses version numbers in namespaces. I.e. if the schema uses a target namespace com.example.namespace.fun.v5_7
, you can use a packageRoot com.example.namespace.fun
to select your beans.
Tip
|
Instead of using the packageRoot attribute in situations where the schema uses a versioned targetNamespace , you may want to apply custom java packages to avoid having to fix lots of import statements for every version change. The bindings file in the section Support Multiple Schema Versions is an example of this technique.
If you apply a custom java package, the original versioned namespace will still be preserved during marshalling via the package definition in package-info.java.
If you do not customize marshalling, each version of a schema with versioned targetNamespace requires you to generate a different set of JAXB beans. In order to support multiple versions of a schema with a single set of JAXB beans, it is necessary to customize marshalling. Eclipselink MOXy is able to do this, as shown in the section in the section Support Multiple Schema Versions.
|
The section References lists some blog entries on XML versioning.
Allows to specify a prefix to be used for the target namespace URI when used with hydra-jsonld. When the plugin detects hydra-jsonld on the classpath, it annotates the beans with @Term(define = "cust", as = "http://example.com/person#")
, i.e. cust
represents the target namespace of the schema.
Describes an expected bean. Super classes will be included automatically. If an expected bean is not defined by the schema, an error is thrown. This allows you to detect and fix breaking changes early.
Alias bean name to be used instead of the original bean name.
Note
|
Aliasing also removes abstract modifiers from base classes during renaming, so that unknown incoming subtypes of such base classes can be unmarshalled safely, rather than breaking the unmarshalling process. Without that, an unknown subtype of an abstract base class in a new schema version would break the client. |
List of expected bean properties as space-separated strings. Required properties are included automatically, i.e. you only need to define elements having minOccurs=0 and attributes without required=true. If an included property has a complex type, the bean for that type will be included automatically.
In cases where you do not simply expect a property, but you also want to rename it, use a tr:alias element instead.
Describes a property which should be generated with an alias name, one tr:alias element per property. The generated property will be renamed either to the content of the alias element, or to the value of the alias attribute of the tr:alias element. See the explanation of the alias attribute below for examples.
The property you want to rename is given with the property attribute (see below).
May be used in combination with the properties attribute of the tr:bean element, i.e. you may have some properties you expect with their original name and some other, aliased properties.
Original property name of a tr:alias element which will be renamed. Must be omitted when defining a computed property.
A tr:alias element can define the alias name to be used as content of the element:
<tr:alias property="foo">bar</tr:alias>
As an alternative, it is also valid to define the alias name with an alias attribute. Must be used with tr:adapter and tr:compute.
<tr:alias property="foo" alias="bar" />
Adapter specification to adapt a field, for use inside of a tr:alias element. Will annotate the property with an @XmlJavaTypeAdapter
annotation. If an adapter is applied, the alias name must be given with an alias
attribute, not as content of the tr:alias element.
In the example below, a ValueWrapperXmlAdapter
adapts the field role
of complex type ValueWrapper
to a simple String by extracting the wrapped value.
<tr:alias property="role" alias="function">
<tr:adapter class="com.example.ValueWrapperXmlAdapter"
to="java.lang.String" />
</tr:alias>
Warning
|
The tr:adapter element is mainly useful in situations where a bean property type needs to be adjusted to a different type in XML, and where the bean property can fully represent the information in XML. If that is not the case and you need to write back information to a server, tr:compute with tr:set is probably a better fit, so you can expose several fields of a complex type as distinct fields on your JAXB bean, which preserve the full information. |
Fully qualified class name of the type to which the adapter adapts the field. By default, this is java.lang.String
.
Note
|
The TolerantReaderPlugin cannot determine this type automatically for adapters from the adapter class. At the time of schema compilation the class of an XmlAdapter implementation cannot be available, since the XmlAdapter implementation requires the JAXB type for compilation.
|
Specifies a computed field which will be generated as @XmlTransient
, for use inside of a tr:alias
element.
A computed field requires you to provide an expression inside the expr
attribute or a tr:expr child element; furthermore, if the expression does not evaluate to String
, the type to which the expression evaluates in the to
attribute. Consider the examples in the supported expression languages below.
The expression can be written with SpringEL, javax.el 3.0 or as plain java expression.
Include spring-expression as plugin dependency of the maven-jaxb2-plugin to use tr:compute
:
<!-- inside plugin configuration -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<dependencies>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
</dependencies>
</plugin>
That allows you to use expressions with Spring EL’s safe navigation ?.
and Elvis ?:
operators, and you have access to static utilities, too:
<tr:alias alias="displayName">
<tr:compute to="java.lang.String"
expr="T(org.apache.commons.lang3.StringUtils).trimToNull(
(name?.firstName?:'') + ' ' + (name?.lastName?:''))" />
</tr:alias>
If you use Spring EL, a tr:compute element also supports a tr:regex to parse an underlying string property. The attribute propertyExpr
expects a Spring EL expression which specifies the property path on the current bean to which the regex should be applied. The matcher will be available as #matcher
in Spring EL:
<tr:alias alias="lastNameBeforeBlank">
<tr:compute>
<tr:regex propertyExpr="name.lastName">^(.*) (.*)$</tr:regex>
<tr:expr>#matcher.group(1)</tr:expr>
</tr:compute>
</tr:alias>
You can also use javax.el 3.0 (starting from Java 1.7) if you add it as dependency to the maven-jaxb2-plugin:
<!-- inside plugin configuration -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<dependencies>
...
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</plugin>
The generated code for javax.el 3.0 always addresses the current jaxb bean by the name bean
:
<tr:alias alias="displayName">
<tr:compute to="java.lang.String"
expr="((not empty bean.name.firstName ? bean.name.firstName : '')
+= ' ' += (not empty bean.name.lastName ? bean.name.lastName : '')).trim()"
</tr:alias>
If you include no EL dependencies, you can still write Java expressions, which requires you to use the xml entities for double quotes, ampersand etc., and you have to handle null
explicitly.
<tr:alias alias="displayName">
<tr:compute to="java.lang.String"
expr="(name.firstName == null ? "" : name.firstName)
+ (name.firstName != null && name.lastName != null ? " " : "")
+ (name.lastName == null? "" : name.lastName)
</tr:alias>
Specifies a synthetic setter which uses Spring EL and Spring’s BeanWrapper to assign the incoming value, for use inside of a tr:alias
element, usually in conjunction with a tr:compute
element.
Include spring-expression and spring-beans as plugin dependency of the maven-jaxb2-plugin to use tr:set
:
<!-- inside plugin configuration -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<dependencies>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</plugin>
The plugin assumes that the incoming value is of type String by default, and makes that value accessible under the default variable name #value
to Spring EL.
There are several ways in which you can use tr:set
. The first is to parse an incoming String value using a tr:regex element and assign the matching groups to fields on the current bean using tr:assign elements:
<tr:set>
<tr:regex>^([\S\s]+?)\s+([\d-\s]*?)\s*([a-zA-Z])?$</tr:regex>
<tr:assign>strasse = #matcher.group(1)</tr:assign>
<tr:assign>hausnummer = #matcher.group(2)</tr:assign>
<tr:assign>hausnummerzusatz = #matcher.group(3)</tr:assign>
</tr:set>
Another possibility is to read properties from an incoming bean and assign them to properties on the current bean.
The example below assumes that the current bean has a roleText
and a roleValue
property.
The ValueWrapper represents a human-readable text and a technical value for a person’s role.
<tr:set paramType="com.example.person.ValueWrapper">
<tr:assign>roleText = #value.text</tr:assign>
<tr:assign>roleValue = #value.value</tr:assign>
</tr:set>
A third possibility is to flatten a complex type onto the target bean, creating distinct setters and getters.
This example assumes that the current bean has a complex property employer
.
<tr:alias alias="employerFirstName">
<tr:compute expr="employer.name.firstName"/>
<tr:set>
<tr:assign>employer.name.firstName = #value</tr:assign>
</tr:set>
</tr:alias>
<tr:alias alias="employerLastName">
<tr:compute expr="employer.name.lastName"/>
<tr:set>
<tr:assign>employer.name.lastName = #value</tr:assign>
</tr:set>
</tr:alias>
If the incoming type is of a type other than String, that type needs to be specified as fully qualified class name using the paramType
attribute on tr:set
.
The @Expose
annotation of hydra-jsonld can be applied automatically to generate JSON-LD directly from the JAXB beans.
In order to annotate your beans with @Expose
have the following plugin dependency in your pom.xml.
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.1</version>
<dependencies>
...
<dependency>
<groupId>de.escalon.hypermedia</groupId>
<artifactId>hydra-jsonld</artifactId>
<version>0.3.1</version>
</dependency>
</dependencies>
...
The plugin detects the presence of hydra-jsonld and annotates the beans with @Expose
. Sample Person:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Person")
@Term(define = "cust", as = "http://example.com/person#")
@Expose("cust:Person")
public class Person {
@Expose("cust:Person.name")
public Name getName() {
return name;
}
}
This section is for people who do not want to use the plugin, but who want to build the plugin themselves.
Normally it should be sufficient to invoke mvn clean install
on the plugin parent project.
If you are behind a company proxy, add proxy system properties to your MAVEN_OPTS or adjust the proxy settings for the integration tests in src/it/settings.xml.
If you run the maven build of the plugin project with embedded maven (e.g. inside Eclipse), make sure you have an environment variable M2_HOME
pointing to a standalone maven installation which can be picked up by the maven invoker plugin during integration test.
As a plugin developer you may want to execute the plugin manually, but you want its output in the same place where maven puts it.
In launch and debug configurations you can execute the plugin via the com.sun.tools.xjc.Driver
Java main class, with the tolerant-reader-plugin and the xjc-classinfo-patch on the classpath (make sure the xjc-classinfo-patch comes before tolerant-reader-plugin on the classpath in your launch configuration).
One way to achieve this in Eclipse is to create a Java Application launch configuration for com.sun.tools.xjc.Driver
while the jaxb2-tolerant-reader
project is selected, so that it becomes the launch configuration’s project. Then switch to the Classpath tab, highlight User Entries and add the xjc-classinfo-patch
project to the classpath. Finally, hit Up to move it above the jaxb2-tolerant-reader
entry.
In Idea, create a run configuration for com.sun.tools.xjc.Driver
and configure it to Use classpath of module: jaxb2-tolerant-reader. In order to adjust the classpath to apply the xjc-classinfo-patch, choose Open Module Settings for the jaxb2-tolerant-reader module while it is highlighted (hit F4). On the Dependencies tab, hit the + icon on the right hand side and choose Module Dependency… to add xjc-classinfo-patch. Then select xjc-classinfo-patch in the dependencies list and hit the up arrow icon until it is at the top of all dependencies.
Build the plugin project with Maven. This is necessary to create an executable maven test project in target/it/person.
Use the target/it/person project as current working directory of the launch configuration and pass the following arguments:
-extension -no-header -d target/generated-sources/xjc -Xtolerant-reader -b src/main/wsdl/example/bindings.xjb src/main/wsdl/example/Person.xsd
The sample project in src/it makes use of placeholders for the maven invoker plugin. Therefore it cannot run as-is; you have to import the project created by maven-invoker-plugin in target/it.
-
Import the parent project as Maven project
-
Execute a maven build on the parent (with standalone maven; or make sure you have an
M2_HOME
environment variable) so that the invoker plugin creates a runnable project in target/it. -
Open the parent project
-
Open the module tolerant-reader-plugin
-
Navigate to target/it/person
-
Right click the person folder and select "Import as Project"
-
Right click the newly imported project and select "Run As - Maven build"
-
Papers on XML Versioning and Extensibility by David Orchard
-
XML Schema Versioning by XFront
-
XML Versioning vs Extensibility Subbu Allamaraju: "My conclusion is that extensibility and versioning are two different beasts and require different solutions"
-
Versioning XML Schemas "Once you publish an interface, it is set in stone, and you should not introduce incompatible changes"
-
Processing Versioned XML Documents discusses possibilities to let multiple versions of instance documents look like the version supported by the consumer of an instance document.
-
David Tiller, Make a Surgical Strike with a Custom XJC Plugin and Extending XJC Functionality With a Custom Plugin
-
Dr. Aleksei Valikov, whose answer on stackoverflow encouraged me to write this plugin
-
Nicolas Fraenkel’s blog entry Customize your JAXB bindings shows additional ways to customize your JAXB classes, e.g. with base classes and converters.