JAXB: Problem deserializing a class B that extends a class A - java

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

Related

#XmlJavaTypeAdapter not being called when used with #XmlSeeAlso

I can't get the XmlJavaTypeAdapter working when it is set at class-level on a subclass of an abstract class annotated with XmlSeeAlso. The serialization is done but the adapter is never being called.
#XmlRootElement(name = "root")
public class TopLevelClassBeingSerialized {
private Set<MyAbstractClass> set = new HashSet();
}
#XmlSeeAlso({MyImplClass.class})
//#XmlJavaTypeAdapter(MyAbstractClassAdapter.class)
public class MyAbstractClass {
}
#XmlJavaTypeAdapter(MyImplClassAdapter.class)
public class MyImplClass {
private int test = 2;
}
When the comment is uncommented, the adapter is called. I have also tried the setAdapter() method and it doesn't work. Is this a limitation of JAXB? If yes what's the best and cleanest workaround?

injecting generics with roboguice

I'm trying to inject instances with generics and i'm getting the following error:
HasOne<ModelClass> cannot be used as a key; It is not fully specified.
I've read elsewhere that safest way to do this is to explicitly name the class to be used in the generic when using the injector to get an instance but i'd like to be a little cleaner. I'm trying to create Relationship objects between Models.
Here is my simplified Model class
public class Model {
#Inject
Injector injector;
public <ModelClass extends Model> HasOne<ModelClass> hasOne(Class<ModelClass> clazz) {
HasOne<ModelClass> hasOne = injector.getInstance(Key.get(new TypeLiteral<HasOne<ModelClass>>() {
}));
hasOne.init(clazz);
return hasOne;
}
}
My HasOne relationship
public class HasOne<T extends Model> {
Class clazz;
public void init(Class<T> clazz){
this.clazz = clazz;
}
#Inject
Injector injector;
public T get(){
return (T) injector.getInstance(clazz);
}
}
Test Model #1
public class TestModel extends Model {
public HasOne<ExampleModel> exampleModel(){
return hasOne(ExampleModel.class);
}
}
Test Model #2
public class ExampleModel extends Model {
}
I get the error when doing this
TestModel testModel = RoboGuice.getInjector(context).getInstance(TestModel.class);
HasOne<ExampleModel> relationship = testModel.exampleModel();
I'm trying to hide away the ugly relationship creation and keep it in the Model class
You cannot use new TypeLiteral<T>() { } if T is a type parameter, it has to be a fully-specified type. Luckily, since you have an instance of Class<ModelClass>, you can do this:
(Key<HasOne<ModelClass>>) Key.get(TypeLiteral.get(Types.newParameterizedType(HasOne.class, clazz)))
You'll get a warning on the cast but it is safe to suppress it.

GWT request factory - how to get locator working

