JAXB How to force xsi:type in array of subclasses? (JBoss 4.2.3) - jboss

(Please note; i'm completely new to webservices, so the following may be stupid/incorrect but please be patient)
In my WebServices' #WebMethod I'm returning an array of an abstract base class (JAXB annotated entities in JBoss 4.2.3).
Obviously unless the type information is encoded per array element this will fail...
So how do I ensure that JAXB adds the xsi:type attribute?
My WebService interface has the following annotation, and I've tried every legal combination:
#SOAPBinding(style = RPC, parameterStyle = WRAPPED, use = LITERAL)
the methods on this interface take x2 parameters annotated #WebParam(name="...", mode=IN)
Other methods with similar signatures that don't return a heterogeneous array work perfectly.
Some related things:
Looks like JBoss uses the types defined in the method signatures to decide what classes to load into the JAXBContext - if I change the return types to Object[] it throws an error stating that the AbstractBase class "nor any of its super class is known to this context." I've added dummy methods returning the specific subclasses so that the generated WSDL has a list of all of them.
when I try to write tests for this, all is ok for single elements, but JAXB throws an error for array types: unable to marshal type "[LAbstractBase;" as an element because it is missing an #XmlRootElement annotation
From code like that shown below (note: AbstractBase, ConcreteOne and ConcreteTwo all have #XmlRootElement annotations)
private static final Class<?>[] CLASSES_TO_BE_BOUND = new Class<?>[]{
//Note; adding AbstractBase[].class doesn't work either
AbstractBase.class, ConcreteOne.class, ConcreteTwo.class
};
#Test
public void testXsiTypeAttributeIsIncludedInHeterogeneousArray()
{
AbstractBase[] array = new AbstractBase[2];
array[0] = new ConcreteOne();
array[1] = new ConcreteTwo();
Marshaller marshaller = createMarshaller();
StringWriter sw = new StringWriter();
marshaller.marshal(array, sw);
String output = sw.toString();
Assert.assertTrue(output.contains("xsi:type=\""));
}
private Marshaller createMarshaller() throws Exception {
JAXBContext context = JAXBContext.newInstance(CLASSES_TO_BE_BOUND);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
}
Ideally I'd like to be able to test some bits relating to arrays, but it's far more critical that I can get the type information encoded per element in the JBoss environment.
Edit: Own Answer
JBoss (4.2.3) is doing something clever, but not too clever - it will handle the returning of arrays but not polymorphic arrays. This threw me a bit as I tried to get this way of doing it working in my tests.
Instead of trying to solve the JBoss WebService issue I made my tests more comprehensive - making the array a member of a simple container class and then annotating the array's getter with:
#XmlElementRefs({
#XmlElementRef(type = ConcreteOne.class),
#XmlElementRef(type = ConcreteTwo.class)
})
public AbstractBase[] getItems() { /*...*/ }
Which worked, and returning this in the JBoss WebService also worked! Though I'm using:
#SOAPBinding(style = DOCUMENT, parameterStyle = BARE, use = LITERAL)
So it's not adding the xsi:type attribute, but document nodes are correctly tagged:
<ConcreteOne>...</ConcreteOne>
At some point I'll change the WebService to use RPC as I don't really like the single argument restriction, but for now this is working fine.

Related

Is using default method in an interface a good pattern to avoid code duplications?

We have a lot of code duplication in data holder classes that can be serialized to a XML string:
public String toXml() throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(this.getClass());
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
final StringWriter stringWriter = new StringWriter();
marshaller.marshal(this, stringWriter);
return stringWriter.toString();
}
Why not move this code to a single interface with default implementation? So a simple implements ToXmlUtf8 would be enough to share the default implementation and avoid code duplicates:
public interface ToXml {
default String toXml() throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(this.getClass());
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
final StringWriter stringWriter = new StringWriter();
marshaller.marshal(this, stringWriter);
return stringWriter.toString();
}
}
Has anybody done this before successfully?
Other solutions?
I could also imagine using an annotation to generate this code.
Are there any ready to use solutions available?
Yes, default methods can be used in that way.
Although the intended use case of default methods is adding new functionality to existing interfaces without breaking old code, default methods have other uses as well. Default methods are also used in interfaces which were added in Java 8, such as in java.util.Predicate, so even the Java designers recognized that adding new functionality to existing interfaces is not the only valid use of default methods.
A disadvantage could be that the implemented interfaces are part of a class's public contract, but in your case this does not seem to be a problem.
If you're using the exact same method then an interface won't help, what you want to do is make a static method and put that in a util class

