JAXB, abstract classes, and multi-module projects - java

Let's say I have a JAR xxx-core.jar with the following classes:
#XmlRootElement
#XmlSeeAlso({Imp1.class, Imp2.class})
public abstract class Abst {...}
#XmlRootElement
public class Imp1 extends Abst {...}
#XmlRootElement
public class Imp2 extends Abst {...}
public class Main {
#XmlElement
private Abst abst;
public static void load(File file) {
// unmarshal this
}
public void save(File file) {
// marshal this
}
}
So far, so good. Main can be marshalled and unmarshalled, and the correct implementation of Abst is used.
Now, what happens when somebody else comes along and creates another project xxx-extension.jar that uses xxx-core.jar, but contains the following class:
#XmlRootElement
public class ExtensionImp extends Abst {...}
and assigns an instance of this new implementation to Main's member variable? Since it's not explicitly given in the XmlSeeAlso annotation, how can I make sure that ExtensionImp will be correctly marshalled/unmarshalled? (I've played with the class list in JAXBContext.newInstance(), but that doesn't seem to solve the problem.)

The #XmlSeeAlso annotation is just a mechanism that lets your JAXB (JSR-222) impl know to look at other classes. Alternatively you could just include then in the classes used to bootstrap the JAXBContext. When you create your JAXBContext you just need to do:
JAXBContext jc = JAXBContext.newInstance(Abst.class, ExtensionImp.class);
UPDATE 1
I've played with the class list in JAXBContext.newInstance(), but that
doesn't seem to solve the problem.
This should definitely solve the problem, what happens when you do this.
UPDATE 2
I suspect your issue is due to the document you are unmarshalling and not how you are bootstrapping. The following should help.
Inheritance - xsi:type
With the way that you currently have your mappings, you need to ensure that your XML looks like the following to have an instance of Imp2 instantiated and populated into the abst field on the Main class.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<main>
<abst xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="imp2"/>
</main>
For more info see:
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
Inheritance Substitution Groups
If you would rather unmarshal an XML document like the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<main>
<imp2/>
</main>
Then you need to leverage the #XmlElementRef annotation.
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Main {
#XmlElementRef
Abst abst;
}
For more info see:
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html

I solved similar problem using #XmlAnyElement(lax=true). Try to put this annotation instead of #XmlElement into main class.

Related

Is there a way of parsing strings to class types from YAML?