I have a working "request factory" example and i want to refactor it, so that i can move the generic methods like "persist()" and "remove()" out of the domain object into a generic locator. Currently i have the following (working) code:
A generic super class that holds the id and the version for all domain objects:
#MappedSuperclass
public class EntityBase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
#Column(name = "version")
private Integer version;
// setter & getter
}
A domain object. It has the persist() and remove()-methods, which i want to refactore out of the class:
#Entity
#Table(name = "article")
public class Article extends EntityBase{
public static Article findArticle(Long id) {
//find article
}
public void persist() {
// persist
}
public void remove() {
// remove
}
}
A proxy object for the domain object:
#ProxyFor(value = Article.class)
public interface ArticleProxy extends EntityProxy {
// some getter and setters
}
The request object for my domain object:
#Service(value = Article.class)
public interface ArticleRequest extends RequestContext {
Request<ArticleProxy> findArticle(Long id);
InstanceRequest<ArticleProxy, Void> persist();
InstanceRequest<ArticleProxy, Void> remove();
}
My request factory:
public interface MyRequestFactory extends RequestFactory {
ArticleRequest articleRequest();
}
---------------------------------------
Now my refactored code that is not working anymore:
I removed the persist() and remove()-method out of my domain object:
#Entity
#Table(name = "article")
public class Article extends EntityBase{
public static Article findArticle(Long id) {
//find article
}
}
I created my locator like this and added the methods "remove()" and "persist()" here (alongside the other default methods):
public class EntityLocator extends Locator<EntityBase, Long> {
#Override
public EntityBase create(Class<? extends EntityBase> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
#Override
public EntityBase find(Class<? extends EntityBase> clazz, Long id) {
return null;
}
#Override
public Class<EntityBase> getDomainType() {
return null;
}
#Override
public Long getId(EntityBase domainObject) {
return null;
}
#Override
public Class<Long> getIdType() {
return null;
}
#Override
public Object getVersion(EntityBase domainObject) {
return null;
}
public void persist(EntityBase domainObject){
// persist something
}
public void remove(EntityBase domainObject){
// remove
}
}
My proxy object is linked to the locator (locator=EntityLocator.class):
#ProxyFor(value = Article.class, locator=EntityLocator.class)
public interface ArticleProxy extends EntityProxy {
// getter and setters here
}
My new Request object looks like this. I made the "InstanceRequests" to "Requests", changed return types and parameter according to my new methods in the locator:
#Service(value = Article.class)
public interface ArticleRequest extends RequestContext {
Request<ArticleProxy> findArticle(Long id);
Request<Void> persist(ArticleProxy article);
Request<Void> remove(ArticleProxy article);
}
But now i get the error "Could not find domain method similar to java.lang.Void persist()" for the persist() and remove()-method. Why doesn't the lookup in the EntityLocator work? Do i need a ServiceLocator? I did not fully understand the google tutorial and the linked example is not available anymore.
I had the same question as you. The guide on GWTProject.org (http://www.gwtproject.org/doc/latest/DevGuideRequestFactory.html) is not very clear on how to correctly implement this, although it is written between the lines.
The following tutorial made the solution clear to me: http://cleancodematters.com/2011/06/04/tutorial-gwt-request-factory-part-i/
For me the use of the term DAO obfuscated things. I'm not going to use the DAO pattern. That's what my transparent persistence layer is for. However, the use of the Locator requires an extra class to put the persist, remove and findX methods in. They call it a Data Access Object (which it is, actually), I'd rather call it the Manager.
tl;dr
The methods you're trying to put in the Locator don't go there. You need an extra class (call it a DAO or a Manager).
Use the DAO/Manager as service in your RequestContext
I don't think you can place the persist and remove methods in the locator. The documentation doesn't suggest you can add arbitrary methods to the locator interface and reference them from the client. If you just want to avoid duplicating the persist and remove methods in every entity class then you can put them in your EntityBase class. I've done this and it works nicely.
If you also want to avoid repeating the functions in each of your request interfaces, you can make a generic base class Request like so:
#SkipInterfaceValidation
public interface BaseEntityRequest<P extends EntityProxy> extends RequestContext {
InstanceRequest<P, Void> persist();
InstanceRequest<P, Void> remove();
}
and use it like so:
public interface ArticleRequest extends BaseEntityRequest<ArticleRequest> {
...
}
Although it makes sense that persist() and remove() were in the Locator, so as the entity was completely agnostic about the persistence layer, this is not supported by current RF api. As consequence you have to deal with that adding those methods to your BaseEntity and figuring out a way to call the persist method in your locator.
I think you could open a gwt issue requiring this feature though.
Another way to avoid having certain methods in your entities, is to use ValueProxy insteadof EntityProxy, but in this case you have to provide methods to save/delete those objects from the client.
Your interface ArticleRequest isn't configured properly. You need correct it like this.
#Service(value = SentenceServiceImpl.class, locator = SpringServiceLocator.class)
public interface SentenceServiceRequest extends RequestContext {
Request<List<SentenceProxy>> getSentences();
Request<Void> saveOrUpdate(SentenceProxy sentence);
}
Locator:
public class SpringServiceLocator implements ServiceLocator {
public Object getInstance(Class<?> clazz) {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestFactoryServlet.getThreadLocalServletContext());
return context.getBean(clazz);
}
}

JAXB/MOXy - afterUnmarshal() appears to be called inconsistently

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;
}

Jersey REST/ JAXB error , mapping an Interface

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

Categories

Resources