I'm having trouble where the afterUnmarshal() methods on my classes are not called if the class is a member of a collection.
Beyond declaring the method on a class which is created via unmarshalling, is there any other steps I'm required to perform? (I can't see anything else in the docs)
Here's a test which shows the problem I'm having:
Given these two domain classes:
#XmlRootElement(name="Parent")
public class Parent {
public boolean unmarshalCalled = false;
#XmlPath("Children/Child")
List<Child> children;
void afterUnmarshal(Unmarshaller u, Object parent)
{
unmarshalCalled = true;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public class Child {
public boolean unmarshalCalled = false;
#Getter #Setter
#XmlPath("#name")
private String name;
void afterUnmarshal(Unmarshaller u, Object parent)
{
unmarshalCalled = true;
}
}
This test fails:
public class UnmarshalTest {
#Test
#SneakyThrows
public void testUnmarshal()
{
String xml = "<Parent><Children><Child name='Jack' /><Child name='Jill' /></Children></Parent>";
JAXBContext context = getContext();
Parent parent = (Parent) context.createUnmarshaller().unmarshal(new StringReader(xml));
assertTrue(parent.unmarshalCalled);
for (Child child : parent.children)
{
assertThat(child.getName(),notNullValue());
assertTrue(child.unmarshalCalled); // This assertion fails
}
}
#SneakyThrows
public static JAXBContext getContext()
{
JAXBContext context;
context = org.eclipse.persistence.jaxb.JAXBContext.newInstance(Parent.class);
return context;
}
}
Is this a bug, or have I missed some steps to get this to work correctly?
The issue you are seeing is due to the following EclipseLink MOXy bug:
https://bugs.eclipse.org/364410
This bug has already been fixed in the EclipseLink 2.3.3 stream, a nightly download can be obtained from:
http://www.eclipse.org/eclipselink/downloads/nightly.php
Workaround
You can workaround the issue that you are seeing by ensuring that all of the classes with event methods are included in the array of classes passed in to create the JAXBContext. I have modified you code below to do this:
#SneakyThrows
public static JAXBContext getContext()
{
JAXBContext context;
context = org.eclipse.persistence.jaxb.JAXBContext.newInstance(Parent.class, Child.class);
return context;
}
Related
I am using Lombok framework for boilerplate code generation, for example:
import lombok.*;
#Builder
#Value
public final class SocketConfig {
#Builder.Default
private int soTimeoutMilliseconds = 0;
#Builder.Default
private boolean soReuseAddress = false;
#Builder.Default
private int soLingerSeconds = -1;
private boolean soKeepAlive;
#Builder.Default
private boolean tcpNoDelay = false;
}
In order to create builder instances I used to invoke SocketConfig.builder(). But for better integration with spring beans creation I tried to create a FactoryBean. But got a compilation error due to lack of default constructor on the builder class, didn't find any documentation about it. Is it possible with Lombok? I mean to create a default constructor on the builder not on the original class. In other words, I want 2 options to create the builder instance: SocketConfig.builder() or through new SocketConfig.SocketConfigBuilder().
import org.springframework.beans.factory.FactoryBean;
public class SocketConfigFactoryBean extends SocketConfig.SocketConfigBuilder implements FactoryBean<SocketConfig> {
#Override
public SocketConfig getObject() throws Exception {
return build();
}
#Override
public Class<?> getObjectType() {
return SocketConfig.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
Use the annotation NoArgsConstructor:
Generates a no-args constructor. Will generate an error message if
such a constructor cannot be written due to the existence of final
fields.
Read also this.
My module:
#Module
public class TcpManagerModule {
private ITcpConnection eventsTcpConnection;
private ITcpConnection commandsTcpConnection;
public TcpManagerModule(Context context) {
eventsTcpConnection = new EventsTcpConnection(context);
commandsTcpConnection = new CommandsTcpConnection(context);
}
#Provides
#Named("events")
public ITcpConnection provideEventsTcpConnection() {
return eventsTcpConnection;
}
#Provides
#Named("commands")
public ITcpConnection provideCommandsTcpConnection() {
return commandsTcpConnection;
}
}
Component:
#Component(modules = TcpManagerModule.class)
public interface TcpManagerComponent {
void inject(ITcpManager tcpManager);
}
class where injection happens:
public class DefaultTcpManager implements ITcpManager {
private TcpManagerComponent tcpComponent;
#Inject #Named("events") ITcpConnection eventsTcpConnection;
#Inject #Named("commands") ITcpConnection commandsTcpConnection;
public DefaultTcpManager(Context context){
tcpComponent = DaggerTcpManagerComponent.builder().tcpManagerModule(new TcpManagerModule(context)).build();
tcpComponent.inject(this);
}
#Override
public void startEventsConnection() {
eventsTcpConnection.startListener();
eventsTcpConnection.connect();
}
}
When I call startEventsConnection, I get NullPointerException - meaning the injection didn't populate the fields.
I followed the example exactly the way it is on the Docs, what is the issue?
Note: on the builder line
tcpComponent = DaggerTcpManagerComponent.builder().tcpManagerModule(new TcpManagerModule(context)).build();
I have a warning saying "tcpManagerModule is deprecated". I read the answer here about this issue, and its saying
It is safe to say that you can just ignore the deprecation. It is intended to notify you of unused methods and modules. As soon as you actually require / use Application somewhere in your subgraph the module is going to be needed, and the deprecation warning will go away.
So, am I not requiring/using the instances? What is the issue here?
You could try changing your Component defining the specific class for injection:
#Component(modules = TcpManagerModule.class)
public interface TcpManagerComponent {
void inject(DefaultTcpManager tcpManager);
}
So that Dagger knows exactly about DefaultTcpManager.class.
I find using JAXB together with Guice possible, but challenging: Both libraries "fight" for control over object creation, you have to be careful to avoid cyclic dependencies, and it can get messy with all the JAXB Adapters and Guice Providers and stuff. My questions are:
How do you deal with this configuration? What general strategies / rules of thumb can be applied?
Can you point me to a good tutorial or well written sample code?
How to visualize the dependencies (including the Adapters and Providers)?
For some sample code, some example work was done here: http://jersey.576304.n2.nabble.com/Injecting-JAXBContextProvider-Contextprovider-lt-JAXBContext-gt-with-Guice-td5183058.html
At the line that says "Wrong?", put in the recommended line.
I looks like this:
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = { UserBasic.class, UserBasicInformation.class };
public JAXBContextResolver() throws Exception {
this.context =
new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
/*
for (Class type : types) {
if (type == objectType) {
return context;
}
} // There should be some kind of exception for the wrong type.
*/
return context;
}
}
//My resource method:
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public JAXBElement<UserBasic> get(#QueryParam("userName") String userName) {
ObjectFactory ob = new ObjectFactory();
UserDTO dto = getUserService().getByUsername(userName);
if(dto==null) throw new NotFoundException();
UserBasic ub = new UserBasic();
ub.setId(dto.getId());
ub.setEmailAddress(dto.getEmailAddress());
ub.setName(dto.getName());
ub.setPhoneNumber(dto.getPhoneNumber());
return ob.createUserBasic(ub);
}
//My Guice configuration module:
public class MyServletModule extends ServletModule {
public static Module[] getRequiredModules() {
return new Module[] {
new MyServletModule(),
new ServiceModule(),
new CaptchaModule()
};
}
#Override
protected void configureServlets() {
bind(UserHttpResource.class);
bind(JAXBContextResolver.class);
serve("/*").with(GuiceContainer.class);
}
}
I have to use an interface in my REST web service. Here is the Interface Specs.java :
#XmlJavaTypeAdapter(MyAdapter.class)
public interface Specs {
public BaseProperties getBaseProps();
public void setBaseProps(BaseProperties baseProps);
}
MyAdapter.java :
public class MyAdapter extends XmlAdapter<Object,Object>
{
public Object unmarshal(Object v)
{
return v;
}
public Object marshal(Object v)
{
return v;
}
}
RegSpecs.java
#XmlType
public class RegSpecs implements Specs{
private BaseProperties baseProps;
public BaseProperties getBaseProps()
{
return baseProps;
}
public void setBaseProps(BaseProperties baseProps)
{
this.baseProps = baseProps;
}
}
MapSpecs.java
#XmlType
public class MagSpecs implements Specs {
private BaseProperties baseProps;
private Features features;
public BaseProperties getBaseProps()
{
return baseProps;
}
public void setBaseProps(BaseProperties baseProps)
{
this.baseProps = baseProps;
}
public Features getFeatures() {
return features;
}
public void setFeatures(Features features) {
this.features = features;
}
}
Accessing this service throws the following error :
javax.xml.bind.MarshalException
- with linked exception:
[javax.xml.bind.JAXBException: class entities.MagSpecs nor any of its super class is known to this context.]
How to modify my context ? I am using JAXB bundled with Jersey 1.5
Thanks !
EDIT : In an attempt to update my context, I added this code to my client (resource) class :
public class BookService implements ContextResolver<JAXBContext>
{
private JAXBContext jaxbContext;
public BookService() {
try {
// Bootstrap your JAXBContext will all necessary classes
jaxbContext = JAXBContext.newInstance(Specs.class,MagSpecs.class, RegSpecs.class);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public JAXBContext getContext(Class<?> clazz) {
if(BookService.class == clazz) {
return jaxbContext;
}
return null;
}
In this case I get error :
entities.Specs is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at entities.Specs
entities.Specs does not have a no-arg default constructor.
this problem is related to the following location:
at entities.Specs
The client of the Specs interface needs to know that MagSpecs can be an instance of it so that it knows to look at it for tooling purposes. The easiest way of doing this is to put an #XmlSeeAlso annotation on the Specs interface:
#XmlSeeAlso({ MagSpecs.class, RegSpecs.class })
#XmlJavaTypeAdapter(MyAdapter.class) // Never needed this annotation myself...
public interface Specs {
public BaseProperties getBaseProps();
public void setBaseProps(BaseProperties baseProps);
}
In general, whenever I'm working with JAXB annotations I make sure I write plenty of tests to check that an XML schema can be generated from the classes in question, checking that from each (sane) entry point into the web of classes and interfaces I can generate a sensible schema without exceptions. For example (and I apologize for this being a bit long):
private SchemaOutputResolver sink;
StringWriter schema;
#Before
public void init() {
schema = new StringWriter();
sink = new SchemaOutputResolver() {
#Override
public Result createOutput(String namespaceUri,
String suggestedFileName) throws IOException {
StreamResult sr = new StreamResult(schema);
sr.setSystemId("/dev/null");
return sr;
}
};
Assert.assertTrue(schema.toString().isEmpty());
}
private void testJAXB(Class<?>... classes) throws Exception {
JAXBContext.newInstance(classes).generateSchema(sink);
Assert.assertTrue(schema.toString().length() > 0);
}
#Test
public void testJAXBForSpecs() throws Exception {
testJAXB(Specs.class);
}
[EDIT]: You also need to change the Specs interface into a class and have the current implementations inherit from it. It can be a fully abstract class if you want. As long as you're not putting serious functionality in the classes, it should work.
EclipseLink JAXB (MOXy) can map interfaces to XML (Note I'm the tech lead). You need to be sure to have a create method on the corresponding object factory to return a concrete impl:
http://bdoughan.blogspot.com/2010/07/moxy-jaxb-map-interfaces-to-xml.html
MOXy integrates cleaning into REST environments:
http://bdoughan.blogspot.com/2010/08/creating-restful-web-service-part-35.html
Please consider the following example:
There is a ClassA and a ClassB which extends it. My problem is now that I have to unmarshall a ClassB from an xml file. Please note that ClassA can not be changed as it is not under my control.
Several problems are noted in this example:
The main problem is that ClassA does not have a default no-arg constructor which is required by JAXB without Adapter. Therefore I implemented MyAdapter which maps ClassB to the simple class ValB which can be processed by JAXB without any problems.
The main problem is how to make JAXB use this adapter? Neither defining the #XmlJavaTypeAdapter on class level nor registering the Adapter to the unmarshaller does it.
Does anybody know how to make JAXB use MyAdapter so that the unmarshaller returns an object that is an instance of ClassA?
public class JaxbTest {
public static abstract class ClassA {
public ClassA(String id) {
}
}
#XmlRootElement
#XmlJavaTypeAdapter(MyAdapter.class) // does not have an effect
public static class ClassB extends ClassA {
public String text;
public ClassB() {
super("");
}
}
public static class ValB {
public String text;
}
public static class MyAdapter extends XmlAdapter<ValB, ClassB> {
#Override
public ClassB unmarshal(ValB v) throws Exception {
ClassB b = new ClassB();
b.text = v.text;
return b;
}
#Override
public ValB marshal(ClassB v) throws Exception {
ValB b = new ValB();
b.text = v.text;
return b;
}
}
public static void main(String[] args) {
try {
JAXBContext context = JAXBContext.newInstance(ClassB.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setAdapter(new MyAdapter()); // does not have an effect
ClassA a = (ClassA) unmarshaller.unmarshal(new File("test.xml"));
// do somthing with a
} catch (Exception e) {
e.printStackTrace();
}
}
}
BTW: Don't take the code too serious - it is just an example demonstrating the problem. I know that the definition of ClassA and ClassB are not really useful.
UPDATE
We have addressed this issue in the upcoming EclipseLink JAXB (MOXy) 2.2.0 release (see bug #332742). In this release abstract classes will not be checked for a no-arg constructor.
Pre-release versions with this fix can be obtained here starting December 18th:
http://www.eclipse.org/eclipselink/downloads/nightly.php
Workaround
This is what the #XmlTransient annotation is for. If possible do the following:
#XmlTransient
public static abstract class ClassA {
public ClassA(String id) {
}
}
If it is not possible to annotate ClassA directly, you could leverage an EclipseLink JAXB (MOXy) extension to do this. MOXy allows you to specify JAXB metadata as an XML file. This is useful when you can't modify a model class:
http://bdoughan.blogspot.com/2010/12/extending-jaxb-representing-annotations.html
Below are some articles explaining #XmlAdapter:
http://bdoughan.blogspot.com/2010/12/jaxb-and-immutable-objects.html
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html