JAXB #XmlSeeAlso causing tight coupling to domain objects - java

I'm using JAXB bindings to unmarshal directly to my domain layer objects, which are subclasses of the generated webservice types. This is a nice solution as I can override methods and provide write custom logic, etc. However, the XJC compiler is insisting on putting the #XmlSeeAlso({MySubclass.class}) annotations on all the generated classes, which is causing them to be tightly coupled to my domain objects. This is obviously undesirable and causing all kinds of reference issues between my projects that I won't get into here.
Is it possible to generate classes that don't have the #XmlSeeAlso annotation? The actual work of unmarshaling to the subclass seems to happen in the ObjectFactory class. Is it possible to omit the jaxb binding, and substitute a custom ObjectFactory for each application? This would allow me to have autogenerated webservice types in a shared util while each web project could unmarshal to different subclasses of these types.
<jaxb:bindings node="//xs:complexType[#name='AutogeneratedWebserviceType']">
<jaxb:class implClass="my.project.CustomSubclass" />
</jaxb:bindings>
This binding will create a method in the ObjectFactory that seems to do the actual work of unmarshaling to my subclass:
public AutogeneratedWebserviceType createAutogeneratedWebserviceType() {
return new CustomSubclass();
}
I want this behavior without the #XmlSeeAlso annotation by providing a customer ObjectFactory, if possible.

Did you try running XJC with the argument -target 2.0? I believe this will disable the generation of the #XmlSeeAlso annotation.

Related

How to handle various concrete implementations of an Interface with JAX-B

I have a class that any I need to marshal to XML.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ClassToBeMarshalled {
public Interface object;
}
The Interface is implemented by a lot of concrete classes and most of them are vendor specific whose source code I don't have access to.
So my problem is:
If I try to marshal that class, JAX-B will complain that the current concrete implementation of Interface is not known in the context - In another words, the concrete class was not loaded into the context by calling JAXBContext.newInstance providing the current implementation.
The most common ways to sort out that problem are:
1) Use XMLSeeAlso - not a viable option as there are a lot of concrete classes
2) Annotate each class with #XmlRootElement - not a viable option as I don't have access to all the classes
Does anyone know a way to make JAX-B load the concrete class into its context as the need arises?
Thanks.
P.S.: I'm using JAX-B RI
You could mark your object as #XmlAnyElement(InterfaceHandler.class) where InterfaceHandler is a DomHandler capable of translating between a DOM representation and the actual implementing classes. That handler should probably store the class name when marshalling, and use that class name to create the instance when unmarshalling. It might either configure the instance manually, perhaps using some helper classes designed to work with beans, or it might use another jaxb context which includes that specifically named class and will handle that object with all its nested children.
Also have a look at the #XmlElementRef annotation. I fear that in order to make this work properly, you'd have to at least know all the implementing classes at compile time, but perhaps there is a way you can make this work for you as well, with less trouble than the generic solution outlined in the previous paragraph.

JAXB own object factory not used when unmarshalling

Image you've got auto generated beans and you want to extend them with some extra functionality. So you extend the auto generated beans and in the same file you also extend the ObjectFactory and annotate it with #XmlRegistry.
When unmarshalling some object the extended ObjectFactory ins't used and therefore the auto generated beans are created.
What step am I missing to get my custom beans created? Can I tell JAXB which Factory to use? Or does JAXB not use the factory at all?
Yes, you need to specify the ObjectFactory when configuring your Unmarshaller. I think you can use something like this to provide your own ObjectFactory implemenation:
unmarshaller.setProperty("com.sun.xml.bind.ObjectFactory",new MyObjectFactory());

Possible to use the #XmlSeeAlso dyncamiclly?

