Unmarshalling collections in JaxB - java

suppose I have this class:
public class A {
private HashMap<String, B> map;
#XmlElement
private void setB(ArrayList<B> col) {
...
}
private ArrayList<B> getB() {
...
}
}
When trying to unmarshall an xml document to this class using JaxB I notice that instead of calling the setB() method and sending me the list of B instances JaxB actually calls the getB() and adds the B instances to the returned list. Why?
The reason I want the setter to be called is that the list is actually just a temporary storage from which I want to build the map field, so I thought to do it in the setter.
Thanks.

thats the way jaxb is handling collections. you have to be sure you have a non null collection when jaxb try to unmarshal.
there is a plugin (never used it myself) but can be helpful:
https://jaxb2-commons.dev.java.net/collection-setter-injector/

Hy,
you can use it with jaxb, it's work !!! (with Maven....)
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-Xcollection-setter-injector</arg>
</args>
<plugins>
<plugin>
<groupId>net.java.dev.vcc.thirdparty</groupId>
<artifactId>collection-setter-injector</artifactId>
<version>0.5.0-1</version>
</plugin>
</plugins>
<schemaDirectory>src/schemas</schemaDirectory>
<generateDirectory>src/main/java</generateDirectory>
<extension>true</extension>
</configuration>
</plugin>
and you get your setter for your Collection
Hope it would help people
bye

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The behaviour you are seeing will vary among JAXB implementations. If you do not initialize a value for the List property then EclipseLink JAXB (MOXy) will call the set method as you expect.
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2011/01/jaxb-and-choosing-list-implementation.html
EXAMPLE
A
package forum1032152;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class A {
private ArrayList<B> b;
#XmlElement
public void setB(ArrayList<B> col) {
System.out.println("Called setB");
for(B b : col) {
System.out.println(b);
}
this.b = col;
}
public ArrayList<B> getB() {
return b;
}
}
B
package forum1032152;
public class B {
}
Demo
package forum1032152;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(A.class);
File xml = new File("src/forum1032152/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.unmarshal(xml);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b></b>
<b></b>
</a>
Output
Called setB
forum1032152.B#8bdcd2
forum1032152.B#4e79f1

JAXB has problems supporting interfaces and abstract classes; it usually doesn't know what subclass to instantiate. The problem is, it's a common pattern to have a class along the lines of:
ArrayList list;
#XMLElement
public List getList() {return this.list;}
To get around this, JAXB doesn't even try to instantiate the property class (e.g. List) derived from the getter/setter pair if it's a Collection. It just assumes that it's non-null and modifiable.
Probably the simplest work around is to mark your business interface with #XMLTransient and add a different getter/setter pair with #XMLElement for the view for the data that you want to expose to JAXB. I usually make these protected rather than public, because I don't care to have the somewhat-goofy JAXB behavior as part of my classes' public contract.

Jaxb2 UnMarshaller defines a listener interface which is called any time an object has been un-marshaled. You can define a custom listener to invoke setter methods on all collections (or on sub-objects). This should be pretty easy to do with any one of the bean utils classes. I'm looking for an existing implementation, though I don't see one.
JAXBContext context = JAXBContext.newInstance( classesToBeBound );
m_unmarshaller = context.createUnmarshaller();
m_unmarshaller.setListener(
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
for (Property p : getBeanProperties(target.getClass()))
if (p.isCollectionType() || p.isCompositeType())
p.invokeSetter(p.invokeGetter());
}
});
If you are using the spring framework, its pretty straightforward:
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
BeanWrapper wrapper = new BeanWrapperImpl(target);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
if (pd.getPropertyType() != null) {
if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
try {
Method setter = pd.getWriteMethod();
if (setter != null) {
Method getter = pd.getReadMethod();
if (getter != null)
setter.invoke(target, getter.invoke(target));
}
}
catch (Exception ex) {
s_logger.error("can't invoke setter", ex);
}
}
}
}
}
}