Spring MVC REST using #RequestBody List<?> returns HTTP 400 syntactically incorrect

I am using Spring 4 + Jackson 2 and have written a fully functional POST method using #RequestBody on a custom class. This method has no trouble unmarshalling the object.
#ResponseBody
#RequestMapping(value="store", method = RequestMethod.POST)
public ServiceResponse store(#RequestBody CustomClass list) {
...
}
// Request: { code: "A", amount: 200 }
When I attempted to add another method to handle a collection of the same class instead, my POST requests were returning with the following error.
HTTP Status 400: The request sent by the client was syntactically incorrect.
I note that this error typically occurs when the JSON submitted does not match the entity class. However, all I am doing is submitting an array of the same object instead of the object itself, which has already proven to work.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody List<CustomClass> list) {
...
}
// Request: [{ code: "A", amount: 200 }, { code: "B", amount: 400 }]
Am I missing something here?
In Java, type information for generics is erased at runtime, so Spring sees your List<CustomClass> object as List<Object> object, thus it cannot understand how to parse it.
One of ways to solve it, you could capture the type information by creating a wrapper class for your list, like this:
public class CustomClassList extends ArrayList<CustomClass> {
}
Sergey is right that the issue is due to type erasure. Your easiest way out is to bind to an array, so
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody CustomClass[] object) {
...
}
The answer is that Spring 4 doesn't actually get rid of type erasure, contrary to what some other solutions suggest. While experimenting on debugging via manual unmarshalling, I decided to just handle that step myself instead of an implicit cast that I have no control over. I do hope someone comes along and proves me wrong, demonstrating a more intuitive solution though.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody String json) {
try {
List<CustomClass> list = new ObjectMapper().readValue(json, new TypeReference<List<CustomClass>>() { });
...
} catch (Exception e) {
...
}
}
Bonus: Right after I got this working, I bumped into this exception:
IllegalStateException: Already had POJO for id
If anyone gets this, it's because the objects in the list happen to reference some object that another item in the list already references. I could work around this since that object was identical for my entire collection, so I just removed the reference from the JSON side from all but the first object. I then added the missing references back after the JSON was unmarshalled into the List object.
Two-liner for the Java 8 users (the User object reference was the issue in my case):
User user = list.get(0).getUser();
list.stream().filter(c -> c.getUser() == null).forEach(t -> t.setUser(user));

Vaadin 7 createEntityItem and addentity to JPAContainer Eclipse shows syntax error in code

