I've a JSON something like this:
objects:[{
id:"p452365",
type:"photo",
link:"http://xxx.zz"
},
{
id:"v7833",
type:"video",
link:"http://xxx.yy",
length:"4.12"
}
]
In superclass Entity, there're 2 instance variables: id and type. In my extended XmlAdapter class I tried to cast my Entity instances to a subtype for ex. Photo
public HashMap<String, List<Column>> unmarshal(Feeds f) throws Exception {
for(Feed feed : f.getFeeds()){
System.out.println("Entity id for feed : " + feed.getId());
for(Entity e:feed.getObjects()){
if (e instanceof Photo){
// Of course it's not
}
}
}
return (HashMap<String, List<Column>>)fm.map(f.getFeeds());
}
Of course e isn't an instanceof Photo, I took a shot there.:)
What I wanna do is to interfere the JAXB process sometime and unmarshall according to the type value in JSON.I wonder where and how.
One of my previous answers to a similar question may help here. Essentially it is using the #XmlDescrimatorNode in EclipseLink JAXB (MOXy). Note I'm the MOXy tech lead.
Java/JAXB: Unmarshall Xml to specific subclass based on an attribute
You could also do this with an XmlAdapter. AdaptedEntity would have all the properties from Entity and it's subclasses.
JAXB #XmlElements, different types but same name?
Related
In order to marshal jaxb classes with Apache Camel the jaxb class needs to include a XmlRootElement annotation.
When generating jaxb classes from XSD the XmlRootElement annotation might not be generated.
This will lead to an Exception during marshalling
"No type converter available to convert from type: "
As soon as I add the #XmlRootElement manually, everything works fine, but since these Jaxb classes are generated, adding the anntotation manually is no option.
According to the Camel documentation in such a case, the JaxbDataFormat can be set to 'fragement(true)
JaxbDataFormat jaxbMarshal = new JaxbDataFormat();
jaxbMarshal.setContextPath(ObjectFactory.class.getPackage().getName());
jaxbMarshal.setFragment(true);
Unfortunately I still get the same exception.
Is there a way to configure JaxbDataFormat different, i.e. to define the JAXBElement which is the root element, like I would do in Java
marshaller.marshal( new JAXBElement( new QName("uri","local"),
MessageType.class, messageType ));
or is there another strategy available to get the XML marshalled?
EDIT
the used route :
from("file://inbox").unmarshal(jaxbDataFormat)
.marshal(jaxbDataFormat).to("file://outbox");
the stacktrace:
java.io.IOException: org.apache.camel.NoTypeConversionAvailableException: No type converter
available to convert from type: com.xyz.AddressType to the required
type: java.io.InputStream with value com.xyz.AddressType#32317e9d at
org.apache.camel.converter.jaxb.JaxbDataFormat.marshal(JaxbDataFormat.java:148)
~[camel-jaxb-2.16.0.jar:2.16.0] at
org.apache.camel.processor.MarshalProcessor.process(MarshalProcessor.java:83)
~[camel-core-2.16.0.jar:2.16.0] at
...
[na:1.8.0_25] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_25]
Caused by: org.apache.camel.NoTypeConversionAvailableException: No
type converter available to convert from type: com.xyz.AddressType to
the required type: java.io.InputStream with value
com.xyz.AddressType#32317e9d at
org.apache.camel.impl.converter.BaseTypeConverterRegistry.mandatoryConvertTo(BaseTypeConverterRegistry.java:185)
~[camel-core-2.16.0.jar:2.16.0] at
...
In Camel 2.17, the #XmlRootElement was not required. As of 2.21, it is. Unless...
The class org.apache.camel.converter.jaxb.FallBackTypeConverter changed it's implementation from:
protected <T> boolean isJaxbType(Class<T> type) {
return hasXmlRootElement(type) || JaxbHelper.getJaxbElementFactoryMethod(camelContext, type) != null;
}
To:
protected <T> boolean isJaxbType(Class<T> type) {
if (isObjectFactory()) {
return hasXmlRootElement(type) || JaxbHelper.getJaxbElementFactoryMethod(camelContext, type) != null;
} else {
return hasXmlRootElement(type);
}
}
By default the isObjectFactory() method returns false. If you set the property CamelJaxbObjectFactoryon your CamelContext to true. then the JaxbHelper.getJaxbElementFactoryMethod(camelContext, type) will return true and the deserialization works again as before without the need for an #XmlRootElement. For completeness:
<camelContext xmlns="http://camel.apache.org/schema/spring" id="camelContext">
<properties>
<property key="CamelJaxbObjectFactory" value="true"/>
</properties>
</camelContext>
I experienced the equivalent behaviour with JaxB (#XmlRootElement annotation not present in the generated class), and I suppose it comes from the way the root element is defined in the XML schema.
For example:
<xsd:element name="DiffReport" type="DiffReportType" />
<xsd:complexType name="DiffReportType">
...
</xsd:complexType>
it will generate you the DiffReportType class without the #XmlRootElement annotation. But if you directly define your root element as following, you'll get the annotation set in your generated class (the name of the root class is then DiffReport in my example).
<xsd:element name="DiffReport">
<xsd:complexType>
...
Note: I used the first way to define the complex types in my schema for class name consistency.
You can use the "partClass" option of the jaxb data format of camel. Your question is answered in the camel docs for jaxb, which describes how to marshall XML fragments (or XML generated without the XmlRootElement annotation).
Use partClass and provide the actual class name to which you wish to marshall. In case of marshalling you also have to provide the partNamespace which is the target namespace of the desired XML object.
I am calling a generic method with two different classes as below:
FillDataPointsInOrder<Metrics>(dataPoints.Where(O => O.SortOrder != null).OrderBy(O => O.SortOrder));
FillDataPointsInOrder<Metric>(angieStatsCmp.GetDataColumns());
private void FillDataPointsInOrder<T>(IEnumerable<T> dataPoints)
{
foreach (T dpoint in dataPoints)
{
if (!dpoint.IsPhone)
FillDrp(this.EmailDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
if (dpoint.IsPhone && this.IsPhoneShop)
FillDrp(this.PhoneDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
}
}
in "FillDataPointsInOrder" method I am getting compile errors :
'T' does not contain a definition for 'IsPhone' and no extension method 'IsPhone' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Same errors for Name , MetricId and VName properties.
Not sure why T is not able to access properties of Metrics and Metric.
If I remove the code from generic method and write it directly in foreach over dataPoints it is working fine.
Can somebody advise what is wrong here?
FillDataPointsInOrder only knows it will be called with a T. T could actually be string, int or anything.
If you want to call properties on T, you will have to use a where constraint.
But in this case it looks like your method does not even need to be generic.
If both Metric and Metrics share a base class or an interface that has the properties you need:
interface IMetric {
bool IsPhone {get; }
}
you could just have:
private void FillDataPointsInOrder(IEnumerable<IMetric> dataPoints)
Note that IEnumerable is covariant, so if Metric is a IMetric, IENumerable<Metric> is a IEnumerable<IMetric>
You need to at least tell the compiler something about T if you want to do that. Do you have an interface that has members like IsPhone, Name, MetricId, etc. that your classes implement?
If so you can add a 'where' constraint to your class definition:
public class Something<T> where T : ISomethingElse
...where ISomethingElse is the interface that implements IsPhone.
I need to marshal a list of entities using jax-b in a jax-rs client, without creating a wrapper class for each entity needed (there are manny entities!). I notice the service is able to marshal a list of customers like this:
<customers>
<customer>.....</customer>
<customer>.....</customer>
</customers>
Which I on the client side is able to unmarshal by finding all customer nodes and adding them to a list manually. (I guess there's a better way to do this?)
Now, the real problem here is when I want to send a list of an entity(eg. customers) to the service. I want to marshal this list into an xml string before writing this string as the payload of the request to the service. This does not work since java.util.List or its descendants is not known to the marshaller.
javax.xml.bind.Marshaller.marshal(list, StringWriter);
javax.xml.bind.Unmarshaller.unmarshal(org.w3c.dom.node)
Any help is much appreciated!
Thanks!
-Runar
Edit:
Here's a snippet from the customer class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer implements Serializable {
private String name;
.....
}
I'm trying to write a lightweight client using no 3rd party libraries not part of the standard implementation. Because of this I have written my own httpclient taking in payload objects, marhalling them and passing them to the payload of the request. When the response is received I read the xml and send it to unmarshalling. It would be awsome if I could do the marshalling/unmarshalling directly to/from string just as my jax-rs service does.
Ok, so I found no good solution to this. But since it seems the pattern for my jax-rs service is to generate a root node called <class name " + "s>, I did this to be able to send lists of objects to the service:
if (obj instanceof List) {
List list = (List) obj;
if (!list.isEmpty()) {
// Since the jax-b marshaller does not allow to send lists directly,
// we must attempt to create the xml as the jax-rs service would expect them,
// wrapped with the classname pluss an s.
String wrapper = list.get(0).getClass().getSimpleName() + "s";
// Make first letter in class-name lower-case
wrapper = Character.toLowerCase(wrapper.charAt(0)) +
(wrapper.length() > 1 ? wrapper.substring(1) : "");
marshaller.setProperty(javax.xml.bind.Marshaller.JAXB_FRAGMENT, true);
writer.write("<" + wrapper + ">");
for (Object o : (List) obj) {
marshaller.marshal(o, writer);
}
writer.write("</" + wrapper + ">");
}
} else {
marshaller.marshal(obj, writer);
}
To start: This is also for REST deserialiaztion, so a custom XmlSerializer is out of the question.
I have a hjierarchy of classes that need to be serializable and deserializable from an "Envelope". It has an arrayelement named "Items" that can contain subclasses of the abstract "Item".
[XmlArray("Items")]
public Item [] Items { get; set; }
Now I need to add XmlArrayItem, but the number is not "fixed". We use so far reflection to find all subclasses with a KnownTypeProvider so it is easy to extend the assembly with new subtypes. I dont really want to hardcode all items here.
The class is defined accordingly:
[XmlRoot]
[KnownType("GetKnownTypes")]
public class Envelope {
but it does not help.
Changing Items to:
[XmlArray("Items")]
[XmlArrayItem(typeof(Item))]
public Item [] Items { get; set; }
results in:
{"The type
xxx.Adjustment
was not expected. Use the XmlInclude
or SoapInclude attribute to specify
types that are not known statically."}
when tyrying to serialize.
Anyone an idea how I can use XmlInclude to point to a known type provider?
The KnownTypesAttribute does not work for XmlSerializer. It's only used by a DataContractSerializer. I'm quite sure that you can exchange the serializer in WCF, because I have done that for the DataContractSerializer. But if that's not an option, you have to implement IXmlSerializable yourself and handle type lookup there.
Before disqualifying this solution: You just have to implement IXmlSerializable just for a special class which replaces Item[]. Everything else can be handled by the default serializer.
According to: http://social.msdn.microsoft.com/Forums/en-US/asmxandxml/thread/83181d16-a048-44e5-b675-a0e8ef82f5b7/
you can use different XmlSerializer constructor:
new XmlSerializer(typeof(Base), new Type[] { typeof(Derived1), ..});
Instead of enumerating all derived classes in the base definition like this:
[System.Xml.Serialization.XmlInclude(typeof(Derived1))]
[System.Xml.Serialization.XmlInclude(typeof(Derived2))]
[System.Xml.Serialization.XmlInclude(typeof(DerivedN))]
I think you should be able to use your KnownTypeProvider to fill the array in the XmlSerializer's constructor.
I am using a sping ws endpoint with jaxb marshalling/unmarshalling to proudce a list of Organisation objects (our local type). The endpoint is SOAP 1.1, no parameters supplied on the request message.
I understand JAXB doesn't handle lists very well, so I use a wrapper class.
#XmlRootElement(name="orgResponse", namespace=....)
public class OrganisationListWrapper {
private ArrayList<Organisation> organisationList;
public getOrganisationList() {
return organisationList;
}
public setOrganisationList(ArrayList<Organisation> organisationList) {
this.organisationList = organisationList;
}
}
The endpoint....
#PayloadRoot(localPart=.... namespace=....)
#ResponsePayload
public OrganisationListWrapper getOrganisations() {
OrganisationListWrapper wrapper = new OrganisationListWrapper();
wrapper.setOrganisationList(.... call service layer get list ....);
return wrapper;
}
This works fine and I get a SOAP payload with
<orgResponse>
<organisationList>
... contents of organisation 1
</organisationList>
<organisationList>
... comtents of organisation 2
</organisationList>
.... etc ....
</orgResponse>
The Organisation class is not JAXB annotated. It is part of a large list of pre-existing classes that are being exposed through web services for the first time. Trying to get by without going in and annotating them all by hand.
I was able to override the name OrganisationWrapper with orgResponse in the XmlRootElement annotation. I would like to override the organisationList name in the child element with organisation but haven't been able to find an annotation that does this.
I can replace the array list name with organisation and it will work fine, but our coding standard here required us to put List on the end of our list names. I would like to try and stick to that. I have tried XmlElement, but that produced a jaxb exception.
Any suggestions would be appreciated.
Because JAXB default the access type to PUBLIC_MEMBER, make sure you annotate the property (getter) and not the field:
#XmlElement(name="organisation")
public getOrganisationList() {
return organisationList;
}
If you want to annotate the field then add the following annotation to your class:
#XmlAccessorType(XmlAccessType.FIELD)