package com.openMap1.mapper.util;
import javax.xml.soap.*;
import javax.xml.soap.Node;
import java.util.*;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.InputDialog;
import org.osgi.framework.Bundle;
import org.w3c.dom.*;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.impl.MapperPlugin;
/**
* Composes and sends SOAP messages, and receives replies,
* when the Eclipse Mapper
* tools are acting as clients for the translation compiler web service.
*
* @author robert
*
*/
public class SOAPClient {
// types of request header that can be sent to the server
public static int COMPILE_REQUEST = 0;
public static int MANAGER_REQUEST = 1;
// types of reply header that can return from the server
public static int SHOW_ERROR_MESSAGE = 0;
public static int USE_SUCCESSFUL_COMPILE = 1;
public static int USE_MANAGER_RESULT = 2;
private int SERVERS_TO_TRY = 3;
private boolean tracing = false;
/**
* the addresses of the web services to be tried out are stored
* in file plugin.properties for the Mapper plugin,
* There are three to be tried in order, with names '_TS_ServiceAddress_1' , 2 and 3
* @param i = 1,2, or 3 in successive attempts
* @return the URL of the web service
*/
private String getAddress(int i)
{
String pluginParameterName = "_TS_ServiceAddress_" + i;
return MapperPlugin.INSTANCE.getString(pluginParameterName);
}
public static String serviceNamespaceURI = "http://openMap1.com/ws/ProcCompiler/";
public static String serviceNamespacePrefix = "p0";
public static String xsdPrefix = "xsd";
public static String SOAPURI = "http://schemas.xmlsoap.org/soap/envelope/";
public static String SOAPPrefix = "soapenv";
private SOAPConnectionFactory soapConnectionFactory;
private SOAPConnection connection;
private SOAPFactory soapFactory;
private MessageFactory factory;
private SOAPMessage message;
/* namespaces for the document being sent. key = prefix; value = URI.
* This assumes a namespace does not appear with more than one prefix. */
private Hashtable<String,String> namespaces;
public SOAPClient() throws MapperException
{
try
{
soapConnectionFactory = SOAPConnectionFactory.newInstance();
connection = soapConnectionFactory.createConnection();
soapFactory = SOAPFactory.newInstance();
factory = MessageFactory.newInstance();
}
catch (SOAPException ex) {throw new MapperException("SOAP Exception: " + ex.getMessage());}
}
/**
* main method to send a SOAP message and get the XML out of the reply
* @param requestType must be COMPILE_REQUEST
* @param request XML Elements to be sent
* @return XML Elements sent back
* @throws MapperException
*/
public Element[] getReply(int requestType, Element[] request) throws MapperException
{
trace("getReply");
Element[] reply = new Element[0];
try
{
SOAPBody body = getEmptySOAPBody();
if ( (requestType == COMPILE_REQUEST)|
(requestType == MANAGER_REQUEST))
{
try
{
namespaces = new Hashtable<String,String>();
Name bodyName = soapFactory.createName("CServiceRequest", serviceNamespacePrefix, serviceNamespaceURI);
SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
// put all elements of the array request in the SOAP body
for (int i = 0; i < request.length; i++)
addSoapElement(bodyElement,request[i]);
// try out 3 service addresses in turn
SOAPMessage response = null;
response = getResponse(1);
if (response == null)
throw new MapperException("Empty reply from translation service.");
reply = handleResponse(response);
connection.close();
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
else throw new MapperException("Unsupported request type: " + requestType);
}
catch (SOAPException ex) {throw new MapperException("SOAP Exception: " + ex.getMessage());}
return reply;
}
/**
* make a limited number of attempts to get a SOAP response from different
* server addresses - each time trying the next one if you get a 'Message send failed'
* @param attempt integer attempt number - from 1 to SERVERS_TO_TRY
* @return the response
* @throws MapperException if some other exception occurred, of if no attempt succeeded
* @throws MalformedURLException
*/
private SOAPMessage getResponse(int attempt) throws MapperException, MalformedURLException
{
SOAPMessage response = null;
URL endpoint = new URL(getAddress(attempt));
try{response = connection.call(message, endpoint);}
catch (SOAPException ex)
{
// SOAPException soapEx = (SOAPException)ex;
if (attempt < SERVERS_TO_TRY)
{
System.out.println("Failed to send to " + getAddress(attempt));
response = getResponse(attempt + 1);
}
else throw new MapperException(ex.getMessage());
}
catch (Exception ex)
{
System.out.println("Non-SOAP Exception sending message to translation service");
ex.printStackTrace();
}
return response;
}
/**
* true if an exception message contains the string ' Message send failed'
* @param message
* @return
Not used now - 26/6/2009
private boolean sendFailed(String exMessage)
{
boolean failed = false;
StringTokenizer st = new StringTokenizer(exMessage," ");
while (st.hasMoreTokens())
{
if ( (st.nextToken().equalsIgnoreCase("Message")) &&
(st.hasMoreTokens()) && (st.nextToken().equals("send")) &&
(st.hasMoreTokens()) && (st.nextToken().equals("failed")))
failed = true;
}
return failed;
} */
/**
* Create an empty SOAP body, doing other necessary SOAP stuff
* @return a SOAP Body for the request
* @throws SOAPException
*/
private SOAPBody getEmptySOAPBody() throws SOAPException
{
// create the message (instance variable)
message = factory.createMessage();
message.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true");
// Add a SoapAction header to the http, which is necessary
MimeHeaders hd = message.getMimeHeaders();
hd.addHeader("SOAPAction", "urn:openMap1SoapAction");
// make the envelope
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope se = sp.getEnvelope();
se.addNamespaceDeclaration(xsdPrefix, XMLUtil.SCHEMAURI);
se.addNamespaceDeclaration(XMLUtil.SCHEMAINSTANCEPREFIX, XMLUtil.SCHEMAINSTANCEURI);
// the SOAP header has nothing in it yet, so it can be left off
SOAPHeader header = message.getSOAPHeader();
header.detachNode();
// make the body
SOAPBody body = message.getSOAPBody();
return body;
}
/**
* extract XML Elements from the response
* @param response SOAP message from the server
* @return array of XML Elements in the message
* @throws SOAPException
*/
private Element[] handleResponse(SOAPMessage response) throws SOAPException
{
SOAPBody soapBody = response.getSOAPBody();
Vector<Element> nodes = new Vector<Element>();
// put the reply into a Vector of Elements
for (Iterator<?> it = soapBody.getChildElements(); it.hasNext();)
{
Node next = (Node)it.next();
if (next instanceof SOAPBodyElement) nodes.add((SOAPBodyElement)next);
}
// turn a Vector of Elements into an Array of Elements
Element[] reply = new Element[nodes.size()];
for (int i = 0; i < nodes.size(); i++) reply[i] = nodes.elementAt(i);
return reply;
}
/**
* add a tree of SOAPElements to a SOAP parent element,
* copying a tree of DOM Elements
* @param sel the SOAP Element to be added to
* @param el root of the DOM tree
*/
private void addSoapElement(SOAPElement sel,Element el) throws SOAPException
{
// record any new namespace declarations on this element
addNamespaces(el);
// create the child SOAPElement with correct name, and add it to the parent
String tagName = el.getTagName();
Name name = soapFactory.createName(getLocalName(tagName));
String prefix = getPrefix(tagName);
if (prefix != null)
{
String uri = namespaces.get(prefix);
if (uri == null) System.out.println("Found no namespace for Element prefix'" + prefix + "'");
else name = soapFactory.createName(getLocalName(tagName),prefix,uri);
}
SOAPElement child = sel.addChildElement(name);
// add all attributes to the child node (including namespace declarations)
NamedNodeMap attMap = el.getAttributes();
for (int a = 0; a < attMap.getLength();a++)
{
org.w3c.dom.Node nd = attMap.item(a);
if (nd instanceof Attr)
{
Attr at = (Attr)nd;
String attPrefix = getPrefix(at.getName());
Name attName = soapFactory.createName(at.getName());
child.addAttribute(attName, at.getValue());
if ((attPrefix != null) && (attPrefix.equals("xmlns")))
{
// System.out.println("Declared '" + getLocalName(at.getName()) + "' by '" + attName.getQualifiedName() + "'");
}
}
}
//add the correct descendant Elements and text elements to the new child
NodeList nl = el.getChildNodes();
for (int i = 0; i < nl.getLength();i++)
{
org.w3c.dom.Node n = nl.item(i);
if (n instanceof org.w3c.dom.Text)
{
org.w3c.dom.Text t = (org.w3c.dom.Text)n;
String text = t.getWholeText();
child.addTextNode(text);
}
else if (n instanceof Element)
{
Element e = (Element)n;
addSoapElement(child,e);
}
}
}
// record any new namespace declarations on this element
private void addNamespaces(Element el)
{
NamedNodeMap attMap = el.getAttributes();
for (int a = 0; a < attMap.getLength();a++)
{
org.w3c.dom.Node nd = attMap.item(a);
if (nd instanceof Attr)
{
Attr at = (Attr)nd;
String attName = at.getName();
if (attName.startsWith("xmlns"))
{
String prefix = "";
if (attName.length() > 6) prefix = attName.substring(6);
String uri = at.getValue();
namespaces.put(prefix, uri);
//System.out.println("Recorded namespace " + prefix);
}
}
}
}
/**
* @param tagName
* @return the prefix from a tag name, or null if there is none
*/
private String getPrefix(String tagName)
{
StringTokenizer st = new StringTokenizer(tagName,":");
if (st.countTokens() == 1) return null;
return st.nextToken();
}
/**
* @param tagName
* @return the part of a tag name after ':', or the whole name if there is no ':'
*/
private String getLocalName(String tagName)
{
StringTokenizer st = new StringTokenizer(tagName,":");
if (st.countTokens() == 1) return tagName;
st.nextToken();// remove prefix
return st.nextToken();
}
//-----------------------------------------------------------------------------------
// Getting the user's email address and key
//-----------------------------------------------------------------------------------
/**
* @return the location and name of the key file
*/
public static String keyFileLocation()
{
Bundle thisPlugin = Platform.getBundle("com.openMap1.mapper");
IPath path = Platform.getStateLocation(thisPlugin);
String location = path.toString() + "/key.xml";
return location;
}
/**
* @return the email address which the tool has stored, or null
* if none is stored
*/
public static String getStoredEmail()
{
String email = null;
try
{
Element keyRoot = XMLUtil.getRootElement(keyFileLocation());
email = keyRoot.getAttribute("email");
}
catch (Exception ex) {}
return email;
}
/**
* @return the key for use of the xslt plugin which the tool has stored, or null
* if none is stored
*/
public static String getXSLTKey()
{
String xslKey = null;
try
{
Element keyRoot = XMLUtil.getRootElement(keyFileLocation());
xslKey = keyRoot.getAttribute("xslkey");
}
catch (Exception ex) {}
return xslKey;
}
/**
* @return the translation service key which the tool has stored, or null
* if none is stored
*/
public static String getStoredKey()
{
String key = null;
try
{
Element keyRoot = XMLUtil.getRootElement(keyFileLocation());
key = keyRoot.getAttribute("key");
}
catch (Exception ex) {}
return key;
}
/**
* persuade the user to enter an email address, and store it in the local file.
* @return null if the user cancels
*/
public static String getEmailFromUser() throws MapperException
{
String prompt = "Please enter an email address to get a new key for the compilation service";
String email = requestTextFromUser(prompt);
if (email != null) makeKeyFile(email,"");
return email;
}
/**
* show a dialogue with a prompt to the user, and return the text entered
* @param the prompt text
* @return the text entered
* (if the user clicks OK) or null if he cancels.
*/
public static String requestTextFromUser(String prompt)
{
String text = null;
// null = shell; null = title; "" = initial text; null = text validator
InputDialog inputDialog = new InputDialog(null,null,prompt,"",null);
if (inputDialog.open() == InputDialog.OK) text = inputDialog.getValue();
return text;
}
/**
* make or replace the local file holding the user's email address and
* key for the translation service
*/
public static void makeKeyFile(String email, String compilerKey) throws MapperException
{
try{
Document keyDoc = XMLUtil.makeOutDoc();
Element rootEl = XMLUtil.newElement(keyDoc, "CompileKey");
keyDoc.appendChild(rootEl);
rootEl.setAttribute("key",compilerKey);
rootEl.setAttribute("email",email);
XMLUtil.writeOutput(keyDoc, keyFileLocation(), true);
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
* set the xslt generation key, remembering the user's email and the compile key
* @param xslKey
* @throws MapperException if theere is no key file
*/
public static void setXSLTKey(String xslKey) throws MapperException
{
try{
Element oldKeyRoot = XMLUtil.getRootElement(keyFileLocation());
Document keyDoc = XMLUtil.makeOutDoc();
Element rootEl = XMLUtil.newElement(keyDoc, "CompileKey");
keyDoc.appendChild(rootEl);
rootEl.setAttribute("key",oldKeyRoot.getAttribute("key"));
rootEl.setAttribute("email",oldKeyRoot.getAttribute("email"));
rootEl.setAttribute("xslkey",xslKey);
XMLUtil.writeOutput(keyDoc, keyFileLocation(), true);
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
* find the user's email address and key for the translation service,
* from a file 'key.xml' in the plugin storage area;
*
* If there is no such file, get an email address from the user,
* and send an empty key (which will cause the server to make a
* new key and return it with the compile result)
*
* @return Element 'requestHeader' with three attributes 'requestType', 'email' and 'key'
* 'requestType' is SOAPClient.COMPILE_REQUEST.
* or return null if the user cancels the dialogue requesting an email address.
* @throws XMLException
*/
public static Element getEmailAndKey() throws MapperException
{
Element el = null;
String email = getStoredEmail();
if (email == null) email = getEmailFromUser();
if (email == null) return null; // if the user cancelled the dialogue to enter the email address
// if the key has been found, make out a request header for a compilation
Document doc = XMLUtil.makeOutDoc();
el = XMLUtil.newElement(doc, "requestHeader");
el.setAttribute("requestType",new Integer(SOAPClient.COMPILE_REQUEST).toString());
el.setAttribute("email", email);
el.setAttribute("key",getStoredKey()); // will be "" if the user has just entered an email
return el;
}
private void trace(String s) {if (tracing) System.out.println(s);}
}