Thursday, February 28, 2013

Detyped WS contract browsing and endpoint invocation

In response to some good feedback received from the community after the recent 2.0 release, I've been enriching the API of Wise Core to further simplify ws testing using it.
Till now the Wise core API has fundamentally been based on the WSEndpoint, WSMethod, WebParameter abstraction:
public interface WSEndpoint {
public Map<String, WSMethod> getWSMethods();
public ClassLoader getClassLoader();
// ...
}
public interface WSMethod {
public InvocationResult invoke(Object args) throws InvocationException, IllegalArgumentException, MappingException;
public Map<String, ? extends WebParameter> getWebParams();
// ...
}
public interface WebParameter {
public String getName();
public Type getType();
// ...
}
view raw gistfile1.java hosted with ❤ by GitHub
A given WSDL contract is mapped to at least one WSEndpoint instance having one or more WSMethod instances. Each WSMethod can have multiple WebParameter objects depending on the WSDL operation signature. By inspecting the type information in WebParameter instances, the user is meant to build the actual objects to be passed as arguments to WSMethod:invoke(Object args). While effective, this might turn out to be not very practical and easy (due to reflection, see here) if the consumed WSDL contract is complex and includes multiple nested schema types.
It's well known that Wise core supports Smooks transformations, with the sake of allowing conversion of existing application data models into the object structure required for performing the invocation. However that's not really helping a lot when the target is programmatically testing a given endpoint.
In particular some users expressed the interest in the tree view offered by the Wise GUI on top of the core model: similarly to what is shown in the GUI, a tree model (think about e.g. the DOM tree approach) could be provided to describe each parameter type of a given WSMethod. Each element of the tree would allow getting children and setting values (for leaves only). So here is what I came up with:
public interface Element extends Serializable, Cloneable {
public boolean isLeaf();
public boolean isRemovable();
public Type getClassType();
public String getName();
public boolean isNil();
public void setNil(boolean nil);
public String getId();
public boolean isNillable();
public boolean isGroup();
public void removeChild(String id);
public Element getChild(String id);
public Element getChildByName(String name);
public Iterator<String> getChildrenIDs();
public Iterator<? extends Element> getChildren();
public String getValue();
public void setValue(String value);
public Element getPrototype();
public Element incrementChildren();
public int getChildrenCount();
public boolean isLazy();
public boolean isResolved();
public Element clone();
public Object toObject();
}
view raw Element.java hosted with ❤ by GitHub
The current Wise core 2.0.1-SNAPSHOT features an implementation of the Element interface above, as well as a builder implementation for converting a WebParameter into an Element tree:
public interface ElementBuilder {
public ElementBuilder client(WSDynamicClient client);
public ElementBuilder request(boolean request);
public ElementBuilder useDefautValuesForNullLeaves(boolean useDefValuesForNullLeaves);
public Element buildTree(Type type, String name, Object value, boolean nillable);
}
The end result is in a really simplified contract browsing of a WS endpoint as well as simple invocation (see the integration test here too):
//select a method and the parameter(s) to deal with...
WSMethod method = client.getWSMethod("RegistrationServiceImplService", "RegistrationServiceImplPort", "Register");
Map<String, ? extends WebParameter> pars = method.getWebParams();
WebParameter customerPar = pars.get("Customer");
//browse the paramter structure and eventually populate the parameter...
ElementBuilder builder = ElementBuilderFactory.getElementBuilder().client(client).request(true).useDefautValuesForNullLeaves(true);
Element customerElement = builder.buildTree(customerPar.getType(), customerPar.getName(), null, true);
customerElement.getChildByName("id").setValue("1234");
customerElement.getChildByName("name").getChildByName("firstName").setValue("Foo");
customerElement.getChildByName("name").getChildByName("lastName").setValue("Bar");
customerElement.getChildByName("name").getChildByName("middleName").setValue("The");
//convert back the tree to an Object and perform the invocation...
Map<String, Object> args = new java.util.HashMap<String, Object>();
args.put(customerElement.getName(), customerElement.toObject());
InvocationResult result = method.invoke(args);
view raw gistfile1.java hosted with ❤ by GitHub
The tree view can of course be used to convert and access the data returned in the InvocationResult instance.
Please note how the parameters type info is completely hidden to the user, who basically ends up setting string values for leaves elements. Strings are parsed into proper primitives (and wrappers) depending on the actual parameter type. Wise default Element impl is currently able to convert values to String, Character, all numerical types, QName, XMLGregorianCalendar and Duration classes.
So, leveraging Wise, a user can invoke / test an endpoint by browsing its contract and setting parameters in a fully dynamic and detyped way, while still being sure the generated SOAP request is compliant with the contract requirements and constraints.

The additions described above are currently being tested and will be included soon in next release. Any comment / feedback is welcome as usual!