You can just use an array instead of List )

The reason I want the setter to be called is that the list is actually
just a temporary storage from which I want to build the map field,
so I thought to do it in the setter.
JAXB can handle maps directly, hence, this could make the call to setB() a moot point. If that is an acceptable solution for you, see the example I maintain on my blog to create an adaptor for maps in JAXB.

Related

JAXB Serialization Failure with Generic Class

I am trying to write a class that can serailize and deserailize settings to XML using Java. I have this code successfully written in C# and it is very useful so I would like something similar in my java app.
I have the following base class that every class I want t serialize to XML must implement.
package serializers;
import java.lang.reflect.ParameterizedType;
abstract class XmlSerializableObject<T> {
abstract T getDefault();
abstract String getSerializedFilePath();
String getGenericName() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();
}
ClassLoader getClassLoader() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getClassLoader();
}
}
where the getGenericName and getClassLoader are for use with instantiating the JAXBContext. I then have a basic implementation of this as a settings provider
public class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
private Settings settings;
#Override
public SettingsProvider getDefault() {
return null;
}
#Override
public String getSerializedFilePath() {
return "C:\\Data\\__tmp.settings";
}
public Settings getSettings() {
return settings;
};
public void setSettings(Settings settings) {
this.settings = settings;
}
}
class Settings {
private String tmp;
public String getTmp() {
return tmp;
}
public void setTmp(String tmp) {
this.tmp = tmp;
}
}
Now I have the following serializer class
package serializers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class XmlSerializer {
private static final Logger logger = LoggerFactory.getLogger(XmlSerializer.class);
public static <T extends XmlSerializableObject> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
String name = o.getGenericName();
ClassLoader classLoader = o.getClassLoader();
// THE FOLLOWING LINE throws.
JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); // also tried JAXBContext.newInstance(name);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
// Deserialize below.
}
I then have the following test to check the results of serialization
package serializers;
import org.junit.Before;
import org.junit.Test;
public class XmlSerializerTest {
private Settings settings = new Settings();
private SettingsProvider provider;
#Before
public void setUp() throws Exception {
settings.setTmp("testing");
provider = new SettingsProvider();
provider.setSettings(settings);
}
#Test
public void serialize() throws Exception {
XmlSerializer.Serialize(provider);
}
}
The problem is the call to JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); which throws
javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index
- with linked exception:
[javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index]
I have tried with and without the ClassLoader object to no avail. How can I serialize a generic type in this way?
Thanks for your time.
Let us look at the line of code that is throwing the exception:
JAXBContext jaxbContext = JAXBContext.newInstance(name);
In the above line of code, the argument name that you are passing is the name of the class that is to be deserialized and is determined at runtime (viz., serializers.SettingsProvider in the given sample). This may not be sufficient for JAXB to determine all the classes that constitutes the JAXB context. So instead, try passing the name of the package(s) that contain all the classes that this instance of JAXBContext should be deserializing -- all the classes in that package(s) is your JAXB context. This is something that will be known at compile time. So, try the following line of code instead:
JAXBContext jaxbContext = JAXBContext.newInstance("serializers");
Here, "serializers" is the name of the package that contains all the classes that you want to be deserializing, i.e., the JAXB context for the given sample.
You may like to refer the Oracle JAXB tutorial and note the following lines of code:
import primer.po.*;
...
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
Please refer this Javadoc and note that in case the classes to be deserialized are spread over multiple packages, then a list of colon separated package names should be passed, e.g.,--
JAXBContext.newInstance( "com.acme.foo:com.acme.bar" )
In case you must pass class names instead of package names, then first read this Javadoc very carefully. Note that the JAXBContext instance will be initialized only with classes passed as parameter and the classes that are statically reachable from these classes. Prefer to write your program in such a way that class names being passed are known at compile time.
Also, it may be helpful for you to note that generics in Java are different (especially w.r.t type erasure) than those in C# -- please see What is the concept of erasure in generics in Java?.
Also, given the class declaration:
class XmlSerializableObject<T> {
}
which states that the class XmlSerializableObject deals with type T, the following class declaration:
class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
}
which states that the class SettingsProvider deals with its own type sounds convoluted.
Or did you instead mean it to declare like as follows:
class SettingsProvider extends XmlSerializableObject<Settings> {
}
which states that the class SettingsProvider deals with type Settings?
That looks like it should be JAXBContext.newInstance(SettingsProvider.class) .
The JAXBContext.newInstance(String ...) versions of the method are expecting a package name, which as the error message says should then contain an ObjectFactory class, or jaxb.index list to guide it to the classes.
You are using this newInstance method :
Parameters:
contextPath - list of java package names that contain schema derived class and/or java to schema (JAXB-annotated) mapped classes
classLoader - This class loader will be used to locate the implementation classes.
So df778899 is right, you should not use this signature as getGenericName returns a fully qualified class name and not a package. And even if it was a package, you will still miss ObjectFactory.class or jaxb.index
But JAXBContext.newInstance(SettingsProvider.class) won't work either. You will get a MarshalException indicating that #XmlRootElement is missing
Annotate SettingsProvider like this :
#XmlRootElement(name = "root")
static class SettingsProvider extends XmlSerializableObject<SettingsProvider>
{
private Settings settings;
// [...]
And finally you will get :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<settings>
<tmp>testing</tmp>
</settings>
</root>
This was done by using the following interfaces
public interface IXmlSerializableObject {
String getSerializedFilePath();
}
The crucial one being
public interface IPersistanceProvider<T> extends IXmlSerializableObject {
void save();
void restoreDefaults();
Class<T> getTypeParameterClass();
}
The crucial property is Class<T> getTypeParameterClass(). This is then used in
public static <T extends PersistanceProviderBase> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
JAXBContext jaxbContext = JAXBContext.newInstance(o.getTypeParameterClass());
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
where PersistanceProviderBase implements the IPersistanceProvider interface.

Way to write java annotation processor to require method call? [duplicate]

Let's say I define a custom annotation called #Unsafe.
I'd like to provide an annotation processor which will detect references to methods annotated with #Unsafe and print a warning.
For example, given this code ...
public class Foo {
#Unsafe
public void doSomething() { ... }
}
public class Bar {
public static void main(String[] args) {
new Foo().doSomething();
}
}
... I want the compiler to print something like:
WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()
It is very similar in spirit to #Deprecated, but my annotation is communicating something different, so I can't use #Deprecated directly. Is there a way to achieve this with an annotation processor? The annotation processor API seems to be more focused on the entities applying the annotations (Foo.java in my example) than entities which reference annotated members.
This question provides a technique to achieve it as a separate build step using ASM. But I'm wondering if I can do it in a more natural way with javac & annotation processing?
I think I could have technically achieved my goal using the response from #mernst, so I appreciate the suggestion. However, I found another route that worked better for me as I'm working on a commercial product and cannot incoporate the Checker Framework (its GPL license is incompatible with ours).
In my solution, I use my own "standard" java annotation processor to build a listing of all the methods annotated with #Unsafe.
Then, I developed a javac plugin. The Plugin API makes it easy to find every invocation of any method in the AST. By using some tips from this question, I was able to determine the class and method name from the MethodInvocationTree AST node. Then I compare those method invocations with the earlier "listing" I created containing methods annotated with #Unsafe and issue warnings where required.
Here is an abbreviated version of my javac Plugin.
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
public class UnsafePlugin implements Plugin, TaskListener {
#Override
public String getName() {
return "UnsafePlugin";
}
#Override
public void init(JavacTask task, String... args) {
task.addTaskListener(this);
}
#Override
public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
String className = invokedClass.toString();
String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\\.", "");
System.out.println("Method Invocation: " + className + " : " + methodName);
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override
public void started(TaskEvent taskEvt) {
}
}
Note - in order for the javac plugin to be invoked, you must provide arguments on the command line:
javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
Also, you must have a file META-INF/services/com.sun.source.util.Plugin in unsafe-plugin.jar containing the fully qualified name of the plugin:
com.unsafetest.javac.UnsafePlugin
Yes, this is possible using annotation processing.
One complication is that a standard annotation processor does not descend into method bodies (it only examines the method declaration). You want an annotation processor that examines every line of code.
The Checker Framework is designed to build such annotation processors. You just need to define a callback that, given a method call and issues a javac warning if the call is not acceptable. (In your case, it's simply whether the method's declaration has an #Unsafe annotation.) The Checker Framework runs that callback on every method call in the program.
The AbstractProcessor below processes greghmerrill's #Unsafe annotation and emits warnings on method calls to #Unsafe annotated methods.
It is a slight modification of greghmerrills own answer, which was great, but I had some problems getting my IDEs incremental compiler (I am using Netbeans) to detect the warnings/errors etc emitted from the plugin - only those I printed from the processor was shown, though the behaviour was as expected when I ran 'mvn clean compile' ( I am using Maven). Whether this is due to some problem from my hand, or a points to difference between Plugins and AbstractProcessors/the phases of the compilation process, I do not know.
Anyway:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process #Unsafe annotated methods if needed
return true;
}
#Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override public void started(TaskEvent taskEvt) { } }
When using the annotation and making calls to the annotated method it will look like this:
One needs to remember to add the fully qualified class name of the annotation processor to a META-INF/service file named javax.annotation.processing.Processor. This makes it available to the ServiceLoader framework.
Maven users having trouble with the com.sun** imports may find this answer from AnimeshSharma helpful.
I keep my annotation + annotation processor in a separate project. I had to disable annotation processing by adding the following to the pom:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Using the annotation and having the processor do its work was simple: In my other project (the one where the screenshot of method foo() is from) I simply added a dependency to the project containing the annotation and processor.
Lastly it should be mentioned that I am new to AbstractProcessors and TaskListeners. I do, fx, not have an overview of the performance or robustness of the code. The goal was simply to "get it to work" and provide a stub for similar projects.

Mapping an object to an immutable object with builder (using immutables annotation processor) in mapstruct

We are using the immutables framework to generate all DTOs. Now we would like to map these objects one to another with mapstruct. But the generated DTOs are immutable and have no setters and no constructor, corresponding to the builder pattern. They are only filled through the corresponding builder accessed by a static builder()-method.
We instead tried to map DTO1 to DTO2.Builder which would work if mapstruct would recognize the setter in the Builder but these do not have void return type but return the Builder itself for fluent concatenation.
So here is the code of the example.
We have two Interfaces
#Value.Immutable
public interface MammalDto {
public Integer getNumberOfLegs();
public Long getNumberOfStomachs();
}
and
#Value.Immutable
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
Then we have the Mapper interface for mapstruct:
#Mapper(uses = ObjectFactory.class)
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
ImmutableMammalEntity.Builder toTarget(MammalDto source);
}
For mapstruct to find the Builder we need a Factory:
public class ObjectFactory {
public ImmutableMammalDto.Builder createMammalDto() {
return ImmutableMammalDto.builder();
}
public ImmutableMammalEntity.Builder createMammalEntity() {
return ImmutableMammalEntity.builder();
}
}
In order to generate the code the compiler plugin was instructed to use both annotation processors:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>2.2.8</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Beta3</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Note: This will work only with mapstruct version > 1.2.x. Older versions have the problem in a clean build (mvn clean compile) that they do not find the sources that immutables just built. In a second build (without clean) they would find the immutables implementations because they were on the classpath before annotation processors were run. This bug is fixed now.
This works like a charm. First the Immutable implementations of the interfactes are generated and mapstruct uses them to generate the builder.
But the Test shows that no properties are set:
#Test
public void test() {
MammalDto s = ImmutableMammalDto.builder().numberOfLegs(4).numberOfStomachs(3l).build();
MammalEntity t = SourceTargetMapper.MAPPER.toTarget(s).build();
assertThat(t.getNumberOfLegs()).isEqualTo(4);
assertThat(t.getNumberOfStomachs()).isEqualTo(3);
}
The asserts fail. One look at the mapper generated by mapstruct shows that it has obviously not found any setters:
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
//...
)
public class SourceTargetMapperImpl implements SourceTargetMapper {
private final ObjectFactory objectFactory = new ObjectFactory();
#Override
public Builder toTarget(MammalDto source) {
if ( source == null ) {
return null;
}
Builder builder = objectFactory.createMammalEntity();
return builder;
}
}
The empty builder is returned. I think the reason is the setter implementation of the generated builder because it returns itself to create a fluent API:
public final Builder numberOfLegs(Long numberOfLegs) {
this.numberOfLegs = Objects.requireNonNull(numberOfLegs, "numberOfLegs");
return this;
}
Is there a way to let mapstruct find these setters? Or even a better way to deal with such immutable objects with builders?
EDIT: As I stated in the comment I ran into Issue #782. In version 1.2.0.Beta3 builders are still not supported. But there are several discussions on this topic so it might be interesting to follow the issue if one has the same problem.
You can configure Immutables to generate setters in the builder:
#Value.Immutable
#Value.Style(init = "set*")
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
And you don't need the ObjectBuilder, you can directly use the generated Immutable class
#Mapper(uses = ImmutableMammalEntity.class)
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
ImmutableMammalEntity.Builder toTarget(MammalDto source);
}
You can even define these settings in your own annotation
#Value.Style(init = "set*")
public #interface SharedData {}
and use that instead
#SharedData
#Value.Immutable
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
Since 1.3 MapStruct supports Immutables. Look here for more details.
We had same issue on our project.
As workaround we've been using Modifiable implementation of our immutable dto.
You can also try it. It's better that direct usage of builders and object factories.
#Value.Modifiable generates implementation with setters.
#Value.Style(create = "new") generates public no args constructor.
#Value.Immutable
#Value.Modifiable
#Value.Style(create = "new")
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
Then your mapper will be simpler, no need in object factory.
#Mapper
public interface SourceTargetMapper {
ModifiableMammalEntity toTarget(MammalDto source);
}
In this case MapStruct can see setters in ModifiableMammalEntity
Usage of such mapper will looks like
// Here you don't need to worry about implementation of MammalEntity is. The interface `MammalEntity` is immutable.
MammalEntity mammalEntity = sourceTargetMapper.toTarget(source);