Instead of this
#XmlSeeAlso({User.class,Role.class,Function.class})
I would like something like this:
#XmlSeeAlso(Access.getWebServiceClasses())
Is it possible?
I want this since my webservice just contains interfaces and if I change the implementation I would just like to change in my factory so it returns the right classes instead of having to change in the webservice itself.
This isn't possible, since annotation elements must be simple types (strings, primitives or classes (see annotations).
It is possible however (in CXF) to override org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean.getExtraClass() method which, by default checks #XmlSeeAlso of the interface. My implementation returns additional ObjectFactory classes.
Chances are that you're using <jaxws:endpoint /> in CXF's Spring XML configuration. To be able to override this method, you have create few classes:
org.apache.cxf.jaxws.spring.EndpointDefinitionParser must use class derived from org.apache.cxf.jaxws.spring.EndpointDefinitionParser.SpringEndpointImpl (when using JAXWS 2.1) or from org.apache.cxf.jaxws22.spring.JAXWS22SpringEndpointImpl (JAXWS 2.2)
This class must call super.setServiceFactory() passing org.apache.cxf.jaxws.support.JaxWsServerFactoryBean with overriden getExtraClass()
You have to provide your own org.apache.cxf.jaxws.spring.NamespaceHandler (you can create derived class) for your own namespace (e.g. http://cxf.apache.org/jaxws/dynamic) which will register your own parser for jaxws:endpoint element:
registerBeanDefinitionParser("endpoint", new EndpointDefinitionParser());
(sorry, I can't provide full example - I'm writing from memory and CXF's source code)

Best way to add annotations to inherited methods

I have a number of abstract superclasses from which my concrete class inherit various methods. Some of these methods need to have JPA or JAXB annotations placed on them in the concrete class. Currently I do this via the following:
#MyLocalAnnotations
#Override
public method inheritedMethodHere (yadda yadda)
{
super.inheritedMethodHere(yadda yadda);
}
Is there a way to do this without overriding the method? It seems like such a waste to override a method only to supply local annotations.
Unfortunately, there isn't a better way than what you are doing now. To do what you are describing you will have to override the method, considering that your JPA annotation will need information specific to the concrete class.
With JPA annotations, you actually have two options -- you can annotate the methods, or you can annotate the properties. On our project we've standardized on annotating properties rather than methods, but this won't help you either, as the properties are presumably part of the concrete class. (If they are somehow shared in the super-class, then you REALLY will need to annotate the methods, and override them).
Its hard to make a recommendation without seeing your schema, but if your entity classes have so much in common that they share many properties in the super-class -- might they simply be stored in the same table, perhaps with a differentiating type column?
Or alternatively if they are not nearly identical, might you just reproduce the common properties in each concrete class? You might be causing yourself more work rather than saving yourself by trying to capture the common properties in the super class. Since you will have to annotate them individually in the concrete classes, just declare them in the concrete classes. If you need common methods that interact with these properties, a separate Utility class to capture those functions could be the answer.
On our project we DO sometimes use a common super class for entities, but it captures some meta-data about the entity -- nothing that would impact persistence logic. Therefore, no JPA annotations are needed (nor would they make sense) in the abstract class.

problem subclassing ObjectFactory in jax-ws web service

In a jax-ws web service I cannot directly access the JaxbContext object. JaxbContext uses the class ObjectFactory. I tried extending it (like in Jaxb Adding Behaviors). I put generated code in ObjectFactoryBase, then added
public class ObjectFactory extends ObjectFactoryBase {//.. overriden methods}
However IllegalAnnotationsException came up when publishing to weblogic, because it cannot find a certain #XmlElementDecl present in ObjectFactoryBase. When I move the method with this #XmlElementDecl to ObjectFactory it works.
No luck with adding #XmlSeeAlso({ ObjectFactoryBase.class }) either.
Edit: I now discovered that the generated ObjectFactory is not even used by the jaxws web service. So the above error message are not so relevant any more. Any idea why it is generated but not used?
Any ideas?
JAXB ObjectFactories are strange beasts. Your question has many facets, so I'll just answer with a bullet list:
JAXB1 relied on ObjectFactory to create instances of the bound classes, but with JAXB2 everything is a POJO, and the ObjectFactory becomes mostly unnecessary. It's still generated by XJC, partly for reasons of backwards compatibility.
The annotations on an ObjectFactory are complex and non-obvious, but since it's a generated class, this usually doesn't matter, and most people don't look at it anyway.
ObjectFactory is still useful on occasion because it provides factory methods for bound classes that need a JAXBElement wrapper, and it's much easier to use the provided factory methods than to do this by hand.
The JAXWS web service may choose not to use the ObjectFactory, because it's not strictly speaking necessary. However the JAXBContext may still load and parse it, depending on how the context was initialized.
I've never tried creating an ObjectFactory myself; if the model was XJC-generated then the generated ObjectFactory is usually enough, and if you have a hand-written JAXB model, the ObjectFactory is usually completely unnecessary anyway.

Categories