I was wondering if there is any way of loading a YAML configuration like this:
classes:
a: A.class
b: B.class
in such a way that A.class and B.class can later be retrieved as Class type (something similar to Map<String, Class>
Update #1
I'm using Spring Boot to load configuration in this class:
#Getter
#Setter
#Configuration
#ConfigurationProperties
public class ClassesConfig {
private Map<String, Class<?>> classes;
}
I could do something like this, but I don't actually know how to achieve that while maintaining the use of this class...
This should work:
#Component
#ConfigurationPropertiesBinding
public class ClassConverter implements Converter<String, Class<?>> {
#Override
public Class<?> convert(String from) {
return Class.forName(from);
}
}
Tweak it according to your needs; for example, if you want the .class suffix in the YAML file, remove that from the string from because it is not part of the class name.
Mind that you need to give the fully qualified name of the class. So if A and B are in a package, you need to give that package as prefix.

Call a personal Java method from eXistdb XQuery Processor

I have noticed i can call from eXistdb XQuery processor any Java classes, i need just to declare a namespace like the following:
declare namespace string="java:java.lang.String";
I've imported all the methods from the class String.
Now comes the question: is there a way to import my own classes? For example, if i made that class:
package example.model.A
class A1 {
public static example() { ... }
}
Is there a way to use the method example inside my xquery? Can i point the namespace to my personal class?
Thank you very much.

Lombok getters and setters being set privately

I'm using lombok in intellij, and have the plugin installed.
My problem is that when I use the #Date notation in my class, only that class can see the methods created by lombok. So if my class declaration looks like this:
#Document
#Data
public class dbDocument {
#Id
private String uniqueId;
And the method
public String testGetter (dbDocument doc) {
return doc.getUniqueId;
}
Will work inside the dbDocument class, but not in any other class. (where I get a Java: cannot find symbol error)
How can I fix/debug this?
Oops.
Looks like I was spelling the method wrong while invoking it. Nothing to see here.

How to exclude Observable in XSD file generated by JAXB

I have an abstract class extending java.util.Observable. When i generate xsd using Jaxb, there appears a complex type observable in the xsd file.
#XmlAccessorType(XmlAccessType.NONE)
public abstract class MyClass extends Observable implements Runnable, Serializable
I would use #XmlTransient on super type if it was a class i have written. Don't know how to exclude Observable.
I tried creating java.util package with package-info.java class to annotate package, as it was mentioned in this post JAXB: #XmlTransient on third-party or external super class :
#javax.xml.bind.annotation.XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.NONE)
package java.util;
Did not work. Also tried it with XMLTransient:
#javax.xml.bind.annotation.XmlTransient
package java.util;
Again no luck. Anyone has any solution for this ?

JAXB afterUnmarshall not called when using extended class

I wrote a bean (BaseBeanEx) extending a JAXB annotated bean (BaseBean). The BaseBean is in a List somewhere in the datastructure and can't be changed. The Software does an explicit cast to BaseBeanEx whenever it is needed. I also wrote an ObjectFactory to create BaseBeanEx instead of BaseBean. This all works fine, but now I added a afterUnmarshal method to BaseBeanEx which never gets called.
Is this a bug or is this according to the specs? If later is the case, is there some elegant work around?
I'm using the default JAXB engine.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The reason that afterUnmarshal is not being called on BaseBeanEx is that the metadata was built on the BaseBean class. To get your use case to work you need to let your JAXB impl know that you really want to map to instances of BaseBeanEx.
OPTION #1 - Any JAXB Implementation using Annotations
Root
You can use the #XmlElement annotation to override the type of a field/property. In the example below the signature of the method is List<BaseBean>, but the #XmlElement annotation informs the JAXB implementation the property should be interpreted as List<BaseBeanEx>.
package forum10174513;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
private List<BaseBean> baseBeans;
#XmlElement(name="base-bean", type=BaseBeanEx.class)
public List<BaseBean> getBaseBeans() {
return baseBeans;
}
public void setBaseBeans(List<BaseBean> baseBeans) {
this.baseBeans = baseBeans;
}
}
OPTION #2 - Using MOXy's External Mapping Document
The BaseBean is in a List somewhere in the datastructure and can't be
changed.
If you can't modify your domain model and are using MOXy as your JAXB provider then you can leverage its external mapping document to apply metadata without modifying your domain model.
bindings.xml
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum10174513">
<java-types>
<java-type name="Root">
<java-attributes>
<xml-element
java-attribute="baseBeans"
name="base-bean"
type="forum10174513.BaseBeanEx"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Demo
Below is some code that demonstrates how to bootstrap a JAXBContext that leverages the external mapping document. There is currently a bug where classes only referenced through the external mapping document won't have there event methods registered (http://bugs.eclipse.org/376876). You can work around this issue by explicitly including this class in the list of classes used to create the JAXBContext.
package forum10174513;
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum10174513/bindings.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Root.class, BaseBeanEx.class}, properties);
File xml = new File("src/forum10174513/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(xml);
}
}
BaseBean
package forum10174513;
public class BaseBean {
}
BaseBeanEx
package forum10174513;
import javax.xml.bind.Unmarshaller;
public class BaseBeanEx extends BaseBean {
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
System.out.println("AFTER UNMARSHAL WAS CALLED");
}
}
Output
Below is the output that was generated by running the demo code.
AFTER UNMARSHAL WAS CALLED
AFTER UNMARSHAL WAS CALLED
For More Information
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
did you spell it right? the method name is "afterUnmarshal" (one 'L'). Specs
UPDATE:
thinking about it some more, jaxb probably never finds out about the callback because it doesn't know about your custom subclass. i would assume that JAXB examines all the classes during JAXBContext setup. at that point, JAXB only knows about the base bean class, not your custom subclass, and therefore never finds the callback methods.
2 thoughts. you could use the "external callback" mechanism (use a separate event handler which does what you need for your custom classes). or, you could try to generate (or add later) the base bean classes with the callback methods. then JAXB will probably recognize and call the methods, which you can then override in your custom subclass.

Categories

Resources