I have the following situation:
There is a tree structure for logical expressions which is used in our application and defined by a four class hierarchie:
Node is an abstract super class
OrNode is a sub class of Node representing OR
AndNode is a sub class of Node representing AND
Leaf is a sub class of Node representing a leaf node holding some data
Now the tree structure should be transfered to a web service which will do some operation on the tree (e.g. evaluating the result by gathering some other information)
The signature of that WS-Operation could be look like the following:
public TheResult evaluateTree(Node tree);
We are using JAX-WS and generate the web services classes with wsimport. First, there are no classes generated for OrNode, AndNode and Leaf. So, we added them manually. We convert the classes used on the client side to the generated classes created by wsimport. Next we want to call the web service operation with the converted tree as parameter. But here an exception occurs saying something like:
javax.xml.ws.soap.SOAPFaultException: javax.xml.bind.UnmarshalException - with linked exception: [javax.xml.bind.UnmarshalException: Unable to create an instance of InterfaceEntities.Node - with linked exception: [java.lang.InstantiationException]]
Here are the Wrapper classes added by us and generated classes:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "OrNode")
public class OrNode
extends Node
{
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "AndNode")
public class AndNode
extends Node
{
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "leaf")
public class Leaf
extends Node
{
...
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "node", propOrder = {
"children",
"resultSet",
})
#XmlSeeAlso({
Leaf.class,
OrNode.class,
AndNode.class
})
public abstract class Node {
...
}
EDIT:
Here is the generated XML-File when using Marshaller as described in Blaise Doughan's blog (see answer below):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:treeInfo xmlns:ns2="http://webservice.api.process/">
<tree xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:OrNode">
<children xsi:type="ns2:leaf">
<!-- some fields holding values -->
</children>
<children xsi:type="ns2:leaf">
<!-- some fields holding values -->
</children>
</tree>
<!-- some fields holding values -->
</ns2:treeInfo>
It is a simple tree consisting of one orNode and two leaf nodes,
treeInfo represents the class holding the Node/tree object with some other information. It is marked as the XmlRootElement with the corresponding annotation.
Did we miss anything?
Thanks in advance!
I've found the problem. We are using several web services, for each we generate the wrapper classes via wsimport. And some webservices are using the node class. Now, as I mentioned in my question, we had to implement some wrapper classes manually as they were not auto generated by wsimport, i.e. we had to add wrapper classes for the OrNode and AndNode. You also have to add the XmlSeeAlso element to the super class so that it knows its sub classes. We added the XmlSeeAlso element for one web service but missed it for the other one. This caused the exception mentioned above.
If you are in development, a quick fix is to simply remove the abstract key word from the class definition of the "abstract" class. If the class is no longer abstract, it will no longer throw the error. This may be a hack that you want to fix before releasing your code into production, but it can enable you to keep developing other aspects of your application before you resolve the deeper problem.
Assuming in your question that your SearchNode class should really be Node then you need to make sure that the XML contains the appropriate xsi:type attribute to specify the subclass.
For the Leaf class it would be:
<node xsi:type="leaf" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
</node>
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
Related
I'm trying to create a dynamic product discount using values from a webservice.
I've searched some guides on the internet about this matter and I found that I needed to use checkout_cart_product_add_after and checkout_cart_update_items_after.
However, I followed some guides. Created my own module (which is visible in Magento back office: Configuration > Advanced > Modules) and a observer for this module. I didn't create anything more but it's not working. Since I can see the module in that menu, I believe the problem is on the observer/event call.
Here is the config.xml (which is inside app\code\local\namespace\MyModule\etc) for my module:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<namespace_MyModule>
<version>0.1.0</version>
</namespace_MyModule>
</modules>
<global>
<events>
<checkout_cart_product_add_after>
<observers>
<namespace_MyModule_Discount>
<class>MyModule/Observer</class>
<method>MyModulePriceChange</method>
</namespace_MyModule_Discount>
</observers>
</checkout_cart_product_add_after>
</events>
</global>
</config>
And this is my Observer (which is inside app\code\local\namespace\MyModule\Model) for my module:
<?php
class namespace_MyModule_Model_Observer
{
public function MyModulePriceChange(Varien_Event_Observer $obs)
{
// Get the quote item
$item = $obs->getQuoteItem();
// Ensure we have the parent item, if it has one
$item = ( $item->getParentItem() ? $item->getParentItem() : $item );
// Load the custom price
$price = $this->_getPriceByItem($item);
// Set the custom price
$item->setCustomPrice($price);
$item->setOriginalCustomPrice($price);
// Enable super mode on the product.
$item->getProduct()->setIsSuperMode(true);
}
protected function _getPriceByItem(Mage_Sales_Model_Quote_Item $item)
{
$price = 4;
//use $item to determine your custom price.
return $price;
}
}
?>
Also, is it possible do call soap client to use a webservice inside a observer?
I hope my question is clear, thank you in advance for helping.
I see some issues with your config.xml. First of all, use capitalized both company name and module name. namespace_MyModule will become your namespace.
You have to declare models under global section like this:
<models>
<mycompany_mymodule>
<class>Mycompany_Mymodule_Model</class>
</mycompany_mymodule>
</models>
This will tell magento you want to use mycompany_mymodule for models in your module, and class name of each module will start with Mycompany_Mymodule_Model.
Where Mycompany and Mymodule are respective to folders of your module: app/code/local/Mycompany/Mymodule.
The modules section of config.xml should also have this namespace (Mycompany_Mymodule), matching name of your file app/etc/modules and folder structure in app/code/local.
The observers then become the following (I added type, and changed class):
<observers>
<namespace_MyModule_Discount>
<type>singleton</type>
<class>mycompany_mymodule/Observer</class>
<method>MyModulePriceChange</method>
</namespace_MyModule_Discount>
</observers>
Then try to test your observer method by adding there some code like die("message").
You haven't declared the models tag in the config.xml file.
An observer is a model after all and magento will not know where to find it (that MyModule/Observer you reference). Below it's an example of declaring models tag:
<models>
<MyModule>
<class>Namespace_Modulename_Model</class>
</MyModule>
</models>
Yes, you can do soap api calls inside observer.
When invoking a web service I get a dynamic response in XML format.
So response could be :
<response>
<test1>test1</test1>
<test2>test1</test2>
<test3>test1</test3>
<test4>test1</test4>
</response>
or :
<response>
<test1>test1</test1>
<test2>test1</test2>
</response>
But I think the response should be static in order for the Java class to be unmarshalled correctly from the XML.
So instead of
<response>
<test1>test1</test1>
<test2>test1</test2>
</response>
This should be :
<response>
<test1>test1</test1>
<test2>test1</test2>
<test3></test3>
<test4></test4>
</response>
This means I can now handle the response and check for missing data.
Am I correct in my thinking?
Default Null Representation
By default a JAXB (JSR-222) implementation will treat a property as an optional element. As such null values are represented as the element being absent from the document.
Alternate Representation of Null
Alternatively you have null represented by including the xsi:nil="true" attribute on it. This is achieved by annotating your property with #XmlElement(nillable=true).
<date xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
Invalid Null Representation
An empty element is not a valid representation for null. It will be treated as an empty String which will be invalid for all non-String types.
<date/>
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html
Update
SO test1 test1 is
a valid response but the fields test3 & test4 will be set to null ?
What happens is that nothing is done for the fields/properties that correspond to absent nodes. They will keep there default values, which are by default initialized to null.
Java Model (Root)
In the model class below I have initialed the fields to have values that are not null.
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
#XmlElement
String foo = "Hello";
String bar = "World";
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
The document being marshalled <root/> does not have any elements corresponding to the mapped fields/properties in the model class.
import java.io.StringReader;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<root/>");
Root root = (Root) unmarshaller.unmarshal(xml);
System.out.println(root.foo);
System.out.println(root.bar);
}
}
Output
We see that the default values were output. This shows that a set operation was not performed for the absent nodes.
Hello
World
Refer to JAXB Marshalling with null fields
and also What's the purpose of minOccurs, nillable and restriction?
Use #XmlElement(nillable = true) for those null/blank value fields to display; but pay particular attention to your date fields.
I am trying to receive data from the Web Service and I am getting the Data from Web Service back but it is form of [object Object]. Can anybody help me on this.
Below is the code for my web service:
public class WebServiceAccess
{
private var webService:WebService;
private var serviceOperation:AbstractOperation;
private var myValueObjects:ValueObjects;
private var method:String;
[Bindable]
public var employeeData:ArrayCollection;
[Bindable]
public var employees:ArrayCollection;
public function WebServiceAccess(url:String, method:String)
{
webService = new WebService();
this.method = method;
webService.loadWSDL(url);
webService.addEventListener(LoadEvent.LOAD, ServiceRequest);
}
public function ServiceRequest():void
{
serviceOperation = webService.getOperation(method);
serviceOperation.addEventListener(FaultEvent.FAULT, DisplayError);
serviceOperation.addEventListener(ResultEvent.RESULT, DisplayResult);
serviceOperation.send();
}
public function DisplayError(evt:FaultEvent):void
{
Alert.show(evt.fault.toString());
}
public function DisplayResult(evt:ResultEvent):void
{
employeeData = evt.result as ArrayCollection;
Alert.show(employeeData.toString());
}
}
First of all, evt.result is not an ArrayCollection, it is an Object (unless your SOAP service/WSDL are completely screwed up/malformed XML).
Second, you can't just display an Array or ArrayCollection (or generic Object, even) as a String (even though the .toString() method always seems to imply that) anyway, you have to parse the data to get what you want from it.
Now, the WebService class is nice in that it automatically parses the XML file that a SOAP service returns into a single usable Object. So that is actually the hard part.
What you need to do is call various properties of the object to get the data you need.
So if the XML return (look at your WSDL to see what the return should be, I also highly suggest soapUI) is this:
<employee name="Josh">
<start date="89384938984"/>
<photo url="photo.jpg"/>
</employee>
And you wanted to display "Josh" and the photo, you would do this.
var name:String = e.result.employee.name;
var url:String = e.result.employee.photo.url;
It does get more complicated. If the WSDL allows for multiple nodes with the same name at the same level, it does return an ArrayCollection. Then you have to loop through the array and find the exact item you need.
Just remember: The WSDL is god. Period. If it says there can be multiple "employee" nodes, you have to code accordingly, even if you don't see more than one in your tests. The issue is that there always could be multiple nodes.
Suppose I want to design an Ecore metamodel that looks something like this, designed to be used to "run" a list of classes:
JavaClassRunnerList
0..* JavaClass
And assume I have some Java project that has classes named PrintsHello, PrintsSeparator, and PrintsWorld.
I'd like to be able to then write models that look like this:
JavaClassRunnerList
PrintsHello.class
PrintsSeparator.class
PrintsWorld.class
PrintsSeparator.class
PrintsSeparator.class
I want my model to be able to include a Java project and to recognize its classes as choices for the model references (possibly co-located in the same project the model is in.)
Is this possible?
Ed Merks said the following. See the thread for the remainder of the discussion.
You can use Ecore's EJavaClass data type to create a multi-valued
attribute. You might be better just to use class names, and use a
class loader to convert therm to actual class instances.
Same goes for
wanting references to IProject; you can use a string and then resolve
it to an IProject using the the workspace root.
You should define additional EDatatypes to your ecore for each Java class you want to reference (with 'Instance Type Name' = java class qualified name), and simply use these datatypes to type some of your EAttributes.
Note that you will have to implement specific converters for each created EDatatype if you want to persist EAttribute values in your Resource files.
Example with an EDatatype named 'Date', with instanceTypeName='java.util.Date', you would have to give implementation for the following two methods in your factory implementation:
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* #generated
*/
public Date createDateFromString(EDataType eDataType, String initialValue) {
// TODO replace with your implementation
return (Date)super.createFromString(eDataType, initialValue);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* #generated
*/
public String convertDateToString(EDataType eDataType, Object instanceValue) {
// TODO replace with your implementation
return super.convertToString(eDataType, instanceValue);
}
I would like to create a service that accepts a complex nested type. In a sample asmx file I created:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class ServiceNest : System.Web.Services.WebService
{
public class Block
{
[XmlElement(IsNullable = false)]
public int number;
}
public class Cell
{
[XmlElement(IsNullable = false)]
public Block block;
}
public class Head
{
[XmlElement(IsNullable = false)]
public Cell cell;
}
public class Nest
{
public Head head;
}
[WebMethod]
public void TakeNest(Nest nest)
{
}
}
When I view the asmx file in IE the test page shows the example SOAP post request as:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<TakeNest xmlns="http://schemas.intellicorp.com/livecompare/">
<nest>
<head>
<cell>
<block xsi:nil="true" />
</cell>
</head>
</nest>
</TakeNest>
</soap:Body>
</soap:Envelope>
It hasn't expanded the <block> into its number member.
Looking at the WSDL, the types all look good. So is this just a limitation of the post demo page creator?
Thanks.
But those elements ARE null. You need to construct them before they show up otherwise they are just null.
As Kevin pointed out the example POST XML indicates that those elements are nil. I should have simply tried to consume the web service. Once I did that I could see that the importer (either .NET, Java or Ruby) correctly created all the types. So really there is no question here after all.
The .NET code did not give up after a certain number of levels.
If you look at the code generated by "Add Web Reference", you'll find that there's a bool numberSpecified field. Only if the client sets that to true will the number be serialized.
If you look at the XML Schema, you'll see that the number element might be absent. If it were of a reference type, then that could be represented in the client by a null value. Since it's an int, this additional flag is necessary to indicate whether or not to serialize this optional value.