How to configure multiple bindings for the same child type

I'm new to Java and I'm jumping right in the deep end and using Guice in a project but I can't work out how to bind different values to a constructor based on what parent object the current object will be injected into.
In my code I want to be able to pass in the path to some XSLT files in the constructor of the MessageTransformer but these should be different depending on what it's being injected into.
public class MessageTransformer {
public MessageTransformer(String XsltRequestPath, String XsltReplyPath {
....
}
}
public class SomeClassThatTransformsMessages {
public SomeClassThatTransformsMessages(Transformer transformer) {
....
}
public class SomeOtherClassThatTransformsMessages {
public SomeOtherClassThatTransformsMessages(Transforms transformer) {
....
}
}
So if I was doing it manually I'm trying to recreate the following:
variable1 = new SomeClassThatTransformsMessages (new Transformer("fileA", "fileB"));
variable2 = new SomeOtherClassThatTransformsMessages (new Transformer("fileC", "fileD"));
I don't think I can use the #Named attribute on the transformer but i can possibly use that on the parent classes but not sure how to wire up the files.
I've looked at all the bindings help but none appear to match.
Thanks

JAXB inheritance, unmarshal to subclass of marshaled class

I'm using JAXB to read and write XML. What I want is to use a base JAXB class for marshalling and an inherited JAXB class for unmarshalling. This is to allow a sender Java application to send XML to another receiver Java application. The sender and receiver will share a common JAXB library. I want the receiver to unmarshall the XML into a receiver specific JAXB class which extends the generic JAXB class.
Example:
This is the common JAXB class which is used by the sender.
#XmlRootElement(name="person")
public class Person {
public String name;
public int age;
}
This is the receiver specific JAXB class used when unmarshalling the XML. The receiver class has logic specific to the receiver application.
#XmlRootElement(name="person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff() ...
}
Marshalling works as expected. The problem is with unmarshalling, it still unmarshals to Person despite the JAXBContext using the package name of the subclassed ReceiverPerson.
JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
What I want is to unmarshall to ReceiverPerson. The only way I've been able to do this is to remove #XmlRootElement from Person. Unfortunately doing this prevents Person from being marshaled. It's as if JAXB starts at the base class and works its way down until it finds the first #XmlRootElement with the appropriate name. I've tried adding a createPerson() method which returns ReceiverPerson to ObjectFactory but that doesn't help.
The following snippet is a method of a Junit 4 test with a green light:
#Test
public void testUnmarshallFromParentToChild() throws JAXBException {
Person person = new Person();
int age = 30;
String name = "Foo";
person.name = name;
person.age= age;
// Marshalling
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(person, writer);
String outString = writer.toString();
assertTrue(outString.contains("</person"));
// Unmarshalling
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(outString);
RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);
assertEquals(name, reciever.name);
assertEquals(age, reciever.age);
}
The important part is the use of the JAXBContext.newInstance(Class... classesToBeBound) method for the unmarshalling context:
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
With this call, JAXB will compute a reference closure on the classes specified and will recognize RecieverPerson. The test passes. And if you change the parameters order, you'll get a java.lang.ClassCastException (so they must be passed in this order).
You're using JAXB 2.0 right? (since JDK6)
There is a class:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
which one can subclass, and override following methods:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
Example:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>{
#Override public Person unmarshal(ReceiverPerson v){
return v;
}
#Override public ReceiverPerson marshal(Person v){
return new ReceiverPerson(v); // you must provide such c-tor
}
}
Usage is done by as following:
#Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
#XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
}
I'm pretty sure, by using this concept you can control the marshalling/unmarshalling process by yourself (including the choice the correct [sub|super]type to construct).
Subclass Person twice, once for receiver and once for sender, and only put the XmlRootElement on these subclassses (leaving the superclass, Person, without an XmlRootElement). Note that sender and receiver both share the same JAXB base classes.
#XmlRootElement(name="person")
public class ReceiverPerson extends Person {
// receiver specific code
}
#XmlRootElement(name="person")
public class SenderPerson extends Person {
// sender specific code (if any)
}
// note: no #XmlRootElement here
public class Person {
// data model + jaxb annotations here
}
[tested and confirmed to work with JAXB]. It circumvents the problem you note, when multiple classes in the inheritance hierarchy have the XmlRootElement annotation.
This is arguably also a neater and more OO approach, because it separates out the common data model, so it's not a "workaround" at all.
Create a custom ObjectFactory to instantiate the desired class during unmarshalling. Example:
JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;
public class ReceiverPersonObjectFactory extends ObjectFactory {
public Person createPerson() {
return new ReceiverPerson();
}
}
I am not sure why you would want to do this... it doesn't seem all that safe to me.
Consider what would happen in ReceiverPerson has additional instance variables... then you would wind up with (I guess) those variables being null, 0, or false... and what if null is not allowed or the number must be greater than 0?
I think what you probably want to do is read in the Person and then construct a new ReceiverPerson from that (probably provide a constructor that takes a Person).
Since you really have two separate apps, compile them with different versions of the class "Person" - with the receiver app not having #XmlRootElement(name="person") on Person. Not only is this ugly, but it defeats the maintainability you wanted from using the same definition of Person for both sender and receiver. Its one redeeming feature is that it works.

Categories

Resources