I am trying to add a new item using formlayout form to JPAContainer in vaadin 7. I use this code snippet:
String paramDAO = "PersonDAO"; //this parameter can be acquired from url, http request, data file and etc
Class<?> TC = null; //DAO class
InitialContext ic;
ic = new InitialContext();
TO = ic.lookup("java:app/MyDAOProject/"+paramDao);
// PersonDAO class extends JPAContainer<PersonEntity>
container = (JPAContainer<?>) TO;
T = container.getEntityClass();
if(event.getButton() == newButton)
{
final EntityItem newRecord = container.createEntityItem(T.newInstance()); //eclipse shows syntax error here
final EditorWindow editor = new EditorWindow(newRecord, T, visibleColumns, this.entytiPropFactory);
editor.setCaption("New record");
editor.addListener(new EditorSavedListener()
{
#Override
public void editorSaved(EditorSavedEvent event)
{
container.addEntity(newRecord.getEntity()); //eclipse shows syntax error here
new Notification("New record created.", null, Notification.Type.TRAY_NOTIFICATION, true).show(Page.getCurrent());
}
}
);
UI.getCurrent().addWindow(editor);
}
The main idea is that I want to create a unified vaadin table with editing form that fits for mostly entities (data base tables) in my project. So I pass dao object name as string parameter (don't ask why) and then i lookup it through jndi service. The dao object is tied with entity and extends JPAcontainer<entityClass> class. This approach worked perfectly in Vaadin 6 version with JPAContainer 2.2.0 but after migrating to Vaadin 7 and JPAContainer 3.0.0 eclipse throws me a syntax error in these lines (errors are in comments respectively every line below):
final EntityItem newRecord = container.createEntityItem(T.newInstance());
//The method createEntityItem(capture#9-of ?) in the type JPAContainer<capture#9-of ?> is not applicable for the arguments (capture#10-of ?)
container.addEntity(newRecord.getEntity());
//The method addEntity(capture#12-of ?) in the type JPAContainer<capture#12-of ?> is not applicable for the arguments (Object)
It seems your declaration of the container variable is something like this:
JPAContainer<?> container;
So your warnings could be avoided by declaring the container variable like this:
JPAContainer<Object> container; Or this:
JPAContainer container;
Here you will find some additional information about wildcards in java generics:
http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html

Use GuidRepresentation.Standard with MongoDB

I am implementing a custom IBsonSerializer with the official MongoDB driver (C#). I am in the situation where I must serialize and deserialize a Guid.
If I implement the Serialize method as follow, it works:
public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
BsonBinaryData data = new BsonBinaryData(value, GuidRepresentation.CSharpLegacy);
bsonWriter.WriteBinaryData(data);
}
However I don't want the Guid representation to be CSharpLegacy, I want to use the standard representation. But if I change the Guid representation in that code, I get the following error:
MongoDB.Bson.BsonSerializationException: The GuidRepresentation for the writer is CSharpLegacy, which requires the subType argument to be UuidLegacy, not UuidStandard.
How do I serialize a Guid value using the standard representation?
Old question but in case someone finds it on google like I did...
Do this once:
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
For example, in a Web Application/Web API, your Global.asax.cs file is best place to add it once
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
//Other code...below
}
}
If you don't want to modify the global setting BsonDefaults.GuidRepresentation (and you shouldn't, because modifying globals is a bad pattern), you can specify the setting when you create your collection:
IMongoDatabase db = ???;
string collectionName = ???;
var collectionSettings = new MongoCollectionSettings {
GuidRepresentation = GuidRepresentation.Standard
};
var collection = db.GetCollection<BsonDocument>(collectionName, collectionSettings);
Then any GUIDs written to the collection will be in the standard format.
Note that when you read records from the database, you will get a System.FormatException if the GUID format in the database is different from the format in your collection settings.
It looks like what's happening is when you are not explicitly passing the GuidRepresentation to BsonBinaryData constructor, it defaults to passing GuidRepresentation.Unspecified and that ultimately maps to GuidRepresentation.Legacy (see this line in the source)
So you need to explicitly pass the guidRepresentation as a third argument to BsonBinaryData set to GuidRepresentation.Standard.
edit: As was later pointed out, you can set BsonDefaults.GuidRepresentation = GuidRepresentation.Standard if that's what you always want to use.

Can't insert new entry into deserialized AutoBean Map

When i try to insert a new entry to a deserialized Map instance i get no exception but the Map is not modified. This EntryPoint code probes it. I'm doing anything wrong?
public class Test2 implements EntryPoint {
public interface SomeProxy {
Map<String, List<Integer>> getStringKeyMap();
void setStringKeyMap(Map<String, List<Integer>> value);
}
public interface BeanFactory extends AutoBeanFactory {
BeanFactory INSTANCE = GWT.create(BeanFactory.class);
AutoBean<SomeProxy> someProxy();
}
#Override
public void onModuleLoad() {
SomeProxy proxy = BeanFactory.INSTANCE.someProxy().as();
proxy.setStringKeyMap(new HashMap<String, List<Integer>>());
proxy.getStringKeyMap().put("k1", new ArrayList<Integer>());
proxy.getStringKeyMap().put("k2", new ArrayList<Integer>());
String payload = AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(proxy)).toString();
proxy = AutoBeanCodex.decode(BeanFactory.INSTANCE, SomeProxy.class, payload).as();
// insert a new entry into a deserialized map
proxy.getStringKeyMap().put("k3", new ArrayList<Integer>());
System.out.println(proxy.getStringKeyMap().keySet()); // the keySet is [k1, k2] :-( ¿where is k3?
}
}
Shouldn't AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(proxy)).toString(); be getPayLoad()
I'll check the code later, and I don't know if that is causing the issue. But it did stand out as different from my typical approach.
Collection classes such as java.util.Set and java.util.List are tricky because they operate in terms of Object instances. To make collections serializable, you should specify the particular type of objects they are expected to contain through normal type parameters (for example, Map<Foo,Bar> rather than just Map). If you use raw collections or maps you will get bloated code and be vulnerable to denial of service attacks.
Font: http://www.gwtproject.org/doc/latest/DevGuideServerCommunication.html#DevGuideSerializableTypes