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.
Related
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.
I am trying to use JAXB with fields of the LocalDateTime type. I wrote an adapter to handle conversion:
public class LocalDateTimeXmlAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public String marshal(LocalDateTime arg0) throws Exception {
return arg0.toString();
}
#Override
public LocalDateTime unmarshal(String arg) throws Exception {
return LocalDateTime.parse(arg);
}
}
I registered the adapter in package-info.java like so:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=LocalDateTime.class, value=LocalDateTimeXmlAdapter.class)
})
package xml;
import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
This seems to be sufficient according to this page.
However, I keep getting the following error:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.time.LocalDateTime does not have a no-arg default constructor.
I understand the reason for the exception being thrown, but I can hardly add a default constructor to java.time.LocalDateTime. This seems to be a shortcoming of the class / a strange design decision. Are there any workarounds?
What you have should work. One of the following may be wrong:
Since you have specified the #XmlJavaTypeAdapter at the package level it will only apply to properties on classes in your package called xml. Is there a class in your model from a different package that has a mapped property of type LocalDateTime?
It is also possible that your package-info.java file is not being compiled.
Had same behaviour: IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions.
My pbm was: I have several packages (three) where the package-info.java file is needed, like shown in the following picture.
I "solved" this pbm by adding a package-info.java in each of the three directories. Example for package fr.gouv.agriculture.dal.ct.planCharge.metier.dao.charge.xml:
#XmlJavaTypeAdapter(type = LocalDate.class, value = LocalDateXmlAdapter.class)
package fr.gouv.agriculture.dal.ct.planCharge.metier.dao.charge.xml;
If someone has a better idea than copy/paste into several package-info.java files, thanks in advance.
I have an annotation that can be added on METHOD and TYPE and is used in thousands of places in our project.
#Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
#Target({METHOD, TYPE})
#Inherited
public #interface RequiredStore{
Store value();
}
Is it possible to make the annotation deprecated only on methods while keeping it non-deprecated on types? I want other developers to be notified by IDE that it should not be used on methods any more, until we'll refactor all existing usages and finally remove the METHOD part.
If it's not possible, is there any Way to handle such case beside creating new annotation only for types and deprecating the old one?
You could use an annotation Processor.
For example, the annotation and its processor would be placed in its own .jar file and added as a dependency of the sources that use the annotation.
The custom .jar would have the following structure:
src/main/
java/com/company/annotations/
RequiredStore.java
RequiredStoreProcessor.java
resources/META-INF/services
javax.annotation.processing.Processor
RequiredStore.java stays as you have it above.
RequiredStoreProcessor.java could look something like this:
package com.company.annotations;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes("com.company.annotations.RequiredStore")
public class RequiredStoreProcessor extends AbstractProcessor {
#Override
public boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element element
: roundEnv.getElementsAnnotatedWith(RequiredStore.class)) {
if (element.getKind().equals(ElementKind.METHOD)) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Using #RequiredStore on methods has been deprecated\n"
+ "Class: " + element.getEnclosingElement() + "\n"
+ "Method: " + element.getSimpleName() + "\n");
}
}
// Other processing...
return false;
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
The javax.annotation.processing.Processor file allows javac to pickup the Processor via SPI and simply contains
com.company.annotations.RequiredStoreProcessor
Finally, compile this into a .jar and add it to the classpath where the annotations are being used. Any methods that have the #RequiredStore will produce a compiler warning. For example, for this class,
package com.company.business;
import com.company.annotations.RequiredStore;
#RequiredStore
public interface Business {
#RequiredStore
public void someMethod();
}
The compiler warning would be this:
warning: Using #RequiredStore on methods has been deprecated
Class: com.company.business.Business
Method: someMethod
As for an indication in the IDE, you might have to write a custom inspection and unfortunately this depends on the IDE used.
Notes:
Decent custom annotations reference: Code Generation using Annotation Processors in the Java language
If you are okay about using native aspectj, another option is to use AspectJ's code enforcement policy this way:
public aspect RequiredStoreAnnotationCheck {
declare warning: execution(#RequiredStore * *.*(..)) : "Required store annotation not appropriate for methods..";
}
If the IDE is integrated with AspectJ, this would be flagged as a compile time check.
AspectJ in action book has a good amount of detail on this too.
Here is one of my blog articles for more context: http://www.java-allandsundry.com/2012/03/code-policy-enforcement-using-aspectj.html
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.
I'm using CXF 2.4 with JAXB.
Could I have a global XmlAdapter for all instances of my owm class (e.g. LWDate)?
I wrote a class:
public class LWDateAdapter extends XmlAdapter<Date, LWDate>
Right now I have to add #XmlJavaTypeAdapter on each param, method or package that I plan to use with CXF. E.g.
#WebMethod void test (#WebParam(name="Birthdate") #XmlJavaTypeAdapter(LWDateAdapter.class) LWDate pBirthdate){}
I wish to ask CXF/JAXB always bind my class LWDate to java.util.Date is it possible?
UPDATE: #XmlJavaTypeAdapter works on a package level staring from version 2.4.4 according to that issue.
For your use case using the #XmlJavaTypeAdapter annotation at the package level is your best option. Below is a post where I use this strategy for the Joda-Time classes:
http://blog.bdoughan.com/2011/05/jaxb-and-joda-time-dates-and-times.html
If you have a domain class that you always want to be handled with an XmlAdapter you can use the #XmlJavaTypeAdapter annotation at the type level:
http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html