blob: b3dbf8668e4aed00bcb84256d4c5440491a75406 [file] [log] [blame]
package groovy.net.soap;
import java.io.FileInputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.transform.TransformerException;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.xfire.client.Client;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import groovy.lang.Binding;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovyShell;
import org.apache.log4j.Logger;
import org.codehaus.xfire.util.DOMUtils;
/**
* <p>Dynamic Groovy proxy around Xfire stack.</p>
*
* @author Guillaume Alleon
*
*/
public class SoapClient extends GroovyObjectSupport {
private Client client;
static private Logger logger=Logger.getLogger(SoapClient.class);
/**
* <p>Transform a string so that it can be used in a Groovy
* bean. Whitespace are removed and the first letter is
* replaced by its lower case counterpart.</p>
* <p/>
*
* @param str the string to be uncapitalized.
*/
private static String uncapitalize(String str) {
int len = str.length();
StringBuffer buffer = new StringBuffer(len);
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
if (i != 0) {
if (ch != ' ' && ch != ':') buffer.append(ch);
} else {
buffer.append(Character.toLowerCase(ch));
}
}
return buffer.toString();
}
/**
* <p>Generate a Map representing the data types corresponding
* to the XML document</p>
* <p/>
*
* @param node a Node of tha XML document.
* @param type a Map representing the datatypes
*/
private void generateType(Node node, Map type) {
// TODO rajouter le test d'existence
if (node.hasChildNodes() && !type.containsKey(node.getNodeName())) {
Map members = new HashMap();
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++){
Node n = children.item(i);
if (n.getNodeType()==Node.ELEMENT_NODE) {
Integer valence = (Integer)members.get(n.getNodeName());
if (valence == null) {
valence = new Integer(1);
} else {
valence ++;
}
members.put(n.getNodeName(), valence);
}
generateType(n, type);
}
if(members.size()!=0)
type.put(node.getNodeName(), members);
}
}
/**
* <p>Transform the xfire response to a java/groovy type
* by generating and interpreting a Groovy script when a
* XML document</p>
* <p/>
*
* @param obj the xfire response.
*/
private Object toReturn(Object obj) {
if (obj instanceof Document) {
Map type = new HashMap();
StringBuffer classSource = new StringBuffer();
// Extract the root node from the Document
Element root = ((Document) obj).getDocumentElement();
// Clean the XML document
cleanNode(root);
// if (logger.isDebugEnabled()) {
// try {
// DOMUtils.writeXml((Node) root, System.out);
// } catch (TransformerException ex) {
// ex.printStackTrace();
// }
// }
// generate a map that associates to each type its members
generateType(root, type);
// Test if we have a complex type
if ((type.keySet().size() != 1) || (((Map)type.get(root.getNodeName())).keySet().size() != 1)) {
for (Iterator iterator = type.keySet().iterator(); iterator.hasNext();) {
String aType = (String) iterator.next();
// classSource.append("class ")
// .append(uncapitalize(aType))
// .append(" {\n");
String tt = new String(uncapitalize(aType));
if ("return".equals(tt)) tt = "out";
classSource.append("class ")
.append(tt)
.append(" {\n");
Map members = (Map) type.get(aType);
for (Iterator it1 = members.keySet().iterator(); it1.hasNext();) {
String member = (String)it1.next();
classSource.append(" @Property ");
if ((Integer)members.get(member) > 1) classSource.append("List ");
classSource.append(uncapitalize(member))
.append("\n");
}
classSource.append("}\n");
}
}
// Test if the return type is different from void
if (type.get(root.getNodeName()) != null) {
classSource.append("result = ");
createCode(root, type, classSource);
}
if (logger.isDebugEnabled()) logger.debug(classSource);
Binding binding = new Binding();
try {
Object back = new GroovyShell(binding).evaluate(classSource.toString());
if (logger.isDebugEnabled()) logger.debug(back.toString());
return back;
} catch (CompilationFailedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
} else {
if (logger.isDebugEnabled()) logger.debug("returned object is of type " + obj.getClass().getName());
return obj;
}
}
/**
* <p>Remove dead text in an XML tree.</p>
* <p/>
*
* @param element the considered element of the XML document.
*/
private void cleanNode(Element element){
// TODO rajouter le test d'existence
Node child;
Node next = (Node)element.getFirstChild();
while ((child = next) != null){
next = child.getNextSibling();
if (child.getNodeType() == Node.TEXT_NODE) {
if (child.getNodeValue().trim().length() == 0) element.removeChild(child);
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
cleanNode((Element)child);
}
}
}
/**
* <p>Create instances of the data types previously generated
* from the XML document.</p>
*
* @param element Element of the XML document.
* @param classSource StringBuffer containing the generated code.
*/
private void createCode(Element root, Map type, StringBuffer classSource){
if ((type.keySet().size() == 1) && (((Map)type.get(root.getNodeName())).keySet().size() == 1)) {
createCodeST(root, classSource);
} else {
createCodeCT(root, type, classSource);
}
}
/**
* <p>Create Complex types instances of the data types previously generated
* from the XML document.</p>
*
* @param element Element of the XML document.
* @param classSource StringBuffer containing the generated code.
*/
private void createCodeCT(Element element, Map type, StringBuffer classSource){
Node child = null;
Node fnode = (Node)element.getFirstChild();
Node lnode = (Node)element.getLastChild();
Node next = fnode;
Node prev = null;
boolean opened = false;
Map members = (Map)type.get(element.getNodeName());
if (logger.isDebugEnabled()) logger.debug("Entering createCode");
String tt = new String(uncapitalize(element.getNodeName()));
if ("return".equals(tt)) tt = "out";
if (members == null) {
classSource.append("\"\"");
} else {
// classSource.append("new " + uncapitalize(element.getNodeName()) + "(");
classSource.append("new " + tt + "(");
while ((child = next) != null){
next = child.getNextSibling();
if (!opened) {
classSource.append(uncapitalize(child.getNodeName()) + ":");
if ((Integer)members.get(child.getNodeName()) > 1) {
classSource.append("[");
opened = true;
}
}
if (child.hasChildNodes() && (child.getChildNodes().getLength() == 1) && (child.getFirstChild().getNodeType() == Node.TEXT_NODE)) {
// Test for the depth
classSource.append("\""+child.getFirstChild().getNodeValue()+"\"");
} else {
createCodeCT((Element)child, type, classSource);
}
if (child != lnode) {
classSource.append(",");
}
prev = child;
}
if (opened) classSource.append("]");
classSource.append(")");
}
}
/**
* <p>Create Simple types instances of the data types previously generated
* from the XML document.</p>
*
* @param element Element of the XML document.
* @param classSource StringBuffer containing the generated code.
*/
private void createCodeST(Element element, StringBuffer classSource){
Node child = null;
Node fnode = (Node)element.getFirstChild();
Node lnode = (Node)element.getLastChild();
Node next = fnode;
Node prev = null;
boolean opened = false;
if (logger.isDebugEnabled()) logger.debug("Entering createCodeST");
classSource.append("[");
while ((child = next) != null){
next = child.getNextSibling();
classSource.append("\""+child.getFirstChild().getNodeValue()+"\"");
if (child != lnode) {
classSource.append(",");
}
prev = child;
}
classSource.append("]");
}
/**
* Invoke a method on a gsoap component using the xfire
* dynamic client </p>
* <p>Example of Groovy code:</p>
* <code>
* client = new SoapClient("http://www.webservicex.net/WeatherForecast.asmx?WSDL")
* def answer = client.GetWeatherByPlaceName("Seattle")
* </code>
*
* @param name name of the method to call
* @param args parameters of the method call
* @return the value returned by the method call
*/
public Object invokeMethod(String name, Object args) {
Object[] objs = InvokerHelper.getInstance().asArray(args);
try {
Object[] response = client.invoke(name, objs);
// TODO Parse the answer
if (response.length == 0) {
return true;
} else {
return toReturn(response[0]);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Object testGoogle(String fileName) {
try {
return toReturn(DOMUtils.readXml(new FileInputStream(fileName)));
} catch(Exception ex){
ex.printStackTrace();
}
return null;
}
public SoapClient() {}
/**
* Create a SoapClient using a URL
* <p>Example of Groovy code:</p>
* <code>
* client = new SoapClient("http://www.webservicex.net/WeatherForecast.asmx?WSDL")
* </code>
*
* @param URLLocation the URL pointing to the WSDL
*/
public SoapClient(String URLLocation){
try {
client = new Client(new URL(URLLocation));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}